Merge pull request #70 from operacle/develop
- Improve service listing to display all records correctly - Fetch all data and handle pagination for better UX - Improve SSL Certs listing to display all records correctly - Added ISSUE TEMPLATE - Added CODE OF CONDUCT
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Documentation Improvement
|
||||
description: Suggest improvements or report issues in documentation.
|
||||
title: "[Docs]: "
|
||||
labels: [documentation]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for helping improve our documentation!
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: What part of the documentation needs improvement?
|
||||
description: |
|
||||
Describe the issue clearly.
|
||||
placeholder: "The installation guide is missing steps for..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: location
|
||||
attributes:
|
||||
label: Location
|
||||
description: |
|
||||
Provide a link or path to the affected documentation.
|
||||
placeholder: "https://github.com/operacle/checkcle/docs/INSTALL.md"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: suggestion
|
||||
attributes:
|
||||
label: Suggested Change
|
||||
description: |
|
||||
How would you improve it?
|
||||
placeholder: "I suggest adding a section about..."
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: confirmation
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I have searched existing issues for similar documentation problems.
|
||||
required: true
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea to improve this project.
|
||||
title: "[Feature]: "
|
||||
labels: [enhancement]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thank you for suggesting a feature!**
|
||||
Please fill out this form so we can better understand your idea.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
description: |
|
||||
A clear and concise description of the feature 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
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Security Issue
|
||||
description: Report a potential security vulnerability.
|
||||
title: "[Security]: "
|
||||
labels: [security]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠️ **IMPORTANT: Please do NOT disclose sensitive security details in this public issue.**
|
||||
|
||||
If you believe you have found a security vulnerability, **please report it privately** to help keep users safe.
|
||||
|
||||
📧 **Disclosure Process:**
|
||||
- Email: [security@checkcle.io](mailto:security@checkcle.io)
|
||||
- Or follow our [SECURITY.md](../../blob/main/SECURITY.md) policy.
|
||||
|
||||
You can use this issue only to let maintainers know that you have sent or will send a report.
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
description: |
|
||||
Briefly describe the type of vulnerability you believe you have found (without including sensitive details).
|
||||
placeholder: "Example: Possible SQL injection in the user login endpoint."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: contact
|
||||
attributes:
|
||||
label: Contact Information
|
||||
description: |
|
||||
Provide an email or other way for maintainers to contact you if we need clarification.
|
||||
placeholder: "your.email@example.com"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: confirmation
|
||||
attributes:
|
||||
label: Confirmation
|
||||
description: |
|
||||
Please confirm:
|
||||
options:
|
||||
- label: I will not share sensitive exploit details in this issue.
|
||||
required: true
|
||||
- label: I have sent or will send a full report to the private disclosure contact.
|
||||
required: true
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Translation Request
|
||||
description: Request a translation or report translation issues.
|
||||
title: "[Translation]: "
|
||||
labels: [translation]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Help us make this project accessible in more languages!
|
||||
|
||||
- type: input
|
||||
id: language
|
||||
attributes:
|
||||
label: Language
|
||||
description: What language is this about?
|
||||
placeholder: "Example: Spanish"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Details
|
||||
description: |
|
||||
Describe what 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
|
||||
@@ -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
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!-- IMPORTANT: DO NOT REMOVE THIS SCRIPT TAG OR THIS VERY COMMENT! -->
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getAuthHeaders, getBaseUrl, validateEmail } from '../utils';
|
||||
import { SettingsApiResponse } from '../types';
|
||||
|
||||
const createEmailTemplate = (template: string, data: any): { subject: string; htmlBody: string } => {
|
||||
let subject = 'Test Email from ReamStack';
|
||||
let subject = 'Test Email from CheckCle';
|
||||
let htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -13,7 +13,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p>If you received this email, your SMTP configuration is working correctly.</p>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">
|
||||
Sent from ReamStack Monitoring System<br>
|
||||
Sent from CheckCle Monitoring System<br>
|
||||
Template: ${template}<br>
|
||||
${data.collection ? `Collection: ${data.collection}` : ''}
|
||||
</p>
|
||||
@@ -24,7 +24,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
|
||||
switch (template) {
|
||||
case 'verification':
|
||||
subject = 'Email Verification Test - ReamStack';
|
||||
subject = 'Email Verification Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -37,14 +37,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'password-reset':
|
||||
subject = 'Password Reset Test - ReamStack';
|
||||
subject = 'Password Reset Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -57,14 +57,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'email-change':
|
||||
subject = 'Email Change Confirmation Test - ReamStack';
|
||||
subject = 'Email Change Confirmation Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -76,7 +76,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Template:</strong> Email Change Confirmation</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -178,10 +178,6 @@ export const sendTestEmail = async (data: any): Promise<SettingsApiResponse> =>
|
||||
smtpPort: smtpSettings.port || 587
|
||||
});
|
||||
|
||||
// For now, we'll simulate a successful email send
|
||||
// In a real implementation, you would integrate with your email service here
|
||||
// This could be nodemailer, SendGrid, or your PocketBase email system
|
||||
|
||||
// Simulate processing time
|
||||
console.log('Simulating email send...');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getAuthHeaders, getBaseUrl, validateEmail } from '../utils';
|
||||
import { SettingsApiResponse } from '../types';
|
||||
|
||||
const createEmailTemplate = (template: string, data: any): { subject: string; htmlBody: string } => {
|
||||
let subject = 'Test Email from ReamStack';
|
||||
let subject = 'Test Email from CheckCle';
|
||||
let htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -12,7 +12,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p>If you received this email, your SMTP configuration is working correctly.</p>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">
|
||||
Sent from ReamStack Monitoring System<br>
|
||||
Sent from CheckCle Monitoring System<br>
|
||||
Template: ${template}<br>
|
||||
${data.collection ? `Collection: ${data.collection}` : ''}
|
||||
</p>
|
||||
@@ -23,7 +23,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
|
||||
switch (template) {
|
||||
case 'verification':
|
||||
subject = 'Email Verification Test - ReamStack';
|
||||
subject = 'Email Verification Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -36,14 +36,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'password-reset':
|
||||
subject = 'Password Reset Test - ReamStack';
|
||||
subject = 'Password Reset Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -56,14 +56,14 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Collection:</strong> ${data.collection || '_superusers'}</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
break;
|
||||
case 'email-change':
|
||||
subject = 'Email Change Confirmation Test - ReamStack';
|
||||
subject = 'Email Change Confirmation Test - CheckCle';
|
||||
htmlBody = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
@@ -75,7 +75,7 @@ const createEmailTemplate = (template: string, data: any): { subject: string; ht
|
||||
<p><strong>Template:</strong> Email Change Confirmation</p>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="font-size: 12px; color: #666;">Sent from ReamStack Monitoring System</p>
|
||||
<p style="font-size: 12px; color: #666;">Sent from CheckCle Monitoring System</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
@@ -38,8 +38,9 @@ interface EditOperationalPageDialogProps {
|
||||
|
||||
export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOperationalPageDialogProps) => {
|
||||
const [selectedComponents, setSelectedComponents] = useState<Partial<StatusPageComponentRecord>[]>([]);
|
||||
const [existingComponents, setExistingComponents] = useState<StatusPageComponentRecord[]>([]);
|
||||
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
|
||||
const [componentsLoaded, setComponentsLoaded] = useState(false);
|
||||
|
||||
const updateMutation = useUpdateOperationalPage();
|
||||
const createComponentMutation = useCreateStatusPageComponent();
|
||||
const deleteComponentMutation = useDeleteStatusPageComponent();
|
||||
@@ -63,28 +64,42 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (page) {
|
||||
form.reset({
|
||||
title: page.title,
|
||||
description: page.description,
|
||||
slug: page.slug,
|
||||
theme: page.theme,
|
||||
status: page.status,
|
||||
is_public: page.is_public === 'true',
|
||||
logo_url: page.logo_url || '',
|
||||
custom_domain: page.custom_domain || '',
|
||||
custom_css: page.custom_css || '',
|
||||
page_style: page.page_style || '',
|
||||
});
|
||||
}
|
||||
}, [page, form]);
|
||||
// Memoize the form reset values to prevent unnecessary re-renders
|
||||
const formValues = useMemo(() => {
|
||||
if (!page) return null;
|
||||
return {
|
||||
title: page.title,
|
||||
description: page.description,
|
||||
slug: page.slug,
|
||||
theme: page.theme,
|
||||
status: page.status,
|
||||
is_public: page.is_public === 'true',
|
||||
logo_url: page.logo_url || '',
|
||||
custom_domain: page.custom_domain || '',
|
||||
custom_css: page.custom_css || '',
|
||||
page_style: page.page_style || '',
|
||||
};
|
||||
}, [page?.id, page?.title, page?.description, page?.slug, page?.theme, page?.status, page?.is_public, page?.logo_url, page?.custom_domain, page?.custom_css, page?.page_style]);
|
||||
|
||||
// Reset form when page data changes
|
||||
useEffect(() => {
|
||||
if (components && components.length > 0) {
|
||||
console.log('Loading existing components:', components);
|
||||
setExistingComponents(components);
|
||||
// Convert existing components to the format expected by ComponentsSelector
|
||||
if (formValues) {
|
||||
form.reset(formValues);
|
||||
}
|
||||
}, [formValues, form]);
|
||||
|
||||
// Convert components to selector format and initialize state - only when dialog opens and components change
|
||||
useEffect(() => {
|
||||
if (!open || !page?.id || !components) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only update if components actually changed or haven't been loaded yet
|
||||
const componentIds = components.map(c => c.id).sort().join(',');
|
||||
const currentSelectedIds = selectedComponents.map(c => c.id).filter(Boolean).sort().join(',');
|
||||
|
||||
if (componentIds !== currentSelectedIds || !componentsLoaded) {
|
||||
// console.log('Loading existing components:', components);
|
||||
const existingComponentsForSelector = components.map(comp => ({
|
||||
id: comp.id,
|
||||
name: comp.name,
|
||||
@@ -94,25 +109,31 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
display_order: comp.display_order,
|
||||
operational_status_id: comp.operational_status_id,
|
||||
}));
|
||||
|
||||
setSelectedComponents(existingComponentsForSelector);
|
||||
} else {
|
||||
setExistingComponents([]);
|
||||
setComponentsLoaded(true);
|
||||
}
|
||||
}, [open, page?.id, components, componentsLoaded, selectedComponents]);
|
||||
|
||||
// Reset state when dialog closes
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setComponentsLoaded(false);
|
||||
setSelectedComponents([]);
|
||||
}
|
||||
}, [components]);
|
||||
}, [open]);
|
||||
|
||||
const handleComponentDelete = async (componentId: string) => {
|
||||
const handleComponentDelete = useCallback(async (componentId: string) => {
|
||||
try {
|
||||
console.log('Deleting component:', componentId);
|
||||
// console.log('Deleting component:', componentId);
|
||||
await deleteComponentMutation.mutateAsync(componentId);
|
||||
|
||||
// Update local state to remove the deleted component
|
||||
setSelectedComponents(prev => prev.filter(comp => comp.id !== componentId));
|
||||
setExistingComponents(prev => prev.filter(comp => comp.id !== componentId));
|
||||
} catch (error) {
|
||||
console.error('Error deleting component:', error);
|
||||
// console.error('Error deleting component:', error);
|
||||
}
|
||||
};
|
||||
}, [deleteComponentMutation]);
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
if (!page) return;
|
||||
@@ -133,21 +154,17 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
page_style: data.page_style || '',
|
||||
};
|
||||
|
||||
console.log('Updating operational page with payload:', payload);
|
||||
// console.log('Updating operational page with payload:', payload);
|
||||
await updateMutation.mutateAsync({ id: page.id, data: payload });
|
||||
|
||||
// Handle component changes
|
||||
const currentComponentIds = existingComponents.map(c => c.id);
|
||||
const currentComponentIds = components.map(c => c.id);
|
||||
const newComponentsToCreate = selectedComponents.filter(comp => !comp.id);
|
||||
const componentsToKeep = selectedComponents.filter(comp => comp.id && currentComponentIds.includes(comp.id));
|
||||
const componentsToDelete = existingComponents.filter(comp => !selectedComponents.some(selected => selected.id === comp.id));
|
||||
const componentsToDelete = components.filter(comp => !selectedComponents.some(selected => selected.id === comp.id));
|
||||
|
||||
// Delete removed components (only if not already deleted via handleComponentDelete)
|
||||
// Delete removed components
|
||||
for (const component of componentsToDelete) {
|
||||
if (selectedComponents.some(selected => selected.id === component.id)) {
|
||||
continue; // Skip if already handled by handleComponentDelete
|
||||
}
|
||||
console.log('Deleting component during save:', component.id);
|
||||
// console.log('Deleting component during save:', component.id);
|
||||
await deleteComponentMutation.mutateAsync(component.id);
|
||||
}
|
||||
|
||||
@@ -162,13 +179,13 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
display_order: component.display_order || 1,
|
||||
};
|
||||
|
||||
console.log('Creating component with payload:', componentPayload);
|
||||
// console.log('Creating component with payload:', componentPayload);
|
||||
await createComponentMutation.mutateAsync(componentPayload);
|
||||
}
|
||||
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error('Error updating operational page:', error);
|
||||
// console.error('Error updating operational page:', error);
|
||||
} finally {
|
||||
setIsFormSubmitting(false);
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ export const IncidentBasicFields: React.FC = () => {
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const usersList = await userService.getUsers();
|
||||
console.log("Fetched users for incident assignment:", usersList);
|
||||
// console.log("Fetched users for incident assignment:", usersList);
|
||||
return Array.isArray(usersList) ? usersList : [];
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch users:", error);
|
||||
// console.error("Failed to fetch users:", error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
@@ -53,7 +53,7 @@ export const IncidentBasicFields: React.FC = () => {
|
||||
// Add user to assigned_to
|
||||
const addUser = (userId: string) => {
|
||||
// For now, we're using a single user assignment
|
||||
console.log("Setting user ID in form:", userId);
|
||||
// console.log("Setting user ID in form:", userId);
|
||||
form.setValue('assigned_to', userId, { shouldValidate: true, shouldDirty: true });
|
||||
setSelectedUserIds([userId]);
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
isFetching
|
||||
} = useServerHistoryData(serverId);
|
||||
|
||||
console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange);
|
||||
//console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange);
|
||||
|
||||
// Memoize latest data calculation to prevent unnecessary recalculations
|
||||
const latestData = useMemo(() => {
|
||||
@@ -71,7 +71,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
}
|
||||
|
||||
if (error) {
|
||||
console.error('ServerHistoryCharts: Error loading data:', error);
|
||||
// console.error('ServerHistoryCharts: Error loading data:', error);
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -122,7 +122,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange);
|
||||
// console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -15,7 +15,7 @@ export function ServerSystemInfoCard({ server }: ServerSystemInfoCardProps) {
|
||||
? JSON.parse(server.system_info)
|
||||
: server.system_info;
|
||||
} catch (error) {
|
||||
console.log('Error parsing system_info:', error);
|
||||
// console.log('Error parsing system_info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,21 +47,21 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
||||
newSet.delete(serverId);
|
||||
return newSet;
|
||||
});
|
||||
console.log('Resume server monitoring:', serverId);
|
||||
// console.log('Resume server monitoring:', serverId);
|
||||
} else {
|
||||
setPausedServers(prev => new Set(prev).add(serverId));
|
||||
console.log('Pause server monitoring:', serverId);
|
||||
// console.log('Pause server monitoring:', serverId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (serverId: string) => {
|
||||
// TODO: Implement edit functionality
|
||||
console.log('Edit server:', serverId);
|
||||
// console.log('Edit server:', serverId);
|
||||
};
|
||||
|
||||
const handleDelete = (serverId: string) => {
|
||||
// TODO: Implement delete functionality
|
||||
console.log('Delete server:', serverId);
|
||||
// console.log('Delete server:', serverId);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -61,14 +61,14 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange):
|
||||
const bufferMinutes = timeRange === '60m' ? 5 : timeRange === '1d' ? 30 : 60;
|
||||
const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000) - (bufferMinutes * 60 * 1000));
|
||||
|
||||
console.log('filterMetricsByTimeRange: timeRange:', timeRange, 'cutoffTime:', cutoffTime.toISOString());
|
||||
// console.log('filterMetricsByTimeRange: timeRange:', timeRange, 'cutoffTime:', cutoffTime.toISOString());
|
||||
|
||||
const filtered = metrics.filter(metric => {
|
||||
const metricTime = new Date(metric.created || metric.timestamp);
|
||||
return metricTime >= cutoffTime && metricTime <= now;
|
||||
});
|
||||
|
||||
console.log('filterMetricsByTimeRange: Filtered', metrics.length, 'to', filtered.length, 'metrics');
|
||||
// console.log('filterMetricsByTimeRange: Filtered', metrics.length, 'to', filtered.length, 'metrics');
|
||||
|
||||
// Sort by timestamp for proper chart display
|
||||
return filtered.sort((a, b) =>
|
||||
@@ -129,12 +129,12 @@ const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => {
|
||||
|
||||
// Optimized chart data formatting with better performance
|
||||
export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
||||
console.log('formatChartData: Input metrics count:', metrics?.length || 0, 'timeRange:', timeRange);
|
||||
// console.log('formatChartData: Input metrics count:', metrics?.length || 0, 'timeRange:', timeRange);
|
||||
|
||||
if (!metrics?.length) return [];
|
||||
|
||||
const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
|
||||
console.log('formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics');
|
||||
// console.log('formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics');
|
||||
|
||||
if (!filteredMetrics.length) return [];
|
||||
|
||||
@@ -154,7 +154,7 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
||||
? filteredMetrics.filter((_, index) => index % Math.ceil(filteredMetrics.length / maxDataPoints) === 0)
|
||||
: filteredMetrics;
|
||||
|
||||
console.log('formatChartData: After sampling:', sampledMetrics.length, 'metrics for display');
|
||||
// console.log('formatChartData: After sampling:', sampledMetrics.length, 'metrics for display');
|
||||
|
||||
// Batch process the data transformation for better performance
|
||||
return sampledMetrics.map((metric) => {
|
||||
|
||||
@@ -17,9 +17,9 @@ export const useServerHistoryData = (serverId: string) => {
|
||||
} = useQuery({
|
||||
queryKey: ['server-metrics-history', serverId, timeRange],
|
||||
queryFn: async () => {
|
||||
console.log('useServerHistoryData: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
|
||||
// console.log('useServerHistoryData: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
|
||||
const result = await serverService.getServerMetrics(serverId, timeRange);
|
||||
console.log('useServerHistoryData: Raw metrics result for timeRange', timeRange, ':', result?.length || 0, 'records');
|
||||
// console.log('useServerHistoryData: Raw metrics result for timeRange', timeRange, ':', result?.length || 0, 'records');
|
||||
return result || [];
|
||||
},
|
||||
enabled: !!serverId,
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ServiceUptimeHistory({
|
||||
const { data: uptimeHistory, isLoading, error } = useQuery({
|
||||
queryKey: ['uptimeHistory', serviceId, serviceType, startDate?.toISOString(), endDate?.toISOString()],
|
||||
queryFn: () => {
|
||||
console.log(`ServiceUptimeHistory: Fetching for service ${serviceId} of type ${serviceType}`);
|
||||
// console.log(`ServiceUptimeHistory: Fetching for service ${serviceId} of type ${serviceType}`);
|
||||
return uptimeService.getUptimeHistory(serviceId, 200, startDate, endDate, serviceType);
|
||||
},
|
||||
enabled: !!serviceId && !!serviceType,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious
|
||||
} from "@/components/ui/pagination";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "@/components/ui/select";
|
||||
import { PageSize } from "@/hooks/useServicesPagination";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
interface ServicesPaginationProps {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
pageSize: PageSize;
|
||||
totalItems: number;
|
||||
onPageChange: (page: number) => void;
|
||||
onPageSizeChange: (size: PageSize) => void;
|
||||
}
|
||||
|
||||
export function ServicesPagination({
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
}: ServicesPaginationProps) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
// Generate page numbers to display
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
const halfVisible = Math.floor(maxVisiblePages / 2);
|
||||
|
||||
let startPage = Math.max(1, currentPage - halfVisible);
|
||||
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||
|
||||
// Adjust start page if we don't have enough pages at the end
|
||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
const startItem = Math.min((currentPage - 1) * pageSize + 1, totalItems);
|
||||
const endItem = Math.min(currentPage * pageSize, totalItems);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between py-4 px-4 border-t border-border">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("rowsPerPage") ?? "Rows per page"}:
|
||||
</span>
|
||||
<Select
|
||||
value={pageSize.toString()}
|
||||
onValueChange={(value) => onPageSizeChange(parseInt(value) as PageSize)}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-[70px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="30">30</SelectItem>
|
||||
<SelectItem value="50">50</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||
{totalItems > 0
|
||||
? `${startItem}-${endItem} of ${totalItems} ${t("services") || "services"}`
|
||||
: `0 ${t("services") || "services"}`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
||||
className={
|
||||
currentPage === 1
|
||||
? "pointer-events-none opacity-50"
|
||||
: "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{getPageNumbers().map((page) => (
|
||||
<PaginationItem key={page}>
|
||||
<PaginationLink
|
||||
isActive={page === currentPage}
|
||||
onClick={() => onPageChange(page)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
|
||||
className={
|
||||
currentPage === totalPages
|
||||
? "pointer-events-none opacity-50"
|
||||
: "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { Service } from "@/types/service.types";
|
||||
import { ServicesTableView } from "./ServicesTableView";
|
||||
import { ServicesPagination } from "./ServicesPagination";
|
||||
import { ServiceDeleteDialog } from "./ServiceDeleteDialog";
|
||||
import { ServiceHistoryDialog } from "./ServiceHistoryDialog";
|
||||
import { ServiceEditDialog } from "./ServiceEditDialog";
|
||||
import { useServiceActions, useDialogState } from "./hooks";
|
||||
import { useServicesPagination } from "@/hooks/useServicesPagination";
|
||||
|
||||
interface ServicesTableContainerProps {
|
||||
services: Service[];
|
||||
@@ -36,6 +38,16 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
handleDeleteDialogChange
|
||||
} = useDialogState();
|
||||
|
||||
const {
|
||||
paginatedServices,
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
} = useServicesPagination({ services: localServices });
|
||||
|
||||
// Update local services state when props change
|
||||
useEffect(() => {
|
||||
updateServices(services);
|
||||
@@ -62,7 +74,7 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full">
|
||||
<ServicesTableView
|
||||
services={localServices}
|
||||
services={paginatedServices}
|
||||
onViewDetail={handleViewDetail}
|
||||
onPauseResume={handlePauseResume}
|
||||
onEdit={onEdit}
|
||||
@@ -70,6 +82,15 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
onMuteAlerts={handleMuteAlerts}
|
||||
/>
|
||||
|
||||
<ServicesPagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
pageSize={pageSize}
|
||||
totalItems={totalItems}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
/>
|
||||
|
||||
<ServiceHistoryDialog
|
||||
isOpen={isHistoryDialogOpen}
|
||||
onOpenChange={setIsHistoryDialogOpen}
|
||||
@@ -91,4 +112,4 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ export const ServicesTableView = ({
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg overflow-hidden border border-border flex-1 flex flex-col shadow-sm`}>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg overflow-hidden border border-border shadow-sm`}>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader className={`${theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'} sticky top-0 z-10`}>
|
||||
<TableRow className={`${theme === 'dark' ? 'border-gray-700 hover:bg-gray-800' : 'border-gray-200 hover:bg-gray-100'}`}>
|
||||
@@ -65,4 +65,4 @@ export const ServicesTableView = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -22,11 +22,11 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
|
||||
const notificationChannels = form.watch("notificationChannels") || [];
|
||||
const alertTemplate = form.watch("alertTemplate");
|
||||
|
||||
console.log("Current notification values:", {
|
||||
notificationStatus,
|
||||
notificationChannels,
|
||||
alertTemplate
|
||||
});
|
||||
// console.log("Current notification values:", {
|
||||
// notificationStatus,
|
||||
// notificationChannels,
|
||||
// alertTemplate
|
||||
// });
|
||||
|
||||
// Fetch alert configurations for notification channels
|
||||
const { data: alertConfigsData } = useQuery({
|
||||
@@ -42,16 +42,16 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
|
||||
setAlertConfigs(enabledChannels);
|
||||
|
||||
// Debug log to check what alert configs are loaded
|
||||
console.log("Loaded alert configurations:", enabledChannels);
|
||||
// console.log("Loaded alert configurations:", enabledChannels);
|
||||
}
|
||||
}, [alertConfigsData]);
|
||||
|
||||
// Log when form values change to debug
|
||||
useEffect(() => {
|
||||
console.log("Notification values changed:", {
|
||||
notificationStatus: form.getValues("notificationStatus"),
|
||||
notificationChannels: form.getValues("notificationChannels")
|
||||
});
|
||||
// console.log("Notification values changed:", {
|
||||
// notificationStatus: form.getValues("notificationStatus"),
|
||||
// notificationChannels: form.getValues("notificationChannels")
|
||||
// });
|
||||
}, [form.watch("notificationStatus"), form.watch("notificationChannels")]);
|
||||
|
||||
const handleChannelAdd = (channelId: string) => {
|
||||
@@ -161,7 +161,7 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
|
||||
render={({ field }) => {
|
||||
// Don't convert existing values to "default"
|
||||
const displayValue = field.value || "default";
|
||||
console.log("Rendering alert template field with value:", displayValue);
|
||||
// console.log("Rendering alert template field with value:", displayValue);
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
|
||||
@@ -19,10 +19,10 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
queryKey: ['strictDefaultUptimeHistory', serviceId, serviceType],
|
||||
queryFn: async () => {
|
||||
if (!serviceId) {
|
||||
console.log('No serviceId provided, skipping fetch');
|
||||
// console.log('No serviceId provided, skipping fetch');
|
||||
return [];
|
||||
}
|
||||
console.log(`Fetching STRICT Default (Agent ID 1) uptime data for service ${serviceId} of type ${serviceType}`);
|
||||
// console.log(`Fetching STRICT Default (Agent ID 1) uptime data for service ${serviceId} of type ${serviceType}`);
|
||||
|
||||
// Get raw uptime history data
|
||||
const rawData = await uptimeService.getUptimeHistory(serviceId, 50, undefined, undefined, serviceType);
|
||||
@@ -38,15 +38,15 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
);
|
||||
|
||||
if (isStrictDefault) {
|
||||
console.log(`✓ ACCEPTED Default record: ${record.id} - ${record.timestamp} - Pure default monitoring`);
|
||||
// console.log(`✓ ACCEPTED Default record: ${record.id} - ${record.timestamp} - Pure default monitoring`);
|
||||
} else {
|
||||
console.log(`✗ REJECTED non-default record: ${record.id} - region: ${record.region_name || 'none'}, agent: ${record.agent_id || 'none'}`);
|
||||
// console.log(`✗ REJECTED non-default record: ${record.id} - region: ${record.region_name || 'none'}, agent: ${record.agent_id || 'none'}`);
|
||||
}
|
||||
|
||||
return isStrictDefault;
|
||||
});
|
||||
|
||||
console.log(`ULTRA-STRICT FILTER: Reduced ${rawData.length} records to ${strictDefaultData.length} pure default monitoring records`);
|
||||
//console.log(`ULTRA-STRICT FILTER: Reduced ${rawData.length} records to ${strictDefaultData.length} pure default monitoring records`);
|
||||
return strictDefaultData;
|
||||
},
|
||||
enabled: !!serviceId,
|
||||
@@ -61,7 +61,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
const processUptimeData = (data: UptimeData[], intervalSeconds: number): UptimeData[] => {
|
||||
if (!data || data.length === 0) return [];
|
||||
|
||||
console.log(`Processing ${data.length} strict default monitoring records for service ${serviceId}`);
|
||||
// console.log(`Processing ${data.length} strict default monitoring records for service ${serviceId}`);
|
||||
|
||||
// Sort data by timestamp (newest first)
|
||||
const sortedData = [...data].sort((a, b) =>
|
||||
@@ -76,9 +76,9 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
|
||||
if (!timestampMap.has(exactTimestamp)) {
|
||||
timestampMap.set(exactTimestamp, record);
|
||||
console.log(`✓ Added unique default record: ${record.id} - ${exactTimestamp}`);
|
||||
// console.log(`✓ Added unique default record: ${record.id} - ${exactTimestamp}`);
|
||||
} else {
|
||||
console.log(`✗ REJECTED absolute duplicate timestamp: ${record.id} - ${exactTimestamp} (exact timestamp match)`);
|
||||
// console.log(`✗ REJECTED absolute duplicate timestamp: ${record.id} - ${exactTimestamp} (exact timestamp match)`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,7 +86,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
const uniqueRecords = Array.from(timestampMap.values());
|
||||
const recentData = uniqueRecords.slice(0, 20);
|
||||
|
||||
console.log(`FINAL RESULT: ${recentData.length} absolutely unique default monitoring records`);
|
||||
//console.log(`FINAL RESULT: ${recentData.length} absolutely unique default monitoring records`);
|
||||
|
||||
return recentData;
|
||||
};
|
||||
@@ -94,12 +94,12 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
// Update history items when data changes
|
||||
useEffect(() => {
|
||||
if (uptimeData && uptimeData.length > 0) {
|
||||
console.log(`Received ${uptimeData.length} strict default monitoring records for service ${serviceId}`);
|
||||
// console.log(`Received ${uptimeData.length} strict default monitoring records for service ${serviceId}`);
|
||||
const processedData = processUptimeData(uptimeData, interval);
|
||||
setHistoryItems(processedData);
|
||||
} else if (!serviceId || (uptimeData && uptimeData.length === 0)) {
|
||||
// Generate placeholder data when no real data is available
|
||||
console.log(`No strict default monitoring data available for service ${serviceId}, generating placeholder`);
|
||||
// console.log(`No strict default monitoring data available for service ${serviceId}, generating placeholder`);
|
||||
|
||||
const statusValue = (status === "up" || status === "down" || status === "warning" || status === "paused")
|
||||
? status
|
||||
@@ -157,7 +157,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
seenTimestamps.add(item.timestamp);
|
||||
absolutelyUniqueItems.push(item);
|
||||
} else {
|
||||
console.log(`FINAL VALIDATION: Removing absolute duplicate timestamp ${item.timestamp} from display`);
|
||||
// console.log(`FINAL VALIDATION: Removing absolute duplicate timestamp ${item.timestamp} from display`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ export const useDefaultUptimeData = ({ serviceId, serviceType, status, interval
|
||||
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||
);
|
||||
|
||||
console.log(`DISPLAY READY: ${sortedValidated.length} absolutely validated default monitoring items`);
|
||||
// console.log(`DISPLAY READY: ${sortedValidated.length} absolutely validated default monitoring items`);
|
||||
return sortedValidated.slice(0, 20);
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export const ServiceRowActions = ({
|
||||
try {
|
||||
if (service.status === "paused") {
|
||||
// Resume monitoring
|
||||
console.log(`Resuming monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
// console.log(`Resuming monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
|
||||
// First ensure we update the status
|
||||
await serviceService.resumeMonitoring(service.id);
|
||||
@@ -53,7 +53,7 @@ export const ServiceRowActions = ({
|
||||
});
|
||||
} else {
|
||||
// Pause monitoring
|
||||
console.log(`Pausing monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
// console.log(`Pausing monitoring for service ${service.id} (${service.name}) from dropdown`);
|
||||
await serviceService.pauseMonitoring(service.id);
|
||||
|
||||
toast({
|
||||
@@ -65,7 +65,7 @@ export const ServiceRowActions = ({
|
||||
// Call the parent handler to refresh the UI
|
||||
onPauseResume(service);
|
||||
} catch (error) {
|
||||
console.error("Error toggling monitoring:", error);
|
||||
// console.error("Error toggling monitoring:", error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
@@ -83,10 +83,10 @@ export const ServiceRowActions = ({
|
||||
|
||||
if (onMuteAlerts) {
|
||||
try {
|
||||
console.log(`Attempting to ${alertsMuted ? 'unmute' : 'mute'} alerts for service ${service.id} (${service.name})`);
|
||||
// console.log(`Attempting to ${alertsMuted ? 'unmute' : 'mute'} alerts for service ${service.id} (${service.name})`);
|
||||
await onMuteAlerts(service);
|
||||
} catch (error) {
|
||||
console.error("Error toggling alerts:", error);
|
||||
// console.error("Error toggling alerts:", error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
|
||||
@@ -40,7 +40,6 @@ export const AboutSystem: React.FC = () => {
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">{t('aboutSystem')}</h1>
|
||||
<p className="text-muted-foreground text-base leading-relaxed mt-2">
|
||||
{t('aboutCheckcle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -83,7 +82,7 @@ export const AboutSystem: React.FC = () => {
|
||||
<span>{t('links')}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="font-medium text-base">
|
||||
{systemName || 'ReamStack'} {t('resources').toLowerCase()}
|
||||
{systemName || 'CheckCle'} {t('resources').toLowerCase()}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 pt-6">
|
||||
|
||||
@@ -52,7 +52,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
|
||||
const queryClient = useQueryClient();
|
||||
const isEditMode = !!templateId;
|
||||
|
||||
console.log("Template form initialized with templateId:", templateId, "isEditMode:", isEditMode);
|
||||
// console.log("Template form initialized with templateId:", templateId, "isEditMode:", isEditMode);
|
||||
|
||||
const form = useForm<TemplateFormData>({
|
||||
resolver: zodResolver(templateFormSchema),
|
||||
@@ -74,7 +74,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
|
||||
// Set form values when template data is loaded
|
||||
useEffect(() => {
|
||||
if (templateData && open) {
|
||||
console.log("Setting form values with template data:", templateData);
|
||||
// console.log("Setting form values with template data:", templateData);
|
||||
|
||||
form.reset({
|
||||
name: templateData.name || "",
|
||||
@@ -120,7 +120,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
|
||||
onSuccess();
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error creating template:", error);
|
||||
// console.error("Error creating template:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create template. Please check your inputs and try again.",
|
||||
@@ -142,7 +142,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
|
||||
onSuccess();
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error updating template:", error);
|
||||
// console.error("Error updating template:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to update template. Please check your inputs and try again.",
|
||||
@@ -155,7 +155,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = (formData: TemplateFormData) => {
|
||||
console.log("Submitting form data:", formData);
|
||||
// console.log("Submitting form data:", formData);
|
||||
|
||||
// Ensure all required fields are present
|
||||
const completeData: CreateUpdateTemplateData = {
|
||||
@@ -173,10 +173,10 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
|
||||
};
|
||||
|
||||
if (isEditMode && templateId) {
|
||||
console.log("Updating template with ID:", templateId);
|
||||
// console.log("Updating template with ID:", templateId);
|
||||
updateMutation.mutate({ id: templateId, data: completeData });
|
||||
} else {
|
||||
console.log("Creating new template");
|
||||
// console.log("Creating new template");
|
||||
createMutation.mutate(completeData);
|
||||
}
|
||||
};
|
||||
@@ -184,7 +184,7 @@ export const useTemplateForm = ({ templateId, open, onOpenChange, onSuccess }: U
|
||||
// Reset form when dialog closes
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
console.log("Dialog closed, resetting form");
|
||||
// console.log("Dialog closed, resetting form");
|
||||
form.reset(defaultFormValues);
|
||||
}
|
||||
}, [open, form]);
|
||||
|
||||
@@ -13,16 +13,16 @@ export const useUsersList = () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
console.log("Fetching users list");
|
||||
// console.log("Fetching users list");
|
||||
const data = await userService.getUsers();
|
||||
console.log("Received users data:", data);
|
||||
// console.log("Received users data:", data);
|
||||
|
||||
if (Array.isArray(data) && data.length >= 0) {
|
||||
setUsers(data);
|
||||
// Clear any previous errors
|
||||
setError(null);
|
||||
} else {
|
||||
console.error("Invalid users data format:", data);
|
||||
// console.error("Invalid users data format:", data);
|
||||
setUsers([]);
|
||||
setError("Invalid data format received from server");
|
||||
toast({
|
||||
@@ -32,7 +32,7 @@ export const useUsersList = () => {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching users:", error);
|
||||
// console.error("Error fetching users:", error);
|
||||
setError(error instanceof Error ? error.message : "Unknown error");
|
||||
toast({
|
||||
title: "Error fetching users",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { Service } from '@/types/service.types';
|
||||
|
||||
export type PageSize = 10 | 30 | 50;
|
||||
|
||||
interface UseServicesPaginationProps {
|
||||
services: Service[];
|
||||
initialPageSize?: PageSize;
|
||||
}
|
||||
|
||||
export const useServicesPagination = ({
|
||||
services,
|
||||
initialPageSize = 10
|
||||
}: UseServicesPaginationProps) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState<PageSize>(initialPageSize);
|
||||
|
||||
const { paginatedServices, totalPages } = useMemo(() => {
|
||||
const totalItems = services.length;
|
||||
const pages = Math.ceil(totalItems / pageSize);
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
|
||||
return {
|
||||
paginatedServices: services.slice(startIndex, endIndex),
|
||||
totalPages: Math.max(1, pages)
|
||||
};
|
||||
}, [services, currentPage, pageSize]);
|
||||
|
||||
// Reset to first page when page size changes or services change
|
||||
const handlePageSizeChange = (newPageSize: PageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
// Reset to first page if current page exceeds total pages
|
||||
const handlePageChange = (page: number) => {
|
||||
const validPage = Math.min(Math.max(1, page), totalPages);
|
||||
setCurrentPage(validPage);
|
||||
};
|
||||
|
||||
return {
|
||||
paginatedServices,
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems: services.length,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
};
|
||||
};
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
@@ -24,7 +24,7 @@ const ServerDetail = () => {
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
||||
const [currentUser, setCurrentUser] = useState(authService.getCurrentUser());
|
||||
|
||||
console.log('ServerDetail component loaded with serverId:', serverId);
|
||||
//console.log('ServerDetail component loaded with serverId:', serverId);
|
||||
|
||||
const {
|
||||
data: server,
|
||||
@@ -46,7 +46,7 @@ const ServerDetail = () => {
|
||||
};
|
||||
|
||||
if (serverError) {
|
||||
console.error('Server detail error:', serverError);
|
||||
// console.error('Server detail error:', serverError);
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
||||
<Sidebar collapsed={sidebarCollapsed} />
|
||||
|
||||
@@ -38,8 +38,8 @@ export const generatePdf = async (incident: IncidentItem): Promise<string> => {
|
||||
doc.setProperties({
|
||||
title: title,
|
||||
subject: 'Incident Report',
|
||||
author: 'ReamStack System',
|
||||
creator: 'ReamStack',
|
||||
author: 'CheckCle System',
|
||||
creator: 'CheckCle',
|
||||
});
|
||||
|
||||
// Add header section
|
||||
|
||||
@@ -28,11 +28,11 @@ export const addHeader = (doc: jsPDF, incident: IncidentItem): number => {
|
||||
doc.setFontSize(10);
|
||||
doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 105, yPos, { align: 'center' });
|
||||
|
||||
// Add ReamStack logo or text
|
||||
// Add CheckCle logo or text
|
||||
yPos += 8;
|
||||
doc.setFontSize(12);
|
||||
doc.setFont(fonts.italic);
|
||||
doc.text('ReamStack Incident Management', 105, yPos, { align: 'center' });
|
||||
doc.text('CheckCle Incident Management', 105, yPos, { align: 'center' });
|
||||
|
||||
// Add horizontal line
|
||||
yPos += 5;
|
||||
@@ -53,7 +53,7 @@ export const addFooter = (doc: jsPDF): void => {
|
||||
doc.setFontSize(8);
|
||||
doc.setTextColor(100, 100, 100);
|
||||
doc.text(
|
||||
`Page ${i} of ${pageCount} | Generated by ReamStack Incident Management System`,
|
||||
`Page ${i} of ${pageCount} | Generated by CheckCle Incident Management System`,
|
||||
105,
|
||||
285,
|
||||
{ align: 'center' }
|
||||
|
||||
@@ -31,8 +31,8 @@ export async function pauseMonitoring(serviceId: string): Promise<void> {
|
||||
|
||||
// We'll skip the notification here since it will be handled by the UI component
|
||||
// This prevents duplicate notifications for the paused status
|
||||
console.log(`Service ${service.name} paused at ${now}, skipping notification to prevent duplication`);
|
||||
// console.log(`Service ${service.name} paused at ${now}, skipping notification to prevent duplication`);
|
||||
} catch (error) {
|
||||
console.error("Error pausing monitoring:", error);
|
||||
// console.error("Error pausing monitoring:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
// Fetch the current service to get its name for better logging
|
||||
const service = await pb.collection('services').getOne(serviceId);
|
||||
|
||||
console.log(`Resuming service ${service.name} at ${now}`);
|
||||
// console.log(`Resuming service ${service.name} at ${now}`);
|
||||
|
||||
// First, clear any existing interval just to be safe
|
||||
const existingInterval = monitoringIntervals.get(serviceId);
|
||||
@@ -57,7 +57,7 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
const alertsMuted = service.alerts === "muted" || serviceForNotification.alerts === "muted";
|
||||
|
||||
if (!alertsMuted) {
|
||||
console.log(`Alerts NOT muted for service ${service.name}, sending resume notification`);
|
||||
// console.log(`Alerts NOT muted for service ${service.name}, sending resume notification`);
|
||||
// Send notification that service has been resumed
|
||||
await notificationService.sendNotification({
|
||||
service: serviceForNotification,
|
||||
@@ -65,7 +65,7 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
timestamp: now
|
||||
});
|
||||
} else {
|
||||
console.log(`Alerts muted for service ${service.name}, skipping resume notification`);
|
||||
// console.log(`Alerts muted for service ${service.name}, skipping resume notification`);
|
||||
}
|
||||
|
||||
// IMPORTANT: Wait a brief moment to ensure the status update is processed
|
||||
@@ -76,6 +76,6 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
|
||||
console.log(`Service ${service.name} resumed and ready for monitoring`);
|
||||
} catch (error) {
|
||||
console.error("Error resuming service:", error);
|
||||
// console.error("Error resuming service:", error);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export const serverService = {
|
||||
const records = await pb.collection('servers').getFullList<Server>();
|
||||
return records;
|
||||
} catch (error) {
|
||||
console.error('Error fetching servers:', error);
|
||||
// console.error('Error fetching servers:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
@@ -17,22 +17,22 @@ export const serverService = {
|
||||
const record = await pb.collection('servers').getOne<Server>(serverId);
|
||||
return record;
|
||||
} catch (error) {
|
||||
console.error('Error fetching server:', error);
|
||||
// console.error('Error fetching server:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getServerMetrics(serverId: string, timeRange?: string): Promise<any[]> {
|
||||
try {
|
||||
console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
|
||||
// console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
|
||||
|
||||
// First, get the server to find the correct server_id for metrics
|
||||
let server;
|
||||
try {
|
||||
server = await this.getServer(serverId);
|
||||
console.log('serverService.getServerMetrics: Found server:', server);
|
||||
// console.log('serverService.getServerMetrics: Found server:', server);
|
||||
} catch (error) {
|
||||
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
||||
// console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
||||
}
|
||||
|
||||
// Try multiple filter strategies to find data
|
||||
@@ -43,17 +43,17 @@ export const serverService = {
|
||||
if (server && server.server_id) {
|
||||
metricsServerId = server.server_id;
|
||||
filter = `server_id = "${metricsServerId}"`;
|
||||
console.log('serverService.getServerMetrics: Strategy 1 - Using server.server_id for metrics:', metricsServerId);
|
||||
// console.log('serverService.getServerMetrics: Strategy 1 - Using server.server_id for metrics:', metricsServerId);
|
||||
} else {
|
||||
// Strategy 2: Use the serverId directly
|
||||
filter = `server_id = "${serverId}"`;
|
||||
console.log('serverService.getServerMetrics: Strategy 2 - Using serverId directly for metrics:', serverId);
|
||||
// console.log('serverService.getServerMetrics: Strategy 2 - Using serverId directly for metrics:', serverId);
|
||||
}
|
||||
|
||||
// Add agent_id filter if available in server data
|
||||
if (server && server.agent_id) {
|
||||
filter += ` && agent_id = "${server.agent_id}"`;
|
||||
console.log('serverService.getServerMetrics: Added agent_id filter:', server.agent_id);
|
||||
// console.log('serverService.getServerMetrics: Added agent_id filter:', server.agent_id);
|
||||
}
|
||||
|
||||
// Add time range filter
|
||||
@@ -83,10 +83,10 @@ export const serverService = {
|
||||
|
||||
const cutoffISO = cutoffTime.toISOString();
|
||||
filter += ` && created >= "${cutoffISO}"`;
|
||||
console.log('serverService.getServerMetrics: Using time filter from:', cutoffISO, 'to now');
|
||||
// console.log('serverService.getServerMetrics: Using time filter from:', cutoffISO, 'to now');
|
||||
}
|
||||
|
||||
console.log('serverService.getServerMetrics: Final filter:', filter);
|
||||
// console.log('serverService.getServerMetrics: Final filter:', filter);
|
||||
|
||||
// Fetch filtered records with proper sorting
|
||||
let records = await pb.collection('server_metrics').getFullList({
|
||||
@@ -95,11 +95,11 @@ export const serverService = {
|
||||
requestKey: null
|
||||
});
|
||||
|
||||
console.log('serverService.getServerMetrics: Found', records.length, 'records with primary filter');
|
||||
// console.log('serverService.getServerMetrics: Found', records.length, 'records with primary filter');
|
||||
|
||||
// If no records found with primary strategy, try fallback strategies
|
||||
if (records.length === 0) {
|
||||
console.log('serverService.getServerMetrics: No records found, trying fallback strategies...');
|
||||
// console.log('serverService.getServerMetrics: No records found, trying fallback strategies...');
|
||||
|
||||
// Fallback 1: Try without agent_id filter
|
||||
let fallbackFilter = `server_id = "${metricsServerId}"`;
|
||||
@@ -131,19 +131,19 @@ export const serverService = {
|
||||
fallbackFilter += ` && created >= "${cutoffISO}"`;
|
||||
}
|
||||
|
||||
console.log('serverService.getServerMetrics: Trying fallback filter without agent_id:', fallbackFilter);
|
||||
// console.log('serverService.getServerMetrics: Trying fallback filter without agent_id:', fallbackFilter);
|
||||
records = await pb.collection('server_metrics').getFullList({
|
||||
filter: fallbackFilter,
|
||||
sort: '-created',
|
||||
requestKey: null
|
||||
});
|
||||
|
||||
console.log('serverService.getServerMetrics: Fallback found', records.length, 'records');
|
||||
// console.log('serverService.getServerMetrics: Fallback found', records.length, 'records');
|
||||
|
||||
// Fallback 2: Try with different server_id strategies
|
||||
if (records.length === 0) {
|
||||
const alternativeIds = [serverId, server?.server_id, server?.id].filter(Boolean);
|
||||
console.log('serverService.getServerMetrics: Trying alternative server IDs:', alternativeIds);
|
||||
// console.log('serverService.getServerMetrics: Trying alternative server IDs:', alternativeIds);
|
||||
|
||||
for (const altId of alternativeIds) {
|
||||
if (altId && altId !== metricsServerId) {
|
||||
@@ -176,7 +176,7 @@ export const serverService = {
|
||||
altFilter += ` && created >= "${cutoffISO}"`;
|
||||
}
|
||||
|
||||
console.log('serverService.getServerMetrics: Trying alternative ID filter:', altFilter);
|
||||
// console.log('serverService.getServerMetrics: Trying alternative ID filter:', altFilter);
|
||||
const altRecords = await pb.collection('server_metrics').getFullList({
|
||||
filter: altFilter,
|
||||
sort: '-created',
|
||||
@@ -184,7 +184,7 @@ export const serverService = {
|
||||
});
|
||||
|
||||
if (altRecords.length > 0) {
|
||||
console.log('serverService.getServerMetrics: Alternative ID found', altRecords.length, 'records');
|
||||
// console.log('serverService.getServerMetrics: Alternative ID found', altRecords.length, 'records');
|
||||
records = altRecords;
|
||||
break;
|
||||
}
|
||||
@@ -193,14 +193,14 @@ export const serverService = {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('serverService.getServerMetrics: Final result:', records.length, 'records found');
|
||||
// console.log('serverService.getServerMetrics: Final result:', records.length, 'records found');
|
||||
if (records.length > 0) {
|
||||
console.log('serverService.getServerMetrics: Sample record:', records[0]);
|
||||
// console.log('serverService.getServerMetrics: Sample record:', records[0]);
|
||||
}
|
||||
|
||||
return records;
|
||||
} catch (error) {
|
||||
console.error('Error fetching server metrics:', error);
|
||||
// console.error('Error fetching server metrics:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,7 +9,15 @@ export type { Service, UptimeData, CreateServiceParams };
|
||||
export const serviceService = {
|
||||
async getServices(): Promise<Service[]> {
|
||||
try {
|
||||
const response = await pb.collection('services').getList(1, 50, {
|
||||
|
||||
// First get the total count of records
|
||||
const countResponse = await pb.collection('services').getList(1, 1, {
|
||||
sort: 'name',
|
||||
});
|
||||
const totalRecords = countResponse.totalItems;
|
||||
|
||||
// Then fetch all records using the total count as the limit
|
||||
const response = await pb.collection('services').getList(1, totalRecords, {
|
||||
sort: 'name',
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { toast } from "sonner";
|
||||
* This should be called once per day
|
||||
*/
|
||||
export async function checkAllCertificatesAndNotify(): Promise<void> {
|
||||
console.log("Starting daily SSL certificates check...");
|
||||
// console.log("Starting daily SSL certificates check...");
|
||||
|
||||
try {
|
||||
// Fetch all SSL certificates from database
|
||||
@@ -18,16 +18,16 @@ export async function checkAllCertificatesAndNotify(): Promise<void> {
|
||||
// Properly cast the items as SSLCertificate
|
||||
const certificates = response.items as unknown as SSLCertificate[];
|
||||
|
||||
console.log(`Found ${certificates.length} certificates to check`);
|
||||
// console.log(`Found ${certificates.length} certificates to check`);
|
||||
|
||||
// Check each certificate
|
||||
for (const cert of certificates) {
|
||||
await checkCertificateAndNotify(cert);
|
||||
}
|
||||
|
||||
console.log("Daily SSL certificates check completed");
|
||||
// console.log("Daily SSL certificates check completed");
|
||||
} catch (error) {
|
||||
console.error("Error during SSL certificates daily check:", error);
|
||||
// console.error("Error during SSL certificates daily check:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function checkAllCertificatesAndNotify(): Promise<void> {
|
||||
* Note: SSL checking is now handled by the Go service, this function focuses on notifications
|
||||
*/
|
||||
export async function checkCertificateAndNotify(certificate: SSLCertificate): Promise<boolean> {
|
||||
console.log(`Checking certificate for ${certificate.domain}...`);
|
||||
// console.log(`Checking certificate for ${certificate.domain}...`);
|
||||
|
||||
try {
|
||||
// Use the current certificate data (updated by Go service)
|
||||
@@ -47,7 +47,7 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
|
||||
const warningThreshold = Number(certificate.warning_threshold) || 30;
|
||||
const expiryThreshold = Number(certificate.expiry_threshold) || 7;
|
||||
|
||||
console.log(`Certificate ${certificate.domain} thresholds: warning=${warningThreshold}, expiry=${expiryThreshold}, days left=${daysLeft}`);
|
||||
// console.log(`Certificate ${certificate.domain} thresholds: warning=${warningThreshold}, expiry=${expiryThreshold}, days left=${daysLeft}`);
|
||||
|
||||
// Update status based on thresholds
|
||||
const status = determineSSLStatus(daysLeft, warningThreshold, expiryThreshold);
|
||||
@@ -67,7 +67,7 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
|
||||
isCritical = false;
|
||||
}
|
||||
|
||||
console.log(`${certificate.domain}: ${daysLeft} days left, status: ${status}, should notify: ${shouldNotify}, critical: ${isCritical}`);
|
||||
// console.log(`${certificate.domain}: ${daysLeft} days left, status: ${status}, should notify: ${shouldNotify}, critical: ${isCritical}`);
|
||||
|
||||
// Update certificate status in database
|
||||
await pb.collection('ssl_certificates').update(certificate.id, {
|
||||
@@ -76,7 +76,7 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
|
||||
|
||||
// Send notification if needed
|
||||
if (shouldNotify && certificate.notification_channel) {
|
||||
console.log(`Sending notification for ${certificate.domain}`);
|
||||
// console.log(`Sending notification for ${certificate.domain}`);
|
||||
|
||||
// Different message based on expiry threshold
|
||||
const message = isCritical
|
||||
@@ -92,28 +92,28 @@ export async function checkCertificateAndNotify(certificate: SSLCertificate): Pr
|
||||
last_notified: new Date().toISOString()
|
||||
});
|
||||
|
||||
console.log(`Notification sent for ${certificate.domain}`);
|
||||
// console.log(`Notification sent for ${certificate.domain}`);
|
||||
// Show toast for manual checks
|
||||
toast.success(`Notification sent for ${certificate.domain}`);
|
||||
return true;
|
||||
} else {
|
||||
console.error(`Failed to send notification for ${certificate.domain}`);
|
||||
// console.error(`Failed to send notification for ${certificate.domain}`);
|
||||
// Show error toast for manual checks
|
||||
toast.error(`Failed to send notification for ${certificate.domain}`);
|
||||
return false;
|
||||
}
|
||||
} else if (shouldNotify && !certificate.notification_channel) {
|
||||
console.log(`No notification channel set for ${certificate.domain}, skipping notification`);
|
||||
// console.log(`No notification channel set for ${certificate.domain}, skipping notification`);
|
||||
toast.info(`No notification channel set for ${certificate.domain}, skipping notification`);
|
||||
} else {
|
||||
console.log(`No notification needed for ${certificate.domain} (${daysLeft} days left)`);
|
||||
// console.log(`No notification needed for ${certificate.domain} (${daysLeft} days left)`);
|
||||
// For manual checks, inform the user that thresholds weren't met
|
||||
toast.info(`Certificate for ${certificate.domain} is valid (${daysLeft} days left)`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`Error checking certificate for ${certificate.domain}:`, error);
|
||||
// console.error(`Error checking certificate for ${certificate.domain}:`, error);
|
||||
toast.error(`Error checking certificate: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function sendSSLNotification(
|
||||
try {
|
||||
// Check if notification channel is set
|
||||
if (!certificate.notification_channel) {
|
||||
console.log(`No notification channel set for certificate: ${certificate.domain}`);
|
||||
// console.log(`No notification channel set for certificate: ${certificate.domain}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ export async function sendSSLNotification(
|
||||
const alertConfigRecord = await pb.collection('alert_configurations').getOne(certificate.notification_channel);
|
||||
|
||||
if (!alertConfigRecord) {
|
||||
console.error(`Alert configuration not found for ID: ${certificate.notification_channel}`);
|
||||
// console.error(`Alert configuration not found for ID: ${certificate.notification_channel}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!alertConfigRecord.enabled) {
|
||||
console.log(`Alert configuration is disabled for certificate: ${certificate.domain}`);
|
||||
// console.log(`Alert configuration is disabled for certificate: ${certificate.domain}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function sendSSLNotification(
|
||||
return await sendNotificationByType(alertConfig, certificate, message, isCritical, sslNotification);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error sending SSL notification:", error);
|
||||
// console.error("Error sending SSL notification:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ async function sendNotificationByType(
|
||||
// case 'slack':
|
||||
// return await sendSlackNotification(alertConfig, certificate, message, isCritical);
|
||||
default:
|
||||
console.log(`Notification type ${alertConfig.notification_type} not implemented yet for SSL certificates`);
|
||||
// console.log(`Notification type ${alertConfig.notification_type} not implemented yet for SSL certificates`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ async function sendTelegramNotification(
|
||||
isCritical: boolean
|
||||
): Promise<boolean> {
|
||||
if (!alertConfig.bot_token || !alertConfig.telegram_chat_id) {
|
||||
console.error("Missing Telegram bot token or chat ID");
|
||||
// console.error("Missing Telegram bot token or chat ID");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,10 +112,10 @@ export const deleteSSLCertificate = async (id: string): Promise<boolean> => {
|
||||
*/
|
||||
export const refreshAllCertificates = async (): Promise<{ success: number; failed: number }> => {
|
||||
try {
|
||||
const response = await pb.collection("ssl_certificates").getList(1, 100);
|
||||
const response = await pb.collection("ssl_certificates").getList(1, 200);
|
||||
const certificates = response.items as unknown as SSLCertificate[];
|
||||
|
||||
console.log(`Refreshing ${certificates.length} certificates...`);
|
||||
// console.log(`Refreshing ${certificates.length} certificates...`);
|
||||
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
@@ -125,14 +125,14 @@ export const refreshAllCertificates = async (): Promise<{ success: number; faile
|
||||
await checkCertificateAndNotify(cert);
|
||||
success++;
|
||||
} catch (error) {
|
||||
console.error(`Failed to refresh certificate ${cert.domain}:`, error);
|
||||
// console.error(`Failed to refresh certificate ${cert.domain}:`, error);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
return { success, failed };
|
||||
} catch (error) {
|
||||
console.error("Error refreshing certificates:", error);
|
||||
// console.error("Error refreshing certificates:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
import type { SSLCheckerResponse } from "./types";
|
||||
import { toast } from "sonner";
|
||||
import { checkWithFetch } from "./sslPrimaryChecker";
|
||||
import { normalizeDomain, createErrorResponse } from "./sslCheckerUtils";
|
||||
|
||||
/**
|
||||
@@ -10,7 +9,7 @@ import { normalizeDomain, createErrorResponse } from "./sslCheckerUtils";
|
||||
*/
|
||||
export const checkSSLCertificate = async (domain: string): Promise<SSLCheckerResponse> => {
|
||||
try {
|
||||
console.log(`Checking SSL certificate for domain: ${domain}`);
|
||||
// console.log(`Checking SSL certificate for domain: ${domain}`);
|
||||
|
||||
// Normalize domain (remove protocol if present)
|
||||
const normalizedDomain = normalizeDomain(domain);
|
||||
@@ -20,11 +19,11 @@ export const checkSSLCertificate = async (domain: string): Promise<SSLCheckerRes
|
||||
}
|
||||
|
||||
// Use the working CORS proxy approach
|
||||
const result = await checkWithFetch(normalizedDomain);
|
||||
console.log("SSL check result:", result);
|
||||
return result;
|
||||
// const result = await checkWithFetch(normalizedDomain);
|
||||
// console.log("SSL check result:", result);
|
||||
// return result;
|
||||
} catch (error) {
|
||||
console.error("SSL check failed completely:", error);
|
||||
//console.error("SSL check failed completely:", error);
|
||||
toast.error(`SSL check failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
return createErrorResponse(domain, error);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
|
||||
const endpoint = "/api/collections/ssl_certificates/records";
|
||||
const params = {
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
perPage: 200,
|
||||
sort: "-created",
|
||||
cache: Date.now() // Prevent caching by adding a timestamp
|
||||
};
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
import { createErrorResponse } from "./sslCheckerUtils";
|
||||
import type { SSLCheckerResponse } from "./types";
|
||||
|
||||
/**
|
||||
* Check SSL certificate using fetch with CORS proxy
|
||||
* This is our primary method that's proven to work
|
||||
*/
|
||||
export const checkWithFetch = async (domain: string): Promise<SSLCheckerResponse> => {
|
||||
console.log(`Checking SSL via CORS proxy for: ${domain}`);
|
||||
|
||||
try {
|
||||
// Use the working CORS proxy
|
||||
const corsProxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(`https://ssl-checker.io/api/v1/check/${domain}`)}`;
|
||||
console.log("Using CORS proxy for SSL check:", corsProxyUrl);
|
||||
|
||||
const proxyResponse = await fetch(corsProxyUrl);
|
||||
if (!proxyResponse.ok) {
|
||||
throw new Error(`CORS proxy request failed with status: ${proxyResponse.status}`);
|
||||
}
|
||||
|
||||
const proxyData = await proxyResponse.json();
|
||||
console.log("CORS proxy returned SSL data:", proxyData);
|
||||
return proxyData;
|
||||
} catch (error) {
|
||||
console.error("SSL check failed:", error);
|
||||
return createErrorResponse(domain, error);
|
||||
}
|
||||
};
|
||||
@@ -8,7 +8,7 @@ export const serverService = {
|
||||
const records = await pb.collection('servers').getFullList<Server>();
|
||||
return records;
|
||||
} catch (error) {
|
||||
console.error('Error fetching servers:', error);
|
||||
// console.error('Error fetching servers:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
@@ -18,29 +18,29 @@ export const serverService = {
|
||||
const record = await pb.collection('servers').getOne<Server>(serverId);
|
||||
return record;
|
||||
} catch (error) {
|
||||
console.error('Error fetching server:', error);
|
||||
// console.error('Error fetching server:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getServerMetrics(serverId: string): Promise<any[]> {
|
||||
try {
|
||||
console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId);
|
||||
// console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId);
|
||||
|
||||
// First, get the server to find the correct server_id for metrics
|
||||
let server;
|
||||
try {
|
||||
server = await this.getServer(serverId);
|
||||
console.log('serverService.getServerMetrics: Found server:', server);
|
||||
// console.log('serverService.getServerMetrics: Found server:', server);
|
||||
} catch (error) {
|
||||
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
||||
// console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
||||
}
|
||||
|
||||
// Try to get metrics using the server's server_id field if available
|
||||
let metricsServerId = serverId;
|
||||
if (server && server.server_id) {
|
||||
metricsServerId = server.server_id;
|
||||
console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
|
||||
// console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
|
||||
}
|
||||
|
||||
// Try filtering by server_id first
|
||||
@@ -50,11 +50,11 @@ export const serverService = {
|
||||
requestKey: null
|
||||
});
|
||||
|
||||
console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length);
|
||||
// console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length);
|
||||
|
||||
// If no records found with server_id, try alternative approaches
|
||||
if (filteredRecords.length === 0) {
|
||||
console.log('serverService.getServerMetrics: No records found with server_id filter, trying alternatives...');
|
||||
// console.log('serverService.getServerMetrics: No records found with server_id filter, trying alternatives...');
|
||||
|
||||
// Get all records to see what's available
|
||||
const allRecords = await pb.collection('server_metrics').getFullList({
|
||||
@@ -62,24 +62,24 @@ export const serverService = {
|
||||
requestKey: null
|
||||
});
|
||||
|
||||
console.log('serverService.getServerMetrics: Total server_metrics records:', allRecords.length);
|
||||
// console.log('serverService.getServerMetrics: Total server_metrics records:', allRecords.length);
|
||||
if (allRecords.length > 0) {
|
||||
console.log('serverService.getServerMetrics: Sample record fields:', Object.keys(allRecords[0]));
|
||||
console.log('serverService.getServerMetrics: Sample server_id values:', allRecords.slice(0, 5).map(r => r.server_id));
|
||||
// console.log('serverService.getServerMetrics: Sample record fields:', Object.keys(allRecords[0]));
|
||||
// console.log('serverService.getServerMetrics: Sample server_id values:', allRecords.slice(0, 5).map(r => r.server_id));
|
||||
}
|
||||
|
||||
// For now, return some sample data from available records if server matches pattern
|
||||
// This is temporary until the correct server_id mapping is established
|
||||
if (allRecords.length > 0) {
|
||||
console.log('serverService.getServerMetrics: Using available records as fallback');
|
||||
// console.log('serverService.getServerMetrics: Using available records as fallback');
|
||||
filteredRecords = allRecords.slice(0, 50); // Get recent 50 records
|
||||
}
|
||||
}
|
||||
|
||||
console.log('serverService.getServerMetrics: Returning', filteredRecords.length, 'records');
|
||||
// console.log('serverService.getServerMetrics: Returning', filteredRecords.length, 'records');
|
||||
return filteredRecords;
|
||||
} catch (error) {
|
||||
console.error('Error fetching server metrics:', error);
|
||||
// console.error('Error fetching server metrics:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
export interface AboutTranslations {
|
||||
aboutReamStack: string;
|
||||
aboutCheckCle: string;
|
||||
systemDescription: string;
|
||||
systemVersion: string;
|
||||
license: string;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -62,30 +62,47 @@ func (c *PocketBaseClient) GetService(serviceID string) (*Service, error) {
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) GetActiveServices() ([]Service, error) {
|
||||
// Only fetch services that are not paused
|
||||
req, err := http.NewRequest("GET",
|
||||
fmt.Sprintf("%s/api/collections/services/records?filter=(status!='paused')", c.baseURL), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var allServices []Service
|
||||
page := 1
|
||||
perPage := 30 // Use default pagination size
|
||||
|
||||
for {
|
||||
// Fetch services page by page with filter for non-paused services
|
||||
req, err := http.NewRequest("GET",
|
||||
fmt.Sprintf("%s/api/collections/services/records?page=%d&perPage=%d&filter=(status!='paused')",
|
||||
c.baseURL, page, perPage), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No authentication header needed for public access
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch active services, status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var servicesResponse ServicesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&servicesResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add current page items to the result
|
||||
allServices = append(allServices, servicesResponse.Items...)
|
||||
|
||||
// Check if we've fetched all pages
|
||||
if page >= servicesResponse.TotalPages || len(servicesResponse.Items) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
// No authentication header needed for public access
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch active services, status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var servicesResponse ServicesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&servicesResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return servicesResponse.Items, nil
|
||||
return allServices, nil
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) UpdateServiceStatus(serviceID string, status string, responseTime int64, errorMessage string) error {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package pocketbase
|
||||
|
||||
import (
|
||||
@@ -11,37 +10,53 @@ import (
|
||||
)
|
||||
|
||||
type SSLCertificatesResponse struct {
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"perPage"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
Items []types.SSLCertificate `json:"items"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"perPage"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
Items []types.SSLCertificate `json:"items"`
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) GetSSLCertificates() ([]types.SSLCertificate, error) {
|
||||
url := fmt.Sprintf("%s/api/collections/ssl_certificates/records", c.baseURL)
|
||||
|
||||
resp, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch SSL certificates: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var allCertificates []types.SSLCertificate
|
||||
page := 1
|
||||
perPage := 200 // You can increase up to 500 if needed
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("PocketBase returned status %d", resp.StatusCode)
|
||||
for {
|
||||
url := fmt.Sprintf(
|
||||
"%s/api/collections/ssl_certificates/records?page=%d&perPage=%d",
|
||||
c.baseURL, page, perPage,
|
||||
)
|
||||
|
||||
resp, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch SSL certificates: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("PocketBase returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var response SSLCertificatesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode SSL certificates response: %v", err)
|
||||
}
|
||||
|
||||
allCertificates = append(allCertificates, response.Items...)
|
||||
|
||||
if page >= response.TotalPages || len(response.Items) == 0 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
var response SSLCertificatesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode SSL certificates response: %v", err)
|
||||
}
|
||||
|
||||
return response.Items, nil
|
||||
return allCertificates, nil
|
||||
}
|
||||
|
||||
func (c *PocketBaseClient) UpdateSSLCertificate(id string, data map[string]interface{}) error {
|
||||
url := fmt.Sprintf("%s/api/collections/ssl_certificates/records/%s", c.baseURL, id)
|
||||
|
||||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal SSL certificate data: %v", err)
|
||||
@@ -53,7 +68,7 @@ func (c *PocketBaseClient) UpdateSSLCertificate(id string, data map[string]inter
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update SSL certificate: %v", err)
|
||||
@@ -69,7 +84,7 @@ func (c *PocketBaseClient) UpdateSSLCertificate(id string, data map[string]inter
|
||||
|
||||
func (c *PocketBaseClient) GetSSLCertificateByID(id string) (*types.SSLCertificate, error) {
|
||||
url := fmt.Sprintf("%s/api/collections/ssl_certificates/records/%s", c.baseURL, id)
|
||||
|
||||
|
||||
resp, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch SSL certificate: %v", err)
|
||||
@@ -86,4 +101,4 @@ func (c *PocketBaseClient) GetSSLCertificateByID(id string) (*types.SSLCertifica
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user