Merge pull request #65 from operacle/develop

feat: Implement notification channel based on status(multi selection) and Refactor Split ServiceForm into smaller components.
This commit is contained in:
Tola Leng
2025-07-10 22:17:02 +07:00
committed by GitHub
47 changed files with 980 additions and 654 deletions
+5 -5
View File
@@ -11,19 +11,19 @@ const api = {
* Handle API requests
*/
async handleRequest(path, method, body) {
console.log(`API request: ${method} ${path}`, body);
// console.log(`API request: ${method} ${path}`, body);
// Route to the appropriate handler
if (path === '/api/realtime') {
console.log("Routing to realtime handler");
// console.log("Routing to realtime handler");
return await realtime(body);
} else if (path === '/api/settings' || path.startsWith('/api/settings/')) {
console.log("Routing to settings handler");
// console.log("Routing to settings handler");
return await settingsApi(body, path);
}
// Return 404 for unknown routes
console.error(`Endpoint not found: ${path}`);
// console.error(`Endpoint not found: ${path}`);
return {
status: 404,
json: {
@@ -40,7 +40,7 @@ const originalFetch = window.fetch;
window.fetch = async (url, options = {}) => {
// Check if this is an API request to our mock endpoints
if (typeof url === 'string' && url.startsWith('/api/')) {
console.log('Intercepting API request:', url, options);
// console.log('Intercepting API request:', url, options);
try {
let body = {};
+2 -2
View File
@@ -8,7 +8,7 @@ import { testEmail } from './actions/testEmail';
* Settings API handler
*/
const settingsApi = async (body: any, path?: string) => {
console.log('Settings API called with path:', path, 'body:', body);
// console.log('Settings API called with path:', path, 'body:', body);
// Handle test email endpoint specifically
if (path === '/api/settings/test/email') {
@@ -18,7 +18,7 @@ const settingsApi = async (body: any, path?: string) => {
// Handle regular settings API with action-based routing
const action = body?.action;
console.log('Settings API called with action:', action, 'data:', body?.data);
// console.log('Settings API called with action:', action, 'data:', body?.data);
switch (action) {
case 'getSettings':
@@ -53,7 +53,7 @@ export const Header = ({
// Log avatar data for debugging
useEffect(() => {
if (currentUser) {
console.log("Avatar URL in Header:", currentUser.avatar);
// console.log("Avatar URL in Header:", currentUser.avatar);
}
}, [currentUser]);
@@ -66,7 +66,7 @@ export const Header = ({
} else {
avatarUrl = currentUser.avatar;
}
console.log("Final avatar URL:", avatarUrl);
// console.log("Final avatar URL:", avatarUrl);
}
return (
@@ -22,12 +22,12 @@ export const ScheduleIncidentContent = () => {
// Initialize maintenance notifications when the component mounts
useEffect(() => {
console.log("Initializing maintenance notifications");
// console.log("Initializing maintenance notifications");
initMaintenanceNotifications();
// Clean up when the component unmounts
return () => {
console.log("Cleaning up maintenance notifications");
// console.log("Cleaning up maintenance notifications");
stopMaintenanceNotifications();
};
}, []);
@@ -43,7 +43,7 @@ export const ScheduleIncidentContent = () => {
const handleMaintenanceCreated = () => {
// Refresh data by incrementing the refresh trigger
const newTriggerValue = refreshTrigger + 1;
console.log("Maintenance created, refreshing data with new trigger value:", newTriggerValue);
// console.log("Maintenance created, refreshing data with new trigger value:", newTriggerValue);
setRefreshTrigger(newTriggerValue);
// Show success toast
@@ -56,7 +56,7 @@ export const ScheduleIncidentContent = () => {
const handleIncidentCreated = () => {
// Refresh data by incrementing the refresh trigger
const newTriggerValue = incidentRefreshTrigger + 1;
console.log("Incident created, refreshing data with new trigger value:", newTriggerValue);
// console.log("Incident created, refreshing data with new trigger value:", newTriggerValue);
setIncidentRefreshTrigger(newTriggerValue);
// Show success toast
@@ -22,13 +22,13 @@ export const useIncidentData = ({ refreshTrigger = 0 }: UseIncidentDataProps) =>
const fetchIncidentData = useCallback(async (force = false) => {
// Skip if already fetching
if (isFetchingRef.current) {
console.log('Already fetching data, skipping additional request');
// console.log('Already fetching data, skipping additional request');
return;
}
// Skip if not forced and already initialized
if (initialized && !force) {
console.log('Data already initialized and no force refresh, skipping fetch');
// console.log('Data already initialized and no force refresh, skipping fetch');
return;
}
@@ -46,22 +46,22 @@ export const useIncidentData = ({ refreshTrigger = 0 }: UseIncidentDataProps) =>
setError(null);
try {
console.log(`Fetching incident data (force=${force})`);
// console.log(`Fetching incident data (force=${force})`);
const allIncidents = await incidentService.getAllIncidents(force);
if (Array.isArray(allIncidents)) {
setIncidents(allIncidents);
console.log(`Successfully set ${allIncidents.length} incidents to state`);
// console.log(`Successfully set ${allIncidents.length} incidents to state`);
} else {
setIncidents([]);
console.warn('No incidents returned from service');
// console.warn('No incidents returned from service');
}
setInitialized(true);
setLoading(false);
setIsRefreshing(false);
} catch (error) {
console.error('Error fetching incident data:', error);
// console.error('Error fetching incident data:', error);
setError('Failed to load incident data. Please try again later.');
setIncidents([]);
setInitialized(true);
@@ -79,7 +79,7 @@ export const useIncidentData = ({ refreshTrigger = 0 }: UseIncidentDataProps) =>
useEffect(() => {
// Skip if the refresh trigger hasn't changed (prevents duplicate effect calls)
if (refreshTrigger === lastRefreshTriggerRef.current && initialized) {
console.log('Refresh trigger unchanged, skipping fetch');
// console.log('Refresh trigger unchanged, skipping fetch');
return;
}
@@ -90,7 +90,7 @@ export const useIncidentData = ({ refreshTrigger = 0 }: UseIncidentDataProps) =>
const abortController = new AbortController();
let isMounted = true;
console.log(`useIncidentData effect running, refreshTrigger: ${refreshTrigger}`);
// console.log(`useIncidentData effect running, refreshTrigger: ${refreshTrigger}`);
// Use a longer delay to ensure we don't trigger too many API calls
const fetchTimer = setTimeout(() => {
@@ -101,7 +101,7 @@ export const useIncidentData = ({ refreshTrigger = 0 }: UseIncidentDataProps) =>
// Cleanup function to abort any in-flight requests and clear timers
return () => {
console.log('Cleaning up incident data fetch effect');
// console.log('Cleaning up incident data fetch effect');
isMounted = false;
clearTimeout(fetchTimer);
abortController.abort();
@@ -112,7 +112,7 @@ export const useIncidentData = ({ refreshTrigger = 0 }: UseIncidentDataProps) =>
const incidentData = useMemo(() => {
if (!initialized || incidents.length === 0) return [];
console.log(`Filtering incidents by: ${filter}`);
// console.log(`Filtering incidents by: ${filter}`);
if (filter === "unresolved") {
return incidents.filter(item => {
@@ -27,7 +27,7 @@ export const MaintenanceNotificationSettingsField = () => {
try {
setIsLoading(true);
const channels = await alertConfigService.getAlertConfigurations();
console.log("Fetched notification channels for form:", channels);
// console.log("Fetched notification channels for form:", channels);
// Only show enabled channels
const enabledChannels = channels.filter(channel => channel.enabled);
@@ -38,18 +38,18 @@ export const MaintenanceNotificationSettingsField = () => {
const currentChannel = getValues('notification_channel_id');
const shouldNotify = getValues('notify_subscribers');
console.log("Current notification values:", {
currentChannel,
shouldNotify,
availableChannels: enabledChannels.length
});
// console.log("Current notification values:", {
// currentChannel,
// shouldNotify,
// availableChannels: enabledChannels.length
// });
if (shouldNotify && (!currentChannel || currentChannel === 'none') && enabledChannels.length > 0) {
console.log("Setting default notification channel:", enabledChannels[0].id);
// console.log("Setting default notification channel:", enabledChannels[0].id);
setValue('notification_channel_id', enabledChannels[0].id);
}
} catch (error) {
console.error('Error fetching notification channels:', error);
// console.error('Error fetching notification channels:', error);
toast({
title: t('error'),
description: t('errorFetchingNotificationChannels'),
@@ -64,12 +64,12 @@ export const MaintenanceNotificationSettingsField = () => {
}, [t, toast, setValue, getValues]);
// Log value changes for debugging
useEffect(() => {
console.log("Current notification settings:", {
channel_id: getValues('notification_channel_id'),
notify: notifySubscribers
});
}, [notifySubscribers, notificationChannelId, getValues]);
// useEffect(() => {
// console.log("Current notification settings:", {
// channel_id: getValues('notification_channel_id'),
// notify: notifySubscribers
// });
// }, [notifySubscribers, notificationChannelId, getValues]);
return (
<div className="space-y-6">
@@ -98,7 +98,7 @@ export const MaintenanceNotificationSettingsField = () => {
checked={field.value}
onCheckedChange={(checked) => {
field.onChange(checked);
console.log("Notification toggle changed to:", checked);
// console.log("Notification toggle changed to:", checked);
// If notifications are disabled, also clear the notification channel
if (!checked) {
setValue('notification_channel_id', '');
@@ -120,10 +120,10 @@ export const MaintenanceNotificationSettingsField = () => {
// Make sure to handle both empty string and "none" as special cases
const displayValue = field.value || "";
console.log("Rendering notification channel field with value:", {
fieldValue: field.value,
displayValue
});
// console.log("Rendering notification channel field with value:", {
// fieldValue: field.value,
// displayValue
// });
return (
<FormItem>
@@ -135,7 +135,7 @@ export const MaintenanceNotificationSettingsField = () => {
<Select
value={displayValue}
onValueChange={(value) => {
console.log("Setting notification channel to:", value);
// console.log("Setting notification channel to:", value);
field.onChange(value === "none" ? "" : value);
}}
disabled={!notifySubscribers}
@@ -31,17 +31,17 @@ export const AssignedUsersField = () => {
// Ensure assigned_users is initialized as an array
useEffect(() => {
const currentValue = form.getValues('assigned_users');
console.log("Initial assigned_users value:", currentValue);
// console.log("Initial assigned_users value:", currentValue);
// Initialize as empty array if no value or invalid value
if (!currentValue || !Array.isArray(currentValue)) {
console.log("Initializing assigned_users as empty array");
// console.log("Initializing assigned_users as empty array");
form.setValue('assigned_users', [], { shouldValidate: false, shouldDirty: true });
}
}, [form]);
console.log("Current form values:", form.getValues());
console.log("Current assigned_users:", form.getValues('assigned_users'));
//console.log("Current form values:", form.getValues());
//console.log("Current assigned_users:", form.getValues('assigned_users'));
// Fetch users for the assignment dropdown
const { data: users = [], isLoading } = useQuery({
@@ -49,10 +49,10 @@ export const AssignedUsersField = () => {
queryFn: async () => {
try {
const usersList = await userService.getUsers();
console.log("Fetched users for assignment:", usersList);
// console.log("Fetched users for assignment:", usersList);
return Array.isArray(usersList) ? usersList : [];
} catch (error) {
console.error("Failed to fetch users:", error);
// console.error("Failed to fetch users:", error);
return [];
}
},
@@ -64,7 +64,7 @@ export const AssignedUsersField = () => {
? form.watch('assigned_users')
: [];
console.log("Selected user IDs:", selectedUserIds);
// console.log("Selected user IDs:", selectedUserIds);
// Function to add a user
const addUser = (userId: string) => {
@@ -73,7 +73,7 @@ export const AssignedUsersField = () => {
: [];
if (!currentValues.includes(userId)) {
console.log("Adding user:", userId);
// console.log("Adding user:", userId);
form.setValue('assigned_users', [...currentValues, userId], { shouldValidate: true, shouldDirty: true });
}
};
@@ -84,7 +84,7 @@ export const AssignedUsersField = () => {
? [...form.getValues('assigned_users')]
: [];
console.log("Removing user:", userId);
// console.log("Removing user:", userId);
form.setValue(
'assigned_users',
currentValues.filter(id => id !== userId),
@@ -94,7 +94,7 @@ export const AssignedUsersField = () => {
// Get selected users data
const selectedUsers = users.filter(user => selectedUserIds.includes(user.id));
console.log("Matched selected users:", selectedUsers);
// console.log("Matched selected users:", selectedUsers);
// Function to get user initials from name
const getUserInitials = (user: any): string => {
@@ -184,7 +184,7 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) {
if (data?.agent_id && data.agent_id !== '1') {
label = `${regionName} (${data.agent_id})`;
} else if (regionName === 'Default' && data?.agent_id === '1') {
label = `Default (Agent 1)`;
label = `Default System Check (Agent 1)`;
}
const colorIndex = index % modernColors.length;
@@ -22,12 +22,12 @@ export const useRealTimeUpdates = ({
useEffect(() => {
if (!serviceId) return;
console.log(`Setting up real-time updates for service: ${serviceId}`);
// console.log(`Setting up real-time updates for service: ${serviceId}`);
try {
// Subscribe to the service record for real-time updates
const subscription = pb.collection('services').subscribe(serviceId, function(e) {
console.log("Service updated:", e.record);
// console.log("Service updated:", e.record);
// Update our local state with the new data
if (e.record) {
@@ -47,7 +47,7 @@ export const useRealTimeUpdates = ({
// Subscribe to uptime data updates
const uptimeSubscription = pb.collection('uptime_data').subscribe('*', function(e) {
if (e.record && e.record.service_id === serviceId) {
console.log("New uptime data:", e.record);
// console.log("New uptime data:", e.record);
// Add the new uptime data to our list if it's within the selected date range
const timestamp = new Date(e.record.timestamp);
@@ -73,16 +73,16 @@ export const useRealTimeUpdates = ({
// Clean up the subscriptions
return () => {
console.log(`Cleaning up subscriptions for service: ${serviceId}`);
// console.log(`Cleaning up subscriptions for service: ${serviceId}`);
try {
pb.collection('services').unsubscribe(serviceId);
pb.collection('uptime_data').unsubscribe('*');
} catch (error) {
console.error("Error cleaning up subscriptions:", error);
// console.error("Error cleaning up subscriptions:", error);
}
};
} catch (error) {
console.error("Error setting up real-time updates:", error);
// console.error("Error setting up real-time updates:", error);
}
}, [serviceId, startDate, endDate, setService, setUptimeData]);
};
@@ -38,7 +38,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
description: `Service status changed to ${newStatus}`,
});
} catch (error) {
console.error("Failed to update service status:", error);
// console.error("Failed to update service status:", error);
setService(prevService => prevService);
toast({
@@ -52,12 +52,12 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string, regionalAgent?: string) => {
try {
if (!service) {
console.log('No service data available for uptime fetch');
// console.log('No service data available for uptime fetch');
return [];
}
const currentAgent = regionalAgent || selectedRegionalAgent;
console.log(`Fetching uptime data: ${start.toISOString()} to ${end.toISOString()} for range: ${selectedRange}, service type: ${service.type}, regional agent: ${currentAgent}`);
// console.log(`Fetching uptime data: ${start.toISOString()} to ${end.toISOString()} for range: ${selectedRange}, service type: ${service.type}, regional agent: ${currentAgent}`);
// Clear existing data immediately when switching agents
setUptimeData([]);
@@ -70,17 +70,17 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
limit = 400;
}
console.log(`Using limit ${limit} for range ${selectedRange}`);
// console.log(`Using limit ${limit} for range ${selectedRange}`);
let history: UptimeData[] = [];
if (currentAgent === "all") {
// Fetch data from all sources (default + all online regional agents)
console.log(`Fetching data from all monitoring sources`);
// console.log(`Fetching data from all monitoring sources`);
// Fetch default monitoring data
const defaultData = await uptimeService.getUptimeHistory(serviceId, limit, start, end, service.type);
console.log(`Retrieved ${defaultData.length} default monitoring records`);
// console.log(`Retrieved ${defaultData.length} default monitoring records`);
// Mark default data with source identifier
const markedDefaultData = defaultData.map(record => ({
@@ -100,7 +100,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
const regionalData = await uptimeService.getUptimeHistoryByRegionalAgent(
serviceId, limit, start, end, service.type, agent.region_name, agent.agent_id
);
console.log(`Retrieved ${regionalData.length} records from ${agent.region_name}`);
// console.log(`Retrieved ${regionalData.length} records from ${agent.region_name}`);
// Mark regional data with source identifier
const markedRegionalData = regionalData.map(record => ({
@@ -112,18 +112,18 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
history = [...history, ...markedRegionalData];
} catch (error) {
console.error(`Error fetching data from ${agent.region_name}:`, error);
// console.error(`Error fetching data from ${agent.region_name}:`, error);
}
}
console.log(`Total combined records: ${history.length}`);
// console.log(`Total combined records: ${history.length}`);
} else {
// Fetch regional agent specific data
const [regionName, agentId] = currentAgent.split("|");
console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`);
// console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`);
history = await uptimeService.getUptimeHistoryByRegionalAgent(serviceId, limit, start, end, service.type, regionName, agentId);
console.log(`Retrieved ${history.length} regional monitoring records`);
// console.log(`Retrieved ${history.length} regional monitoring records`);
}
// Sort by timestamp (newest first)
@@ -131,11 +131,11 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
);
console.log(`Final dataset: ${filteredHistory.length} records for ${currentAgent === "all" ? "all sources" : "regional"} monitoring`);
//console.log(`Final dataset: ${filteredHistory.length} records for ${currentAgent === "all" ? "all sources" : "regional"} monitoring`);
setUptimeData(filteredHistory);
return filteredHistory;
} catch (error) {
console.error("Error fetching uptime data:", error);
// console.error("Error fetching uptime data:", error);
toast({
variant: "destructive",
title: "Error",
@@ -146,7 +146,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
};
const handleRegionalAgentChange = (agent: string) => {
console.log(`Regional agent changed from ${selectedRegionalAgent} to: ${agent}`);
// console.log(`Regional agent changed from ${selectedRegionalAgent} to: ${agent}`);
// Clear data immediately when switching
setUptimeData([]);
@@ -154,7 +154,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
// Refetch data with new agent selection
if (serviceId && !isLoading && service) {
console.log(`Refetching data for new agent: ${agent}`);
// console.log(`Refetching data for new agent: ${agent}`);
fetchUptimeData(serviceId, startDate, endDate, '24h', agent);
}
};
@@ -196,13 +196,13 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
alerts: serviceData.alerts || "unmuted"
};
console.log(`Loaded service: ${formattedService.name} (${formattedService.type})`);
// console.log(`Loaded service: ${formattedService.name} (${formattedService.type})`);
setService(formattedService);
// Small delay to ensure state is updated before fetching uptime data
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error("Error fetching service:", error);
// console.error("Error fetching service:", error);
toast({
variant: "destructive",
title: "Error",
@@ -220,7 +220,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
// Update data when date range changes or when service is loaded
useEffect(() => {
if (serviceId && !isLoading && service) {
console.log(`Date range changed or service loaded, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`);
// console.log(`Date range changed or service loaded, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`);
fetchUptimeData(serviceId, startDate, endDate, '24h', selectedRegionalAgent);
}
}, [startDate, endDate, serviceId, isLoading, service, selectedRegionalAgent, regionalAgents]);
@@ -1,228 +1,3 @@
import { Form } from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState, useEffect } from "react";
import { useToast } from "@/hooks/use-toast";
import { serviceSchema, ServiceFormData } from "./add-service/types";
import { ServiceBasicFields } from "./add-service/ServiceBasicFields";
import { ServiceTypeField } from "./add-service/ServiceTypeField";
import { ServiceConfigFields } from "./add-service/ServiceConfigFields";
import { ServiceNotificationFields } from "./add-service/ServiceNotificationFields";
import { ServiceFormActions } from "./add-service/ServiceFormActions";
import { serviceService } from "@/services/serviceService";
import { Service } from "@/types/service.types";
import { ServiceRegionalFields } from "./add-service/ServiceRegionalFields";
interface ServiceFormProps {
onSuccess: () => void;
onCancel: () => void;
initialData?: Service | null;
isEdit?: boolean;
onSubmitStart?: () => void;
}
export function ServiceForm({
onSuccess,
onCancel,
initialData,
isEdit = false,
onSubmitStart
}: ServiceFormProps) {
const { toast } = useToast();
const [isSubmitting, setIsSubmitting] = useState(false);
// Initialize form with default values
const form = useForm<ServiceFormData>({
resolver: zodResolver(serviceSchema),
defaultValues: {
name: "",
type: "http",
url: "",
port: "",
interval: "60",
retries: "3",
notificationChannel: "",
alertTemplate: "",
regionalMonitoringEnabled: false,
regionalAgent: "",
},
mode: "onBlur",
});
// Populate form when initialData changes (separate from initialization)
useEffect(() => {
if (initialData && isEdit) {
// Ensure the type is one of the allowed values
const serviceType = (initialData.type || "http").toLowerCase();
const validType = ["http", "ping", "tcp", "dns"].includes(serviceType)
? serviceType as "http" | "ping" | "tcp" | "dns"
: "http";
// For PING services, use host field; for DNS use domain field; for TCP use host field; others use url
let urlValue = "";
let portValue = "";
if (validType === "ping") {
urlValue = initialData.host || "";
} else if (validType === "dns") {
urlValue = initialData.domain || "";
} else if (validType === "tcp") {
urlValue = initialData.host || "";
portValue = String(initialData.port || "");
} else {
urlValue = initialData.url || "";
}
// Handle regional monitoring data - check regional_status field
const isRegionalEnabled = initialData.regional_status === "enabled";
const regionalAgent = isRegionalEnabled && initialData.region_name && initialData.agent_id
? `${initialData.region_name}|${initialData.agent_id}`
: "";
// Reset the form with initial data values
form.reset({
name: initialData.name || "",
type: validType,
url: urlValue,
port: portValue,
interval: String(initialData.interval || 60),
retries: String(initialData.retries || 3),
notificationChannel: initialData.notificationChannel === "none" ? "" : initialData.notificationChannel || "",
alertTemplate: initialData.alertTemplate === "default" ? "" : initialData.alertTemplate || "",
regionalMonitoringEnabled: isRegionalEnabled,
regionalAgent: regionalAgent,
});
// Log for debugging
console.log("Populating form with data:", {
type: validType,
url: urlValue,
port: portValue,
regionalAgent,
regionalMonitoringEnabled: isRegionalEnabled,
regional_status: initialData.regional_status,
region_name: initialData.region_name,
agent_id: initialData.agent_id
});
}
}, [initialData, isEdit, form]);
const handleSubmit = async (data: ServiceFormData) => {
if (isSubmitting) return;
setIsSubmitting(true);
if (onSubmitStart) onSubmitStart();
try {
console.log("Form data being submitted:", data); // Debug log for submitted data
// Parse regional agent selection
let regionName = "";
let agentId = "";
let regionalStatus: "enabled" | "disabled" = "disabled";
// Set regional status and agent data based on form values
if (data.regionalMonitoringEnabled) {
regionalStatus = "enabled";
if (data.regionalAgent && data.regionalAgent !== "") {
const [parsedRegionName, parsedAgentId] = data.regionalAgent.split("|");
regionName = parsedRegionName || "";
agentId = parsedAgentId || "";
}
}
// Prepare service data with proper field mapping
const serviceData = {
name: data.name,
type: data.type,
interval: parseInt(data.interval),
retries: parseInt(data.retries),
notificationChannel: data.notificationChannel === "none" ? "" : data.notificationChannel,
alertTemplate: data.alertTemplate === "default" ? "" : data.alertTemplate,
// Use regional_status field instead of regionalMonitoringEnabled
regionalStatus: regionalStatus,
regionName: regionName,
agentId: agentId,
// Map the URL field to appropriate database field based on service type
...(data.type === "dns"
? { domain: data.url, url: "", host: "", port: undefined } // DNS: store in domain field
: data.type === "ping"
? { host: data.url, url: "", domain: "", port: undefined } // PING: store in host field
: data.type === "tcp"
? { host: data.url, port: parseInt(data.port || "80"), url: "", domain: "" } // TCP: store in host and port fields
: { url: data.url, domain: "", host: "", port: undefined } // HTTP: store in url field
)
};
console.log("Service data being sent:", serviceData);
if (isEdit && initialData) {
// Update existing service
await serviceService.updateService(initialData.id, serviceData);
toast({
title: "Service updated",
description: `${data.name} has been updated successfully.`,
});
} else {
// Create new service
await serviceService.createService(serviceData);
toast({
title: "Service created",
description: `${data.name} has been added to monitoring.`,
});
}
onSuccess();
if (!isEdit) {
form.reset();
}
} catch (error) {
console.error(`Error ${isEdit ? 'updating' : 'creating'} service:`, error);
toast({
title: `Failed to ${isEdit ? 'update' : 'create'} service`,
description: `An error occurred while ${isEdit ? 'updating' : 'creating'} the service.`,
variant: "destructive",
});
} finally {
setIsSubmitting(false);
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6 pb-6">
<div className="space-y-6">
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Basic Information</h3>
<ServiceBasicFields form={form} />
<ServiceTypeField form={form} />
</div>
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Configuration</h3>
<ServiceConfigFields form={form} />
</div>
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Regional Monitoring</h3>
<ServiceRegionalFields form={form} />
</div>
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Notifications</h3>
<ServiceNotificationFields form={form} />
</div>
</div>
<ServiceFormActions
isSubmitting={isSubmitting}
onCancel={onCancel}
submitLabel={isEdit ? "Update Service" : "Create Service"}
/>
</form>
</Form>
);
}
// Re-export the ServiceForm component from the add-service directory
export { ServiceForm } from './add-service/ServiceForm';
@@ -28,7 +28,7 @@ export function ServiceMonitoringButton({ service, onStatusChange }: ServiceMoni
if (isMonitoring) {
// Pause monitoring
console.log(`Pausing monitoring for service ${service.id} (${service.name})`);
// console.log(`Pausing monitoring for service ${service.id} (${service.name})`);
await serviceService.pauseMonitoring(service.id);
setIsMonitoring(false);
@@ -36,7 +36,7 @@ export function ServiceMonitoringButton({ service, onStatusChange }: ServiceMoni
// Send notification for paused status (only here, not in pauseMonitoring.ts)
if (service.alerts !== "muted") {
console.log("Sending pause notification from UI component");
// console.log("Sending pause notification from UI component");
// IMPORTANT: Direct call to the notification service to ensure a message is sent
await notificationService.sendNotification({
service: service,
@@ -51,7 +51,7 @@ export function ServiceMonitoringButton({ service, onStatusChange }: ServiceMoni
});
} else {
// Start/resume monitoring
console.log(`Starting monitoring for service ${service.id} (${service.name})`);
// console.log(`Starting monitoring for service ${service.id} (${service.name})`);
// First ensure we update the status in the database to not be paused anymore
await serviceService.resumeMonitoring(service.id);
@@ -66,7 +66,7 @@ export function ServiceMonitoringButton({ service, onStatusChange }: ServiceMoni
});
}
} catch (error) {
console.error("Error toggling monitoring:", error);
// console.error("Error toggling monitoring:", error);
toast({
variant: "destructive",
title: "Error",
@@ -56,7 +56,7 @@ export const UptimeBar = ({ uptime, status, serviceId, interval, serviceType }:
);
}
console.log('UptimeBar rendering consolidated items:', consolidatedItems.length);
// console.log('UptimeBar rendering consolidated items:', consolidatedItems.length);
return (
<div className="w-52">
@@ -78,9 +78,9 @@ export const UptimeBar = ({ uptime, status, serviceId, interval, serviceType }:
statuses.includes('paused') ? 'paused' : 'unknown';
}
console.log(`Bar ${index} - Timestamp: ${slot.timestamp}, Items: ${slot.items.length}, Primary Status: ${primaryStatus}, Has Valid Data: ${hasValidData}`);
// console.log(`Bar ${index} - Timestamp: ${slot.timestamp}, Items: ${slot.items.length}, Primary Status: ${primaryStatus}, Has Valid Data: ${hasValidData}`);
slot.items.forEach((item, itemIndex) => {
console.log(` Item ${itemIndex}: Source=${item.source}, Status=${item.status}, ResponseTime=${item.responseTime}, IsDefault=${item.isDefault}`);
// console.log(` Item ${itemIndex}: Source=${item.source}, Status=${item.status}, ResponseTime=${item.responseTime}, IsDefault=${item.isDefault}`);
});
return (
@@ -4,7 +4,6 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState, useEffect } from "react";
import { useToast } from "@/hooks/use-toast";
import { useQueryClient } from "@tanstack/react-query";
import { serviceSchema, ServiceFormData } from "./types";
import { ServiceBasicFields } from "./ServiceBasicFields";
import { ServiceTypeField } from "./ServiceTypeField";
@@ -14,6 +13,7 @@ import { ServiceFormActions } from "./ServiceFormActions";
import { serviceService } from "@/services/serviceService";
import { Service } from "@/types/service.types";
import { ServiceRegionalFields } from "./ServiceRegionalFields";
import { getServiceFormDefaults, mapServiceToFormData, mapFormDataToServiceData } from "./serviceFormUtils";
interface ServiceFormProps {
onSuccess: () => void;
@@ -36,73 +36,28 @@ export function ServiceForm({
// Initialize form with default values
const form = useForm<ServiceFormData>({
resolver: zodResolver(serviceSchema),
defaultValues: {
name: "",
type: "http",
url: "",
port: "",
interval: "60",
retries: "3",
notificationChannel: "",
alertTemplate: "",
regionalMonitoringEnabled: false,
regionalAgent: "",
},
defaultValues: getServiceFormDefaults(),
mode: "onBlur",
});
// Populate form when initialData changes (separate from initialization)
useEffect(() => {
if (initialData && isEdit) {
// Ensure the type is one of the allowed values
const serviceType = (initialData.type || "http").toLowerCase();
const validType = ["http", "ping", "tcp", "dns"].includes(serviceType)
? serviceType as "http" | "ping" | "tcp" | "dns"
: "http";
// For PING services, use host field; for DNS use domain field; for TCP use host field; others use url
let urlValue = "";
let portValue = "";
if (validType === "ping") {
urlValue = initialData.host || "";
} else if (validType === "dns") {
urlValue = initialData.domain || "";
} else if (validType === "tcp") {
urlValue = initialData.host || "";
portValue = String(initialData.port || "");
} else {
urlValue = initialData.url || "";
}
// Handle regional monitoring data - ensure proper assignment display
const regionalAgent = initialData.region_name && initialData.agent_id
? `${initialData.region_name}|${initialData.agent_id}`
: "";
// Reset the form with initial data values
form.reset({
name: initialData.name || "",
type: validType,
url: urlValue,
port: portValue,
interval: String(initialData.interval || 60),
retries: String(initialData.retries || 3),
notificationChannel: initialData.notificationChannel || "",
alertTemplate: initialData.alertTemplate || "",
regionalMonitoringEnabled: Boolean(initialData.regional_monitoring_enabled),
regionalAgent: regionalAgent,
});
const formData = mapServiceToFormData(initialData);
form.reset(formData);
// Log for debugging
console.log("Populating form with data:", {
type: validType,
url: urlValue,
port: portValue,
regionalAgent,
regionalMonitoringEnabled: Boolean(initialData.regional_monitoring_enabled),
type: formData.type,
url: formData.url,
port: formData.port,
regionalAgent: formData.regionalAgent,
regionalMonitoringEnabled: formData.regionalMonitoringEnabled,
regional_status: initialData.regional_status,
region_name: initialData.region_name,
agent_id: initialData.agent_id
agent_id: initialData.agent_id,
notification_status: initialData.notification_status,
notificationChannels: formData.notificationChannels
});
}
}, [initialData, isEdit, form]);
@@ -114,42 +69,9 @@ export function ServiceForm({
if (onSubmitStart) onSubmitStart();
try {
console.log("Form data being submitted:", data); // Debug log for submitted data
console.log("Form data being submitted:", data);
// Parse regional agent selection
let regionName = "";
let agentId = "";
// Only set region and agent if regional monitoring is enabled AND an agent is selected (not unassign)
if (data.regionalMonitoringEnabled && data.regionalAgent && data.regionalAgent !== "") {
const [parsedRegionName, parsedAgentId] = data.regionalAgent.split("|");
regionName = parsedRegionName || "";
agentId = parsedAgentId || "";
}
// Prepare service data with proper field mapping
const serviceData = {
name: data.name,
type: data.type,
interval: parseInt(data.interval),
retries: parseInt(data.retries),
notificationChannel: data.notificationChannel || undefined,
alertTemplate: data.alertTemplate || undefined,
regionalMonitoringEnabled: data.regionalMonitoringEnabled || false,
// Always set region_name and agent_id - empty strings when unassigned
regionName: regionName,
agentId: agentId,
// Map the URL field to appropriate database field based on service type
...(data.type === "dns"
? { domain: data.url, url: "", host: "", port: undefined } // DNS: store in domain field
: data.type === "ping"
? { host: data.url, url: "", domain: "", port: undefined } // PING: store in host field
: data.type === "tcp"
? { host: data.url, port: parseInt(data.port || "80"), url: "", domain: "" } // TCP: store in host and port fields
: { url: data.url, domain: "", host: "", port: undefined } // HTTP: store in url field
)
};
const serviceData = mapFormDataToServiceData(data);
console.log("Service data being sent:", serviceData);
if (isEdit && initialData) {
@@ -188,7 +110,7 @@ export function ServiceForm({
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6 pb-4">
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6 pb-6">
<div className="space-y-6">
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Basic Information</h3>
@@ -1,10 +1,12 @@
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
import { FormControl, FormField, FormItem, FormLabel, FormDescription } from "@/components/ui/form";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { X } from "lucide-react";
import { UseFormReturn } from "react-hook-form";
import { ServiceFormData } from "./types";
import { useQuery } from "@tanstack/react-query";
import { templateService } from "@/services/templateService";
import { alertConfigService, AlertConfiguration } from "@/services/alertConfigService";
import { useState, useEffect } from "react";
@@ -16,11 +18,13 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
const [alertConfigs, setAlertConfigs] = useState<AlertConfiguration[]>([]);
// Get the current form values for debugging
const notificationChannel = form.watch("notificationChannel");
const notificationStatus = form.watch("notificationStatus");
const notificationChannels = form.watch("notificationChannels") || [];
const alertTemplate = form.watch("alertTemplate");
console.log("Current notification values:", {
notificationChannel,
notificationStatus,
notificationChannels,
alertTemplate
});
@@ -30,12 +34,6 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
queryFn: () => alertConfigService.getAlertConfigurations(),
});
// Fetch templates for template selection
const { data: templates } = useQuery({
queryKey: ['templates'],
queryFn: () => templateService.getTemplates(),
});
// Update alert configs when data is loaded
useEffect(() => {
if (alertConfigsData) {
@@ -51,53 +49,110 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
// Log when form values change to debug
useEffect(() => {
console.log("Notification values changed:", {
notificationChannel: form.getValues("notificationChannel"),
alertTemplate: form.getValues("alertTemplate")
notificationStatus: form.getValues("notificationStatus"),
notificationChannels: form.getValues("notificationChannels")
});
}, [form.watch("notificationChannel"), form.watch("alertTemplate")]);
}, [form.watch("notificationStatus"), form.watch("notificationChannels")]);
const handleChannelAdd = (channelId: string) => {
const currentChannels = form.getValues("notificationChannels") || [];
if (!currentChannels.includes(channelId)) {
form.setValue("notificationChannels", [...currentChannels, channelId]);
}
};
const handleChannelRemove = (channelId: string) => {
const currentChannels = form.getValues("notificationChannels") || [];
form.setValue("notificationChannels", currentChannels.filter(id => id !== channelId));
};
const getSelectedChannelNames = () => {
return (notificationChannels || []).map(channelId => {
const config = alertConfigs.find(c => c.id === channelId);
return config ? `${config.notify_name} (${config.notification_type})` : channelId;
});
};
return (
<>
<FormField
control={form.control}
name="notificationChannel"
render={({ field }) => {
// Important: We need to preserve the actual value for notification channel
const fieldValue = field.value || "";
const displayValue = fieldValue === "" ? "none" : fieldValue;
console.log("Rendering notification channel field with value:", {
fieldValue,
displayValue
});
return (
<FormItem>
<FormLabel>Notification Channel</FormLabel>
<FormControl>
<Select
onValueChange={(value) => {
console.log("Notification channel changed to:", value);
field.onChange(value === "none" ? "" : value);
}}
value={displayValue}
>
<SelectTrigger>
<SelectValue placeholder="Select a notification channel" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">None</SelectItem>
{alertConfigs.map((config) => (
name="notificationStatus"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Enable Notifications
</FormLabel>
<FormDescription>
Enable or disable notifications for this service
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value === "enabled"}
onCheckedChange={(checked) => {
field.onChange(checked ? "enabled" : "disabled");
// Clear notification channels when disabled
if (!checked) {
form.setValue("notificationChannels", []);
}
}}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="notificationChannels"
render={({ field }) => (
<FormItem>
<FormLabel>Notification Channels</FormLabel>
<FormDescription>
{notificationStatus === "enabled"
? "Select notification channels for this service"
: "Enable notifications first to select channels"}
</FormDescription>
{/* Display selected channels as badges */}
{notificationChannels && notificationChannels.length > 0 && (
<div className="flex flex-wrap gap-2 mb-2">
{getSelectedChannelNames().map((channelName, index) => (
<Badge key={notificationChannels[index]} variant="secondary" className="flex items-center gap-1">
{channelName}
<X
className="h-3 w-3 cursor-pointer"
onClick={() => handleChannelRemove(notificationChannels[index])}
/>
</Badge>
))}
</div>
)}
<FormControl>
<Select
onValueChange={handleChannelAdd}
disabled={notificationStatus !== "enabled"}
value="" // Always reset to empty after selection
>
<SelectTrigger className={notificationStatus !== "enabled" ? 'opacity-50' : ''}>
<SelectValue placeholder="Add a notification channel" />
</SelectTrigger>
<SelectContent>
{alertConfigs
.filter(config => !notificationChannels?.includes(config.id || ""))
.map((config) => (
<SelectItem key={config.id} value={config.id || ""}>
{config.notify_name} ({config.notification_type})
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
</FormItem>
);
}}
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
/>
<FormField
@@ -118,24 +173,26 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
field.onChange(value === "default" ? "" : value);
}}
value={displayValue}
disabled={notificationStatus !== "enabled"}
>
<SelectTrigger>
<SelectTrigger className={notificationStatus !== "enabled" ? 'opacity-50' : ''}>
<SelectValue placeholder="Select an alert template" />
</SelectTrigger>
<SelectContent>
<SelectItem value="default">Default</SelectItem>
{templates?.map((template) => (
<SelectItem key={template.id} value={template.id}>
{template.name}
</SelectItem>
))}
{/* Add templates here when available */}
</SelectContent>
</Select>
</FormControl>
<FormDescription>
{notificationStatus === "enabled"
? "Choose a template for alert messages"
: "Enable notifications first to select template"}
</FormDescription>
</FormItem>
);
}}
/>
</>
);
}
}
@@ -0,0 +1,122 @@
import { Service } from "@/types/service.types";
import { ServiceFormData } from "./types";
export const getServiceFormDefaults = (): ServiceFormData => ({
name: "",
type: "http",
url: "",
port: "",
interval: "60",
retries: "3",
notificationStatus: "disabled",
notificationChannels: [],
alertTemplate: "",
regionalMonitoringEnabled: false,
regionalAgent: "",
});
export const mapServiceToFormData = (service: Service): ServiceFormData => {
// Ensure the type is one of the allowed values
const serviceType = (service.type || "http").toLowerCase();
const validType = ["http", "ping", "tcp", "dns"].includes(serviceType)
? serviceType as "http" | "ping" | "tcp" | "dns"
: "http";
// For PING services, use host field; for DNS use domain field; for TCP use host field; others use url
let urlValue = "";
let portValue = "";
if (validType === "ping") {
urlValue = service.host || "";
} else if (validType === "dns") {
urlValue = service.domain || "";
} else if (validType === "tcp") {
urlValue = service.host || "";
portValue = String(service.port || "");
} else {
urlValue = service.url || "";
}
// Handle regional monitoring data - check regional_status field
const isRegionalEnabled = service.regional_status === "enabled";
const regionalAgent = isRegionalEnabled && service.region_name && service.agent_id
? `${service.region_name}|${service.agent_id}`
: "";
// Handle notification channels - convert notification_channel and notificationChannel to array
const notificationChannels: string[] = [];
// Check for notification_channel field (from database)
if (service.notification_channel) {
notificationChannels.push(service.notification_channel);
}
// Also check for notificationChannel field (backward compatibility)
if (service.notificationChannel && !notificationChannels.includes(service.notificationChannel)) {
notificationChannels.push(service.notificationChannel);
}
console.log("Mapping service to form data:", {
serviceName: service.name,
notification_status: service.notification_status,
notification_channel: service.notification_channel,
notificationChannel: service.notificationChannel,
mappedChannels: notificationChannels
});
return {
name: service.name || "",
type: validType,
url: urlValue,
port: portValue,
interval: String(service.interval || 60),
retries: String(service.retries || 3),
notificationStatus: service.notification_status || "disabled",
notificationChannels: notificationChannels,
alertTemplate: service.alertTemplate === "default" ? "" : service.alertTemplate || "",
regionalMonitoringEnabled: isRegionalEnabled,
regionalAgent: regionalAgent,
};
};
export const mapFormDataToServiceData = (data: ServiceFormData) => {
// Parse regional agent selection
let regionName = "";
let agentId = "";
let regionalStatus: "enabled" | "disabled" = "disabled";
// Set regional status and agent data based on form values
if (data.regionalMonitoringEnabled) {
regionalStatus = "enabled";
if (data.regionalAgent && data.regionalAgent !== "") {
const [parsedRegionName, parsedAgentId] = data.regionalAgent.split("|");
regionName = parsedRegionName || "";
agentId = parsedAgentId || "";
}
}
// Prepare service data with proper field mapping
return {
name: data.name,
type: data.type,
interval: parseInt(data.interval),
retries: parseInt(data.retries),
notificationStatus: data.notificationStatus || "disabled",
notificationChannels: data.notificationChannels || [],
alertTemplate: data.alertTemplate === "default" ? "" : data.alertTemplate,
// Use regional_status field instead of regionalMonitoringEnabled
regionalStatus: regionalStatus,
regionName: regionName,
agentId: agentId,
// Map the URL field to appropriate database field based on service type
...(data.type === "dns"
? { domain: data.url, url: "", host: "", port: undefined } // DNS: store in domain field
: data.type === "ping"
? { host: data.url, url: "", domain: "", port: undefined } // PING: store in host field
: data.type === "tcp"
? { host: data.url, port: parseInt(data.port || "80"), url: "", domain: "" } // TCP: store in host and port fields
: { url: data.url, domain: "", host: "", port: undefined } // HTTP: store in url field
)
};
};
@@ -11,11 +11,12 @@ export const serviceSchema = z.object({
port: z.string().optional(),
interval: z.string(),
retries: z.string(),
notificationChannel: z.string().optional(),
notificationStatus: z.enum(["enabled", "disabled"]).optional(),
notificationChannels: z.array(z.string()).optional(),
alertTemplate: z.string().optional(),
// Regional monitoring fields
regionalMonitoringEnabled: z.boolean().optional(),
regionalAgent: z.string().optional(),
});
export type ServiceFormData = z.infer<typeof serviceSchema>;
export type ServiceFormData = z.infer<typeof serviceSchema>;
@@ -46,23 +46,23 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
queryKey: ['consolidatedUptimeHistory', serviceId, serviceType],
queryFn: async () => {
if (!serviceId) {
console.log('No serviceId provided, skipping fetch');
// console.log('No serviceId provided, skipping fetch');
return [];
}
console.log(`Fetching consolidated uptime data for service ${serviceId} of type ${serviceType}`);
// console.log(`Fetching consolidated uptime data for service ${serviceId} of type ${serviceType}`);
// Get ALL uptime history data - this should include both default and regional data
const rawData = await uptimeService.getUptimeHistory(serviceId, 100, undefined, undefined, serviceType);
console.log(`Retrieved ${rawData.length} total records for service ${serviceId}`);
console.log('Raw data sample:', rawData.slice(0, 5).map(d => ({
timestamp: d.timestamp,
region_name: d.region_name,
agent_id: d.agent_id,
status: d.status,
service_id: d.service_id,
response_time: d.responseTime
})));
// console.log(`Retrieved ${rawData.length} total records for service ${serviceId}`);
// console.log('Raw data sample:', rawData.slice(0, 5).map(d => ({
// timestamp: d.timestamp,
// region_name: d.region_name,
// agent_id: d.agent_id,
// status: d.status,
// service_id: d.service_id,
// response_time: d.responseTime
// })));
return rawData;
},
@@ -78,7 +78,7 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
const processConsolidatedData = (data: UptimeData[]): ConsolidatedTimeSlot[] => {
if (!data || data.length === 0) return [];
console.log(`Processing ${data.length} uptime records for consolidation`);
// console.log(`Processing ${data.length} uptime records for consolidation`);
// Create a map to group records by normalized timestamp (minute precision)
const timeSlotMap = new Map<string, Array<UptimeData & { source: string; isDefault: boolean }>>();
@@ -99,17 +99,17 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
// Regional monitoring data
sourceName = `${regionName} (Agent ${agentId})`;
isDefault = false;
console.log(`Found regional data: ${sourceName} for normalized timestamp ${normalizedTimestamp}`);
// console.log(`Found regional data: ${sourceName} for normalized timestamp ${normalizedTimestamp}`);
} else if (agentId && !regionName) {
// Default monitoring with specific agent
sourceName = `Default (Agent ${agentId})`;
isDefault = true;
console.log(`Found default monitoring: ${sourceName} for normalized timestamp ${normalizedTimestamp}`);
// console.log(`Found default monitoring: ${sourceName} for normalized timestamp ${normalizedTimestamp}`);
} else {
// Default monitoring fallback
sourceName = 'Default (Agent 1)';
sourceName = 'Default System Check (Agent 1)';
isDefault = true;
console.log(`Using fallback default monitoring for normalized timestamp ${normalizedTimestamp}`);
// console.log(`Using fallback default monitoring for normalized timestamp ${normalizedTimestamp}`);
}
// Get or create the array for this normalized timestamp
@@ -130,9 +130,9 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
isDefault
});
console.log(`Added record to normalized timestamp ${normalizedTimestamp}: Source=${sourceName}, Status=${record.status}, IsDefault=${isDefault}, ResponseTime=${record.responseTime}ms`);
// console.log(`Added record to normalized timestamp ${normalizedTimestamp}: Source=${sourceName}, Status=${record.status}, IsDefault=${isDefault}, ResponseTime=${record.responseTime}ms`);
} else {
console.log(`Skipping duplicate record for source ${sourceName} at timestamp ${normalizedTimestamp}`);
// console.log(`Skipping duplicate record for source ${sourceName} at timestamp ${normalizedTimestamp}`);
}
});
@@ -150,13 +150,13 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
.slice(0, 20); // Take the most recent 20 time slots
console.log(`Created ${consolidatedTimeline.length} consolidated time slots with normalized timestamps`);
consolidatedTimeline.forEach((slot, index) => {
console.log(`Slot ${index} - Normalized Timestamp: ${slot.timestamp}, Items: ${slot.items.length}`);
slot.items.forEach((item, itemIndex) => {
console.log(` Item ${itemIndex}: Source=${item.source}, Status=${item.status}, ResponseTime=${item.responseTime}ms, IsDefault=${item.isDefault}`);
});
});
// console.log(`Created ${consolidatedTimeline.length} consolidated time slots with normalized timestamps`);
// consolidatedTimeline.forEach((slot, index) => {
// console.log(`Slot ${index} - Normalized Timestamp: ${slot.timestamp}, Items: ${slot.items.length}`);
// slot.items.forEach((item, itemIndex) => {
// console.log(` Item ${itemIndex}: Source=${item.source}, Status=${item.status}, ResponseTime=${item.responseTime}ms, IsDefault=${item.isDefault}`);
// });
// });
return consolidatedTimeline;
};
@@ -164,12 +164,12 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
// Update consolidated items when data changes
useEffect(() => {
if (uptimeData && uptimeData.length > 0) {
console.log(`Processing consolidated uptime data for service ${serviceId}`);
// console.log(`Processing consolidated uptime data for service ${serviceId}`);
const processedData = processConsolidatedData(uptimeData);
// If service is currently paused, override ONLY the latest (first) bar with paused status
if (status === "paused" && processedData.length > 0) {
console.log(`Service ${serviceId} is paused, overriding latest bar with paused status`);
// console.log(`Service ${serviceId} is paused, overriding latest bar with paused status`);
// Create a paused entry for the latest timestamp
const latestTimestamp = new Date();
@@ -191,11 +191,11 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
// Replace the first item with paused status, keep the rest as historical data
const updatedData = [pausedSlot, ...processedData.slice(0, 19)];
console.log(`Updated data with paused latest bar, total bars: ${updatedData.length}`);
// console.log(`Updated data with paused latest bar, total bars: ${updatedData.length}`);
setConsolidatedItems(updatedData);
} else {
// Service is active (up/down/warning) - merge real data with any existing paused bars
console.log(`Service ${serviceId} is active with status: ${status}, merging with existing paused bars`);
// console.log(`Service ${serviceId} is active with status: ${status}, merging with existing paused bars`);
// Get existing paused bars from current state
const existingPausedBars = consolidatedItems.filter(slot =>
@@ -213,7 +213,7 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
const hasConflict = processedData.some(slot => slot.timestamp === pausedSlot.timestamp);
if (!hasConflict) {
mergedData.push(pausedSlot);
console.log(`Preserved paused bar at timestamp: ${pausedSlot.timestamp}`);
// console.log(`Preserved paused bar at timestamp: ${pausedSlot.timestamp}`);
}
});
@@ -222,12 +222,12 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
.slice(0, 20);
console.log(`Final merged data has ${finalData.length} slots`);
// console.log(`Final merged data has ${finalData.length} slots`);
setConsolidatedItems(finalData);
}
} else if (!serviceId || (uptimeData && uptimeData.length === 0)) {
// Generate placeholder data when no real data is available
console.log(`No uptime data available for service ${serviceId}, generating placeholder with status: ${status}`);
// console.log(`No uptime data available for service ${serviceId}, generating placeholder with status: ${status}`);
// Use the actual service status for placeholder data
const statusValue = status === "paused" ? "paused" :
@@ -252,7 +252,7 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
};
});
console.log(`Generated ${placeholderHistory.length} placeholder slots with status: ${statusValue}`);
// console.log(`Generated ${placeholderHistory.length} placeholder slots with status: ${statusValue}`);
setConsolidatedItems(placeholderHistory);
}
}, [uptimeData, serviceId, status, interval, consolidatedItems]);
@@ -23,7 +23,7 @@ export function useServiceActions(initialServices: Service[]) {
};
const handleViewDetail = (service: Service) => {
console.log(`Navigating to service detail for service ID: ${service.id}`);
// console.log(`Navigating to service detail for service ID: ${service.id}`);
navigate(`/service/${service.id}`);
};
@@ -115,7 +115,7 @@ export function useServiceActions(initialServices: Service[]) {
setSelectedService(null);
} catch (error) {
console.error("Error deleting service:", error);
// console.error("Error deleting service:", error);
toast({
variant: "destructive",
title: "Error",
@@ -134,7 +134,7 @@ export function useServiceActions(initialServices: Service[]) {
// Toggle the mute alerts status for this specific service
const newMuteStatus = !isMuted;
console.log(`${newMuteStatus ? "Muting" : "Unmuting"} alerts for service ${service.id} (${service.name})`);
// console.log(`${newMuteStatus ? "Muting" : "Unmuting"} alerts for service ${service.id} (${service.name})`);
// First update the local state immediately for better UI responsiveness
// Using proper type casting to ensure TypeScript knows we're creating valid Service objects
@@ -165,7 +165,7 @@ export function useServiceActions(initialServices: Service[]) {
await queryClient.invalidateQueries({ queryKey: ["services"] });
} catch (error) {
console.error("Error updating alert settings:", error);
// console.error("Error updating alert settings:", error);
// Revert the local state change if the server update failed
const revertedServices = services.map(s => {
@@ -27,8 +27,8 @@ export function LatestChecksTable({ uptimeData }: { uptimeData: UptimeData[] })
// Filter incidents by status
const incidents = useMemo(() => {
const statusChanges = getStatusChangeEvents(uptimeData);
console.log(`Total status changes: ${statusChanges.length}`);
console.log(`Status types in incidents: ${[...new Set(statusChanges.map(i => i.status))].join(', ')}`);
// console.log(`Total status changes: ${statusChanges.length}`);
// console.log(`Status types in incidents: ${[...new Set(statusChanges.map(i => i.status))].join(', ')}`);
if (statusFilter === "all") return statusChanges;
@@ -58,7 +58,7 @@ export function LatestChecksTable({ uptimeData }: { uptimeData: UptimeData[] })
// Calculate items per page for pagination display
const itemsPerPage = pageSize === "all" ? incidents.length : parseInt(pageSize, 10);
console.log(`Status Filter: ${statusFilter}, Incidents: ${incidents.length}, Includes paused: ${incidents.some(i => i.status === 'paused')}`);
// console.log(`Status Filter: ${statusFilter}, Incidents: ${incidents.length}, Includes paused: ${incidents.some(i => i.status === 'paused')}`);
return (
<Card className={`mb-6 transition-colors ${theme === 'dark' ? 'bg-card border-border' : 'bg-white border-gray-200'}`}>
@@ -68,7 +68,7 @@ export const getStatusChangeEvents = (uptimeData: UptimeData[]): UptimeData[] =>
}
}
console.log(`Found ${statusChanges.length} status changes, including paused status changes: ${statusChanges.some(i => i.status === 'paused')}`);
// console.log(`Found ${statusChanges.length} status changes, including paused status changes: ${statusChanges.some(i => i.status === 'paused')}`);
return statusChanges;
};
@@ -13,3 +13,4 @@ export * from './ServicesTableView';
export * from './ServiceDeleteDialog';
export * from './ServiceHistoryDialog';
export * from './ServiceEditDialog';
export * from './ServiceForm';
@@ -40,11 +40,11 @@ export const TemplateDialog: React.FC<TemplateDialogProps> = ({
// For debugging purposes
useEffect(() => {
if (open) {
console.log("Template dialog opened. Edit mode:", isEditMode, "Template ID:", templateId);
// console.log("Template dialog opened. Edit mode:", isEditMode, "Template ID:", templateId);
// Log form values when they change
const subscription = form.watch((value) => {
console.log("Current form values:", value);
// console.log("Current form values:", value);
});
return () => subscription.unsubscribe();
@@ -54,7 +54,7 @@ export const AddSSLCertificateForm = ({
setIsLoading(true);
try {
const configs = await alertConfigService.getAlertConfigurations();
console.log("Fetched notification channels:", configs);
// console.log("Fetched notification channels:", configs);
// Only include enabled channels
const enabledConfigs = configs.filter(config => {
// Handle the possibility of enabled being a string
@@ -66,7 +66,7 @@ export const AddSSLCertificateForm = ({
});
setAlertConfigs(enabledConfigs);
} catch (error) {
console.error("Error fetching notification channels:", error);
// console.error("Error fetching notification channels:", error);
toast.error(t('failedToLoadCertificates'));
} finally {
setIsLoading(false);
@@ -90,7 +90,7 @@ export const AddSSLCertificateForm = ({
await onSubmit(certData);
form.reset();
} catch (error) {
console.error("Error adding SSL certificate:", error);
// console.error("Error adding SSL certificate:", error);
toast.error(t('failedToAddCertificate'));
}
};
@@ -52,7 +52,7 @@ export const EditSSLCertificateForm = ({ certificate, onSubmit, onCancel, isPend
setIsLoading(true);
try {
const configs = await alertConfigService.getAlertConfigurations();
console.log("Fetched notification channels:", configs);
// console.log("Fetched notification channels:", configs);
// Only include enabled channels
const enabledConfigs = configs.filter(config => {
// Handle the possibility of enabled being a string
@@ -64,7 +64,7 @@ export const EditSSLCertificateForm = ({ certificate, onSubmit, onCancel, isPend
});
setAlertConfigs(enabledConfigs);
} catch (error) {
console.error("Error fetching notification channels:", error);
// console.error("Error fetching notification channels:", error);
toast.error(t('failedToLoadCertificates'));
} finally {
setIsLoading(false);
@@ -30,12 +30,12 @@ export const SSLDomainContent = () => {
queryKey: ['ssl-certificates'],
queryFn: async () => {
try {
console.log("Fetching SSL certificates from SSLDomainContent...");
// console.log("Fetching SSL certificates from SSLDomainContent...");
const result = await fetchSSLCertificates();
console.log("Received SSL certificates:", result);
// console.log("Received SSL certificates:", result);
return result;
} catch (error) {
console.error("Error fetching certificates:", error);
// console.error("Error fetching certificates:", error);
toast.error(t('failedToLoadCertificates'));
throw error;
}
@@ -53,7 +53,7 @@ export const SSLDomainContent = () => {
toast.success(t('sslCertificateAdded'));
},
onError: (error) => {
console.error("Error adding SSL certificate:", error);
// console.error("Error adding SSL certificate:", error);
toast.error(error instanceof Error ? error.message : t('failedToAddCertificate'));
}
});
@@ -61,7 +61,7 @@ export const SSLDomainContent = () => {
// Edit certificate mutation - Updated to ensure thresholds are properly updated
const editMutation = useMutation({
mutationFn: async (certificate: SSLCertificate) => {
console.log("Updating certificate with data:", certificate);
// console.log("Updating certificate with data:", certificate);
// Create the update data object
const updateData = {
@@ -70,12 +70,12 @@ export const SSLDomainContent = () => {
notification_channel: certificate.notification_channel,
};
console.log("Update data to be sent:", updateData);
// console.log("Update data to be sent:", updateData);
// Update certificate in the database using PocketBase directly
const updated = await pb.collection('ssl_certificates').update(certificate.id, updateData);
console.log("PocketBase update response:", updated);
// console.log("PocketBase update response:", updated);
// After updating the settings, refresh the certificate to ensure it's up to date
// This will also check if notification needs to be sent based on updated thresholds
@@ -90,7 +90,7 @@ export const SSLDomainContent = () => {
toast.success(t('sslCertificateUpdated'));
},
onError: (error) => {
console.error("Error updating SSL certificate:", error);
// console.error("Error updating SSL certificate:", error);
toast.error(error instanceof Error ? error.message : t('failedToUpdateCertificate'));
}
});
@@ -103,7 +103,7 @@ export const SSLDomainContent = () => {
toast.success(t('sslCertificateDeleted'));
},
onError: (error) => {
console.error("Error deleting SSL certificate:", error);
// console.error("Error deleting SSL certificate:", error);
toast.error(error instanceof Error ? error.message : t('failedToDeleteCertificate'));
}
});
@@ -117,7 +117,7 @@ export const SSLDomainContent = () => {
toast.success(t('sslCertificateRefreshed').replace('{domain}', data.domain));
},
onError: (error) => {
console.error("Error refreshing SSL certificate:", error);
// console.error("Error refreshing SSL certificate:", error);
let errorMessage = t('failedToCheckCertificate');
@@ -149,7 +149,7 @@ export const SSLDomainContent = () => {
}
},
onError: (error) => {
console.error("Error refreshing all certificates:", error);
// console.error("Error refreshing all certificates:", error);
toast.error(t('failedToCheckCertificate'));
setIsRefreshingAll(false);
@@ -174,7 +174,7 @@ export const SSLDomainContent = () => {
};
const handleUpdateCertificate = (certificate: SSLCertificate) => {
console.log("Handling certificate update with data:", certificate);
// console.log("Handling certificate update with data:", certificate);
editMutation.mutate(certificate);
};
+1
View File
@@ -1,3 +1,4 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
+8 -8
View File
@@ -29,7 +29,7 @@ export function useSystemSettings() {
queryKey: ['generalSettings'],
queryFn: async (): Promise<GeneralSettings | null> => {
try {
console.log('Fetching settings from API...');
// console.log('Fetching settings from API...');
const response = await fetch('/api/settings', {
method: 'POST',
headers: {
@@ -38,14 +38,14 @@ export function useSystemSettings() {
body: JSON.stringify({ action: 'getSettings' })
});
console.log('API response status:', response.status);
// console.log('API response status:', response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result: ApiResponse = await response.json();
console.log('API response data:', result);
// console.log('API response data:', result);
if (!result.success) {
throw new Error(result.message || 'Failed to fetch settings');
@@ -53,7 +53,7 @@ export function useSystemSettings() {
return result.data || null;
} catch (error) {
console.error('Error fetching settings:', error);
// console.error('Error fetching settings:', error);
toast({
title: t("errorFetchingSettings", "settings"),
description: error instanceof Error ? error.message : String(error),
@@ -68,7 +68,7 @@ export function useSystemSettings() {
// Update settings mutation
const updateSettingsMutation = useMutation({
mutationFn: async (updatedSettings: GeneralSettings): Promise<GeneralSettings> => {
console.log('Updating settings:', updatedSettings);
// console.log('Updating settings:', updatedSettings);
const response = await fetch('/api/settings', {
method: 'POST',
headers: {
@@ -101,7 +101,7 @@ export function useSystemSettings() {
});
},
onError: (error) => {
console.error('Error updating settings:', error);
// console.error('Error updating settings:', error);
toast({
title: t("errorSavingSettings", "settings"),
description: error instanceof Error ? error.message : String(error),
@@ -113,7 +113,7 @@ export function useSystemSettings() {
// Test email connection
const testEmailConnectionMutation = useMutation({
mutationFn: async (smtpConfig: any): Promise<{success: boolean, message: string}> => {
console.log('Testing email connection:', smtpConfig);
// console.log('Testing email connection:', smtpConfig);
const response = await fetch('/api/settings', {
method: 'POST',
headers: {
@@ -143,7 +143,7 @@ export function useSystemSettings() {
});
},
onError: (error) => {
console.error('Error testing connection:', error);
// console.error('Error testing connection:', error);
toast({
title: t("connectionFailed", "settings"),
description: error instanceof Error ? error.message : String(error),
+2 -2
View File
@@ -20,7 +20,7 @@ const Dashboard = () => {
// For debugging user data
useEffect(() => {
console.log("Current user data:", currentUser);
// console.log("Current user data:", currentUser);
}, [currentUser]);
// Handle logout
@@ -40,7 +40,7 @@ const Dashboard = () => {
useEffect(() => {
const startActiveServices = async () => {
await serviceService.startAllActiveServices();
console.log("Active services monitoring started");
// console.log("Active services monitoring started");
};
startActiveServices();
+4 -4
View File
@@ -26,13 +26,13 @@ const SslDomain = () => {
const { data: certificates = [], isLoading, error, refetch } = useQuery({
queryKey: ['ssl-certificates'],
queryFn: async () => {
console.log("Fetching SSL certificates from SslDomain page...");
// console.log("Fetching SSL certificates from SslDomain page...");
try {
const result = await fetchSSLCertificates();
console.log("SSL certificates fetch successful, count:", result.length);
// console.log("SSL certificates fetch successful, count:", result.length);
return result;
} catch (err) {
console.error("Error fetching SSL certificates from page:", err);
// console.error("Error fetching SSL certificates from page:", err);
throw err;
}
},
@@ -46,7 +46,7 @@ const SslDomain = () => {
const checkCertificates = async () => {
// Check if we should run daily check
if (shouldRunDailyCheck()) {
console.log("Running daily SSL certificate check...");
// console.log("Running daily SSL certificate check...");
await checkAllCertificatesAndNotify();
// Refresh certificate list after daily check
refetch();
+12 -12
View File
@@ -22,13 +22,13 @@ export interface AlertConfiguration {
export const alertConfigService = {
async getAlertConfigurations(): Promise<AlertConfiguration[]> {
console.info("Fetching alert configurations");
// console.info("Fetching alert configurations");
try {
const response = await pb.collection('alert_configurations').getList(1, 50);
console.info("Alert configurations response:", response);
// console.info("Alert configurations response:", response);
return response.items as AlertConfiguration[];
} catch (error) {
console.error("Error fetching alert configurations:", error);
// console.error("Error fetching alert configurations:", error);
toast({
title: "Error",
description: "Failed to load notification settings",
@@ -39,17 +39,17 @@ export const alertConfigService = {
},
async createAlertConfiguration(config: Omit<AlertConfiguration, 'id' | 'collectionId' | 'collectionName' | 'created' | 'updated'>): Promise<AlertConfiguration | null> {
console.info("Creating alert configuration:", config);
// console.info("Creating alert configuration:", config);
try {
const result = await pb.collection('alert_configurations').create(config);
console.info("Alert configuration created:", result);
// console.info("Alert configuration created:", result);
toast({
title: "Success",
description: "Notification settings saved successfully",
});
return result as AlertConfiguration;
} catch (error) {
console.error("Error creating alert configuration:", error);
// console.error("Error creating alert configuration:", error);
toast({
title: "Error",
description: "Failed to save notification settings",
@@ -60,17 +60,17 @@ export const alertConfigService = {
},
async updateAlertConfiguration(id: string, config: Partial<AlertConfiguration>): Promise<AlertConfiguration | null> {
console.info(`Updating alert configuration ${id}:`, config);
// console.info(`Updating alert configuration ${id}:`, config);
try {
const result = await pb.collection('alert_configurations').update(id, config);
console.info("Alert configuration updated:", result);
// console.info("Alert configuration updated:", result);
toast({
title: "Success",
description: "Notification settings updated successfully",
});
return result as AlertConfiguration;
} catch (error) {
console.error("Error updating alert configuration:", error);
// console.error("Error updating alert configuration:", error);
toast({
title: "Error",
description: "Failed to update notification settings",
@@ -81,17 +81,17 @@ export const alertConfigService = {
},
async deleteAlertConfiguration(id: string): Promise<boolean> {
console.info(`Deleting alert configuration ${id}`);
// console.info(`Deleting alert configuration ${id}`);
try {
await pb.collection('alert_configurations').delete(id);
console.info("Alert configuration deleted");
// console.info("Alert configuration deleted");
toast({
title: "Success",
description: "Notification channel removed",
});
return true;
} catch (error) {
console.error("Error deleting alert configuration:", error);
// console.error("Error deleting alert configuration:", error);
toast({
title: "Error",
description: "Failed to remove notification channel",
+7 -7
View File
@@ -19,7 +19,7 @@ export const authService = {
try {
// First try to login as a regular admin user
try {
console.log("Attempting to login as admin user");
// console.log("Attempting to login as admin user");
const authData = await pb.collection('users').authWithPassword(email, password);
return {
@@ -30,7 +30,7 @@ export const authService = {
role: authData.record.role || "admin"
};
} catch (error) {
console.log("Failed to login as admin, trying as superadmin", error);
// console.log("Failed to login as admin, trying as superadmin", error);
// If regular user login fails, try superadmin
const authData = await pb.collection('_superusers').authWithPassword(email, password);
@@ -44,7 +44,7 @@ export const authService = {
};
}
} catch (error) {
console.error('Login failed:', error);
// console.error('Login failed:', error);
throw new Error('Authentication failed. Please check your credentials.');
}
},
@@ -63,7 +63,7 @@ export const authService = {
if (!userData) return null;
// Log the full user data for debugging
console.log("Raw user data from authStore:", userData);
//console.log("Raw user data from authStore:", userData);
// Determine if this is a superadmin by checking the collection name
const isSuperAdmin = userData.collectionName === '_superusers';
@@ -90,13 +90,13 @@ export const authService = {
try {
// Fetch the latest user data from the server
const userId = (pb.authStore.model as any).id;
console.log("Refreshing user data for ID:", userId);
// console.log("Refreshing user data for ID:", userId);
// Use the getOne method directly to refresh the auth store
await pb.collection('users').getOne(userId);
console.log("User data refreshed successfully");
// console.log("User data refreshed successfully");
} catch (error) {
console.error('Failed to refresh user data:', error);
// console.error('Failed to refresh user data:', error);
}
}
};
@@ -3,12 +3,12 @@ import { IncidentItem } from './types';
// Export functions to update and invalidate the cache
export const updateCache = (data: IncidentItem[]) => {
console.log(`Updating cache with ${data.length} incidents`);
// console.log(`Updating cache with ${data.length} incidents`);
// The actual cache is now maintained in incidentFetch.ts
};
// Reset cache and request state
export const invalidateCache = () => {
console.log('Invalidating incidents cache');
// console.log('Invalidating incidents cache');
// The invalidation is handled in incidentFetch.ts implementation
};
@@ -25,7 +25,7 @@ export const isCacheValid = (): boolean => {
export const getAllIncidents = async (forceRefresh = false): Promise<IncidentItem[]> => {
// If a request is in progress, wait for it to complete rather than making a new one
if (isRequestInProgress) {
console.log('Request already in progress, waiting for completion');
// console.log('Request already in progress, waiting for completion');
try {
if (pendingRequest) {
await pendingRequest;
@@ -39,12 +39,12 @@ export const getAllIncidents = async (forceRefresh = false): Promise<IncidentIte
// Use cache if available, not expired, and not forced refresh
if (!forceRefresh && isCacheValid()) {
console.log('Using cached incidents data from', new Date(incidentsCache!.timestamp).toLocaleTimeString());
// console.log('Using cached incidents data from', new Date(incidentsCache!.timestamp).toLocaleTimeString());
return incidentsCache!.data;
}
try {
console.log('Fetching all incidents from API...');
// console.log('Fetching all incidents from API...');
isRequestInProgress = true;
// Implement timeout for the request
@@ -72,7 +72,7 @@ export const getAllIncidents = async (forceRefresh = false): Promise<IncidentIte
isRequestInProgress = false;
if (!result || !result.items) {
console.warn('No incidents found in API response');
// console.warn('No incidents found in API response');
return [];
}
@@ -81,7 +81,7 @@ export const getAllIncidents = async (forceRefresh = false): Promise<IncidentIte
// Update cache
updateCache(normalizedItems);
console.log(`Fetched ${normalizedItems.length} incidents at ${new Date().toLocaleTimeString()}`);
// console.log(`Fetched ${normalizedItems.length} incidents at ${new Date().toLocaleTimeString()}`);
return normalizedItems;
} catch (error) {
if ((error as any)?.isAbort) {
@@ -89,7 +89,7 @@ export const getAllIncidents = async (forceRefresh = false): Promise<IncidentIte
return incidentsCache?.data || [];
}
console.error('Error fetching incidents:', error);
// console.error('Error fetching incidents:', error);
// Clear states to allow retry
pendingRequest = null;
@@ -102,7 +102,7 @@ export const getAllIncidents = async (forceRefresh = false): Promise<IncidentIte
// Still return cached data even on error
if (incidentsCache) {
console.log('Returning stale cached data after error');
// console.log('Returning stale cached data after error');
return incidentsCache.data;
}
@@ -113,13 +113,13 @@ export const getAllIncidents = async (forceRefresh = false): Promise<IncidentIte
// Get incident by id
export const getIncidentById = async (id: string): Promise<IncidentItem | null> => {
try {
console.log(`Fetching incident with ID: ${id}`);
// console.log(`Fetching incident with ID: ${id}`);
// First check if the incident exists in the cache
if (isCacheValid() && incidentsCache) {
const cachedIncident = incidentsCache.data.find(incident => incident.id === id);
if (cachedIncident) {
console.log('Incident found in cache');
// console.log('Incident found in cache');
return cachedIncident;
}
}
@@ -136,7 +136,7 @@ export const getIncidentById = async (id: string): Promise<IncidentItem | null>
return normalizedIncident;
} catch (error) {
console.error(`Error fetching incident with ID ${id}:`, error);
// console.error(`Error fetching incident with ID ${id}:`, error);
if (error instanceof Error) {
throw new Error(`Failed to fetch incident: ${error.message}`);
@@ -16,20 +16,20 @@ export const maintenanceNotificationService = {
*/
async sendMaintenanceNotification({ maintenance, notificationType }: NotificationParams): Promise<boolean> {
try {
console.log(`Preparing to send ${notificationType} notification for maintenance: ${maintenance.title}`);
// console.log(`Preparing to send ${notificationType} notification for maintenance: ${maintenance.title}`);
// Get notification channel ID - try both fields
let notificationChannelId = maintenance.notification_channel_id;
// If notification_channel_id is empty, try to use notification_id
if (!notificationChannelId && maintenance.notification_id) {
console.log(`No notification_channel_id found, using notification_id: ${maintenance.notification_id}`);
// console.log(`No notification_channel_id found, using notification_id: ${maintenance.notification_id}`);
notificationChannelId = maintenance.notification_id;
}
// Check if maintenance has notification channel configured
if (!notificationChannelId) {
console.log("No notification channel configured for this maintenance");
// console.log("No notification channel configured for this maintenance");
return false;
}
@@ -41,19 +41,19 @@ export const maintenanceNotificationService = {
const config = await pb.collection('alert_configurations').getOne(notificationChannelId);
notificationConfig = config as unknown as AlertConfiguration;
} catch (error) {
console.error("Failed to fetch notification configuration:", error);
// console.error("Failed to fetch notification configuration:", error);
return false;
}
if (!notificationConfig.enabled) {
console.log("Notification channel is disabled");
// console.log("Notification channel is disabled");
return false;
}
// Create notification message based on type
const message = this.generateMaintenanceMessage(maintenance, notificationType);
console.log(`Sending ${notificationConfig.notification_type} notification with message: ${message}`);
// console.log(`Sending ${notificationConfig.notification_type} notification with message: ${message}`);
// Send notification based on channel type
if (notificationConfig.notification_type === 'telegram') {
@@ -62,10 +62,10 @@ export const maintenanceNotificationService = {
// Add more notification types here as needed
console.log(`Unsupported notification type: ${notificationConfig.notification_type}`);
// console.log(`Unsupported notification type: ${notificationConfig.notification_type}`);
return false;
} catch (error) {
console.error("Error sending maintenance notification:", error);
// console.error("Error sending maintenance notification:", error);
return false;
}
},
@@ -115,12 +115,12 @@ export const maintenanceNotificationService = {
// Set up scheduled maintenance notifications
export const setupMaintenanceNotificationsScheduler = () => {
console.log("Setting up maintenance notifications scheduler");
// console.log("Setting up maintenance notifications scheduler");
// Check every minute for maintenance that needs notifications
const checkInterval = setInterval(async () => {
try {
console.log("Checking for maintenance notifications to send...");
// console.log("Checking for maintenance notifications to send...");
const now = new Date();
// Fetch upcoming and ongoing maintenance
@@ -143,7 +143,7 @@ export const setupMaintenanceNotificationsScheduler = () => {
startTime <= new Date(now.getTime() + 60000) &&
startTime > new Date(now.getTime() - 60000)) {
console.log(`Maintenance ${maintenance.title} is starting now, sending notification`);
// console.log(`Maintenance ${maintenance.title} is starting now, sending notification`);
await maintenanceNotificationService.sendMaintenanceNotification({
maintenance,
notificationType: 'start'
@@ -160,7 +160,7 @@ export const setupMaintenanceNotificationsScheduler = () => {
endTime <= new Date(now.getTime() + 60000) &&
endTime > new Date(now.getTime() - 60000)) {
console.log(`Maintenance ${maintenance.title} is ending now, sending notification`);
// console.log(`Maintenance ${maintenance.title} is ending now, sending notification`);
await maintenanceNotificationService.sendMaintenanceNotification({
maintenance,
notificationType: 'end'
@@ -173,7 +173,7 @@ export const setupMaintenanceNotificationsScheduler = () => {
}
}
} catch (error) {
console.error("Error checking maintenance notifications:", error);
// console.error("Error checking maintenance notifications:", error);
}
}, 60000); // Check every minute
@@ -186,7 +186,7 @@ let notificationScheduler: NodeJS.Timeout | null = null;
export const initMaintenanceNotifications = () => {
if (notificationScheduler === null) {
notificationScheduler = setupMaintenanceNotificationsScheduler();
console.log("Maintenance notifications scheduler initialized");
// console.log("Maintenance notifications scheduler initialized");
}
};
@@ -194,6 +194,6 @@ export const stopMaintenanceNotifications = () => {
if (notificationScheduler !== null) {
clearInterval(notificationScheduler);
notificationScheduler = null;
console.log("Maintenance notifications scheduler stopped");
// console.log("Maintenance notifications scheduler stopped");
}
};
@@ -12,13 +12,13 @@ export async function startAllActiveServices(): Promise<void> {
filter: 'status != "paused"'
});
console.log(`Starting monitoring for ${result.items.length} active services`);
// console.log(`Starting monitoring for ${result.items.length} active services`);
// Start monitoring each active service
for (const service of result.items) {
await startMonitoringService(service.id);
}
} catch (error) {
console.error("Error starting all active services:", error);
// console.error("Error starting all active services:", error);
}
}
@@ -9,7 +9,7 @@ export async function startMonitoringService(serviceId: string): Promise<void> {
try {
// First check if the service is already being monitored
if (monitoringIntervals.has(serviceId)) {
console.log(`Service ${serviceId} is already being monitored`);
// console.log(`Service ${serviceId} is already being monitored`);
return;
}
@@ -18,11 +18,11 @@ export async function startMonitoringService(serviceId: string): Promise<void> {
// If service was manually paused, don't auto-resume
if (service.status === "paused") {
console.log(`Service ${serviceId} (${service.name}) is paused. Not starting monitoring.`);
// console.log(`Service ${serviceId} (${service.name}) is paused. Not starting monitoring.`);
return;
}
console.log(`Starting monitoring for service ${serviceId} (${service.name})`);
// console.log(`Starting monitoring for service ${serviceId} (${service.name})`);
// Update the service status to active/up in the database
await pb.collection('services').update(serviceId, {
@@ -32,18 +32,18 @@ export async function startMonitoringService(serviceId: string): Promise<void> {
// The actual service checking is now handled by the Go microservice
// This frontend service just tracks the monitoring state
const intervalMs = (service.heartbeat_interval || 60) * 1000;
console.log(`Service ${service.name} monitoring delegated to backend service`);
// console.log(`Service ${service.name} monitoring delegated to backend service`);
// Store a placeholder interval to track that this service is being monitored
const intervalId = window.setInterval(() => {
console.log(`Monitoring active for service ${service.name} (handled by backend)`);
// console.log(`Monitoring active for service ${service.name} (handled by backend)`);
}, intervalMs);
// Store the interval ID for this service
monitoringIntervals.set(serviceId, intervalId);
console.log(`Monitoring registered for service ${serviceId}`);
// console.log(`Monitoring registered for service ${serviceId}`);
} catch (error) {
console.error("Error starting service monitoring:", error);
// console.error("Error starting service monitoring:", error);
}
}
+14 -6
View File
@@ -1,4 +1,3 @@
import { pb } from '@/lib/pocketbase';
import { Service, CreateServiceParams, UptimeData } from '@/types/service.types';
import { monitoringService } from './monitoring';
@@ -28,6 +27,7 @@ export const serviceService = {
interval: item.heartbeat_interval || item.interval || 60,
retries: item.max_retries || item.retries || 3,
notificationChannel: item.notification_id,
notification_status: item.notification_status || "disabled",
alertTemplate: item.template_id,
muteAlerts: item.alerts === "muted", // Convert string to boolean for compatibility
alerts: item.alerts || "unmuted", // Store actual database field
@@ -39,12 +39,12 @@ export const serviceService = {
regional_monitoring_enabled: item.regional_status === "enabled", // Backward compatibility
}));
} catch (error) {
console.error("Error fetching services:", error);
// console.error("Error fetching services:", error);
throw new Error('Failed to load services data.');
}
},
async createService(params: CreateServiceParams): Promise<Service> {
async createService(params: any): Promise<Service> {
try {
// Convert service type to lowercase to avoid validation issues
const serviceType = params.type.toLowerCase();
@@ -61,7 +61,10 @@ export const serviceService = {
last_checked: new Date().toLocaleString(),
heartbeat_interval: params.interval,
max_retries: params.retries,
notification_id: params.notificationChannel,
notification_status: params.notificationStatus || "disabled",
notification_id: params.notificationChannels && params.notificationChannels.length > 0
? params.notificationChannels[0] // Store first channel for backward compatibility
: null,
template_id: params.alertTemplate,
// Regional monitoring fields - use regional_status
regional_status: params.regionalStatus || "disabled",
@@ -98,6 +101,7 @@ export const serviceService = {
interval: record.heartbeat_interval || 60,
retries: record.max_retries || 3,
notificationChannel: record.notification_id,
notification_status: record.notification_status || "disabled",
alertTemplate: record.template_id,
regional_status: record.regional_status || "disabled",
regional_monitoring_enabled: record.regional_status === "enabled",
@@ -115,7 +119,7 @@ export const serviceService = {
}
},
async updateService(id: string, params: CreateServiceParams): Promise<Service> {
async updateService(id: string, params: any): Promise<Service> {
try {
// Convert service type to lowercase to avoid validation issues
const serviceType = params.type.toLowerCase();
@@ -128,7 +132,10 @@ export const serviceService = {
service_type: serviceType,
heartbeat_interval: params.interval,
max_retries: params.retries,
notification_id: params.notificationChannel || null,
notification_status: params.notificationStatus || "disabled",
notification_id: params.notificationChannels && params.notificationChannels.length > 0
? params.notificationChannels[0] // Store first channel for backward compatibility
: null,
template_id: params.alertTemplate || null,
// Regional monitoring fields - use regional_status
regional_status: params.regionalStatus || "disabled",
@@ -172,6 +179,7 @@ export const serviceService = {
interval: record.heartbeat_interval || 60,
retries: record.max_retries || 3,
notificationChannel: record.notification_id,
notification_status: record.notification_status || "disabled",
alertTemplate: record.template_id,
regional_status: record.regional_status || "disabled",
regional_monitoring_enabled: record.regional_status === "enabled",
@@ -8,7 +8,7 @@ import { toast } from "sonner";
*/
export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
try {
console.log("Fetching SSL certificates from PocketBase...");
// console.log("Fetching SSL certificates from PocketBase...");
// Using the direct API path to fetch SSL certificates
const endpoint = "/api/collections/ssl_certificates/records";
@@ -23,7 +23,7 @@ export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
const queryString = new URLSearchParams(params as any).toString();
const fullEndpoint = `${endpoint}?${queryString}`;
console.log("Fetching SSL certificates from:", fullEndpoint);
// console.log("Fetching SSL certificates from:", fullEndpoint);
const response = await pb.send(fullEndpoint, {
method: "GET",
headers: {
@@ -39,14 +39,14 @@ export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
throw new Error("Invalid response format from PocketBase API");
}
console.log("Received SSL certificates:", response.items.length);
// console.log("Received SSL certificates:", response.items.length);
// Map items to SSLCertificate[] type with validation
return response.items.map(item => {
const cert = item as SSLCertificate;
// Log certificate details for debugging
console.log(`Certificate for ${cert.domain}: Issued by ${cert.issuer_o}, Days left: ${cert.days_left}`);
// console.log(`Certificate for ${cert.domain}: Issued by ${cert.issuer_o}, Days left: ${cert.days_left}`);
// Ensure dates are valid
try {
@@ -54,7 +54,7 @@ export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
if (cert.valid_till) new Date(cert.valid_till).toISOString();
if (cert.last_notified) new Date(cert.last_notified).toISOString();
} catch (e) {
console.warn("Invalid date found in certificate", cert.id, e);
// console.warn("Invalid date found in certificate", cert.id, e);
// Fix invalid dates if needed
if (cert.valid_from && isNaN(new Date(cert.valid_from).getTime())) {
cert.valid_from = new Date().toISOString();
@@ -74,7 +74,7 @@ export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
const diffTime = expirationDate.getTime() - currentDate.getTime();
cert.days_left = Math.ceil(diffTime / (1000 * 3600 * 24));
} catch (e) {
console.warn("Error calculating days_left for certificate", cert.id, e);
// console.warn("Error calculating days_left for certificate", cert.id, e);
cert.days_left = 0;
}
}
@@ -82,7 +82,7 @@ export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
return cert;
});
} catch (error) {
console.error("Error fetching SSL certificates:", error);
// console.error("Error fetching SSL certificates:", error);
toast.error("Failed to fetch SSL certificates. Please try again later.");
throw error;
}
+20 -20
View File
@@ -31,7 +31,7 @@ const getCollectionForServiceType = (serviceType: string): string => {
export const uptimeService = {
async recordUptimeData(data: UptimeData): Promise<void> {
try {
console.log(`Recording uptime data for service ${data.serviceId || data.service_id}: Status ${data.status}, Response time: ${data.responseTime}ms`);
// console.log(`Recording uptime data for service ${data.serviceId || data.service_id}: Status ${data.status}, Response time: ${data.responseTime}ms`);
const options = {
$autoCancel: false,
@@ -50,9 +50,9 @@ export const uptimeService = {
const keysToDelete = Array.from(uptimeCache.keys()).filter(key => key.includes(`uptime_${serviceId}`));
keysToDelete.forEach(key => uptimeCache.delete(key));
console.log(`Uptime data recorded successfully with ID: ${record.id}`);
// console.log(`Uptime data recorded successfully with ID: ${record.id}`);
} catch (error) {
console.error("Error recording uptime data:", error);
// console.error("Error recording uptime data:", error);
throw new Error(`Failed to record uptime data: ${error}`);
}
},
@@ -66,7 +66,7 @@ export const uptimeService = {
): Promise<UptimeData[]> {
try {
if (!serviceId) {
console.log('No serviceId provided to getUptimeHistory');
// console.log('No serviceId provided to getUptimeHistory');
return [];
}
@@ -75,13 +75,13 @@ export const uptimeService = {
// Check cache
const cached = uptimeCache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp) < cached.expiresIn) {
console.log(`Using cached uptime history for service ${serviceId}`);
// console.log(`Using cached uptime history for service ${serviceId}`);
return cached.data;
}
// Determine the correct collection based on service type
const collection = serviceType ? getCollectionForServiceType(serviceType) : 'uptime_data';
console.log(`Fetching default uptime history for service ${serviceId} from collection ${collection}, limit: ${limit}`);
// console.log(`Fetching default uptime history for service ${serviceId} from collection ${collection}, limit: ${limit}`);
// Build filter to get records for specific service_id
let filter = `service_id='${serviceId}'`;
@@ -91,7 +91,7 @@ export const uptimeService = {
const startUTC = startDate.toISOString();
const endUTC = endDate.toISOString();
console.log(`Date filter: ${startUTC} to ${endUTC}`);
// console.log(`Date filter: ${startUTC} to ${endUTC}`);
filter += ` && timestamp >= "${startUTC}" && timestamp <= "${endUTC}"`;
}
@@ -102,16 +102,16 @@ export const uptimeService = {
$cancelKey: `uptime_history_${serviceId}_${Date.now()}`
};
console.log(`Filter query for default data: ${filter} on collection: ${collection}`);
// console.log(`Filter query for default data: ${filter} on collection: ${collection}`);
const response = await pb.collection(collection).getList(1, limit, options);
console.log(`Fetched ${response.items.length} records for service ${serviceId} from ${collection}`);
// console.log(`Fetched ${response.items.length} records for service ${serviceId} from ${collection}`);
if (response.items.length > 0) {
console.log(`Date range in results: ${response.items[response.items.length - 1].timestamp} to ${response.items[0].timestamp}`);
// console.log(`Date range in results: ${response.items[response.items.length - 1].timestamp} to ${response.items[0].timestamp}`);
} else {
console.log(`No records found for service_id '${serviceId}' in collection: ${collection}`);
// console.log(`No records found for service_id '${serviceId}' in collection: ${collection}`);
}
// Transform the response items to UptimeData format
@@ -137,18 +137,18 @@ export const uptimeService = {
return uptimeData;
} catch (error) {
console.error(`Error fetching uptime history for service ${serviceId}:`, error);
// console.error(`Error fetching uptime history for service ${serviceId}:`, error);
// Try to return cached data as fallback
const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}_${serviceType || 'default'}_default`;
const cached = uptimeCache.get(cacheKey);
if (cached) {
console.log(`Using expired cached data for service ${serviceId} due to fetch error`);
// console.log(`Using expired cached data for service ${serviceId} due to fetch error`);
return cached.data;
}
// Return empty array instead of throwing to prevent UI crashes
console.log(`Returning empty array for service ${serviceId} due to fetch error`);
// console.log(`Returning empty array for service ${serviceId} due to fetch error`);
return [];
}
},
@@ -164,7 +164,7 @@ export const uptimeService = {
): Promise<UptimeData[]> {
try {
if (!regionName || !agentId) {
console.log('No region name or agent ID provided for regional query');
// console.log('No region name or agent ID provided for regional query');
return [];
}
@@ -173,13 +173,13 @@ export const uptimeService = {
// Check cache
const cached = uptimeCache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp) < cached.expiresIn) {
console.log(`Using cached regional uptime history for service ${serviceId}`);
// console.log(`Using cached regional uptime history for service ${serviceId}`);
return cached.data;
}
// Determine the correct collection based on service type
const collection = serviceType ? getCollectionForServiceType(serviceType) : 'uptime_data';
console.log(`Fetching regional uptime history from collection: ${collection} for service: ${serviceId}, region: ${regionName}, agent: ${agentId}`);
// console.log(`Fetching regional uptime history from collection: ${collection} for service: ${serviceId}, region: ${regionName}, agent: ${agentId}`);
// Build filter for regional agent data
let filter = `service_id="${serviceId}" && region_name="${regionName}" && agent_id="${agentId}"`;
@@ -190,7 +190,7 @@ export const uptimeService = {
filter += ` && timestamp>="${startISO}" && timestamp<="${endISO}"`;
}
console.log(`Regional filter query: ${filter} on collection: ${collection}`);
// console.log(`Regional filter query: ${filter} on collection: ${collection}`);
const records = await pb.collection(collection).getList(1, limit, {
sort: '-timestamp',
@@ -199,7 +199,7 @@ export const uptimeService = {
$cancelKey: `regional_uptime_history_${serviceId}_${Date.now()}`
});
console.log(`Retrieved ${records.items.length} regional uptime records from ${collection} for region ${regionName}, agent ${agentId}`);
// console.log(`Retrieved ${records.items.length} regional uptime records from ${collection} for region ${regionName}, agent ${agentId}`);
const uptimeData = records.items.map(item => ({
id: item.id,
@@ -224,7 +224,7 @@ export const uptimeService = {
return uptimeData;
} catch (error) {
const collectionForError = serviceType ? getCollectionForServiceType(serviceType) : 'uptime_data';
console.error(`Error fetching regional uptime history from ${collectionForError}:`, error);
// console.error(`Error fetching regional uptime history from ${collectionForError}:`, error);
return [];
}
}
+30 -30
View File
@@ -58,7 +58,7 @@ const convertToUserType = (record: any, role: string = "admin"): User => {
export const userService = {
async getUsers(): Promise<User[] | null> {
try {
console.log("Calling getUsers API");
// console.log("Calling getUsers API");
// Get both regular users and superadmins
const regularUsers = await pb.collection('users').getList(1, 50, {
@@ -71,9 +71,9 @@ export const userService = {
superadminUsers = await pb.collection('_superusers').getList(1, 50, {
sort: 'created',
});
console.log("Successfully fetched superadmin users:", superadminUsers);
// console.log("Successfully fetched superadmin users:", superadminUsers);
} catch (error) {
console.log("No superadmin collection or access rights:", error);
// console.log("No superadmin collection or access rights:", error);
}
// Combine both user types and mark superadmins
@@ -82,39 +82,39 @@ export const userService = {
...superadminUsers.items.map((user: any) => convertToUserType(user, "superadmin"))
];
console.log("Combined users list:", allUsers);
// console.log("Combined users list:", allUsers);
return allUsers;
} catch (error) {
console.error("Failed to fetch users:", error);
// console.error("Failed to fetch users:", error);
return null;
}
},
async getUser(id: string): Promise<User | null> {
try {
console.log(`Fetching user with ID: ${id}`);
// console.log(`Fetching user with ID: ${id}`);
// Try fetching from regular users first
try {
const user = await pb.collection('users').getOne(id);
console.log("User fetch result (regular user):", user);
// console.log("User fetch result (regular user):", user);
return convertToUserType(user, user.role || "admin");
} catch (error) {
console.log("User not found in regular users, trying superadmin collection");
// console.log("User not found in regular users, trying superadmin collection");
}
// If not found, try in superadmins
try {
const user = await pb.collection('_superusers').getOne(id);
console.log("User fetch result (superadmin):", user);
// console.log("User fetch result (superadmin):", user);
return convertToUserType(user, "superadmin");
} catch (error) {
console.log("User not found in superadmin collection either");
// console.log("User not found in superadmin collection either");
return null;
}
} catch (error) {
console.error(`Failed to fetch user ${id}:`, error);
// console.error(`Failed to fetch user ${id}:`, error);
return null;
}
},
@@ -130,11 +130,11 @@ export const userService = {
}
});
console.log("Updating user with clean data:", cleanData);
// console.log("Updating user with clean data:", cleanData);
// If there's nothing to update, return the current user
if (Object.keys(cleanData).length === 0) {
console.log("No changes to update");
// console.log("No changes to update");
const currentUser = await this.getUser(id);
return currentUser;
}
@@ -197,16 +197,16 @@ export const userService = {
await pb.collection('_superusers').delete(id);
updatedUser = convertToUserType(newRegularUser, "admin");
}
console.log("User transferred between collections due to role change");
// console.log("User transferred between collections due to role change");
} catch (error) {
console.error("Failed to transfer user between collections:", error);
// console.error("Failed to transfer user between collections:", error);
throw new Error("Failed to change user role: " + (error instanceof Error ? error.message : "Unknown error"));
}
} else {
// Regular update without changing collections
if (Object.keys(cleanData).length > 0) {
console.log("Final update payload to PocketBase:", cleanData);
// console.log("Final update payload to PocketBase:", cleanData);
try {
// Use the appropriate collection
@@ -214,15 +214,15 @@ export const userService = {
const updatedRecord = await pb.collection(collection).update(id, cleanData);
updatedUser = convertToUserType(updatedRecord, isCurrentlySuperadmin ? "superadmin" : "admin");
console.log("PocketBase update response:", updatedUser);
// console.log("PocketBase update response:", updatedUser);
// If email was updated successfully, show success message
if (hasEmailChange) {
console.log("Email updated successfully to:", emailToUpdate);
// console.log("Email updated successfully to:", emailToUpdate);
}
} catch (error) {
console.error("Error updating user:", error);
// console.error("Error updating user:", error);
// Provide more specific error messages for email issues
if (hasEmailChange && error instanceof Error) {
@@ -241,7 +241,7 @@ export const userService = {
return updatedUser;
} catch (error) {
console.error("Failed to update user:", error);
// console.error("Failed to update user:", error);
throw error; // Re-throw to handle in the component
}
},
@@ -253,7 +253,7 @@ export const userService = {
await pb.collection('users').delete(id);
return true;
} catch (error) {
console.log("User not found in regular users, trying superadmin collection");
// console.log("User not found in regular users, trying superadmin collection");
}
// If not found, try deleting from superadmin collection
@@ -261,11 +261,11 @@ export const userService = {
await pb.collection('_superusers').delete(id);
return true;
} catch (error) {
console.error("Failed to delete user from either collection:", error);
// console.error("Failed to delete user from either collection:", error);
return false;
}
} catch (error) {
console.error("Failed to delete user:", error);
// console.error("Failed to delete user:", error);
return false;
}
},
@@ -282,7 +282,7 @@ export const userService = {
if (cleanData.avatar.startsWith('http') ||
cleanData.avatar.startsWith('/upload/') ||
cleanData.avatar.includes('api.dicebear.com')) {
console.log("Removing avatar URL for new user creation:", cleanData.avatar);
// console.log("Removing avatar URL for new user creation:", cleanData.avatar);
delete cleanData.avatar;
}
}
@@ -291,18 +291,18 @@ export const userService = {
const isSuperAdmin = cleanData.role === "superadmin";
const collection = isSuperAdmin ? '_superusers' : 'users';
console.log(`Creating new user in ${collection} collection with data:`, {
...cleanData,
password: "[REDACTED]",
passwordConfirm: "[REDACTED]"
});
/// console.log(`Creating new user in ${collection} collection with data:`, {
// ...cleanData,
// password: "[REDACTED]",
// passwordConfirm: "[REDACTED]"
// });
// Create the user in the appropriate collection
const result = await pb.collection(collection).create(cleanData);
return convertToUserType(result, isSuperAdmin ? "superadmin" : "admin");
} catch (error) {
console.error("Failed to create user:", error);
// console.error("Failed to create user:", error);
throw error;
}
}
+1
View File
@@ -17,6 +17,7 @@ export interface Service {
updated?: string;
notification_channel?: string;
notificationChannel?: string; // Keep for backward compatibility
notification_status?: "enabled" | "disabled"; // Add notification_status field
alertTemplate?: string;
alerts?: "muted" | "unmuted"; // Make sure alerts is properly typed as union
muteAlerts?: boolean; // Keep this to avoid breaking existing code
@@ -0,0 +1,61 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1168766540")
// remove field
collection.fields.removeById("text1404634243")
// update field
collection.fields.addAt(15, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text258142582",
"max": 0,
"min": 0,
"name": "region_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1168766540")
// add field
collection.fields.addAt(17, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1404634243",
"max": 0,
"min": 0,
"name": "agent_ip_address",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// update field
collection.fields.addAt(15, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text258142582",
"max": 0,
"min": 0,
"name": "region",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})
@@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_171698555")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text258142582",
"max": 0,
"min": 0,
"name": "region_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_171698555")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text258142582",
"max": 0,
"min": 0,
"name": "region",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})
@@ -0,0 +1,137 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2018671343");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": "",
"deleteRule": "",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text407168695",
"max": 0,
"min": 0,
"name": "server_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1431356653",
"max": 0,
"min": 0,
"name": "pid",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number1391558374",
"max": null,
"min": null,
"name": "memory_percent",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number2651913466",
"max": null,
"min": null,
"name": "cpu_percent",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2063623452",
"max": 0,
"min": 0,
"name": "status",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_2018671343",
"indexes": [],
"listRule": "",
"name": "server_processes",
"system": false,
"type": "base",
"updateRule": "",
"viewRule": ""
});
return app.save(collection);
})
@@ -0,0 +1,170 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3414192583");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": "",
"deleteRule": "",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text407168695",
"max": 0,
"min": 0,
"name": "server_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3065852031",
"max": 0,
"min": 0,
"name": "message",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text887233555",
"max": 0,
"min": 0,
"name": "notification_type",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "date3805952114",
"max": "",
"min": "",
"name": "read_at",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"hidden": false,
"id": "number1377725610",
"max": null,
"min": null,
"name": "cpu_threshold",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number3087505384",
"max": null,
"min": null,
"name": "ram_threshold",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number2833320134",
"max": null,
"min": null,
"name": "disk_threshold",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number1826572927",
"max": null,
"min": null,
"name": "network_threshold",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number2184331740",
"max": null,
"min": null,
"name": "notification_config_id",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_3414192583",
"indexes": [],
"listRule": "",
"name": "server_notifications",
"system": false,
"type": "base",
"updateRule": "",
"viewRule": ""
});
return app.save(collection);
})
@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_171698555")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_171698555")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})