feat: bug fixes, layout improvements, and new features
Bug fixes: - Remove non-existent `role` column from admin users SQL query (A1) - Fix Nextcloud Talk chat API path v4 → v1 for messages/send/read (A2) - Fix ServiceModeTab sync: useState → useEffect to reflect DB state (A3) - Guard BookStack book_slug with book_id fallback to avoid broken URLs (A4) Layout & UI: - Chat panel: sticky full-height positioning, main content scrolls independently (B1) - Vehicle booking datetime inputs: explicit text color for dark mode (B2) - AnnouncementBanner moved into grid with full-width span (B3) Features: - Per-user widget visibility preferences stored in users.preferences JSONB (C1) - Link collections: grouped external links in admin UI and dashboard widget (C2) - Admin ping history: migration 026, checked_at timestamps, expandable history rows (C4) - Service mode end date picker with scheduled deactivation display (C5) - Vikunja startup config logging and configured:false warnings (C7) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ export interface PingResult {
|
||||
status: 'up' | 'down';
|
||||
latencyMs: number;
|
||||
error?: string;
|
||||
checked_at: string;
|
||||
}
|
||||
|
||||
export interface StatusSummary {
|
||||
@@ -92,6 +93,7 @@ class ServiceMonitorService {
|
||||
url,
|
||||
status: 'up',
|
||||
latencyMs: Date.now() - start,
|
||||
checked_at: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
// Treat any HTTP response (even 4xx) as "service is reachable" only for status.php-type endpoints
|
||||
@@ -105,6 +107,7 @@ class ServiceMonitorService {
|
||||
url,
|
||||
status: 'up',
|
||||
latencyMs: Date.now() - start,
|
||||
checked_at: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -113,6 +116,7 @@ class ServiceMonitorService {
|
||||
url,
|
||||
status: 'down',
|
||||
latencyMs: Date.now() - start,
|
||||
checked_at: new Date().toISOString(),
|
||||
error: axios.isAxiosError(error)
|
||||
? `${error.code ?? 'ERROR'}: ${error.message}`
|
||||
: String(error),
|
||||
@@ -140,9 +144,37 @@ class ServiceMonitorService {
|
||||
})
|
||||
);
|
||||
|
||||
// Store ping results in history (fire-and-forget)
|
||||
this.storePingResults(results).catch(() => {});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getPingHistory(serviceId: string): Promise<Array<{ id: number; service_id: string; status: string; response_time_ms: number | null; checked_at: string }>> {
|
||||
const result = await pool.query(
|
||||
'SELECT * FROM service_ping_history WHERE service_id = $1 ORDER BY checked_at DESC LIMIT 20',
|
||||
[serviceId]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
private async storePingResults(results: PingResult[]): Promise<void> {
|
||||
for (const r of results) {
|
||||
const serviceId = r.name || r.url;
|
||||
await pool.query(
|
||||
'INSERT INTO service_ping_history (service_id, status, response_time_ms, checked_at) VALUES ($1, $2, $3, $4)',
|
||||
[serviceId, r.status, r.latencyMs, r.checked_at]
|
||||
);
|
||||
// Keep only last 20 per service
|
||||
await pool.query(
|
||||
`DELETE FROM service_ping_history WHERE service_id = $1 AND id NOT IN (
|
||||
SELECT id FROM service_ping_history WHERE service_id = $1 ORDER BY checked_at DESC LIMIT 20
|
||||
)`,
|
||||
[serviceId]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getStatusSummary(): Promise<StatusSummary> {
|
||||
const results = await this.pingAll();
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user