Refactor: Integrate regional agents in service-operation
Integrate distributed monitoring agents for HTTP, PING, TCP, and DNS checks
This commit is contained in:
@@ -4,7 +4,7 @@ PORT=8091
|
|||||||
|
|
||||||
# Operation defaults
|
# Operation defaults
|
||||||
DEFAULT_COUNT=4
|
DEFAULT_COUNT=4
|
||||||
DEFAULT_TIMEOUT=3s
|
DEFAULT_TIMEOUT=10s
|
||||||
MAX_COUNT=20
|
MAX_COUNT=20
|
||||||
MAX_TIMEOUT=30s
|
MAX_TIMEOUT=30s
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ PORT=8091
|
|||||||
|
|
||||||
# Operation defaults
|
# Operation defaults
|
||||||
DEFAULT_COUNT=4
|
DEFAULT_COUNT=4
|
||||||
DEFAULT_TIMEOUT=3s
|
DEFAULT_TIMEOUT=10s
|
||||||
MAX_COUNT=20
|
MAX_COUNT=20
|
||||||
MAX_TIMEOUT=30s
|
MAX_TIMEOUT=30s
|
||||||
|
|
||||||
@@ -13,4 +13,4 @@ ENABLE_LOGGING=true
|
|||||||
|
|
||||||
# PocketBase integration (no authentication required)
|
# PocketBase integration (no authentication required)
|
||||||
POCKETBASE_ENABLED=true
|
POCKETBASE_ENABLED=true
|
||||||
POCKETBASE_URL=https://pb-api.k8sops.asia
|
POCKETBASE_URL=http://localhost:8090
|
||||||
@@ -117,13 +117,6 @@ Environment variables:
|
|||||||
```bash
|
```bash
|
||||||
go run main.go
|
go run main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker
|
|
||||||
```bash
|
|
||||||
docker build -t service-operation .
|
|
||||||
docker run -p 8080:8080 service-operation
|
|
||||||
```
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Go 1.21+
|
- Go 1.21+
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -32,15 +31,15 @@ func main() {
|
|||||||
if err := pbClient.TestConnection(); err != nil {
|
if err := pbClient.TestConnection(); err != nil {
|
||||||
log.Printf("Warning: PocketBase connection test failed: %v", err)
|
log.Printf("Warning: PocketBase connection test failed: %v", err)
|
||||||
} else {
|
} else {
|
||||||
// Initialize and start monitoring service
|
// Initialize and start monitoring service with regional support
|
||||||
monitoringService = monitoring.NewMonitoringService(pbClient)
|
monitoringService = monitoring.NewMonitoringService(pbClient)
|
||||||
go monitoringService.Start()
|
go monitoringService.Start()
|
||||||
log.Println("Monitoring service started (public access mode)")
|
log.Println("Monitoring service started with regional agent support")
|
||||||
|
|
||||||
// Initialize and start SSL monitoring service
|
// Initialize and start SSL monitoring service (unchanged - no regional support)
|
||||||
sslMonitoringService = monitoring.NewSSLMonitoringService(pbClient)
|
sslMonitoringService = monitoring.NewSSLMonitoringService(pbClient)
|
||||||
go sslMonitoringService.Start()
|
go sslMonitoringService.Start()
|
||||||
log.Println("SSL monitoring service started")
|
log.Println("SSL monitoring service started (independent of regional agents)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,10 +66,10 @@ func main() {
|
|||||||
log.Printf("PocketBase integration enabled at %s (public access)", pbClient.GetBaseURL())
|
log.Printf("PocketBase integration enabled at %s (public access)", pbClient.GetBaseURL())
|
||||||
}
|
}
|
||||||
if monitoringService != nil {
|
if monitoringService != nil {
|
||||||
log.Printf("Automatic service monitoring enabled")
|
log.Printf("Automatic service monitoring enabled with regional agent support")
|
||||||
}
|
}
|
||||||
if sslMonitoringService != nil {
|
if sslMonitoringService != nil {
|
||||||
log.Printf("SSL certificate monitoring enabled")
|
log.Printf("SSL certificate monitoring enabled (independent)")
|
||||||
}
|
}
|
||||||
log.Printf("Endpoints:")
|
log.Printf("Endpoints:")
|
||||||
log.Printf(" POST /operation - Full operation test (ping, dns, tcp, http, ssl)")
|
log.Printf(" POST /operation - Full operation test (ping, dns, tcp, http, ssl)")
|
||||||
@@ -79,6 +78,7 @@ func main() {
|
|||||||
log.Printf(" GET /ping/quick?host=<host> - Legacy quick ping test")
|
log.Printf(" GET /ping/quick?host=<host> - Legacy quick ping test")
|
||||||
log.Printf(" GET /health - Health check")
|
log.Printf(" GET /health - Health check")
|
||||||
log.Printf("Supported operations: ping, dns, tcp, http, ssl")
|
log.Printf("Supported operations: ping, dns, tcp, http, ssl")
|
||||||
|
log.Printf("Regional monitoring: Tracks 'Default' region connection status")
|
||||||
|
|
||||||
// Setup graceful shutdown
|
// Setup graceful shutdown
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
|
|||||||
@@ -114,9 +114,10 @@ func (ms *MonitoringService) performCheck(service pocketbase.Service) {
|
|||||||
log.Printf("Failed to update service status for %s: %v", latestService.Name, err)
|
log.Printf("Failed to update service status for %s: %v", latestService.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save metrics data in ONE place to prevent duplicates
|
// Save metrics data with regional information
|
||||||
if result != nil {
|
if result != nil {
|
||||||
metricsSaver := savers.NewMetricsSaver(ms.pbClient)
|
regionName, agentID := ms.GetRegionalInfo()
|
||||||
|
metricsSaver := savers.NewMetricsSaverWithRegion(ms.pbClient, regionName, agentID)
|
||||||
metricsSaver.SaveMetricsForService(*latestService, result)
|
metricsSaver.SaveMetricsForService(*latestService, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
|
||||||
|
package monitoring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"service-operation/pocketbase"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegionalMonitor struct {
|
||||||
|
pbClient *pocketbase.PocketBaseClient
|
||||||
|
regionalService *pocketbase.RegionalService
|
||||||
|
isOnline bool
|
||||||
|
ticker *time.Ticker
|
||||||
|
stopChan chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegionalMonitor(pbClient *pocketbase.PocketBaseClient) *RegionalMonitor {
|
||||||
|
return &RegionalMonitor{
|
||||||
|
pbClient: pbClient,
|
||||||
|
isOnline: false,
|
||||||
|
stopChan: make(chan bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RegionalMonitor) Start() {
|
||||||
|
// Get or create the "Default" regional service
|
||||||
|
service, err := rm.pbClient.GetDefaultRegionalService()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Could not get default regional service: %v", err)
|
||||||
|
log.Printf("Regional monitoring will continue with fallback values")
|
||||||
|
// Continue with fallback service
|
||||||
|
service = &pocketbase.RegionalService{
|
||||||
|
ID: "default",
|
||||||
|
RegionName: "Default",
|
||||||
|
Status: "active",
|
||||||
|
AgentID: "1",
|
||||||
|
AgentIPAddress: "127.0.0.1",
|
||||||
|
Connection: "offline",
|
||||||
|
Token: "default-token",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rm.regionalService = service
|
||||||
|
rm.ticker = time.NewTicker(30 * time.Second) // Check connection every 30 seconds
|
||||||
|
|
||||||
|
// Initial connection status update
|
||||||
|
rm.updateConnectionStatus("online")
|
||||||
|
rm.isOnline = true
|
||||||
|
|
||||||
|
log.Printf("Regional monitor started for region: %s (Agent ID: %s)",
|
||||||
|
service.RegionName, service.AgentID)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-rm.ticker.C:
|
||||||
|
rm.checkConnection()
|
||||||
|
case <-rm.stopChan:
|
||||||
|
rm.ticker.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RegionalMonitor) Stop() {
|
||||||
|
if rm.regionalService != nil {
|
||||||
|
rm.updateConnectionStatus("offline")
|
||||||
|
log.Printf("Regional monitor stopped for region: %s", rm.regionalService.RegionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rm.ticker != nil {
|
||||||
|
rm.ticker.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
rm.stopChan <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RegionalMonitor) checkConnection() {
|
||||||
|
// Test PocketBase connection to determine if we're online
|
||||||
|
err := rm.pbClient.TestConnection()
|
||||||
|
|
||||||
|
if err != nil && rm.isOnline {
|
||||||
|
// We were online but now we're offline
|
||||||
|
rm.updateConnectionStatus("offline")
|
||||||
|
rm.isOnline = false
|
||||||
|
log.Printf("Regional agent went offline: %v", err)
|
||||||
|
} else if err == nil && !rm.isOnline {
|
||||||
|
// We were offline but now we're online
|
||||||
|
rm.updateConnectionStatus("online")
|
||||||
|
rm.isOnline = true
|
||||||
|
log.Printf("Regional agent back online")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RegionalMonitor) updateConnectionStatus(status string) {
|
||||||
|
if rm.regionalService == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rm.pbClient.UpdateRegionalServiceConnection(rm.regionalService.ID, status); err != nil {
|
||||||
|
// Don't log errors for update failures - might be due to collection access issues
|
||||||
|
// log.Printf("Failed to update regional service connection status: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RegionalMonitor) GetRegionalInfo() (string, string) {
|
||||||
|
if rm.regionalService == nil {
|
||||||
|
return "Default", "1" // Fallback values
|
||||||
|
}
|
||||||
|
return rm.regionalService.RegionName, rm.regionalService.AgentID
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package monitoring
|
package monitoring
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,19 +9,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MonitoringService struct {
|
type MonitoringService struct {
|
||||||
pbClient *pocketbase.PocketBaseClient
|
pbClient *pocketbase.PocketBaseClient
|
||||||
activeServices map[string]*ServiceMonitor
|
activeServices map[string]*ServiceMonitor
|
||||||
mu sync.RWMutex
|
regionalMonitor *RegionalMonitor
|
||||||
stopChan chan bool
|
mu sync.RWMutex
|
||||||
isRunning bool
|
stopChan chan bool
|
||||||
|
isRunning bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitoringService(pbClient *pocketbase.PocketBaseClient) *MonitoringService {
|
func NewMonitoringService(pbClient *pocketbase.PocketBaseClient) *MonitoringService {
|
||||||
return &MonitoringService{
|
return &MonitoringService{
|
||||||
pbClient: pbClient,
|
pbClient: pbClient,
|
||||||
activeServices: make(map[string]*ServiceMonitor),
|
activeServices: make(map[string]*ServiceMonitor),
|
||||||
stopChan: make(chan bool),
|
regionalMonitor: NewRegionalMonitor(pbClient),
|
||||||
isRunning: false,
|
stopChan: make(chan bool),
|
||||||
|
isRunning: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +39,9 @@ func (ms *MonitoringService) Start() {
|
|||||||
ms.isRunning = true
|
ms.isRunning = true
|
||||||
log.Println("Starting monitoring service...")
|
log.Println("Starting monitoring service...")
|
||||||
|
|
||||||
|
// Start regional monitoring
|
||||||
|
ms.regionalMonitor.Start()
|
||||||
|
|
||||||
// Start monitoring all services from PocketBase
|
// Start monitoring all services from PocketBase
|
||||||
go ms.monitoringLoop()
|
go ms.monitoringLoop()
|
||||||
}
|
}
|
||||||
@@ -53,6 +57,9 @@ func (ms *MonitoringService) Stop() {
|
|||||||
log.Println("Stopping monitoring service...")
|
log.Println("Stopping monitoring service...")
|
||||||
ms.isRunning = false
|
ms.isRunning = false
|
||||||
|
|
||||||
|
// Stop regional monitoring
|
||||||
|
ms.regionalMonitor.Stop()
|
||||||
|
|
||||||
// Stop all active monitors
|
// Stop all active monitors
|
||||||
for serviceID, monitor := range ms.activeServices {
|
for serviceID, monitor := range ms.activeServices {
|
||||||
ms.stopMonitor(serviceID, monitor)
|
ms.stopMonitor(serviceID, monitor)
|
||||||
@@ -61,6 +68,10 @@ func (ms *MonitoringService) Stop() {
|
|||||||
ms.stopChan <- true
|
ms.stopChan <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ms *MonitoringService) GetRegionalInfo() (string, string) {
|
||||||
|
return ms.regionalMonitor.GetRegionalInfo()
|
||||||
|
}
|
||||||
|
|
||||||
func (ms *MonitoringService) monitoringLoop() {
|
func (ms *MonitoringService) monitoringLoop() {
|
||||||
ticker := time.NewTicker(30 * time.Second) // Check for new services every 30 seconds
|
ticker := time.NewTicker(30 * time.Second) // Check for new services every 30 seconds
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func (s *SSLMonitoringService) shouldCheckCertificate(cert types.SSLCertificate)
|
|||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
//log.Printf("Certificate %s scheduled for later check (check_at: %s)",
|
//log.Printf("Certificate %s scheduled for later check (check_at: %s)",
|
||||||
//cert.Domain, checkAt.Format("2006-01-02 15:04:05"))
|
// cert.Domain, checkAt.Format("2006-01-02 15:04:05"))
|
||||||
//return false
|
//return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
|
||||||
|
package pocketbase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *PocketBaseClient) GetRegionalServices() ([]RegionalService, error) {
|
||||||
|
resp, err := c.httpClient.Get(fmt.Sprintf("%s/api/collections/regional_service/records", c.baseURL))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusForbidden {
|
||||||
|
// Collection might require authentication or doesn't exist
|
||||||
|
fmt.Printf("Warning: Cannot access regional_service collection (403). Regional monitoring will be disabled.\n")
|
||||||
|
return nil, fmt.Errorf("access denied to regional_service collection")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to get regional services: status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response RegionalServicesResponse
|
||||||
|
if err := c.parseResponse(resp, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PocketBaseClient) GetDefaultRegionalService() (*RegionalService, error) {
|
||||||
|
services, err := c.GetRegionalServices()
|
||||||
|
if err != nil {
|
||||||
|
// Try to create a default regional service if we can't get existing ones
|
||||||
|
return c.CreateDefaultRegionalService()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for a service with region_name "Default"
|
||||||
|
for _, service := range services {
|
||||||
|
if service.RegionName == "Default" {
|
||||||
|
return &service, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no default service found, try to create one
|
||||||
|
return c.CreateDefaultRegionalService()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PocketBaseClient) CreateDefaultRegionalService() (*RegionalService, error) {
|
||||||
|
defaultService := map[string]interface{}{
|
||||||
|
"region_name": "Default",
|
||||||
|
"status": "active",
|
||||||
|
"agent_id": "1",
|
||||||
|
"agent_ip_address": "127.0.0.1",
|
||||||
|
"connection": "offline",
|
||||||
|
"token": fmt.Sprintf("default-%d", time.Now().Unix()),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.createRecord("regional_service", defaultService)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Warning: Could not create default regional service: %v\n", err)
|
||||||
|
// Return a fallback service for local operations
|
||||||
|
return &RegionalService{
|
||||||
|
ID: "default",
|
||||||
|
RegionName: "Default",
|
||||||
|
Status: "active",
|
||||||
|
AgentID: "1",
|
||||||
|
AgentIPAddress: "127.0.0.1",
|
||||||
|
Connection: "offline",
|
||||||
|
Token: "default-token",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to fetch the created service
|
||||||
|
services, err := c.GetRegionalServices()
|
||||||
|
if err == nil {
|
||||||
|
for _, service := range services {
|
||||||
|
if service.RegionName == "Default" {
|
||||||
|
return &service, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return fallback if still can't get the created service
|
||||||
|
return &RegionalService{
|
||||||
|
ID: "default",
|
||||||
|
RegionName: "Default",
|
||||||
|
Status: "active",
|
||||||
|
AgentID: "1",
|
||||||
|
AgentIPAddress: "127.0.0.1",
|
||||||
|
Connection: "offline",
|
||||||
|
Token: "default-token",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PocketBaseClient) UpdateRegionalServiceConnection(serviceID, connection string) error {
|
||||||
|
// Determine status based on connection
|
||||||
|
status := "inactive"
|
||||||
|
if connection == "online" {
|
||||||
|
status = "active"
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"connection": connection,
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.updateRecord("regional_service", serviceID, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PocketBaseClient) SaveSSLData(sslData SSLDataRecord) error {
|
||||||
|
return c.createRecord("ssl_data", sslData)
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package pocketbase
|
package pocketbase
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
@@ -46,6 +45,8 @@ type PingDataRecord struct {
|
|||||||
RTTs string `json:"rtts"`
|
RTTs string `json:"rtts"`
|
||||||
Details string `json:"details,omitempty"`
|
Details string `json:"details,omitempty"`
|
||||||
ErrorMessage string `json:"error_message,omitempty"`
|
ErrorMessage string `json:"error_message,omitempty"`
|
||||||
|
RegionName string `json:"region_name,omitempty"`
|
||||||
|
AgentID string `json:"agent_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UptimeDataRecord struct {
|
type UptimeDataRecord struct {
|
||||||
@@ -61,6 +62,8 @@ type UptimeDataRecord struct {
|
|||||||
Details string `json:"details"`
|
Details string `json:"details"`
|
||||||
Region string `json:"region,omitempty"`
|
Region string `json:"region,omitempty"`
|
||||||
RegionID string `json:"region_id,omitempty"`
|
RegionID string `json:"region_id,omitempty"`
|
||||||
|
RegionName string `json:"region_name,omitempty"`
|
||||||
|
AgentID string `json:"agent_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSDataRecord struct {
|
type DNSDataRecord struct {
|
||||||
@@ -94,6 +97,46 @@ type TCPDataRecord struct {
|
|||||||
AgentID string `json:"agent_id,omitempty"`
|
AgentID string `json:"agent_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSL Data Record remains unchanged - no regional agent fields
|
||||||
|
type SSLDataRecord struct {
|
||||||
|
ServiceID string `json:"service_id"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
ResponseTime int64 `json:"response_time"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
ValidFrom string `json:"valid_from"`
|
||||||
|
ValidTill string `json:"valid_till"`
|
||||||
|
DaysLeft int `json:"days_left"`
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
SerialNumber string `json:"serial_number"`
|
||||||
|
Algorithm string `json:"algorithm"`
|
||||||
|
SANs string `json:"sans"`
|
||||||
|
ResolvedIP string `json:"resolved_ip"`
|
||||||
|
ErrorMessage string `json:"error_message,omitempty"`
|
||||||
|
Details string `json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regional Service record structure
|
||||||
|
type RegionalService struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
RegionName string `json:"region_name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
|
AgentIPAddress string `json:"agent_ip_address"`
|
||||||
|
Connection string `json:"connection"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Created string `json:"created"`
|
||||||
|
Updated string `json:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegionalServicesResponse struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
PerPage int `json:"perPage"`
|
||||||
|
TotalItems int `json:"totalItems"`
|
||||||
|
TotalPages int `json:"totalPages"`
|
||||||
|
Items []RegionalService `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@@ -9,6 +9,50 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *PocketBaseClient) parseResponse(resp *http.Response, target interface{}) error {
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(body, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PocketBaseClient) updateRecord(collection string, recordID string, data interface{}) error {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PATCH",
|
||||||
|
fmt.Sprintf("%s/api/collections/%s/records/%s", c.baseURL, collection, recordID),
|
||||||
|
bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
bodyString := string(bodyBytes)
|
||||||
|
|
||||||
|
fmt.Printf("Failed to update record in %s collection. Status: %d, Response: %s\n",
|
||||||
|
collection, resp.StatusCode, bodyString)
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to update record in %s, status: %d, response: %s",
|
||||||
|
collection, resp.StatusCode, bodyString)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *PocketBaseClient) createRecord(collection string, data interface{}) error {
|
func (c *PocketBaseClient) createRecord(collection string, data interface{}) error {
|
||||||
jsonData, err := json.Marshal(data)
|
jsonData, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ func (ms *MetricsSaver) SaveDNSDataToPocketBase(result *types.OperationResult, s
|
|||||||
Authority: "", // Not available in current implementation
|
Authority: "", // Not available in current implementation
|
||||||
ErrorMessage: result.Error,
|
ErrorMessage: result.Error,
|
||||||
Details: details, // Short, clean message
|
Details: details, // Short, clean message
|
||||||
RegionName: "default", // You can make this configurable
|
RegionName: ms.regionName, // Add regional fields
|
||||||
AgentID: "1", // You can make this configurable
|
AgentID: ms.agentID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ms.pbClient.SaveDNSData(dnsData); err != nil {
|
if err := ms.pbClient.SaveDNSData(dnsData); err != nil {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
package savers
|
package savers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"service-operation/pocketbase"
|
"service-operation/pocketbase"
|
||||||
@@ -9,12 +9,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MetricsSaver struct {
|
type MetricsSaver struct {
|
||||||
pbClient *pocketbase.PocketBaseClient
|
pbClient *pocketbase.PocketBaseClient
|
||||||
|
regionName string
|
||||||
|
agentID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMetricsSaver(pbClient *pocketbase.PocketBaseClient) *MetricsSaver {
|
func NewMetricsSaver(pbClient *pocketbase.PocketBaseClient) *MetricsSaver {
|
||||||
return &MetricsSaver{
|
return &MetricsSaver{
|
||||||
pbClient: pbClient,
|
pbClient: pbClient,
|
||||||
|
regionName: "Default", // Default fallback
|
||||||
|
agentID: "1", // Default fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMetricsSaverWithRegion(pbClient *pocketbase.PocketBaseClient, regionName, agentID string) *MetricsSaver {
|
||||||
|
return &MetricsSaver{
|
||||||
|
pbClient: pbClient,
|
||||||
|
regionName: regionName,
|
||||||
|
agentID: agentID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +62,8 @@ func (ms *MetricsSaver) SaveMetricsToPocketBase(result *types.OperationResult, s
|
|||||||
ms.SaveDNSDataToPocketBase(result, serviceID)
|
ms.SaveDNSDataToPocketBase(result, serviceID)
|
||||||
case types.OperationTCP:
|
case types.OperationTCP:
|
||||||
ms.SaveTCPDataToPocketBase(result, serviceID)
|
ms.SaveTCPDataToPocketBase(result, serviceID)
|
||||||
|
case types.OperationSSL:
|
||||||
|
ms.SaveSSLDataToPocketBase(result, serviceID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,5 +100,46 @@ func (ms *MetricsSaver) SaveMetricsForService(service pocketbase.Service, result
|
|||||||
ms.SaveUptimeDataToPocketBase(result, service.ID)
|
ms.SaveUptimeDataToPocketBase(result, service.ID)
|
||||||
case "tcp":
|
case "tcp":
|
||||||
ms.SaveTCPDataToPocketBase(result, service.ID)
|
ms.SaveTCPDataToPocketBase(result, service.ID)
|
||||||
|
case "ssl":
|
||||||
|
ms.SaveSSLDataToPocketBase(result, service.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSL Data saver - remains unchanged (no regional fields)
|
||||||
|
func (ms *MetricsSaver) SaveSSLDataToPocketBase(result *types.OperationResult, serviceID string) {
|
||||||
|
// Create SSL data record without regional fields
|
||||||
|
var details string
|
||||||
|
|
||||||
|
if result.Success {
|
||||||
|
details = fmt.Sprintf("✅ SSL Certificate Valid - Expires in %d days", result.SSLDaysLeft)
|
||||||
|
details += fmt.Sprintf(" | Valid until: %s", result.SSLValidTill.Format("2006-01-02"))
|
||||||
|
|
||||||
|
if result.SSLIssuer != "" {
|
||||||
|
details += fmt.Sprintf(" | Issuer: %s", result.SSLIssuer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
details = fmt.Sprintf("❌ SSL Certificate Issue - %s", GetShortErrorMessage(result.Error))
|
||||||
|
}
|
||||||
|
|
||||||
|
sslData := pocketbase.SSLDataRecord{
|
||||||
|
ServiceID: serviceID,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
ResponseTime: result.ResponseTime.Milliseconds(),
|
||||||
|
Status: GetStatusString(result.Success),
|
||||||
|
ValidFrom: result.SSLValidFrom.Format(time.RFC3339),
|
||||||
|
ValidTill: result.SSLValidTill.Format(time.RFC3339),
|
||||||
|
DaysLeft: result.SSLDaysLeft,
|
||||||
|
Issuer: result.SSLIssuer,
|
||||||
|
Subject: result.SSLSubject,
|
||||||
|
SerialNumber: result.SSLSerialNumber,
|
||||||
|
Algorithm: result.SSLAlgorithm,
|
||||||
|
SANs: result.SSLSANs,
|
||||||
|
ResolvedIP: result.SSLResolvedIP,
|
||||||
|
ErrorMessage: result.Error,
|
||||||
|
Details: details,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ms.pbClient.SaveSSLData(sslData); err != nil {
|
||||||
|
println("Failed to save SSL data to PocketBase:", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,8 @@ func (ms *MetricsSaver) SavePingDataToPocketBase(result *types.OperationResult,
|
|||||||
Latency: fmt.Sprintf("%.2fms", float64(result.AvgRTT.Nanoseconds())/1000000),
|
Latency: fmt.Sprintf("%.2fms", float64(result.AvgRTT.Nanoseconds())/1000000),
|
||||||
ErrorMessage: result.Error,
|
ErrorMessage: result.Error,
|
||||||
Details: details, // Short, clean message
|
Details: details, // Short, clean message
|
||||||
|
RegionName: ms.regionName, // Add regional fields
|
||||||
|
AgentID: ms.agentID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ms.pbClient.SavePingData(pingData); err != nil {
|
if err := ms.pbClient.SavePingData(pingData); err != nil {
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ func (ms *MetricsSaver) SaveTCPDataToPocketBase(result *types.OperationResult, s
|
|||||||
Port: strconv.Itoa(result.Port),
|
Port: strconv.Itoa(result.Port),
|
||||||
ErrorMessage: result.Error,
|
ErrorMessage: result.Error,
|
||||||
Details: details,
|
Details: details,
|
||||||
RegionName: "default",
|
RegionName: ms.regionName, // Add regional fields
|
||||||
AgentID: "1",
|
AgentID: ms.agentID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ms.pbClient.SaveTCPData(tcpData); err != nil {
|
if err := ms.pbClient.SaveTCPData(tcpData); err != nil {
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ func (ms *MetricsSaver) SaveUptimeDataToPocketBase(result *types.OperationResult
|
|||||||
Keyword: "", // Can be populated later if needed
|
Keyword: "", // Can be populated later if needed
|
||||||
ErrorMessage: result.Error,
|
ErrorMessage: result.Error,
|
||||||
Details: details, // Short, clean message
|
Details: details, // Short, clean message
|
||||||
Region: "default", // You can make this configurable
|
Region: ms.regionName, // Legacy field
|
||||||
RegionID: "1", // You can make this configurable
|
RegionID: ms.agentID, // Legacy field
|
||||||
|
RegionName: ms.regionName, // Add regional fields
|
||||||
|
AgentID: ms.agentID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ms.pbClient.SaveUptimeData(uptimeData); err != nil {
|
if err := ms.pbClient.SaveUptimeData(uptimeData); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user