mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-22 03:02:35 +00:00
Improve PMG connection testing to validate metrics endpoints
Related to #551 Enhanced the PMG connection test to actually validate the metrics endpoints that Pulse uses for monitoring, rather than only checking the version endpoint. This provides users with immediate feedback if their PMG credentials lack the necessary permissions to collect metrics. Backend changes: - Test mail statistics, cluster status, and quarantine endpoints during connection test (internal/api/config_handlers.go:1695-1714) - Return warnings array in test response when endpoints are unavailable - Increased timeout from 10s to 15s to accommodate multiple endpoint checks - Added warning logs for failed endpoint checks Frontend changes: - Added showWarning() toast function for warning messages - Enhanced NodeModal to display warning status with amber styling - Added warnings list display in test results UI - Updated Settings.tsx to show warnings from connection tests This change helps users identify permission issues immediately rather than discovering later that metrics aren't being collected despite a "successful" connection.
This commit is contained in:
parent
c93581e1aa
commit
449d77504f
4 changed files with 69 additions and 6 deletions
|
|
@ -57,6 +57,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
|||
status: string;
|
||||
message: string;
|
||||
isCluster?: boolean;
|
||||
warnings?: string[];
|
||||
} | null>(null);
|
||||
const [isTesting, setIsTesting] = createSignal(false);
|
||||
|
||||
|
|
@ -362,9 +363,10 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
|||
try {
|
||||
const result = await NodesAPI.testConnection(testData as NodeConfig);
|
||||
setTestResult({
|
||||
status: 'success',
|
||||
status: result.warnings && result.warnings.length > 0 ? 'warning' : 'success',
|
||||
message: result.message || 'Connection successful',
|
||||
isCluster: result.isCluster,
|
||||
warnings: result.warnings,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Test connection error:', error);
|
||||
|
|
@ -1863,7 +1865,9 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
|||
class={`mx-6 p-3 rounded-lg text-sm ${
|
||||
testResult()?.status === 'success'
|
||||
? 'bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 text-green-800 dark:text-green-200'
|
||||
: 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200'
|
||||
: testResult()?.status === 'warning'
|
||||
? 'bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-amber-800 dark:text-amber-200'
|
||||
: 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200'
|
||||
}`}
|
||||
>
|
||||
<div class="flex items-start gap-2">
|
||||
|
|
@ -1881,6 +1885,19 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
|||
<circle cx="12" cy="12" r="10"></circle>
|
||||
</svg>
|
||||
</Show>
|
||||
<Show when={testResult()?.status === 'warning'}>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
class="flex-shrink-0 mt-0.5"
|
||||
>
|
||||
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
||||
</svg>
|
||||
</Show>
|
||||
<Show when={testResult()?.status === 'error'}>
|
||||
<svg
|
||||
width="16"
|
||||
|
|
@ -1896,13 +1913,23 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
|||
<line x1="9" y1="9" x2="15" y2="15"></line>
|
||||
</svg>
|
||||
</Show>
|
||||
<div>
|
||||
<div class="flex-1">
|
||||
<p>{testResult()?.message}</p>
|
||||
<Show when={testResult()?.isCluster}>
|
||||
<p class="mt-1 text-xs opacity-80">
|
||||
✨ Cluster detected! All cluster nodes will be automatically added.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={testResult()?.warnings && testResult()!.warnings!.length > 0}>
|
||||
<div class="mt-2 space-y-1">
|
||||
<p class="text-xs font-semibold opacity-90">Warnings:</p>
|
||||
<ul class="text-xs space-y-0.5 opacity-80">
|
||||
<For each={testResult()?.warnings}>
|
||||
{(warning) => <li>• {warning}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
import type { JSX } from 'solid-js';
|
||||
import { useNavigate, useLocation } from '@solidjs/router';
|
||||
import { useWebSocket } from '@/App';
|
||||
import { showSuccess, showError } from '@/utils/toast';
|
||||
import { showSuccess, showError, showWarning } from '@/utils/toast';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { getPulsePort, getPulseWebSocketUrl } from '@/utils/url';
|
||||
import { logger } from '@/utils/logger';
|
||||
|
|
@ -1843,7 +1843,13 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
// Use the existing node test endpoint which uses stored credentials
|
||||
const result = await NodesAPI.testExistingNode(nodeId);
|
||||
if (result.status === 'success') {
|
||||
showSuccess(result.message || 'Connection successful');
|
||||
// Check for warnings in the response
|
||||
if (result.warnings && Array.isArray(result.warnings) && result.warnings.length > 0) {
|
||||
const warningMessage = result.message + '\n\nWarnings:\n' + result.warnings.map((w: string) => '• ' + w).join('\n');
|
||||
showWarning(warningMessage);
|
||||
} else {
|
||||
showSuccess(result.message || 'Connection successful');
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.message || 'Connection failed');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,3 +23,5 @@ export const showSuccess = (title: string, message?: string, duration?: number)
|
|||
showToast('success', title, message, duration);
|
||||
export const showError = (title: string, message?: string, duration?: number) =>
|
||||
showToast('error', title, message, duration);
|
||||
export const showWarning = (title: string, message?: string, duration?: number) =>
|
||||
showToast('warning', title, message, duration);
|
||||
|
|
|
|||
|
|
@ -1675,7 +1675,7 @@ func (h *ConfigHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Req
|
|||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
version, err := tempClient.GetVersion(ctx)
|
||||
|
|
@ -1692,10 +1692,34 @@ func (h *ConfigHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
}
|
||||
|
||||
// Test actual metrics endpoints to ensure monitoring will work
|
||||
warnings := []string{}
|
||||
|
||||
// Test mail statistics endpoint (core PMG functionality)
|
||||
if _, err := tempClient.GetMailStatistics(ctx, "day"); err != nil {
|
||||
warnings = append(warnings, "Mail statistics endpoint unavailable - check user permissions")
|
||||
log.Warn().Err(err).Msg("PMG connection test: mail statistics check failed")
|
||||
}
|
||||
|
||||
// Test cluster status endpoint
|
||||
if _, err := tempClient.GetClusterStatus(ctx, true); err != nil {
|
||||
warnings = append(warnings, "Cluster status endpoint unavailable")
|
||||
log.Warn().Err(err).Msg("PMG connection test: cluster status check failed")
|
||||
}
|
||||
|
||||
// Test quarantine endpoint
|
||||
if _, err := tempClient.GetQuarantineStatus(ctx, "spam"); err != nil {
|
||||
warnings = append(warnings, "Quarantine endpoint unavailable")
|
||||
log.Warn().Err(err).Msg("PMG connection test: quarantine check failed")
|
||||
}
|
||||
|
||||
message := "Connected to PMG instance"
|
||||
if versionLabel != "" {
|
||||
message = fmt.Sprintf("Connected to PMG instance (version %s)", versionLabel)
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
message += " (some metrics may be unavailable - check logs for details)"
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"status": "success",
|
||||
|
|
@ -1711,6 +1735,10 @@ func (h *ConfigHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
response["warnings"] = warnings
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue