Skip to content

Commit f435da6

Browse files
committed
workout stats in progress
1 parent 65db949 commit f435da6

File tree

11 files changed

+1637
-190
lines changed

11 files changed

+1637
-190
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
<template>
2+
<div v-if="show" class="connection-manager" :class="statusClass">
3+
<div class="connection-icon" :class="statusClass"></div>
4+
<div class="connection-details">
5+
<div class="connection-status">{{ statusMessage }}</div>
6+
<div v-if="showDetails" class="connection-info">{{ detailMessage }}</div>
7+
</div>
8+
<div class="connection-actions">
9+
<button v-if="canRetry" @click="checkConnection" class="retry-btn">
10+
<span class="retry-icon">↻</span> Retry
11+
</button>
12+
<button v-if="showDiagnostics" @click="showDiagnosticsPanel = !showDiagnosticsPanel" class="diagnostics-btn">
13+
Diagnostics
14+
</button>
15+
</div>
16+
17+
<div v-if="showDiagnosticsPanel" class="diagnostics-panel">
18+
<h4>Connection Diagnostics</h4>
19+
<pre>{{ diagnosticsInfo }}</pre>
20+
<div class="endpoint-checks" v-if="endpointChecks.length > 0">
21+
<h5>Endpoint Checks:</h5>
22+
<ul>
23+
<li v-for="(check, index) in endpointChecks" :key="index" :class="{'endpoint-ok': check.available, 'endpoint-error': !check.available}">
24+
{{ check.endpoint }}: {{ check.available ? 'Available' : 'Error ' + (check.statusCode || check.error) }}
25+
</li>
26+
</ul>
27+
</div>
28+
<button @click="runFullDiagnostics" class="diagnostics-run-btn">Run Full Diagnostics</button>
29+
<button @click="showDiagnosticsPanel = false" class="close-btn">Close</button>
30+
</div>
31+
</div>
32+
</template>
33+
34+
<script>
35+
import { checkServerHealth, diagnoseServerConnection, checkApiEndpoint, fallbackHealthCheck } from '@/utils/serverHealth';
36+
37+
export default {
38+
name: 'ConnectionManager',
39+
props: {
40+
showByDefault: {
41+
type: Boolean,
42+
default: false
43+
},
44+
checkIntervalSeconds: {
45+
type: Number,
46+
default: 30
47+
},
48+
criticalEndpoints: {
49+
type: Array,
50+
default: () => ['/api/v1/auth/current-user', '/api/v1/health']
51+
}
52+
},
53+
data() {
54+
return {
55+
status: 'unknown', // unknown, online, offline, error, limited
56+
show: this.showByDefault,
57+
lastCheck: null,
58+
error: null,
59+
checkInterval: null,
60+
diagnostics: null,
61+
showDiagnosticsPanel: false,
62+
endpointChecks: [],
63+
isChecking: false
64+
};
65+
},
66+
computed: {
67+
statusClass() {
68+
return {
69+
'status-online': this.status === 'online',
70+
'status-offline': this.status === 'offline',
71+
'status-error': this.status === 'error',
72+
'status-limited': this.status === 'limited',
73+
'status-unknown': this.status === 'unknown'
74+
};
75+
},
76+
statusMessage() {
77+
switch(this.status) {
78+
case 'online': return 'Connected to server';
79+
case 'offline': return 'Connection lost';
80+
case 'error': return 'Server error';
81+
case 'limited': return 'Limited connectivity';
82+
default: return 'Checking connection...';
83+
}
84+
},
85+
detailMessage() {
86+
if (this.error) {
87+
return `Error: ${this.error}`;
88+
}
89+
if (this.status === 'limited') {
90+
return 'Some API features may be unavailable';
91+
}
92+
if (this.lastCheck) {
93+
return `Last checked: ${new Date(this.lastCheck).toLocaleTimeString()}`;
94+
}
95+
return '';
96+
},
97+
showDetails() {
98+
return this.error || this.status === 'limited' || this.lastCheck;
99+
},
100+
canRetry() {
101+
return this.status !== 'online' && !this.isChecking;
102+
},
103+
showDiagnostics() {
104+
return this.status !== 'online';
105+
},
106+
diagnosticsInfo() {
107+
if (!this.diagnostics) return 'No diagnostic info available';
108+
return JSON.stringify(this.diagnostics, null, 2);
109+
}
110+
},
111+
mounted() {
112+
// Initial check
113+
this.checkConnection();
114+
115+
// Register event listeners
116+
window.addEventListener('online', this.handleOnlineEvent);
117+
window.addEventListener('offline', this.handleOfflineEvent);
118+
119+
// Set up automatic checking
120+
this.checkInterval = setInterval(
121+
this.checkConnection,
122+
this.checkIntervalSeconds * 1000
123+
);
124+
},
125+
beforeUnmount() {
126+
// Clean up
127+
window.removeEventListener('online', this.handleOnlineEvent);
128+
window.removeEventListener('offline', this.handleOfflineEvent);
129+
clearInterval(this.checkInterval);
130+
},
131+
methods: {
132+
async checkConnection() {
133+
// Avoid multiple simultaneous checks
134+
if (this.isChecking) return;
135+
this.isChecking = true;
136+
137+
try {
138+
// Check server health
139+
const health = await checkServerHealth();
140+
this.lastCheck = new Date();
141+
142+
if (health.online) {
143+
this.status = 'online';
144+
this.error = null;
145+
// Auto-hide after 5 seconds if everything is OK
146+
if (this.show && !this.showByDefault) {
147+
setTimeout(() => {
148+
this.show = false;
149+
}, 5000);
150+
}
151+
} else if (health.serverError) {
152+
this.status = 'error';
153+
this.error = health.error;
154+
this.show = true;
155+
// Try fallback methods
156+
await this.tryFallbackMethods();
157+
} else {
158+
this.status = 'offline';
159+
this.error = health.error;
160+
this.show = true;
161+
// Try fallback methods
162+
await this.tryFallbackMethods();
163+
}
164+
} catch (e) {
165+
this.status = 'offline';
166+
this.error = e.message;
167+
this.show = true;
168+
} finally {
169+
this.isChecking = false;
170+
}
171+
},
172+
173+
async tryFallbackMethods() {
174+
try {
175+
const fallback = await fallbackHealthCheck();
176+
if (fallback.online) {
177+
if (fallback.limited) {
178+
this.status = 'limited';
179+
} else {
180+
// API is reachable through alternative means
181+
this.status = 'limited';
182+
}
183+
}
184+
} catch (error) {
185+
console.error('Fallback health check failed:', error);
186+
}
187+
},
188+
189+
async runFullDiagnostics() {
190+
this.diagnostics = {
191+
status: 'Running diagnostics...',
192+
timestamp: new Date().toISOString()
193+
};
194+
195+
try {
196+
// Run connection diagnostics
197+
const diagnosticInfo = await diagnoseServerConnection();
198+
this.diagnostics = diagnosticInfo;
199+
200+
// Check critical endpoints
201+
this.endpointChecks = [];
202+
for (const endpoint of this.criticalEndpoints) {
203+
const check = await checkApiEndpoint(endpoint);
204+
this.endpointChecks.push({
205+
endpoint,
206+
...check
207+
});
208+
}
209+
} catch (error) {
210+
this.diagnostics.error = error.message;
211+
}
212+
},
213+
214+
handleOnlineEvent() {
215+
if (this.status !== 'online') {
216+
this.checkConnection();
217+
}
218+
},
219+
220+
handleOfflineEvent() {
221+
this.status = 'offline';
222+
this.error = 'Browser is offline';
223+
this.show = true;
224+
}
225+
}
226+
};
227+
</script>
228+
229+
<style scoped>
230+
.connection-manager {
231+
position: fixed;
232+
bottom: 20px;
233+
right: 20px;
234+
background-color: white;
235+
border-radius: 8px;
236+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
237+
padding: 12px 16px;
238+
display: flex;
239+
align-items: center;
240+
gap: 12px;
241+
z-index: 1000;
242+
max-width: 400px;
243+
transition: all 0.3s ease;
244+
}
245+
246+
.connection-icon {
247+
width: 12px;
248+
height: 12px;
249+
border-radius: 50%;
250+
flex-shrink: 0;
251+
}
252+
253+
.status-online .connection-icon {
254+
background-color: #4CAF50;
255+
box-shadow: 0 0 8px #4CAF50;
256+
}
257+
258+
.status-offline .connection-icon {
259+
background-color: #F44336;
260+
box-shadow: 0 0 8px #F44336;
261+
}
262+
263+
.status-error .connection-icon {
264+
background-color: #FF9800;
265+
box-shadow: 0 0 8px #FF9800;
266+
}
267+
268+
.status-limited .connection-icon {
269+
background-color: #FFC107;
270+
box-shadow: 0 0 8px #FFC107;
271+
}
272+
273+
.status-unknown .connection-icon {
274+
background-color: #9E9E9E;
275+
}
276+
277+
.connection-details {
278+
flex: 1;
279+
min-width: 0;
280+
font-size: 14px;
281+
}
282+
283+
.connection-status {
284+
font-weight: 500;
285+
}
286+
287+
.connection-info {
288+
font-size: 12px;
289+
color: #666;
290+
margin-top: 2px;
291+
white-space: nowrap;
292+
overflow: hidden;
293+
text-overflow: ellipsis;
294+
}
295+
296+
.connection-actions {
297+
display: flex;
298+
gap: 8px;
299+
}
300+
301+
.retry-btn, .diagnostics-btn, .close-btn, .diagnostics-run-btn {
302+
background: none;
303+
border: 1px solid #ddd;
304+
border-radius: 4px;
305+
padding: 4px 8px;
306+
font-size: 12px;
307+
cursor: pointer;
308+
transition: all 0.2s;
309+
}
310+
311+
.retry-btn:hover, .diagnostics-btn:hover, .close-btn:hover, .diagnostics-run-btn:hover {
312+
background-color: #f0f0f0;
313+
}
314+
315+
.retry-icon {
316+
display: inline-block;
317+
margin-right: 3px;
318+
}
319+
320+
.diagnostics-panel {
321+
position: absolute;
322+
bottom: 100%;
323+
right: 0;
324+
margin-bottom: 8px;
325+
background-color: white;
326+
border-radius: 8px;
327+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
328+
padding: 16px;
329+
width: 350px;
330+
max-height: 400px;
331+
overflow-y: auto;
332+
}
333+
334+
.diagnostics-panel h4 {
335+
margin-top: 0;
336+
margin-bottom: 8px;
337+
font-size: 16px;
338+
}
339+
340+
.diagnostics-panel pre {
341+
font-size: 11px;
342+
background-color: #f5f5f5;
343+
padding: 8px;
344+
border-radius: 4px;
345+
overflow-x: auto;
346+
white-space: pre-wrap;
347+
margin: 8px 0;
348+
}
349+
350+
.endpoint-checks {
351+
margin-top: 12px;
352+
}
353+
354+
.endpoint-checks h5 {
355+
margin: 0 0 8px 0;
356+
font-size: 14px;
357+
}
358+
359+
.endpoint-checks ul {
360+
list-style: none;
361+
padding: 0;
362+
margin: 0;
363+
}
364+
365+
.endpoint-checks li {
366+
padding: 4px;
367+
margin-bottom: 4px;
368+
border-radius: 3px;
369+
font-size: 12px;
370+
}
371+
372+
.endpoint-ok {
373+
background-color: rgba(76, 175, 80, 0.1);
374+
}
375+
376+
.endpoint-error {
377+
background-color: rgba(244, 67, 54, 0.1);
378+
}
379+
380+
.close-btn {
381+
margin-left: 8px;
382+
}
383+
</style>

0 commit comments

Comments
 (0)