Merge branch 'operacle:develop' into develop
This commit is contained in:
@@ -20,7 +20,6 @@
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!-- IMPORTANT: DO NOT REMOVE THIS SCRIPT TAG OR THIS VERY COMMENT! -->
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getAuthHeaders, getBaseUrl, validateEmail } from '../utils';
|
||||
import { SettingsApiResponse } from '../types';
|
||||
|
||||
const createEmailTemplate = (template: string, data: any): { subject: string; htmlBody: string } => {
|
||||
let subject = 'Test Email from ReamStack';
|
||||
let subject = 'Test Email from CheckCle';
|
||||
let htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -13,7 +13,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p>If you received this email, your SMTP configuration is working correctly.</p>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">
|
||||
Sent from ReamStack Monitoring System<br>
|
||||
Sent from CheckCle Monitoring System<br>
|
||||
Template: ${template}<br>
|
||||
${data.collection ? `Collection: ${data.collection}` : ''}
|
||||
</p>
|
||||
@@ -24,7 +24,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
|
||||
switch (template) {
|
||||
case 'verification':
|
||||
subject = 'Email Verification Test - ReamStack';
|
||||
subject = 'Email Verification Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -37,14 +37,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'password-reset':
|
||||
subject = 'Password Reset Test - ReamStack';
|
||||
subject = 'Password Reset Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -57,14 +57,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'email-change':
|
||||
subject = 'Email Change Confirmation Test - ReamStack';
|
||||
subject = 'Email Change Confirmation Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -76,7 +76,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Template:</strong> Email Change Confirmation</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -178,10 +178,6 @@ export const sendTestEmail = async (data: any): Promise<SettingsApiResponse> =>
|
||||
smtpPort: smtpSettings.port || 587
|
||||
});
|
||||
|
||||
// For now, we'll simulate a successful email send
|
||||
// In a real implementation, you would integrate with your email service here
|
||||
// This could be nodemailer, SendGrid, or your PocketBase email system
|
||||
|
||||
// Simulate processing time
|
||||
console.log('Simulating email send...');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getAuthHeaders, getBaseUrl, validateEmail } from '../utils';
|
||||
import { SettingsApiResponse } from '../types';
|
||||
|
||||
const createEmailTemplate = (template: string, data: any): { subject: string; htmlBody: string } => {
|
||||
let subject = 'Test Email from ReamStack';
|
||||
let subject = 'Test Email from CheckCle';
|
||||
let htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -12,7 +12,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p>If you received this email, your SMTP configuration is working correctly.</p>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">
|
||||
Sent from ReamStack Monitoring System<br>
|
||||
Sent from CheckCle Monitoring System<br>
|
||||
Template: ${template}<br>
|
||||
${data.collection ? `Collection: ${data.collection}` : ''}
|
||||
</p>
|
||||
@@ -23,7 +23,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
|
||||
switch (template) {
|
||||
case 'verification':
|
||||
subject = 'Email Verification Test - ReamStack';
|
||||
subject = 'Email Verification Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -36,14 +36,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'password-reset':
|
||||
subject = 'Password Reset Test - ReamStack';
|
||||
subject = 'Password Reset Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -56,14 +56,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'email-change':
|
||||
subject = 'Email Change Confirmation Test - ReamStack';
|
||||
subject = 'Email Change Confirmation Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -75,7 +75,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Template:</strong> Email Change Confirmation</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -47,21 +47,21 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
||||
newSet.delete(serverId);
|
||||
return newSet;
|
||||
});
|
||||
console.log('Resume server monitoring:', serverId);
|
||||
// console.log('Resume server monitoring:', serverId);
|
||||
} else {
|
||||
setPausedServers(prev => new Set(prev).add(serverId));
|
||||
console.log('Pause server monitoring:', serverId);
|
||||
// console.log('Pause server monitoring:', serverId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (serverId: string) => {
|
||||
// TODO: Implement edit functionality
|
||||
console.log('Edit server:', serverId);
|
||||
// console.log('Edit server:', serverId);
|
||||
};
|
||||
|
||||
const handleDelete = (serverId: string) => {
|
||||
// TODO: Implement delete functionality
|
||||
console.log('Delete server:', serverId);
|
||||
// console.log('Delete server:', serverId);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ServiceUptimeHistory({
|
||||
const { data: uptimeHistory, isLoading, error } = useQuery({
|
||||
queryKey: ['uptimeHistory', serviceId, serviceType, startDate?.toISOString(), endDate?.toISOString()],
|
||||
queryFn: () => {
|
||||
console.log(`ServiceUptimeHistory: Fetching for service ${serviceId} of type ${serviceType}`);
|
||||
// console.log(`ServiceUptimeHistory: Fetching for service ${serviceId} of type ${serviceType}`);
|
||||
return uptimeService.getUptimeHistory(serviceId, 200, startDate, endDate, serviceType);
|
||||
},
|
||||
enabled: !!serviceId && !!serviceType,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious
|
||||
} from "@/components/ui/pagination";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "@/components/ui/select";
|
||||
import { PageSize } from "@/hooks/useServicesPagination";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
interface ServicesPaginationProps {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
pageSize: PageSize;
|
||||
totalItems: number;
|
||||
onPageChange: (page: number) => void;
|
||||
onPageSizeChange: (size: PageSize) => void;
|
||||
}
|
||||
|
||||
export function ServicesPagination({
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
}: ServicesPaginationProps) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
// Generate page numbers to display
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
const halfVisible = Math.floor(maxVisiblePages / 2);
|
||||
|
||||
let startPage = Math.max(1, currentPage - halfVisible);
|
||||
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||
|
||||
// Adjust start page if we don't have enough pages at the end
|
||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
const startItem = Math.min((currentPage - 1) * pageSize + 1, totalItems);
|
||||
const endItem = Math.min(currentPage * pageSize, totalItems);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between py-4 px-4 border-t border-border">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("rowsPerPage") ?? "Rows per page"}:
|
||||
</span>
|
||||
<Select
|
||||
value={pageSize.toString()}
|
||||
onValueChange={(value) => onPageSizeChange(parseInt(value) as PageSize)}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-[70px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="30">30</SelectItem>
|
||||
<SelectItem value="50">50</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||
{totalItems > 0
|
||||
? `${startItem}-${endItem} of ${totalItems} ${t("services") || "services"}`
|
||||
: `0 ${t("services") || "services"}`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
||||
className={
|
||||
currentPage === 1
|
||||
? "pointer-events-none opacity-50"
|
||||
: "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{getPageNumbers().map((page) => (
|
||||
<PaginationItem key={page}>
|
||||
<PaginationLink
|
||||
isActive={page === currentPage}
|
||||
onClick={() => onPageChange(page)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
|
||||
className={
|
||||
currentPage === totalPages
|
||||
? "pointer-events-none opacity-50"
|
||||
: "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { Service } from "@/types/service.types";
|
||||
import { ServicesTableView } from "./ServicesTableView";
|
||||
import { ServicesPagination } from "./ServicesPagination";
|
||||
import { ServiceDeleteDialog } from "./ServiceDeleteDialog";
|
||||
import { ServiceHistoryDialog } from "./ServiceHistoryDialog";
|
||||
import { ServiceEditDialog } from "./ServiceEditDialog";
|
||||
import { useServiceActions, useDialogState } from "./hooks";
|
||||
import { useServicesPagination } from "@/hooks/useServicesPagination";
|
||||
|
||||
interface ServicesTableContainerProps {
|
||||
services: Service[];
|
||||
@@ -36,6 +38,16 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
handleDeleteDialogChange
|
||||
} = useDialogState();
|
||||
|
||||
const {
|
||||
paginatedServices,
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
} = useServicesPagination({ services: localServices });
|
||||
|
||||
// Update local services state when props change
|
||||
useEffect(() => {
|
||||
updateServices(services);
|
||||
@@ -62,7 +74,7 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full">
|
||||
<ServicesTableView
|
||||
services={localServices}
|
||||
services={paginatedServices}
|
||||
onViewDetail={handleViewDetail}
|
||||
onPauseResume={handlePauseResume}
|
||||
onEdit={onEdit}
|
||||
@@ -70,6 +82,15 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
onMuteAlerts={handleMuteAlerts}
|
||||
/>
|
||||
|
||||
<ServicesPagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
pageSize={pageSize}
|
||||
totalItems={totalItems}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
/>
|
||||
|
||||
<ServiceHistoryDialog
|
||||
isOpen={isHistoryDialogOpen}
|
||||
onOpenChange={setIsHistoryDialogOpen}
|
||||
@@ -91,4 +112,4 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ export const ServicesTableView = ({
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg overflow-hidden border border-border flex-1 flex flex-col shadow-sm`}>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg overflow-hidden border border-border shadow-sm`}>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader className={`${theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'} sticky top-0 z-10`}>
|
||||
<TableRow className={`${theme === 'dark' ? 'border-gray-700 hover:bg-gray-800' : 'border-gray-200 hover:bg-gray-100'}`}>
|
||||
@@ -65,4 +65,4 @@ export const ServicesTableView = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -22,11 +22,11 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
|
||||
const notificationChannels = form.watch("notificationChannels") || [];
|
||||
const alertTemplate = form.watch("alertTemplate");
|
||||
|
||||
console.log("Current notification values:", {
|
||||
notificationStatus,
|
||||
notificationChannels,
|
||||
alertTemplate
|
||||
});
|
||||
// console.log("Current notification values:", {
|
||||
// notificationStatus,
|
||||
// notificationChannels,
|
||||
// alertTemplate
|
||||
// });
|
||||
|
||||
// Fetch alert configurations for notification channels
|
||||
const { data: alertConfigsData } = useQuery({
|
||||
@@ -42,16 +42,16 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
|
||||
setAlertConfigs(enabledChannels);
|
||||
|
||||
// Debug log to check what alert configs are loaded
|
||||
console.log("Loaded alert configurations:", enabledChannels);
|
||||
// console.log("Loaded alert configurations:", enabledChannels);
|
||||
}
|
||||
}, [alertConfigsData]);
|
||||
|
||||
// Log when form values change to debug
|
||||
useEffect(() => {
|
||||
console.log("Notification values changed:", {
|
||||
notificationStatus: form.getValues("notificationStatus"),
|
||||
notificationChannels: form.getValues("notificationChannels")
|
||||
});
|
||||
// console.log("Notification values changed:", {
|
||||
// notificationStatus: form.getValues("notificationStatus"),
|
||||
// notificationChannels: form.getValues("notificationChannels")
|
||||
// });
|
||||
}, [form.watch("notificationStatus"), form.watch("notificationChannels")]);
|
||||
|
||||
const handleChannelAdd = (channelId: string) => {
|
||||
@@ -161,7 +161,7 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
|
||||
render={({ field }) => {
|
||||
// Don't convert existing values to "default"
|
||||
const displayValue = field.value || "default";
|
||||
console.log("Rendering alert template field with value:", displayValue);
|
||||
// console.log("Rendering alert template field with value:", displayValue);
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
|
||||
@@ -39,7 +39,7 @@ export const ServiceRowActions = ({
|
||||
try {
|
||||
if (service.status === "paused") {
|
||||
// Resume monitoring
|
||||
console.log(`Resuming monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
// console.log(`Resuming monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
|
||||
// First ensure we update the status
|
||||
await serviceService.resumeMonitoring(service.id);
|
||||
@@ -53,7 +53,7 @@ export const ServiceRowActions = ({
|
||||
});
|
||||
} else {
|
||||
// Pause monitoring
|
||||
console.log(`Pausing monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
// console.log(`Pausing monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
await serviceService.pauseMonitoring(service.id);
|
||||
|
||||
toast({
|
||||
@@ -65,7 +65,7 @@ export const ServiceRowActions = ({
|
||||
// Call the parent handler to refresh the UI
|
||||
onPauseResume(service);
|
||||
} catch (error) {
|
||||
console.error("Error toggling monitoring:", error);
|
||||
// console.error("Error toggling monitoring:", error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
@@ -83,10 +83,10 @@ export const ServiceRowActions = ({
|
||||
|
||||
if (onMuteAlerts) {
|
||||
try {
|
||||
console.log(`Attempting to ${alertsMuted ? 'unmute' : 'mute'} alerts for service ${service.id} (${service.name})`);
|
||||
// console.log(`Attempting to ${alertsMuted ? 'unmute' : 'mute'} alerts for service ${service.id} (${service.name})`);
|
||||
await onMuteAlerts(service);
|
||||
} catch (error) {
|
||||
console.error("Error toggling alerts:", error);
|
||||
// console.error("Error toggling alerts:", error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
|
||||
@@ -83,7 +83,7 @@ export const AboutSystem: React.FC = () => {
|
||||
<span>{t('links')}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="font-medium text-base">
|
||||
{systemName || 'ReamStack'} {t('resources').toLowerCase()}
|
||||
{systemName || 'CheckCle'} {t('resources').toLowerCase()}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 pt-6">
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { Service } from '@/types/service.types';
|
||||
|
||||
export type PageSize = 10 | 30 | 50;
|
||||
|
||||
interface UseServicesPaginationProps {
|
||||
services: Service[];
|
||||
initialPageSize?: PageSize;
|
||||
}
|
||||
|
||||
export const useServicesPagination = ({
|
||||
services,
|
||||
initialPageSize = 10
|
||||
}: UseServicesPaginationProps) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState<PageSize>(initialPageSize);
|
||||
|
||||
const { paginatedServices, totalPages } = useMemo(() => {
|
||||
const totalItems = services.length;
|
||||
const pages = Math.ceil(totalItems / pageSize);
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
|
||||
return {
|
||||
paginatedServices: services.slice(startIndex, endIndex),
|
||||
totalPages: Math.max(1, pages)
|
||||
};
|
||||
}, [services, currentPage, pageSize]);
|
||||
|
||||
// Reset to first page when page size changes or services change
|
||||
const handlePageSizeChange = (newPageSize: PageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
// Reset to first page if current page exceeds total pages
|
||||
const handlePageChange = (page: number) => {
|
||||
const validPage = Math.min(Math.max(1, page), totalPages);
|
||||
setCurrentPage(validPage);
|
||||
};
|
||||
|
||||
return {
|
||||
paginatedServices,
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems: services.length,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
};
|
||||
};
|
||||
@@ -161,6 +161,6 @@ export function useSystemSettings() {
|
||||
isUpdating: updateSettingsMutation.isPending,
|
||||
testEmailConnection: testEmailConnectionMutation.mutate,
|
||||
isTestingConnection: testEmailConnectionMutation.isPending,
|
||||
systemName: settings?.system_name || settings?.meta?.appName || 'ReamStack',
|
||||
systemName: settings?.system_name || settings?.meta?.appName || 'CheckCle',
|
||||
};
|
||||
}
|
||||
@@ -38,8 +38,8 @@ export const generatePdf = async (incident: IncidentItem): Promise<string> => {
|
||||
doc.setProperties({
|
||||
title: title,
|
||||
subject: 'Incident Report',
|
||||
author: 'ReamStack System',
|
||||
creator: 'ReamStack',
|
||||
author: 'CheckCle System',
|
||||
creator: 'CheckCle',
|
||||
});
|
||||
|
||||
// Add header section
|
||||
|
||||
@@ -28,11 +28,11 @@ export const addHeader = (doc: jsPDF, incident: IncidentItem): number => {
|
||||
doc.setFontSize(10);
|
||||
doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 105, yPos, { align: 'center' });
|
||||
|
||||
// Add ReamStack logo or text
|
||||
// Add CheckCle logo or text
|
||||
yPos += 8;
|
||||
doc.setFontSize(12);
|
||||
doc.setFont(fonts.italic);
|
||||
doc.text('ReamStack Incident Management', 105, yPos, { align: 'center' });
|
||||
doc.text('CheckCle Incident Management', 105, yPos, { align: 'center' });
|
||||
|
||||
// Add horizontal line
|
||||
yPos += 5;
|
||||
@@ -53,7 +53,7 @@ export const addFooter = (doc: jsPDF): void => {
|
||||
doc.setFontSize(8);
|
||||
doc.setTextColor(100, 100, 100);
|
||||
doc.text(
|
||||
`Page ${i} of ${pageCount} | Generated by ReamStack Incident Management System`,
|
||||
`Page ${i} of ${pageCount} | Generated by CheckCle Incident Management System`,
|
||||
105,
|
||||
285,
|
||||
{ align: 'center' }
|
||||
|
||||
@@ -31,8 +31,8 @@ export async function pauseMonitoring(serviceId: string): Promise<void> {
|
||||
|
||||
// We'll skip the notification here since it will be handled by the UI component
|
||||
// This prevents duplicate notifications for the paused status
|
||||
console.log(`Service ${service.name} paused at ${now}, skipping notification to prevent duplication`);
|
||||
// console.log(`Service ${service.name} paused at ${now}, skipping notification to prevent duplication`);
|
||||
} catch (error) {
|
||||
console.error("Error pausing monitoring:", error);
|
||||
// console.error("Error pausing monitoring:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
// Fetch the current service to get its name for better logging
|
||||
const service = await pb.collection('services').getOne(serviceId);
|
||||
|
||||
console.log(`Resuming service ${service.name} at ${now}`);
|
||||
// console.log(`Resuming service ${service.name} at ${now}`);
|
||||
|
||||
// First, clear any existing interval just to be safe
|
||||
const existingInterval = monitoringIntervals.get(serviceId);
|
||||
@@ -57,7 +57,7 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
const alertsMuted = service.alerts === "muted" || serviceForNotification.alerts === "muted";
|
||||
|
||||
if (!alertsMuted) {
|
||||
console.log(`Alerts NOT muted for service ${service.name}, sending resume notification`);
|
||||
// console.log(`Alerts NOT muted for service ${service.name}, sending resume notification`);
|
||||
// Send notification that service has been resumed
|
||||
await notificationService.sendNotification({
|
||||
service: serviceForNotification,
|
||||
@@ -65,7 +65,7 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
timestamp: now
|
||||
});
|
||||
} else {
|
||||
console.log(`Alerts muted for service ${service.name}, skipping resume notification`);
|
||||
// console.log(`Alerts muted for service ${service.name}, skipping resume notification`);
|
||||
}
|
||||
|
||||
// IMPORTANT: Wait a brief moment to ensure the status update is processed
|
||||
@@ -76,6 +76,6 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
|
||||
console.log(`Service ${service.name} resumed and ready for monitoring`);
|
||||
} catch (error) {
|
||||
console.error("Error resuming service:", error);
|
||||
// console.error("Error resuming service:", error);
|
||||
}
|
||||
}
|
||||
@@ -31,20 +31,20 @@ export const notificationService = {
|
||||
try {
|
||||
const { service, status, responseTime } = data;
|
||||
|
||||
console.log(`Preparing to send notification for service: ${service.name}, status: ${status}`);
|
||||
console.log(`Service alerts status: ${service.alerts}`);
|
||||
// console.log(`Preparing to send notification for service: ${service.name}, status: ${status}`);
|
||||
// console.log(`Service alerts status: ${service.alerts}`);
|
||||
|
||||
// First check if alerts are muted for this service
|
||||
// STRICT equality check against "muted" string value
|
||||
if (service.alerts === "muted") {
|
||||
console.log(`NOTIFICATION BLOCKED: Alerts are muted for service: ${service.name}`);
|
||||
// console.log(`NOTIFICATION BLOCKED: Alerts are muted for service: ${service.name}`);
|
||||
return true; // Return true as this is expected behavior
|
||||
}
|
||||
|
||||
// For paused status, check if this is a duplicate notification from another source
|
||||
// This helps prevent the double-notification issue
|
||||
if (status === "paused" && data._notificationSource === "duplicate_check") {
|
||||
console.log("NOTIFICATION BLOCKED: Duplicate pause notification detected");
|
||||
// console.log("NOTIFICATION BLOCKED: Duplicate pause notification detected");
|
||||
return true; // Return true as this is expected behavior
|
||||
}
|
||||
|
||||
@@ -62,20 +62,20 @@ export const notificationService = {
|
||||
if (timeSinceLastNotif < NOTIFICATION_COOLDOWN) {
|
||||
// Increment count only if we haven't reached max retries
|
||||
if (lastNotif.count < maxRetries) {
|
||||
console.log(`DOWN notification for ${service.name}: ${lastNotif.count + 1}/${maxRetries}`);
|
||||
// console.log(`DOWN notification for ${service.name}: ${lastNotif.count + 1}/${maxRetries}`);
|
||||
lastNotifications[serviceId].count += 1;
|
||||
} else {
|
||||
console.log(`DOWN notification for ${service.name} skipped: Max retries (${maxRetries}) reached. Next notification after cooldown.`);
|
||||
// console.log(`DOWN notification for ${service.name} skipped: Max retries (${maxRetries}) reached. Next notification after cooldown.`);
|
||||
return true; // Skip notification but return success
|
||||
}
|
||||
} else {
|
||||
// Reset count after cooldown period
|
||||
console.log(`Cooldown period elapsed for ${service.name}. Resetting notification count.`);
|
||||
// console.log(`Cooldown period elapsed for ${service.name}. Resetting notification count.`);
|
||||
lastNotifications[serviceId] = { timestamp: now, count: 1 };
|
||||
}
|
||||
} else {
|
||||
// First notification for this service
|
||||
console.log(`First DOWN notification for ${service.name}: 1/${maxRetries}`);
|
||||
// console.log(`First DOWN notification for ${service.name}: 1/${maxRetries}`);
|
||||
lastNotifications[serviceId] = { timestamp: now, count: 1 };
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export const notificationService = {
|
||||
|
||||
// Check if notification channel is set
|
||||
if (!service.notificationChannel) {
|
||||
console.log(`No notification channel set for service: ${service.name}`);
|
||||
// console.log(`No notification channel set for service: ${service.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,12 +93,12 @@ export const notificationService = {
|
||||
const alertConfigRecord = await pb.collection('alert_configurations').getOne(service.notificationChannel);
|
||||
|
||||
if (!alertConfigRecord) {
|
||||
console.error(`Alert configuration not found for ID: ${service.notificationChannel}`);
|
||||
// console.error(`Alert configuration not found for ID: ${service.notificationChannel}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!alertConfigRecord.enabled) {
|
||||
console.log(`Alert configuration is disabled for service: ${service.name}`);
|
||||
// console.log(`Alert configuration is disabled for service: ${service.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ export const notificationService = {
|
||||
try {
|
||||
template = await templateService.getTemplate(service.alertTemplate);
|
||||
} catch (error) {
|
||||
console.error(`Error fetching template for ID: ${service.alertTemplate}`, error);
|
||||
// console.error(`Error fetching template for ID: ${service.alertTemplate}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ export const notificationService = {
|
||||
message += `\n\nAlert ${retryInfo.count}/${maxRetries}`;
|
||||
}
|
||||
|
||||
console.log(`Prepared notification message: ${message}`);
|
||||
// console.log(`Prepared notification message: ${message}`);
|
||||
|
||||
// Send notification based on notification type
|
||||
const notificationType = alertConfig.notification_type;
|
||||
@@ -156,10 +156,10 @@ export const notificationService = {
|
||||
}
|
||||
|
||||
// For other types like discord, slack, etc. (not implemented yet)
|
||||
console.log(`Notification type ${notificationType} not implemented yet`);
|
||||
// console.log(`Notification type ${notificationType} not implemented yet`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error("Error sending notification:", error);
|
||||
// console.error("Error sending notification:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@@ -173,10 +173,10 @@ export const notificationService = {
|
||||
? `Service ${serviceName} is UP${responseTime ? ` (Response time: ${responseTime}ms)` : ''}`
|
||||
: `Service ${serviceName} is DOWN`;
|
||||
|
||||
console.log(`Test notification would have been sent: ${message}`);
|
||||
// console.log(`Test notification would have been sent: ${message}`);
|
||||
return true; // Just log, don't actually send
|
||||
} catch (error) {
|
||||
console.error("Error in test notification:", error);
|
||||
// console.error("Error in test notification:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@@ -187,7 +187,7 @@ export const notificationService = {
|
||||
*/
|
||||
resetNotificationCount(serviceId: string): void {
|
||||
if (lastNotifications[serviceId]) {
|
||||
console.log(`Resetting notification count for service ${serviceId}`);
|
||||
// console.log(`Resetting notification count for service ${serviceId}`);
|
||||
delete lastNotifications[serviceId];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,15 @@ export type { Service, UptimeData, CreateServiceParams };
|
||||
export const serviceService = {
|
||||
async getServices(): Promise<Service[]> {
|
||||
try {
|
||||
const response = await pb.collection('services').getList(1, 50, {
|
||||
|
||||
// First get the total count of records
|
||||
const countResponse = await pb.collection('services').getList(1, 1, {
|
||||
sort: 'name',
|
||||
});
|
||||
const totalRecords = countResponse.totalItems;
|
||||
|
||||
// Then fetch all records using the total count as the limit
|
||||
const response = await pb.collection('services').getList(1, totalRecords, {
|
||||
sort: 'name',
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function sendSSLNotification(
|
||||
try {
|
||||
// Check if notification channel is set
|
||||
if (!certificate.notification_channel) {
|
||||
console.log(`No notification channel set for certificate: ${certificate.domain}`);
|
||||
// console.log(`No notification channel set for certificate: ${certificate.domain}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ export async function sendSSLNotification(
|
||||
const alertConfigRecord = await pb.collection('alert_configurations').getOne(certificate.notification_channel);
|
||||
|
||||
if (!alertConfigRecord) {
|
||||
console.error(`Alert configuration not found for ID: ${certificate.notification_channel}`);
|
||||
// console.error(`Alert configuration not found for ID: ${certificate.notification_channel}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!alertConfigRecord.enabled) {
|
||||
console.log(`Alert configuration is disabled for certificate: ${certificate.domain}`);
|
||||
// console.log(`Alert configuration is disabled for certificate: ${certificate.domain}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function sendSSLNotification(
|
||||
return await sendNotificationByType(alertConfig, certificate, message, isCritical, sslNotification);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error sending SSL notification:", error);
|
||||
// console.error("Error sending SSL notification:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ async function sendNotificationByType(
|
||||
// case 'slack':
|
||||
// return await sendSlackNotification(alertConfig, certificate, message, isCritical);
|
||||
default:
|
||||
console.log(`Notification type ${alertConfig.notification_type} not implemented yet for SSL certificates`);
|
||||
// console.log(`Notification type ${alertConfig.notification_type} not implemented yet for SSL certificates`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ async function sendTelegramNotification(
|
||||
isCritical: boolean
|
||||
): Promise<boolean> {
|
||||
if (!alertConfig.bot_token || !alertConfig.telegram_chat_id) {
|
||||
console.error("Missing Telegram bot token or chat ID");
|
||||
// console.error("Missing Telegram bot token or chat ID");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,10 +112,10 @@ export const deleteSSLCertificate = async (id: string): Promise<boolean> => {
|
||||
*/
|
||||
export const refreshAllCertificates = async (): Promise<{ success: number; failed: number }> => {
|
||||
try {
|
||||
const response = await pb.collection("ssl_certificates").getList(1, 100);
|
||||
const response = await pb.collection("ssl_certificates").getList(1, 200);
|
||||
const certificates = response.items as unknown as SSLCertificate[];
|
||||
|
||||
console.log(`Refreshing ${certificates.length} certificates...`);
|
||||
// console.log(`Refreshing ${certificates.length} certificates...`);
|
||||
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
@@ -125,14 +125,14 @@ export const refreshAllCertificates = async (): Promise<{ success: number; faile
|
||||
await checkCertificateAndNotify(cert);
|
||||
success++;
|
||||
} catch (error) {
|
||||
console.error(`Failed to refresh certificate ${cert.domain}:`, error);
|
||||
// console.error(`Failed to refresh certificate ${cert.domain}:`, error);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
return { success, failed };
|
||||
} catch (error) {
|
||||
console.error("Error refreshing certificates:", error);
|
||||
// console.error("Error refreshing certificates:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
import type { SSLCheckerResponse } from "./types";
|
||||
import { toast } from "sonner";
|
||||
import { checkWithFetch } from "./sslPrimaryChecker";
|
||||
import { normalizeDomain, createErrorResponse } from "./sslCheckerUtils";
|
||||
|
||||
/**
|
||||
@@ -10,7 +9,7 @@ import { normalizeDomain, createErrorResponse } from "./sslCheckerUtils";
|
||||
*/
|
||||
export const checkSSLCertificate = async (domain: string): Promise<SSLCheckerResponse> => {
|
||||
try {
|
||||
console.log(`Checking SSL certificate for domain: ${domain}`);
|
||||
// console.log(`Checking SSL certificate for domain: ${domain}`);
|
||||
|
||||
// Normalize domain (remove protocol if present)
|
||||
const normalizedDomain = normalizeDomain(domain);
|
||||
@@ -20,11 +19,11 @@ export const checkSSLCertificate = async (domain: string): Promise<SSLCheckerRes
|
||||
}
|
||||
|
||||
// Use the working CORS proxy approach
|
||||
const result = await checkWithFetch(normalizedDomain);
|
||||
console.log("SSL check result:", result);
|
||||
return result;
|
||||
// const result = await checkWithFetch(normalizedDomain);
|
||||
// console.log("SSL check result:", result);
|
||||
// return result;
|
||||
} catch (error) {
|
||||
console.error("SSL check failed completely:", error);
|
||||
//console.error("SSL check failed completely:", error);
|
||||
toast.error(`SSL check failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
return createErrorResponse(domain, error);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
|
||||
const endpoint = "/api/collections/ssl_certificates/records";
|
||||
const params = {
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
perPage: 200,
|
||||
sort: "-created",
|
||||
cache: Date.now() // Prevent caching by adding a timestamp
|
||||
};
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
import { createErrorResponse } from "./sslCheckerUtils";
|
||||
import type { SSLCheckerResponse } from "./types";
|
||||
|
||||
/**
|
||||
* Check SSL certificate using fetch with CORS proxy
|
||||
* This is our primary method that's proven to work
|
||||
*/
|
||||
export const checkWithFetch = async (domain: string): Promise<SSLCheckerResponse> => {
|
||||
console.log(`Checking SSL via CORS proxy for: ${domain}`);
|
||||
|
||||
try {
|
||||
// Use the working CORS proxy
|
||||
const corsProxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(`https://ssl-checker.io/api/v1/check/${domain}`)}`;
|
||||
console.log("Using CORS proxy for SSL check:", corsProxyUrl);
|
||||
|
||||
const proxyResponse = await fetch(corsProxyUrl);
|
||||
if (!proxyResponse.ok) {
|
||||
throw new Error(`CORS proxy request failed with status: ${proxyResponse.status}`);
|
||||
}
|
||||
|
||||
const proxyData = await proxyResponse.json();
|
||||
console.log("CORS proxy returned SSL data:", proxyData);
|
||||
return proxyData;
|
||||
} catch (error) {
|
||||
console.error("SSL check failed:", error);
|
||||
return createErrorResponse(domain, error);
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
export interface AboutTranslations {
|
||||
aboutReamStack: string;
|
||||
aboutCheckCle: string;
|
||||
systemDescription: string;
|
||||
systemVersion: string;
|
||||
license: string;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -62,30 +62,47 @@ func (c *PocketBaseClient) GetService(serviceID string) (*Service, error) {
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) GetActiveServices() ([]Service, error) {
|
||||
// Only fetch services that are not paused
|
||||
req, err := http.NewRequest("GET",
|
||||
fmt.Sprintf("%s/api/collections/services/records?filter=(status!='paused')", c.baseURL), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var allServices []Service
|
||||
page := 1
|
||||
perPage := 30 // Use default pagination size
|
||||
|
||||
for {
|
||||
// Fetch services page by page with filter for non-paused services
|
||||
req, err := http.NewRequest("GET",
|
||||
fmt.Sprintf("%s/api/collections/services/records?page=%d&perPage=%d&filter=(status!='paused')",
|
||||
c.baseURL, page, perPage), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No authentication header needed for public access
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch active services, status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var servicesResponse ServicesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&servicesResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add current page items to the result
|
||||
allServices = append(allServices, servicesResponse.Items...)
|
||||
|
||||
// Check if we've fetched all pages
|
||||
if page >= servicesResponse.TotalPages || len(servicesResponse.Items) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
// No authentication header needed for public access
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch active services, status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var servicesResponse ServicesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&servicesResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return servicesResponse.Items, nil
|
||||
return allServices, nil
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) UpdateServiceStatus(serviceID string, status string, responseTime int64, errorMessage string) error {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package pocketbase
|
||||
|
||||
import (
|
||||
@@ -11,37 +10,53 @@ import (
|
||||
)
|
||||
|
||||
type SSLCertificatesResponse struct {
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"perPage"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
Items []types.SSLCertificate `json:"items"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"perPage"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
Items []types.SSLCertificate `json:"items"`
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) GetSSLCertificates() ([]types.SSLCertificate, error) {
|
||||
url := fmt.Sprintf("%s/api/collections/ssl_certificates/records", c.baseURL)
|
||||
|
||||
resp, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch SSL certificates: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var allCertificates []types.SSLCertificate
|
||||
page := 1
|
||||
perPage := 200 // You can increase up to 500 if needed
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("PocketBase returned status %d", resp.StatusCode)
|
||||
for {
|
||||
url := fmt.Sprintf(
|
||||
"%s/api/collections/ssl_certificates/records?page=%d&perPage=%d",
|
||||
c.baseURL, page, perPage,
|
||||
)
|
||||
|
||||
resp, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch SSL certificates: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("PocketBase returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var response SSLCertificatesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode SSL certificates response: %v", err)
|
||||
}
|
||||
|
||||
allCertificates = append(allCertificates, response.Items...)
|
||||
|
||||
if page >= response.TotalPages || len(response.Items) == 0 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
var response SSLCertificatesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode SSL certificates response: %v", err)
|
||||
}
|
||||
|
||||
return response.Items, nil
|
||||
return allCertificates, nil
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) UpdateSSLCertificate(id string, data map[string]interface{}) error {
|
||||
url := fmt.Sprintf("%s/api/collections/ssl_certificates/records/%s", c.baseURL, id)
|
||||
|
||||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal SSL certificate data: %v", err)
|
||||
@@ -53,7 +68,7 @@ func (c *PocketBaseClient) UpdateSSLCertificate(id string, data map[string]inter
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update SSL certificate: %v", err)
|
||||
@@ -69,7 +84,7 @@ func (c *PocketBaseClient) UpdateSSLCertificate(id string, data map[string]inter
|
||||
|
||||
func (c *PocketBaseClient) GetSSLCertificateByID(id string) (*types.SSLCertificate, error) {
|
||||
url := fmt.Sprintf("%s/api/collections/ssl_certificates/records/%s", c.baseURL, id)
|
||||
|
||||
|
||||
resp, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch SSL certificate: %v", err)
|
||||
@@ -86,4 +101,4 @@ func (c *PocketBaseClient) GetSSLCertificateByID(id string) (*types.SSLCertifica
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user