Merge pull request #70 from operacle/develop

- Improve service listing to display all records correctly
- Fetch all data and handle pagination for better UX
- Improve SSL Certs listing to display all records correctly
- Added ISSUE TEMPLATE 
- Added CODE OF CONDUCT
This commit is contained in:
Tola Leng
2025-07-15 22:18:48 +07:00
committed by GitHub
53 changed files with 958 additions and 304 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
github: [tolaleng]
patreon: # Replace with a single Patreon username
open_collective: checkcle
open_collective: #checkcle
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+53
View File
@@ -0,0 +1,53 @@
name: Ask for Help
description: Ask a question or request guidance about this project.
title: "[Question]: "
labels: [question]
assignees: []
body:
- type: markdown
attributes:
value: |
**Thank you for reaching out!**
Please fill out this form to help us understand your question.
- type: textarea
id: question
attributes:
label: What do you need help with?
description: Clearly describe your question or what you're trying to achieve.
placeholder: |
I am trying to do X but I'm not sure how to...
validations:
required: true
- type: textarea
id: context
attributes:
label: Context
description: |
Provide any additional context, related issues, or links that help explain your question.
placeholder: "Related issues, pull requests, or documentation..."
validations:
required: false
- type: textarea
id: environment
attributes:
label: Environment Details
description: |
If applicable, please provide details about your environment (OS, Node version, browser, etc.).
placeholder: "Example: Ubuntu 22.04, Node.js v20, Chrome 125"
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Before submitting, please confirm:
options:
- label: I have searched existing issues and discussions.
required: true
- label: I have read the documentation.
required: true
+94
View File
@@ -0,0 +1,94 @@
name: Bug Report
description: Report a reproducible bug to help us improve.
title: "[Bug]: "
labels: [bug]
assignees: []
body:
- type: markdown
attributes:
value: |
**Thank you for taking the time to report a bug!**
Please fill out the following template so we can reproduce and fix the issue faster.
- type: input
id: environment
attributes:
label: Environment
description: |
Describe where you encountered the issue.
(e.g., OS, browser, Node version, etc.)
placeholder: "Example: macOS 14.0, Chrome 125, Node.js 20.3.0"
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug Description
description: |
A clear and concise description of what the bug is.
placeholder: "When I do X, Y happens instead of Z..."
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: |
How can we reproduce the behavior?
Please list the steps in order.
placeholder: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen?
placeholder: "The app should..."
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots or Videos
description: |
If applicable, add screenshots or screen recordings to help explain your problem.
placeholder: "Attach screenshots here."
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs
description: |
If applicable, paste logs or error messages here.
Please remove any sensitive information.
render: shell
placeholder: |
```
Error: Something went wrong
at index.js:123:45
```
validations:
required: false
- type: checkboxes
id: terms
attributes:
label: Checklist
description: Before submitting, please confirm:
options:
- label: I have searched existing issues to avoid duplicates.
required: true
- label: I have provided enough information for reproduction.
required: true
+49
View File
@@ -0,0 +1,49 @@
name: Documentation Improvement
description: Suggest improvements or report issues in documentation.
title: "[Docs]: "
labels: [documentation]
assignees: []
body:
- type: markdown
attributes:
value: |
Thank you for helping improve our documentation!
- type: textarea
id: description
attributes:
label: What part of the documentation needs improvement?
description: |
Describe the issue clearly.
placeholder: "The installation guide is missing steps for..."
validations:
required: true
- type: textarea
id: location
attributes:
label: Location
description: |
Provide a link or path to the affected documentation.
placeholder: "https://github.com/operacle/checkcle/docs/INSTALL.md"
validations:
required: true
- type: textarea
id: suggestion
attributes:
label: Suggested Change
description: |
How would you improve it?
placeholder: "I suggest adding a section about..."
validations:
required: false
- type: checkboxes
id: confirmation
attributes:
label: Checklist
options:
- label: I have searched existing issues for similar documentation problems.
required: true
@@ -0,0 +1,63 @@
name: Feature Request
description: Suggest an idea to improve this project.
title: "[Feature]: "
labels: [enhancement]
assignees: []
body:
- type: markdown
attributes:
value: |
**Thank you for suggesting a feature!**
Please fill out this form so we can better understand your idea.
- type: textarea
id: description
attributes:
label: Feature Description
description: |
A clear and concise description of the feature youd like to see.
placeholder: "I would like to have..."
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation
description: |
Please explain why this feature would be useful.
placeholder: "This feature would help because..."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: |
Have you considered any alternative solutions or workarounds?
placeholder: "I have tried..."
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: |
Add any other context or screenshots about the feature request here.
placeholder: "Links to related issues or references..."
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Before submitting, please confirm:
options:
- label: I have searched existing issues to make sure this feature hasnt been requested yet.
required: true
- label: I have described the feature clearly and provided supporting details.
required: true
+51
View File
@@ -0,0 +1,51 @@
name: Security Issue
description: Report a potential security vulnerability.
title: "[Security]: "
labels: [security]
assignees: []
body:
- type: markdown
attributes:
value: |
⚠️ **IMPORTANT: Please do NOT disclose sensitive security details in this public issue.**
If you believe you have found a security vulnerability, **please report it privately** to help keep users safe.
📧 **Disclosure Process:**
- Email: [security@checkcle.io](mailto:security@checkcle.io)
- Or follow our [SECURITY.md](../../blob/main/SECURITY.md) policy.
You can use this issue only to let maintainers know that you have sent or will send a report.
- type: textarea
id: summary
attributes:
label: Summary
description: |
Briefly describe the type of vulnerability you believe you have found (without including sensitive details).
placeholder: "Example: Possible SQL injection in the user login endpoint."
validations:
required: true
- type: textarea
id: contact
attributes:
label: Contact Information
description: |
Provide an email or other way for maintainers to contact you if we need clarification.
placeholder: "your.email@example.com"
validations:
required: true
- type: checkboxes
id: confirmation
attributes:
label: Confirmation
description: |
Please confirm:
options:
- label: I will not share sensitive exploit details in this issue.
required: true
- label: I have sent or will send a full report to the private disclosure contact.
required: true
@@ -0,0 +1,38 @@
name: Translation Request
description: Request a translation or report translation issues.
title: "[Translation]: "
labels: [translation]
assignees: []
body:
- type: markdown
attributes:
value: |
Help us make this project accessible in more languages!
- type: input
id: language
attributes:
label: Language
description: What language is this about?
placeholder: "Example: Spanish"
validations:
required: true
- type: textarea
id: description
attributes:
label: Details
description: |
Describe what youd like to have translated or what issue you found.
placeholder: "Please translate the Getting Started guide..."
validations:
required: true
- type: checkboxes
id: confirmation
attributes:
label: Checklist
options:
- label: I have searched existing issues for similar requests.
required: true
+81
View File
@@ -0,0 +1,81 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders of the **CheckCle** project pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all project spaces, and also applies when an individual is officially representing the project in public spaces. Examples include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project team at:
📧 **Email:** [hello@checkcle.io](mailto:hello@checkcle.io)
All complaints will be reviewed and investigated promptly and fairly.
All project maintainers are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Project maintainers will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
**1. Correction**
- *Community Impact:* Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
- *Consequence:* A private, written warning from project maintainers, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
**2. Warning**
- *Community Impact:* A violation through a single incident or series of actions.
- *Consequence:* A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
**3. Temporary Ban**
- *Community Impact:* A serious violation of community standards, including sustained inappropriate behavior.
- *Consequence:* A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
**4. Permanent Ban**
- *Community Impact:* Demonstrating a pattern of violation of community standards, including sustained harassment, or showing no remorse after being warned.
- *Consequence:* A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at:
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
For answers to common questions about this code of conduct, see:
https://www.contributor-covenant.org/faq
[homepage]: https://www.contributor-covenant.org
-1
View File
@@ -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>
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo, useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
@@ -38,8 +38,9 @@ interface EditOperationalPageDialogProps {
export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOperationalPageDialogProps) => {
const [selectedComponents, setSelectedComponents] = useState<Partial<StatusPageComponentRecord>[]>([]);
const [existingComponents, setExistingComponents] = useState<StatusPageComponentRecord[]>([]);
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
const [componentsLoaded, setComponentsLoaded] = useState(false);
const updateMutation = useUpdateOperationalPage();
const createComponentMutation = useCreateStatusPageComponent();
const deleteComponentMutation = useDeleteStatusPageComponent();
@@ -63,28 +64,42 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
},
});
useEffect(() => {
if (page) {
form.reset({
title: page.title,
description: page.description,
slug: page.slug,
theme: page.theme,
status: page.status,
is_public: page.is_public === 'true',
logo_url: page.logo_url || '',
custom_domain: page.custom_domain || '',
custom_css: page.custom_css || '',
page_style: page.page_style || '',
});
}
}, [page, form]);
// Memoize the form reset values to prevent unnecessary re-renders
const formValues = useMemo(() => {
if (!page) return null;
return {
title: page.title,
description: page.description,
slug: page.slug,
theme: page.theme,
status: page.status,
is_public: page.is_public === 'true',
logo_url: page.logo_url || '',
custom_domain: page.custom_domain || '',
custom_css: page.custom_css || '',
page_style: page.page_style || '',
};
}, [page?.id, page?.title, page?.description, page?.slug, page?.theme, page?.status, page?.is_public, page?.logo_url, page?.custom_domain, page?.custom_css, page?.page_style]);
// Reset form when page data changes
useEffect(() => {
if (components && components.length > 0) {
console.log('Loading existing components:', components);
setExistingComponents(components);
// Convert existing components to the format expected by ComponentsSelector
if (formValues) {
form.reset(formValues);
}
}, [formValues, form]);
// Convert components to selector format and initialize state - only when dialog opens and components change
useEffect(() => {
if (!open || !page?.id || !components) {
return;
}
// Only update if components actually changed or haven't been loaded yet
const componentIds = components.map(c => c.id).sort().join(',');
const currentSelectedIds = selectedComponents.map(c => c.id).filter(Boolean).sort().join(',');
if (componentIds !== currentSelectedIds || !componentsLoaded) {
// console.log('Loading existing components:', components);
const existingComponentsForSelector = components.map(comp => ({
id: comp.id,
name: comp.name,
@@ -94,25 +109,31 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
display_order: comp.display_order,
operational_status_id: comp.operational_status_id,
}));
setSelectedComponents(existingComponentsForSelector);
} else {
setExistingComponents([]);
setComponentsLoaded(true);
}
}, [open, page?.id, components, componentsLoaded, selectedComponents]);
// Reset state when dialog closes
useEffect(() => {
if (!open) {
setComponentsLoaded(false);
setSelectedComponents([]);
}
}, [components]);
}, [open]);
const handleComponentDelete = async (componentId: string) => {
const handleComponentDelete = useCallback(async (componentId: string) => {
try {
console.log('Deleting component:', componentId);
// console.log('Deleting component:', componentId);
await deleteComponentMutation.mutateAsync(componentId);
// Update local state to remove the deleted component
setSelectedComponents(prev => prev.filter(comp => comp.id !== componentId));
setExistingComponents(prev => prev.filter(comp => comp.id !== componentId));
} catch (error) {
console.error('Error deleting component:', error);
// console.error('Error deleting component:', error);
}
};
}, [deleteComponentMutation]);
const onSubmit = async (data: FormData) => {
if (!page) return;
@@ -133,21 +154,17 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
page_style: data.page_style || '',
};
console.log('Updating operational page with payload:', payload);
// console.log('Updating operational page with payload:', payload);
await updateMutation.mutateAsync({ id: page.id, data: payload });
// Handle component changes
const currentComponentIds = existingComponents.map(c => c.id);
const currentComponentIds = components.map(c => c.id);
const newComponentsToCreate = selectedComponents.filter(comp => !comp.id);
const componentsToKeep = selectedComponents.filter(comp => comp.id && currentComponentIds.includes(comp.id));
const componentsToDelete = existingComponents.filter(comp => !selectedComponents.some(selected => selected.id === comp.id));
const componentsToDelete = components.filter(comp => !selectedComponents.some(selected => selected.id === comp.id));
// Delete removed components (only if not already deleted via handleComponentDelete)
// Delete removed components
for (const component of componentsToDelete) {
if (selectedComponents.some(selected => selected.id === component.id)) {
continue; // Skip if already handled by handleComponentDelete
}
console.log('Deleting component during save:', component.id);
// console.log('Deleting component during save:', component.id);
await deleteComponentMutation.mutateAsync(component.id);
}
@@ -162,13 +179,13 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
display_order: component.display_order || 1,
};
console.log('Creating component with payload:', componentPayload);
// console.log('Creating component with payload:', componentPayload);
await createComponentMutation.mutateAsync(componentPayload);
}
onOpenChange(false);
} catch (error) {
console.error('Error updating operational page:', error);
// console.error('Error updating operational page:', error);
} finally {
setIsFormSubmitting(false);
}
@@ -40,10 +40,10 @@ export const IncidentBasicFields: React.FC = () => {
queryFn: async () => {
try {
const usersList = await userService.getUsers();
console.log("Fetched users for incident assignment:", usersList);
// console.log("Fetched users for incident assignment:", usersList);
return Array.isArray(usersList) ? usersList : [];
} catch (error) {
console.error("Failed to fetch users:", error);
// console.error("Failed to fetch users:", error);
return [];
}
},
@@ -53,7 +53,7 @@ export const IncidentBasicFields: React.FC = () => {
// Add user to assigned_to
const addUser = (userId: string) => {
// For now, we're using a single user assignment
console.log("Setting user ID in form:", userId);
// console.log("Setting user ID in form:", userId);
form.setValue('assigned_to', userId, { shouldValidate: true, shouldDirty: true });
setSelectedUserIds([userId]);
};
@@ -24,7 +24,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
isFetching
} = useServerHistoryData(serverId);
console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange);
//console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange);
// Memoize latest data calculation to prevent unnecessary recalculations
const latestData = useMemo(() => {
@@ -71,7 +71,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
}
if (error) {
console.error('ServerHistoryCharts: Error loading data:', error);
// console.error('ServerHistoryCharts: Error loading data:', error);
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
@@ -122,7 +122,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
);
}
console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange);
// console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange);
return (
<div className="space-y-6">
@@ -15,7 +15,7 @@ export function ServerSystemInfoCard({ server }: ServerSystemInfoCardProps) {
? JSON.parse(server.system_info)
: server.system_info;
} catch (error) {
console.log('Error parsing system_info:', error);
// console.log('Error parsing system_info:', error);
}
}
@@ -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) {
@@ -61,14 +61,14 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange):
const bufferMinutes = timeRange === '60m' ? 5 : timeRange === '1d' ? 30 : 60;
const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000) - (bufferMinutes * 60 * 1000));
console.log('filterMetricsByTimeRange: timeRange:', timeRange, 'cutoffTime:', cutoffTime.toISOString());
// console.log('filterMetricsByTimeRange: timeRange:', timeRange, 'cutoffTime:', cutoffTime.toISOString());
const filtered = metrics.filter(metric => {
const metricTime = new Date(metric.created || metric.timestamp);
return metricTime >= cutoffTime && metricTime <= now;
});
console.log('filterMetricsByTimeRange: Filtered', metrics.length, 'to', filtered.length, 'metrics');
// console.log('filterMetricsByTimeRange: Filtered', metrics.length, 'to', filtered.length, 'metrics');
// Sort by timestamp for proper chart display
return filtered.sort((a, b) =>
@@ -129,12 +129,12 @@ const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => {
// Optimized chart data formatting with better performance
export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
console.log('formatChartData: Input metrics count:', metrics?.length || 0, 'timeRange:', timeRange);
// console.log('formatChartData: Input metrics count:', metrics?.length || 0, 'timeRange:', timeRange);
if (!metrics?.length) return [];
const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
console.log('formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics');
// console.log('formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics');
if (!filteredMetrics.length) return [];
@@ -154,7 +154,7 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
? filteredMetrics.filter((_, index) => index % Math.ceil(filteredMetrics.length / maxDataPoints) === 0)
: filteredMetrics;
console.log('formatChartData: After sampling:', sampledMetrics.length, 'metrics for display');
// console.log('formatChartData: After sampling:', sampledMetrics.length, 'metrics for display');
// Batch process the data transformation for better performance
return sampledMetrics.map((metric) => {
@@ -17,9 +17,9 @@ export const useServerHistoryData = (serverId: string) => {
} = useQuery({
queryKey: ['server-metrics-history', serverId, timeRange],
queryFn: async () => {
console.log('useServerHistoryData: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
// console.log('useServerHistoryData: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
const result = await serverService.getServerMetrics(serverId, timeRange);
console.log('useServerHistoryData: Raw metrics result for timeRange', timeRange, ':', result?.length || 0, 'records');
// console.log('useServerHistoryData: Raw metrics result for timeRange', timeRange, ':', result?.length || 0, 'records');
return result || [];
},
enabled: !!serverId,
@@ -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>
@@ -19,10 +19,10 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
queryKey: ['strictDefaultUptimeHistory', serviceId, serviceType],
queryFn: async () => {
if (!serviceId) {
console.log('No serviceId provided, skipping fetch');
// console.log('No serviceId provided, skipping fetch');
return [];
}
console.log(`Fetching STRICT Default (Agent ID 1) uptime data for service ${serviceId} of type ${serviceType}`);
// console.log(`Fetching STRICT Default (Agent ID 1) uptime data for service ${serviceId} of type ${serviceType}`);
// Get raw uptime history data
const rawData = await uptimeService.getUptimeHistory(serviceId, 50, undefined, undefined, serviceType);
@@ -38,15 +38,15 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
);
if (isStrictDefault) {
console.log(`✓ ACCEPTED Default record: ${record.id} - ${record.timestamp} - Pure default monitoring`);
// console.log(`✓ ACCEPTED Default record: ${record.id} - ${record.timestamp} - Pure default monitoring`);
} else {
console.log(`✗ REJECTED non-default record: ${record.id} - region: ${record.region_name || 'none'}, agent: ${record.agent_id || 'none'}`);
// console.log(`✗ REJECTED non-default record: ${record.id} - region: ${record.region_name || 'none'}, agent: ${record.agent_id || 'none'}`);
}
return isStrictDefault;
});
console.log(`ULTRA-STRICT FILTER: Reduced ${rawData.length} records to ${strictDefaultData.length} pure default monitoring records`);
//console.log(`ULTRA-STRICT FILTER: Reduced ${rawData.length} records to ${strictDefaultData.length} pure default monitoring records`);
return strictDefaultData;
},
enabled: !!serviceId,
@@ -61,7 +61,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
const processUptimeData = (data: UptimeData[], intervalSeconds: number): UptimeData[] => {
if (!data || data.length === 0) return [];
console.log(`Processing ${data.length} strict default monitoring records for service ${serviceId}`);
// console.log(`Processing ${data.length} strict default monitoring records for service ${serviceId}`);
// Sort data by timestamp (newest first)
const sortedData = [...data].sort((a, b) =>
@@ -76,9 +76,9 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
if (!timestampMap.has(exactTimestamp)) {
timestampMap.set(exactTimestamp, record);
console.log(`✓ Added unique default record: ${record.id} - ${exactTimestamp}`);
// console.log(`✓ Added unique default record: ${record.id} - ${exactTimestamp}`);
} else {
console.log(`✗ REJECTED absolute duplicate timestamp: ${record.id} - ${exactTimestamp} (exact timestamp match)`);
// console.log(`✗ REJECTED absolute duplicate timestamp: ${record.id} - ${exactTimestamp} (exact timestamp match)`);
}
});
@@ -86,7 +86,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
const uniqueRecords = Array.from(timestampMap.values());
const recentData = uniqueRecords.slice(0, 20);
console.log(`FINAL RESULT: ${recentData.length} absolutely unique default monitoring records`);
//console.log(`FINAL RESULT: ${recentData.length} absolutely unique default monitoring records`);
return recentData;
};
@@ -94,12 +94,12 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
// Update history items when data changes
useEffect(() => {
if (uptimeData && uptimeData.length > 0) {
console.log(`Received ${uptimeData.length} strict default monitoring records for service ${serviceId}`);
// console.log(`Received ${uptimeData.length} strict default monitoring records for service ${serviceId}`);
const processedData = processUptimeData(uptimeData, interval);
setHistoryItems(processedData);
} else if (!serviceId || (uptimeData && uptimeData.length === 0)) {
// Generate placeholder data when no real data is available
console.log(`No strict default monitoring data available for service ${serviceId}, generating placeholder`);
// console.log(`No strict default monitoring data available for service ${serviceId}, generating placeholder`);
const statusValue = (status === "up" || status === "down" || status === "warning" || status === "paused")
? status
@@ -157,7 +157,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
seenTimestamps.add(item.timestamp);
absolutelyUniqueItems.push(item);
} else {
console.log(`FINAL VALIDATION: Removing absolute duplicate timestamp ${item.timestamp} from display`);
// console.log(`FINAL VALIDATION: Removing absolute duplicate timestamp ${item.timestamp} from display`);
}
});
@@ -166,7 +166,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
);
console.log(`DISPLAY READY: ${sortedValidated.length} absolutely validated default monitoring items`);
// console.log(`DISPLAY READY: ${sortedValidated.length} absolutely validated default monitoring items`);
return sortedValidated.slice(0, 20);
};
@@ -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",
@@ -40,7 +40,6 @@ export const AboutSystem: React.FC = () => {
<div>
<h1 className="text-3xl font-bold tracking-tight">{t('aboutSystem')}</h1>
<p className="text-muted-foreground text-base leading-relaxed mt-2">
{t('aboutCheckcle')}
</p>
</div>
@@ -83,7 +82,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">
@@ -52,7 +52,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
const queryClient = useQueryClient();
const isEditMode = !!templateId;
console.log("Template form initialized with templateId:", templateId, "isEditMode:", isEditMode);
// console.log("Template form initialized with templateId:", templateId, "isEditMode:", isEditMode);
const form = useForm<TemplateFormData>({
resolver: zodResolver(templateFormSchema),
@@ -74,7 +74,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
// Set form values when template data is loaded
useEffect(() => {
if (templateData && open) {
console.log("Setting form values with template data:", templateData);
// console.log("Setting form values with template data:", templateData);
form.reset({
name: templateData.name || "",
@@ -120,7 +120,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
onSuccess();
},
onError: (error) => {
console.error("Error creating template:", error);
// console.error("Error creating template:", error);
toast({
title: "Error",
description: "Failed to create template. Please check your inputs and try again.",
@@ -142,7 +142,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
onSuccess();
},
onError: (error) => {
console.error("Error updating template:", error);
// console.error("Error updating template:", error);
toast({
title: "Error",
description: "Failed to update template. Please check your inputs and try again.",
@@ -155,7 +155,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
// Handle form submission
const onSubmit = (formData: TemplateFormData) => {
console.log("Submitting form data:", formData);
// console.log("Submitting form data:", formData);
// Ensure all required fields are present
const completeData: CreateUpdateTemplateData = {
@@ -173,10 +173,10 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
};
if (isEditMode && templateId) {
console.log("Updating template with ID:", templateId);
// console.log("Updating template with ID:", templateId);
updateMutation.mutate({ id: templateId, data: completeData });
} else {
console.log("Creating new template");
// console.log("Creating new template");
createMutation.mutate(completeData);
}
};
@@ -184,7 +184,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
// Reset form when dialog closes
useEffect(() => {
if (!open) {
console.log("Dialog closed, resetting form");
// console.log("Dialog closed, resetting form");
form.reset(defaultFormValues);
}
}, [open, form]);
@@ -13,16 +13,16 @@ export const useUsersList = () => {
setLoading(true);
setError(null);
try {
console.log("Fetching users list");
// console.log("Fetching users list");
const data = await userService.getUsers();
console.log("Received users data:", data);
// console.log("Received users data:", data);
if (Array.isArray(data) && data.length >= 0) {
setUsers(data);
// Clear any previous errors
setError(null);
} else {
console.error("Invalid users data format:", data);
// console.error("Invalid users data format:", data);
setUsers([]);
setError("Invalid data format received from server");
toast({
@@ -32,7 +32,7 @@ export const useUsersList = () => {
});
}
} catch (error) {
console.error("Error fetching users:", error);
// console.error("Error fetching users:", error);
setError(error instanceof Error ? error.message : "Unknown error");
toast({
title: "Error fetching users",
@@ -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,
};
};
+1 -1
View File
@@ -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',
};
}
+2 -2
View File
@@ -24,7 +24,7 @@ const ServerDetail = () => {
const { sidebarCollapsed, toggleSidebar } = useSidebar();
const [currentUser, setCurrentUser] = useState(authService.getCurrentUser());
console.log('ServerDetail component loaded with serverId:', serverId);
//console.log('ServerDetail component loaded with serverId:', serverId);
const {
data: server,
@@ -46,7 +46,7 @@ const ServerDetail = () => {
};
if (serverError) {
console.error('Server detail error:', serverError);
// console.error('Server detail error:', serverError);
return (
<div className="flex h-screen overflow-hidden bg-background text-foreground">
<Sidebar collapsed={sidebarCollapsed} />
@@ -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);
}
}
+18 -18
View File
@@ -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];
}
}
+20 -20
View File
@@ -7,7 +7,7 @@ export const serverService = {
const records = await pb.collection('servers').getFullList<Server>();
return records;
} catch (error) {
console.error('Error fetching servers:', error);
// console.error('Error fetching servers:', error);
throw error;
}
},
@@ -17,22 +17,22 @@ export const serverService = {
const record = await pb.collection('servers').getOne<Server>(serverId);
return record;
} catch (error) {
console.error('Error fetching server:', error);
// console.error('Error fetching server:', error);
throw error;
}
},
async getServerMetrics(serverId: string, timeRange?: string): Promise<any[]> {
try {
console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
// console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
// First, get the server to find the correct server_id for metrics
let server;
try {
server = await this.getServer(serverId);
console.log('serverService.getServerMetrics: Found server:', server);
// console.log('serverService.getServerMetrics: Found server:', server);
} catch (error) {
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
// console.log('serverService.getServerMetrics: Could not fetch server details:', error);
}
// Try multiple filter strategies to find data
@@ -43,17 +43,17 @@ export const serverService = {
if (server && server.server_id) {
metricsServerId = server.server_id;
filter = `server_id = "${metricsServerId}"`;
console.log('serverService.getServerMetrics: Strategy 1 - Using server.server_id for metrics:', metricsServerId);
// console.log('serverService.getServerMetrics: Strategy 1 - Using server.server_id for metrics:', metricsServerId);
} else {
// Strategy 2: Use the serverId directly
filter = `server_id = "${serverId}"`;
console.log('serverService.getServerMetrics: Strategy 2 - Using serverId directly for metrics:', serverId);
// console.log('serverService.getServerMetrics: Strategy 2 - Using serverId directly for metrics:', serverId);
}
// Add agent_id filter if available in server data
if (server && server.agent_id) {
filter += ` && agent_id = "${server.agent_id}"`;
console.log('serverService.getServerMetrics: Added agent_id filter:', server.agent_id);
// console.log('serverService.getServerMetrics: Added agent_id filter:', server.agent_id);
}
// Add time range filter
@@ -83,10 +83,10 @@ export const serverService = {
const cutoffISO = cutoffTime.toISOString();
filter += ` && created >= "${cutoffISO}"`;
console.log('serverService.getServerMetrics: Using time filter from:', cutoffISO, 'to now');
// console.log('serverService.getServerMetrics: Using time filter from:', cutoffISO, 'to now');
}
console.log('serverService.getServerMetrics: Final filter:', filter);
// console.log('serverService.getServerMetrics: Final filter:', filter);
// Fetch filtered records with proper sorting
let records = await pb.collection('server_metrics').getFullList({
@@ -95,11 +95,11 @@ export const serverService = {
requestKey: null
});
console.log('serverService.getServerMetrics: Found', records.length, 'records with primary filter');
// console.log('serverService.getServerMetrics: Found', records.length, 'records with primary filter');
// If no records found with primary strategy, try fallback strategies
if (records.length === 0) {
console.log('serverService.getServerMetrics: No records found, trying fallback strategies...');
// console.log('serverService.getServerMetrics: No records found, trying fallback strategies...');
// Fallback 1: Try without agent_id filter
let fallbackFilter = `server_id = "${metricsServerId}"`;
@@ -131,19 +131,19 @@ export const serverService = {
fallbackFilter += ` && created >= "${cutoffISO}"`;
}
console.log('serverService.getServerMetrics: Trying fallback filter without agent_id:', fallbackFilter);
// console.log('serverService.getServerMetrics: Trying fallback filter without agent_id:', fallbackFilter);
records = await pb.collection('server_metrics').getFullList({
filter: fallbackFilter,
sort: '-created',
requestKey: null
});
console.log('serverService.getServerMetrics: Fallback found', records.length, 'records');
// console.log('serverService.getServerMetrics: Fallback found', records.length, 'records');
// Fallback 2: Try with different server_id strategies
if (records.length === 0) {
const alternativeIds = [serverId, server?.server_id, server?.id].filter(Boolean);
console.log('serverService.getServerMetrics: Trying alternative server IDs:', alternativeIds);
// console.log('serverService.getServerMetrics: Trying alternative server IDs:', alternativeIds);
for (const altId of alternativeIds) {
if (altId && altId !== metricsServerId) {
@@ -176,7 +176,7 @@ export const serverService = {
altFilter += ` && created >= "${cutoffISO}"`;
}
console.log('serverService.getServerMetrics: Trying alternative ID filter:', altFilter);
// console.log('serverService.getServerMetrics: Trying alternative ID filter:', altFilter);
const altRecords = await pb.collection('server_metrics').getFullList({
filter: altFilter,
sort: '-created',
@@ -184,7 +184,7 @@ export const serverService = {
});
if (altRecords.length > 0) {
console.log('serverService.getServerMetrics: Alternative ID found', altRecords.length, 'records');
// console.log('serverService.getServerMetrics: Alternative ID found', altRecords.length, 'records');
records = altRecords;
break;
}
@@ -193,14 +193,14 @@ export const serverService = {
}
}
console.log('serverService.getServerMetrics: Final result:', records.length, 'records found');
// console.log('serverService.getServerMetrics: Final result:', records.length, 'records found');
if (records.length > 0) {
console.log('serverService.getServerMetrics: Sample record:', records[0]);
// console.log('serverService.getServerMetrics: Sample record:', records[0]);
}
return records;
} catch (error) {
console.error('Error fetching server metrics:', error);
// console.error('Error fetching server metrics:', error);
throw error;
}
},
+9 -1
View File
@@ -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',
});
@@ -10,7 +10,7 @@ import { toast } from "sonner";
* This should be called once per day
*/
export async function checkAllCertificatesAndNotify(): Promise<void> {
console.log("Starting daily SSL certificates check...");
// console.log("Starting daily SSL certificates check...");
try {
// Fetch all SSL certificates from database
@@ -18,16 +18,16 @@ export async function checkAllCertificatesAndNotify(): Promise<void> {
// Properly cast the items as SSLCertificate
const certificates = response.items as unknown as SSLCertificate[];
console.log(`Found ${certificates.length} certificates to check`);
// console.log(`Found ${certificates.length} certificates to check`);
// Check each certificate
for (const cert of certificates) {
await checkCertificateAndNotify(cert);
}
console.log("Daily SSL certificates check completed");
// console.log("Daily SSL certificates check completed");
} catch (error) {
console.error("Error during SSL certificates daily check:", error);
// console.error("Error during SSL certificates daily check:", error);
}
}
@@ -37,7 +37,7 @@ export async function checkAllCertificatesAndNotify(): Promise<void> {
* Note: SSL checking is now handled by the Go service, this function focuses on notifications
*/
export async function checkCertificateAndNotify(certificate: SSLCertificate): Promise<boolean> {
console.log(`Checking certificate for ${certificate.domain}...`);
// console.log(`Checking certificate for ${certificate.domain}...`);
try {
// Use the current certificate data (updated by Go service)
@@ -47,7 +47,7 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
const warningThreshold = Number(certificate.warning_threshold) || 30;
const expiryThreshold = Number(certificate.expiry_threshold) || 7;
console.log(`Certificate ${certificate.domain} thresholds: warning=${warningThreshold}, expiry=${expiryThreshold}, days left=${daysLeft}`);
// console.log(`Certificate ${certificate.domain} thresholds: warning=${warningThreshold}, expiry=${expiryThreshold}, days left=${daysLeft}`);
// Update status based on thresholds
const status = determineSSLStatus(daysLeft, warningThreshold, expiryThreshold);
@@ -67,7 +67,7 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
isCritical = false;
}
console.log(`${certificate.domain}: ${daysLeft} days left, status: ${status}, should notify: ${shouldNotify}, critical: ${isCritical}`);
// console.log(`${certificate.domain}: ${daysLeft} days left, status: ${status}, should notify: ${shouldNotify}, critical: ${isCritical}`);
// Update certificate status in database
await pb.collection('ssl_certificates').update(certificate.id, {
@@ -76,7 +76,7 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
// Send notification if needed
if (shouldNotify && certificate.notification_channel) {
console.log(`Sending notification for ${certificate.domain}`);
// console.log(`Sending notification for ${certificate.domain}`);
// Different message based on expiry threshold
const message = isCritical
@@ -92,28 +92,28 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
last_notified: new Date().toISOString()
});
console.log(`Notification sent for ${certificate.domain}`);
// console.log(`Notification sent for ${certificate.domain}`);
// Show toast for manual checks
toast.success(`Notification sent for ${certificate.domain}`);
return true;
} else {
console.error(`Failed to send notification for ${certificate.domain}`);
// console.error(`Failed to send notification for ${certificate.domain}`);
// Show error toast for manual checks
toast.error(`Failed to send notification for ${certificate.domain}`);
return false;
}
} else if (shouldNotify && !certificate.notification_channel) {
console.log(`No notification channel set for ${certificate.domain}, skipping notification`);
// console.log(`No notification channel set for ${certificate.domain}, skipping notification`);
toast.info(`No notification channel set for ${certificate.domain}, skipping notification`);
} else {
console.log(`No notification needed for ${certificate.domain} (${daysLeft} days left)`);
// console.log(`No notification needed for ${certificate.domain} (${daysLeft} days left)`);
// For manual checks, inform the user that thresholds weren't met
toast.info(`Certificate for ${certificate.domain} is valid (${daysLeft} days left)`);
}
return true;
} catch (error) {
console.error(`Error checking certificate for ${certificate.domain}:`, error);
// console.error(`Error checking certificate for ${certificate.domain}:`, error);
toast.error(`Error checking certificate: ${error instanceof Error ? error.message : "Unknown error"}`);
return false;
}
@@ -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);
}
};
+14 -14
View File
@@ -8,7 +8,7 @@ export const serverService = {
const records = await pb.collection('servers').getFullList<Server>();
return records;
} catch (error) {
console.error('Error fetching servers:', error);
// console.error('Error fetching servers:', error);
throw error;
}
},
@@ -18,29 +18,29 @@ export const serverService = {
const record = await pb.collection('servers').getOne<Server>(serverId);
return record;
} catch (error) {
console.error('Error fetching server:', error);
// console.error('Error fetching server:', error);
throw error;
}
},
async getServerMetrics(serverId: string): Promise<any[]> {
try {
console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId);
// console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId);
// First, get the server to find the correct server_id for metrics
let server;
try {
server = await this.getServer(serverId);
console.log('serverService.getServerMetrics: Found server:', server);
// console.log('serverService.getServerMetrics: Found server:', server);
} catch (error) {
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
// console.log('serverService.getServerMetrics: Could not fetch server details:', error);
}
// Try to get metrics using the server's server_id field if available
let metricsServerId = serverId;
if (server && server.server_id) {
metricsServerId = server.server_id;
console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
// console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
}
// Try filtering by server_id first
@@ -50,11 +50,11 @@ export const serverService = {
requestKey: null
});
console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length);
// console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length);
// If no records found with server_id, try alternative approaches
if (filteredRecords.length === 0) {
console.log('serverService.getServerMetrics: No records found with server_id filter, trying alternatives...');
// console.log('serverService.getServerMetrics: No records found with server_id filter, trying alternatives...');
// Get all records to see what's available
const allRecords = await pb.collection('server_metrics').getFullList({
@@ -62,24 +62,24 @@ export const serverService = {
requestKey: null
});
console.log('serverService.getServerMetrics: Total server_metrics records:', allRecords.length);
// console.log('serverService.getServerMetrics: Total server_metrics records:', allRecords.length);
if (allRecords.length > 0) {
console.log('serverService.getServerMetrics: Sample record fields:', Object.keys(allRecords[0]));
console.log('serverService.getServerMetrics: Sample server_id values:', allRecords.slice(0, 5).map(r => r.server_id));
// console.log('serverService.getServerMetrics: Sample record fields:', Object.keys(allRecords[0]));
// console.log('serverService.getServerMetrics: Sample server_id values:', allRecords.slice(0, 5).map(r => r.server_id));
}
// For now, return some sample data from available records if server matches pattern
// This is temporary until the correct server_id mapping is established
if (allRecords.length > 0) {
console.log('serverService.getServerMetrics: Using available records as fallback');
// console.log('serverService.getServerMetrics: Using available records as fallback');
filteredRecords = allRecords.slice(0, 50); // Get recent 50 records
}
}
console.log('serverService.getServerMetrics: Returning', filteredRecords.length, 'records');
// console.log('serverService.getServerMetrics: Returning', filteredRecords.length, 'records');
return filteredRecords;
} catch (error) {
console.error('Error fetching server metrics:', error);
// console.error('Error fetching server metrics:', error);
throw error;
}
},
+1 -1
View File
@@ -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.
View File
+39 -22
View File
@@ -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 {
+40 -25
View File
@@ -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
}
}