diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index cca37f4..e4f6115 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/ask_for_help.yml b/.github/ISSUE_TEMPLATE/ask_for_help.yml new file mode 100644 index 0000000..0b058a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ask_for_help.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ae7046d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..e4c64a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..87cd7ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -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 you’d 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 hasn’t been requested yet. + required: true + - label: I have described the feature clearly and provided supporting details. + required: true diff --git a/.github/ISSUE_TEMPLATE/security_issue.yml b/.github/ISSUE_TEMPLATE/security_issue.yml new file mode 100644 index 0000000..ee44028 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/security_issue.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/translation_request.yml b/.github/ISSUE_TEMPLATE/translation_request.yml new file mode 100644 index 0000000..5e2b226 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/translation_request.yml @@ -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 you’d 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 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b0ca797 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -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 diff --git a/application/index.html b/application/index.html index da62e94..200a3e1 100644 --- a/application/index.html +++ b/application/index.html @@ -20,7 +20,6 @@
- diff --git a/application/src/api/settings/actions/sendTestEmail.ts b/application/src/api/settings/actions/sendTestEmail.ts index 5617cea..10200df 100644 --- a/application/src/api/settings/actions/sendTestEmail.ts +++ b/application/src/api/settings/actions/sendTestEmail.ts @@ -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 = ` @@ -13,7 +13,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

If you received this email, your SMTP configuration is working correctly.


- Sent from ReamStack Monitoring System
+ Sent from CheckCle Monitoring System
Template: ${template}
${data.collection ? `Collection: ${data.collection}` : ''}

@@ -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 = ` @@ -37,14 +37,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

Collection: ${data.collection || '_superusers'}


-

Sent from ReamStack Monitoring System

+

Sent from CheckCle Monitoring System

`; break; case 'password-reset': - subject = 'Password Reset Test - ReamStack'; + subject = 'Password Reset Test - CheckCle'; htmlBody = ` @@ -57,14 +57,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

Collection: ${data.collection || '_superusers'}


-

Sent from ReamStack Monitoring System

+

Sent from CheckCle Monitoring System

`; break; case 'email-change': - subject = 'Email Change Confirmation Test - ReamStack'; + subject = 'Email Change Confirmation Test - CheckCle'; htmlBody = ` @@ -76,7 +76,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

Template: Email Change Confirmation


-

Sent from ReamStack Monitoring System

+

Sent from CheckCle Monitoring System

@@ -178,10 +178,6 @@ export const sendTestEmail = async (data: any): Promise => 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)); diff --git a/application/src/api/settings/actions/testEmail.ts b/application/src/api/settings/actions/testEmail.ts index c3263aa..0557a86 100644 --- a/application/src/api/settings/actions/testEmail.ts +++ b/application/src/api/settings/actions/testEmail.ts @@ -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 = ` @@ -12,7 +12,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

If you received this email, your SMTP configuration is working correctly.


- Sent from ReamStack Monitoring System
+ Sent from CheckCle Monitoring System
Template: ${template}
${data.collection ? `Collection: ${data.collection}` : ''}

@@ -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 = ` @@ -36,14 +36,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

Collection: ${data.collection || '_superusers'}


-

Sent from ReamStack Monitoring System

+

Sent from CheckCle Monitoring System

`; break; case 'password-reset': - subject = 'Password Reset Test - ReamStack'; + subject = 'Password Reset Test - CheckCle'; htmlBody = ` @@ -56,14 +56,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

Collection: ${data.collection || '_superusers'}


-

Sent from ReamStack Monitoring System

+

Sent from CheckCle Monitoring System

`; break; case 'email-change': - subject = 'Email Change Confirmation Test - ReamStack'; + subject = 'Email Change Confirmation Test - CheckCle'; htmlBody = ` @@ -75,7 +75,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht

Template: Email Change Confirmation


-

Sent from ReamStack Monitoring System

+

Sent from CheckCle Monitoring System

diff --git a/application/src/components/operational-page/EditOperationalPageDialog.tsx b/application/src/components/operational-page/EditOperationalPageDialog.tsx index 24cf532..e42a2c1 100644 --- a/application/src/components/operational-page/EditOperationalPageDialog.tsx +++ b/application/src/components/operational-page/EditOperationalPageDialog.tsx @@ -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[]>([]); - const [existingComponents, setExistingComponents] = useState([]); 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 }, }); + // 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 (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 || '', - }); + if (formValues) { + form.reset(formValues); } - }, [page, form]); + }, [formValues, form]); + // Convert components to selector format and initialize state - only when dialog opens and components change useEffect(() => { - if (components && components.length > 0) { - console.log('Loading existing components:', components); - setExistingComponents(components); - // Convert existing components to the format expected by ComponentsSelector + 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); } diff --git a/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx b/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx index 33b35d5..7cb648b 100644 --- a/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx +++ b/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx @@ -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]); }; diff --git a/application/src/components/servers/ServerHistoryCharts.tsx b/application/src/components/servers/ServerHistoryCharts.tsx index 22dcfc9..274f966 100644 --- a/application/src/components/servers/ServerHistoryCharts.tsx +++ b/application/src/components/servers/ServerHistoryCharts.tsx @@ -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 (
@@ -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 (
diff --git a/application/src/components/servers/ServerSystemInfoCard.tsx b/application/src/components/servers/ServerSystemInfoCard.tsx index 893b60d..002a849 100644 --- a/application/src/components/servers/ServerSystemInfoCard.tsx +++ b/application/src/components/servers/ServerSystemInfoCard.tsx @@ -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); } } diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx index d906b44..c73172f 100644 --- a/application/src/components/servers/ServerTable.tsx +++ b/application/src/components/servers/ServerTable.tsx @@ -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) { diff --git a/application/src/components/servers/charts/dataUtils.ts b/application/src/components/servers/charts/dataUtils.ts index 17c935a..d58e2a0 100644 --- a/application/src/components/servers/charts/dataUtils.ts +++ b/application/src/components/servers/charts/dataUtils.ts @@ -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) => { diff --git a/application/src/components/servers/charts/hooks/useServerHistoryData.ts b/application/src/components/servers/charts/hooks/useServerHistoryData.ts index d28eae7..d08a437 100644 --- a/application/src/components/servers/charts/hooks/useServerHistoryData.ts +++ b/application/src/components/servers/charts/hooks/useServerHistoryData.ts @@ -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, diff --git a/application/src/components/services/ServiceUptimeHistory.tsx b/application/src/components/services/ServiceUptimeHistory.tsx index 9dca3a6..fa75419 100644 --- a/application/src/components/services/ServiceUptimeHistory.tsx +++ b/application/src/components/services/ServiceUptimeHistory.tsx @@ -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, diff --git a/application/src/components/services/ServicesPagination.tsx b/application/src/components/services/ServicesPagination.tsx new file mode 100644 index 0000000..a9fab1a --- /dev/null +++ b/application/src/components/services/ServicesPagination.tsx @@ -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 ( +
+
+ + {t("rowsPerPage") ?? "Rows per page"}: + + + + {totalItems > 0 + ? `${startItem}-${endItem} of ${totalItems} ${t("services") || "services"}` + : `0 ${t("services") || "services"}` + } + +
+ + {totalPages > 1 && ( + + + + onPageChange(Math.max(1, currentPage - 1))} + className={ + currentPage === 1 + ? "pointer-events-none opacity-50" + : "cursor-pointer" + } + /> + + + {getPageNumbers().map((page) => ( + + onPageChange(page)} + className="cursor-pointer" + > + {page} + + + ))} + + + onPageChange(Math.min(totalPages, currentPage + 1))} + className={ + currentPage === totalPages + ? "pointer-events-none opacity-50" + : "cursor-pointer" + } + /> + + + + )} +
+ ); +} \ No newline at end of file diff --git a/application/src/components/services/ServicesTableContainer.tsx b/application/src/components/services/ServicesTableContainer.tsx index d8e65db..589f4ee 100644 --- a/application/src/components/services/ServicesTableContainer.tsx +++ b/application/src/components/services/ServicesTableContainer.tsx @@ -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 (
+ +
); -} +} \ No newline at end of file diff --git a/application/src/components/services/ServicesTableView.tsx b/application/src/components/services/ServicesTableView.tsx index 5cdb36c..cc545ca 100644 --- a/application/src/components/services/ServicesTableView.tsx +++ b/application/src/components/services/ServicesTableView.tsx @@ -26,8 +26,8 @@ export const ServicesTableView = ({ const { t } = useLanguage(); return ( -
-
+
+
@@ -65,4 +65,4 @@ export const ServicesTableView = ({ ); -}; +}; \ No newline at end of file diff --git a/application/src/components/services/add-service/ServiceNotificationFields.tsx b/application/src/components/services/add-service/ServiceNotificationFields.tsx index 1d8441a..a8824ff 100644 --- a/application/src/components/services/add-service/ServiceNotificationFields.tsx +++ b/application/src/components/services/add-service/ServiceNotificationFields.tsx @@ -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 ( diff --git a/application/src/components/services/hooks/useDefaultUptimeData.ts b/application/src/components/services/hooks/useDefaultUptimeData.ts index 498bb76..9cdd546 100644 --- a/application/src/components/services/hooks/useDefaultUptimeData.ts +++ b/application/src/components/services/hooks/useDefaultUptimeData.ts @@ -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); }; diff --git a/application/src/components/services/service-row/ServiceRowActions.tsx b/application/src/components/services/service-row/ServiceRowActions.tsx index d2cd039..7cdc1e0 100644 --- a/application/src/components/services/service-row/ServiceRowActions.tsx +++ b/application/src/components/services/service-row/ServiceRowActions.tsx @@ -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", diff --git a/application/src/components/settings/about-system/AboutSystem.tsx b/application/src/components/settings/about-system/AboutSystem.tsx index ae2ce8d..b06e7d8 100644 --- a/application/src/components/settings/about-system/AboutSystem.tsx +++ b/application/src/components/settings/about-system/AboutSystem.tsx @@ -40,7 +40,6 @@ export const AboutSystem: React.FC = () => {

{t('aboutSystem')}

- {t('aboutCheckcle')}

@@ -83,7 +82,7 @@ export const AboutSystem: React.FC = () => { {t('links')} - {systemName || 'ReamStack'} {t('resources').toLowerCase()} + {systemName || 'CheckCle'} {t('resources').toLowerCase()} diff --git a/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts b/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts index 26684f8..02e953b 100644 --- a/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts +++ b/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts @@ -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({ 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]); diff --git a/application/src/components/settings/user-management/hooks/useUsersList.ts b/application/src/components/settings/user-management/hooks/useUsersList.ts index cacb611..288d903 100644 --- a/application/src/components/settings/user-management/hooks/useUsersList.ts +++ b/application/src/components/settings/user-management/hooks/useUsersList.ts @@ -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", diff --git a/application/src/hooks/useServicesPagination.ts b/application/src/hooks/useServicesPagination.ts new file mode 100644 index 0000000..960aca5 --- /dev/null +++ b/application/src/hooks/useServicesPagination.ts @@ -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(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, + }; +}; \ No newline at end of file diff --git a/application/src/hooks/useSystemSettings.tsx b/application/src/hooks/useSystemSettings.tsx index 31bdb82..195eee6 100644 --- a/application/src/hooks/useSystemSettings.tsx +++ b/application/src/hooks/useSystemSettings.tsx @@ -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', }; } \ No newline at end of file diff --git a/application/src/pages/ServerDetail.tsx b/application/src/pages/ServerDetail.tsx index 6522f3a..3dee273 100644 --- a/application/src/pages/ServerDetail.tsx +++ b/application/src/pages/ServerDetail.tsx @@ -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 (
diff --git a/application/src/services/incident/pdf/generator.ts b/application/src/services/incident/pdf/generator.ts index 6337757..5ce2ffd 100644 --- a/application/src/services/incident/pdf/generator.ts +++ b/application/src/services/incident/pdf/generator.ts @@ -38,8 +38,8 @@ export const generatePdf = async (incident: IncidentItem): Promise => { doc.setProperties({ title: title, subject: 'Incident Report', - author: 'ReamStack System', - creator: 'ReamStack', + author: 'CheckCle System', + creator: 'CheckCle', }); // Add header section diff --git a/application/src/services/incident/pdf/headerFooter.ts b/application/src/services/incident/pdf/headerFooter.ts index 9b3ff51..4e0c443 100644 --- a/application/src/services/incident/pdf/headerFooter.ts +++ b/application/src/services/incident/pdf/headerFooter.ts @@ -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' } diff --git a/application/src/services/monitoring/service-status/pauseMonitoring.ts b/application/src/services/monitoring/service-status/pauseMonitoring.ts index 7f353a7..677b09a 100644 --- a/application/src/services/monitoring/service-status/pauseMonitoring.ts +++ b/application/src/services/monitoring/service-status/pauseMonitoring.ts @@ -31,8 +31,8 @@ export async function pauseMonitoring(serviceId: string): Promise { // 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); } } diff --git a/application/src/services/monitoring/service-status/resumeMonitoring.ts b/application/src/services/monitoring/service-status/resumeMonitoring.ts index 47fdcc6..3bc16b1 100644 --- a/application/src/services/monitoring/service-status/resumeMonitoring.ts +++ b/application/src/services/monitoring/service-status/resumeMonitoring.ts @@ -16,7 +16,7 @@ export async function resumeMonitoring(serviceId: string): Promise { // 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 { 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 { 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 { console.log(`Service ${service.name} resumed and ready for monitoring`); } catch (error) { - console.error("Error resuming service:", error); + // console.error("Error resuming service:", error); } } \ No newline at end of file diff --git a/application/src/services/notificationService.ts b/application/src/services/notificationService.ts index a35b556..84ac207 100644 --- a/application/src/services/notificationService.ts +++ b/application/src/services/notificationService.ts @@ -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]; } } diff --git a/application/src/services/serverService.ts b/application/src/services/serverService.ts index e259df5..c808783 100644 --- a/application/src/services/serverService.ts +++ b/application/src/services/serverService.ts @@ -7,7 +7,7 @@ export const serverService = { const records = await pb.collection('servers').getFullList(); 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(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 { 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; } }, diff --git a/application/src/services/serviceService.ts b/application/src/services/serviceService.ts index 4421f5b..af22318 100644 --- a/application/src/services/serviceService.ts +++ b/application/src/services/serviceService.ts @@ -9,7 +9,15 @@ export type { Service, UptimeData, CreateServiceParams }; export const serviceService = { async getServices(): Promise { 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', }); diff --git a/application/src/services/ssl/notification/sslCheckNotifier.ts b/application/src/services/ssl/notification/sslCheckNotifier.ts index 356dadf..5657118 100644 --- a/application/src/services/ssl/notification/sslCheckNotifier.ts +++ b/application/src/services/ssl/notification/sslCheckNotifier.ts @@ -10,7 +10,7 @@ import { toast } from "sonner"; * This should be called once per day */ export async function checkAllCertificatesAndNotify(): Promise { - 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 { // 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 { * Note: SSL checking is now handled by the Go service, this function focuses on notifications */ export async function checkCertificateAndNotify(certificate: SSLCertificate): Promise { - 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; } diff --git a/application/src/services/ssl/notification/sslNotificationSender.ts b/application/src/services/ssl/notification/sslNotificationSender.ts index 32c2f16..c104366 100644 --- a/application/src/services/ssl/notification/sslNotificationSender.ts +++ b/application/src/services/ssl/notification/sslNotificationSender.ts @@ -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 { 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; } diff --git a/application/src/services/ssl/sslCertificateOperations.ts b/application/src/services/ssl/sslCertificateOperations.ts index dc37b67..88dc49f 100644 --- a/application/src/services/ssl/sslCertificateOperations.ts +++ b/application/src/services/ssl/sslCertificateOperations.ts @@ -112,10 +112,10 @@ export const deleteSSLCertificate = async (id: string): Promise => { */ 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; } }; \ No newline at end of file diff --git a/application/src/services/ssl/sslCheckerService.ts b/application/src/services/ssl/sslCheckerService.ts index d970e90..3e76871 100644 --- a/application/src/services/ssl/sslCheckerService.ts +++ b/application/src/services/ssl/sslCheckerService.ts @@ -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 => { 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 => { 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 }; diff --git a/application/src/services/ssl/sslPrimaryChecker.ts b/application/src/services/ssl/sslPrimaryChecker.ts deleted file mode 100644 index 59fbddc..0000000 --- a/application/src/services/ssl/sslPrimaryChecker.ts +++ /dev/null @@ -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 => { - 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); - } -}; \ No newline at end of file diff --git a/application/src/services/types/serverService.ts b/application/src/services/types/serverService.ts index e95d9d7..179096f 100644 --- a/application/src/services/types/serverService.ts +++ b/application/src/services/types/serverService.ts @@ -8,7 +8,7 @@ export const serverService = { const records = await pb.collection('servers').getFullList(); 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(serverId); return record; } catch (error) { - console.error('Error fetching server:', error); + // console.error('Error fetching server:', error); throw error; } }, async getServerMetrics(serverId: string): Promise { 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; } }, diff --git a/application/src/translations/types/about.ts b/application/src/translations/types/about.ts index 244ec53..7197b39 100644 --- a/application/src/translations/types/about.ts +++ b/application/src/translations/types/about.ts @@ -1,6 +1,6 @@ export interface AboutTranslations { - aboutReamStack: string; + aboutCheckCle: string; systemDescription: string; systemVersion: string; license: string; diff --git a/server/pb_data/auxiliary.db-shm b/server/pb_data/auxiliary.db-shm index 18d61e4..9a867ac 100644 Binary files a/server/pb_data/auxiliary.db-shm and b/server/pb_data/auxiliary.db-shm differ diff --git a/server/pb_data/auxiliary.db-wal b/server/pb_data/auxiliary.db-wal index 464dc0c..fe1b1ff 100644 Binary files a/server/pb_data/auxiliary.db-wal and b/server/pb_data/auxiliary.db-wal differ diff --git a/server/pb_data/data.db b/server/pb_data/data.db index 1258910..8047a3c 100644 Binary files a/server/pb_data/data.db and b/server/pb_data/data.db differ diff --git a/server/pb_data/data.db-shm b/server/pb_data/data.db-shm new file mode 100644 index 0000000..fe9ac28 Binary files /dev/null and b/server/pb_data/data.db-shm differ diff --git a/server/pb_data/data.db-wal b/server/pb_data/data.db-wal new file mode 100644 index 0000000..e69de29 diff --git a/server/service-operation/pocketbase/services.go b/server/service-operation/pocketbase/services.go index 249f7a1..11ef4db 100644 --- a/server/service-operation/pocketbase/services.go +++ b/server/service-operation/pocketbase/services.go @@ -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 - } - - // 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 + 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++ + } + + return allServices, nil } func (c *PocketBaseClient) UpdateServiceStatus(serviceID string, status string, responseTime int64, errorMessage string) error { diff --git a/server/service-operation/pocketbase/ssl.go b/server/service-operation/pocketbase/ssl.go index 79a4008..6e39bfd 100644 --- a/server/service-operation/pocketbase/ssl.go +++ b/server/service-operation/pocketbase/ssl.go @@ -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() - - 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) + var allCertificates []types.SSLCertificate + page := 1 + perPage := 200 // You can increase up to 500 if needed + + 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++ } - 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 -} \ No newline at end of file +}