Spaces:
Sleeping
Sleeping
Sasha commited on
Commit ·
43d1175
1
Parent(s): d9a03db
perf: optimize stats polling and add React ErrorBoundary for frontend crash fallback
Browse files- client/src/App.jsx +21 -0
- client/src/main.jsx +75 -1
client/src/App.jsx
CHANGED
|
@@ -453,6 +453,27 @@ export default function App() {
|
|
| 453 |
}
|
| 454 |
};
|
| 455 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
const fetchAuthStatus = async () => {
|
| 457 |
try {
|
| 458 |
const res = await fetch(`${API_BASE}/api/auth/status`, { credentials: 'include' });
|
|
|
|
| 453 |
}
|
| 454 |
};
|
| 455 |
|
| 456 |
+
// Optimize polling based on page visibility (pause if tab is backgrounded)
|
| 457 |
+
useEffect(() => {
|
| 458 |
+
const handleVisibilityChange = () => {
|
| 459 |
+
if (document.visibilityState === 'hidden') {
|
| 460 |
+
stopPolling();
|
| 461 |
+
} else if (document.visibilityState === 'visible' && selectedStreamId && serverStatus === 'online') {
|
| 462 |
+
fetchAllStats(false);
|
| 463 |
+
const selectedStream = streams.find(s => s.id === parseInt(selectedStreamId));
|
| 464 |
+
const isLive = selectedStream && !selectedStream.end_time;
|
| 465 |
+
if (isLive || selectedStreamId === 'all') {
|
| 466 |
+
startPolling();
|
| 467 |
+
}
|
| 468 |
+
}
|
| 469 |
+
};
|
| 470 |
+
|
| 471 |
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
| 472 |
+
return () => {
|
| 473 |
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
| 474 |
+
};
|
| 475 |
+
}, [selectedStreamId, streams, serverStatus]);
|
| 476 |
+
|
| 477 |
const fetchAuthStatus = async () => {
|
| 478 |
try {
|
| 479 |
const res = await fetch(`${API_BASE}/api/auth/status`, { credentials: 'include' });
|
client/src/main.jsx
CHANGED
|
@@ -1,10 +1,84 @@
|
|
|
|
|
| 1 |
import { StrictMode } from 'react'
|
| 2 |
import { createRoot } from 'react-dom/client'
|
| 3 |
import './index.css'
|
| 4 |
import App from './App.jsx'
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
createRoot(document.getElementById('root')).render(
|
| 7 |
<StrictMode>
|
| 8 |
-
<
|
|
|
|
|
|
|
| 9 |
</StrictMode>,
|
| 10 |
)
|
|
|
|
| 1 |
+
import React, { Component } from 'react'
|
| 2 |
import { StrictMode } from 'react'
|
| 3 |
import { createRoot } from 'react-dom/client'
|
| 4 |
import './index.css'
|
| 5 |
import App from './App.jsx'
|
| 6 |
|
| 7 |
+
class ErrorBoundary extends Component {
|
| 8 |
+
constructor(props) {
|
| 9 |
+
super(props);
|
| 10 |
+
this.state = { hasError: false, error: null };
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
static getDerivedStateFromError(error) {
|
| 14 |
+
return { hasError: true, error };
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
componentDidCatch(error, errorInfo) {
|
| 18 |
+
console.error("ErrorBoundary caught an error", error, errorInfo);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
render() {
|
| 22 |
+
if (this.state.hasError) {
|
| 23 |
+
return (
|
| 24 |
+
<div style={{
|
| 25 |
+
display: 'flex',
|
| 26 |
+
flexDirection: 'column',
|
| 27 |
+
alignItems: 'center',
|
| 28 |
+
justifyContent: 'center',
|
| 29 |
+
height: '100vh',
|
| 30 |
+
backgroundColor: '#0b0b0f',
|
| 31 |
+
color: '#fff',
|
| 32 |
+
fontFamily: 'sans-serif',
|
| 33 |
+
textAlign: 'center',
|
| 34 |
+
padding: '20px'
|
| 35 |
+
}}>
|
| 36 |
+
<span style={{ fontSize: '3rem' }}>⚠️</span>
|
| 37 |
+
<h1 style={{ color: '#FF4949', fontSize: '1.5rem', marginTop: '10px' }}>Критическая ошибка интерфейса</h1>
|
| 38 |
+
<p style={{ color: '#ADADB8', maxWidth: '500px', margin: '15px 0', fontSize: '0.9rem', lineHeight: '1.5' }}>
|
| 39 |
+
Произошла критическая ошибка при отрисовке фронтенда. Пожалуйста, попробуйте обновить страницу.
|
| 40 |
+
</p>
|
| 41 |
+
<pre style={{
|
| 42 |
+
backgroundColor: '#16161c',
|
| 43 |
+
padding: '12px',
|
| 44 |
+
borderRadius: '6px',
|
| 45 |
+
maxWidth: '100%',
|
| 46 |
+
width: '450px',
|
| 47 |
+
overflowX: 'auto',
|
| 48 |
+
textAlign: 'left',
|
| 49 |
+
color: '#FF6B6B',
|
| 50 |
+
fontSize: '0.8rem',
|
| 51 |
+
border: '1px solid #2F2F35'
|
| 52 |
+
}}>
|
| 53 |
+
{this.state.error && this.state.error.toString()}
|
| 54 |
+
</pre>
|
| 55 |
+
<button
|
| 56 |
+
onClick={() => window.location.reload()}
|
| 57 |
+
style={{
|
| 58 |
+
padding: '10px 20px',
|
| 59 |
+
backgroundColor: '#9146FF',
|
| 60 |
+
color: '#white',
|
| 61 |
+
border: 'none',
|
| 62 |
+
borderRadius: '5px',
|
| 63 |
+
cursor: 'pointer',
|
| 64 |
+
fontWeight: 'bold',
|
| 65 |
+
marginTop: '15px',
|
| 66 |
+
color: '#fff'
|
| 67 |
+
}}
|
| 68 |
+
>
|
| 69 |
+
Обновить страницу
|
| 70 |
+
</button>
|
| 71 |
+
</div>
|
| 72 |
+
);
|
| 73 |
+
}
|
| 74 |
+
return this.props.children;
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
createRoot(document.getElementById('root')).render(
|
| 79 |
<StrictMode>
|
| 80 |
+
<ErrorBoundary>
|
| 81 |
+
<App />
|
| 82 |
+
</ErrorBoundary>
|
| 83 |
</StrictMode>,
|
| 84 |
)
|