From c6ed816c04f4ebf160740c47b35a978013b949d8 Mon Sep 17 00:00:00 2001 From: gnsworks Date: Mon, 14 Jul 2025 20:32:08 +0900 Subject: [PATCH 01/38] Update Japanese docs --- docs/CONTRIBUTING_ja.md | 8 ++++---- docs/README_ja.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/CONTRIBUTING_ja.md b/docs/CONTRIBUTING_ja.md index 80461f7..1f6649e 100644 --- a/docs/CONTRIBUTING_ja.md +++ b/docs/CONTRIBUTING_ja.md @@ -1,4 +1,4 @@ -## ๐ŸŒ Select Language +## ๐ŸŒ ่จ€่ชž้ธๆŠž @@ -17,7 +17,7 @@ @@ -25,11 +25,11 @@

- Thank you to all our contributors, users, and supporters for making this project thrive. + ใ“ใฎใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใ‚’็™บๅฑ•ใ•ใ›ใฆใใ ใ•ใฃใŸใ™ในใฆใฎ่ฒข็Œฎ่€…ใ€ใƒฆใƒผใ‚ถใƒผใ€ใ‚ตใƒใƒผใ‚ฟใƒผใฎ็š†ๆง˜ใซๆ„Ÿ่ฌใ„ใŸใ—ใพใ™ใ€‚

- ๐Ÿš€ Stay tuned for more updates, features, and improvements. + ๐Ÿš€ ไปŠๅพŒใฎใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ€ๆฉŸ่ƒฝใ€ๆ”นๅ–„ใซใ”ๆœŸๅพ…ใใ ใ•ใ„ใ€‚

# ๐Ÿ› ๏ธ CheckCleใธใฎ่ฒข็Œฎ diff --git a/docs/README_ja.md b/docs/README_ja.md index f2c6938..bcf51f0 100644 --- a/docs/README_ja.md +++ b/docs/README_ja.md @@ -1,4 +1,4 @@ -## ๐ŸŒ Select Language +## ๐ŸŒ ่จ€่ชž้ธๆŠž
Japanese -
Japanese +
ๆ—ฅๆœฌ่ชž
@@ -17,18 +17,18 @@
Japanese -
Japanese +
ๆ—ฅๆœฌ่ชž

- Thank you to all our contributors, users, and supporters for making this project thrive. + ใ“ใฎใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใ‚’็™บๅฑ•ใ•ใ›ใฆใใ ใ•ใฃใŸใ™ในใฆใฎ่ฒข็Œฎ่€…ใ€ใƒฆใƒผใ‚ถใƒผใ€ใ‚ตใƒใƒผใ‚ฟใƒผใฎ็š†ๆง˜ใซๆ„Ÿ่ฌใ„ใŸใ—ใพใ™ใ€‚

- ๐Ÿš€ Stay tuned for more updates, features, and improvements. + ๐Ÿš€ ไปŠๅพŒใฎใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ€ๆฉŸ่ƒฝใ€ๆ”นๅ–„ใซใ”ๆœŸๅพ…ใใ ใ•ใ„ใ€‚

![CheckCle Platform](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/server-detail-page.png) From 8bb78e68eaf2dcc965ebf18b5a67cb161b1682d2 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Thu, 31 Jul 2025 14:57:16 +0700 Subject: [PATCH 02/38] Disable verbose logging - Disable log console output in file to prevent logs from being printed to the console. --- .../regional-monitoring/AddRegionalAgentDialog.tsx | 2 +- .../src/components/servers/DockerOneClickTab.tsx | 8 ++++---- .../ServiceDetailContainer/hooks/useServiceData.tsx | 2 +- .../src/services/ssl/sslCertificateOperations.ts | 10 +++++----- application/src/utils/copyUtils.ts | 10 +++++----- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/application/src/components/regional-monitoring/AddRegionalAgentDialog.tsx b/application/src/components/regional-monitoring/AddRegionalAgentDialog.tsx index c18cd18..ebde414 100644 --- a/application/src/components/regional-monitoring/AddRegionalAgentDialog.tsx +++ b/application/src/components/regional-monitoring/AddRegionalAgentDialog.tsx @@ -114,7 +114,7 @@ export const AddRegionalAgentDialog: React.FC = ({ textarea.setSelectionRange(0, 99999); // For mobile devices } } catch (selectError) { - console.error('Failed to select text:', selectError); + // console.error('Failed to select text:', selectError); } } }; diff --git a/application/src/components/servers/DockerOneClickTab.tsx b/application/src/components/servers/DockerOneClickTab.tsx index 9c23228..33719a9 100644 --- a/application/src/components/servers/DockerOneClickTab.tsx +++ b/application/src/components/servers/DockerOneClickTab.tsx @@ -64,18 +64,18 @@ sudo -E bash ./server-docker-agent.sh`; const handleCopyOneClickCommand = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - console.log('Copy one-click command button clicked'); + // console.log('Copy one-click command button clicked'); const command = getDockerOneClickCommand(); - console.log('Copying one-click command:', command); + // console.log('Copying one-click command:', command); await copyToClipboard(command); }; const handleCopyDockerCommand = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - console.log('Copy docker command button clicked'); + // console.log('Copy docker command button clicked'); const command = getDirectDockerCommand(); - console.log('Copying docker command:', command); + // console.log('Copying docker command:', command); await copyToClipboard(command); }; diff --git a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx index fa42951..0fbcb6b 100644 --- a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx +++ b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx @@ -125,7 +125,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e } else { // Fetch regional agent specific data const [regionName, agentId] = currentAgent.split("|"); - console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`); + // console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`); history = await uptimeService.getUptimeHistoryByRegionalAgent(serviceId, limit, start, end, service.type, regionName, agentId); // console.log(`Retrieved ${history.length} regional monitoring records`); } diff --git a/application/src/services/ssl/sslCertificateOperations.ts b/application/src/services/ssl/sslCertificateOperations.ts index 88dc49f..d1e87a3 100644 --- a/application/src/services/ssl/sslCertificateOperations.ts +++ b/application/src/services/ssl/sslCertificateOperations.ts @@ -38,7 +38,7 @@ export const addSSLCertificate = async ( return record as unknown as SSLCertificate; } catch (error) { - console.error("Error adding SSL certificate:", error); + // console.error("Error adding SSL certificate:", error); throw error; } }; @@ -67,7 +67,7 @@ export const checkAndUpdateCertificate = async ( // Return the current certificate data return typedCertificate; } catch (error) { - console.error("Error updating SSL certificate:", error); + // console.error("Error updating SSL certificate:", error); throw error; } }; @@ -84,10 +84,10 @@ export const triggerImmediateCheck = async (certificateId: string): Promise => { await pb.collection("ssl_certificates").delete(id); return true; } catch (error) { - console.error("Error deleting SSL certificate:", error); + // console.error("Error deleting SSL certificate:", error); throw error; } }; diff --git a/application/src/utils/copyUtils.ts b/application/src/utils/copyUtils.ts index 24483d4..21f29ee 100644 --- a/application/src/utils/copyUtils.ts +++ b/application/src/utils/copyUtils.ts @@ -2,12 +2,12 @@ import { toast } from "@/hooks/use-toast"; export const copyToClipboard = async (text: string) => { - console.log('copyToClipboard called with text:', text); // Debug log + // console.log('copyToClipboard called with text:', text); // Debug log try { // Try modern clipboard API first if (navigator.clipboard && window.isSecureContext) { - console.log('Using modern clipboard API'); // Debug log + // console.log('Using modern clipboard API'); // Debug log await navigator.clipboard.writeText(text); toast({ title: "Copied!", @@ -16,7 +16,7 @@ export const copyToClipboard = async (text: string) => { return; } - console.log('Using fallback clipboard method'); // Debug log + // console.log('Using fallback clipboard method'); // Debug log // Fallback for older browsers or non-secure contexts const textArea = document.createElement("textarea"); @@ -39,7 +39,7 @@ export const copyToClipboard = async (text: string) => { document.body.removeChild(textArea); if (successful) { - console.log('Copy successful with execCommand'); // Debug log + // console.log('Copy successful with execCommand'); // Debug log toast({ title: "Copied!", description: "Content copied to clipboard successfully.", @@ -48,7 +48,7 @@ export const copyToClipboard = async (text: string) => { throw new Error('Copy command failed'); } } catch (error) { - console.error('Failed to copy to clipboard:', error); + // console.error('Failed to copy to clipboard:', error); // Show error toast toast({ From 50b4d1dfb0935ff8ce3a6fff15b53c93597179a4 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Thu, 31 Jul 2025 16:19:16 +0700 Subject: [PATCH 03/38] Refactor: Reduce verbose logging output in Regional Script - Reduce verbose logging in install-regional-agent.sh to provide a more minimal output while retaining all core functionality. --- scripts/install-regional-agent.sh | 229 +++++++++++------------------- 1 file changed, 81 insertions(+), 148 deletions(-) diff --git a/scripts/install-regional-agent.sh b/scripts/install-regional-agent.sh index dbf789d..92dba46 100644 --- a/scripts/install-regional-agent.sh +++ b/scripts/install-regional-agent.sh @@ -17,6 +17,30 @@ BASE_PACKAGE_URL="https://github.com/operacle/Distributed-Regional-Monitoring/re PACKAGE_VERSION="1.0.0" SERVICE_NAME="regional-check-agent" +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + # Function to show usage show_usage() { echo "Usage: $0 [OPTIONS]" @@ -77,38 +101,36 @@ done # Validate required parameters if [[ -z "$REGION_NAME" || -z "$AGENT_ID" || -z "$AGENT_IP_ADDRESS" || -z "$AGENT_TOKEN" || -z "$POCKETBASE_URL" ]]; then - echo "โŒ Error: Missing required parameters" + log_error "Missing required parameters" echo "" show_usage exit 1 fi -echo "๐Ÿš€ CheckCle Regional Monitoring Agent - Universal Installation" -echo "==============================================================" -echo "" +echo "=============================================" +echo " CheckCle Regional Monitoring Agent" +echo " Universal Installation" +echo "=============================================" # Check if running as root if [[ $EUID -ne 0 ]]; then - echo "โŒ This script must be run as root (use sudo)" + log_error "This script must be run as root (use sudo)" echo " Usage: sudo bash $0 [options]" exit 1 fi # Detect operating system -echo "๐Ÿ” Detecting system information..." OS_TYPE=$(uname -s | tr '[:upper:]' '[:lower:]') -echo " Operating System: $OS_TYPE" # Check if it's a supported OS if [[ "$OS_TYPE" != "linux" ]]; then - echo "โŒ Unsupported operating system: $OS_TYPE" - echo " This installer only supports Linux systems" + log_error "Unsupported operating system: $OS_TYPE" + log_info "This installer only supports Linux systems" exit 1 fi # Detect architecture ARCH=$(uname -m) -echo " Hardware Architecture: $ARCH" # Map architecture to package architecture case $ARCH in @@ -120,33 +142,22 @@ case $ARCH in ;; armv7l|armv6l) PKG_ARCH="arm64" - echo "โš ๏ธ ARM 32-bit detected, using ARM64 package (may require compatibility layer)" + log_warning "ARM 32-bit detected, using ARM64 package (may require compatibility layer)" ;; *) - echo "โŒ Unsupported architecture: $ARCH" - echo " Supported architectures: x86_64 (amd64), aarch64 (arm64)" - echo " Please contact support for your architecture: $ARCH" + log_error "Unsupported architecture: $ARCH" + log_info "Supported architectures: x86_64 (amd64), aarch64 (arm64)" exit 1 ;; esac -echo " Package Architecture: $PKG_ARCH" +log_success "System: $OS_TYPE ($ARCH -> $PKG_ARCH)" # Construct package URLs and names PACKAGE_URL="$BASE_PACKAGE_URL/distributed-regional-check-agent_${PACKAGE_VERSION}_${PKG_ARCH}.deb" PACKAGE_NAME="distributed-regional-check-agent_${PACKAGE_VERSION}_${PKG_ARCH}.deb" -echo "" -echo "๐Ÿ“‹ Installation Configuration:" -echo " Region Name: $REGION_NAME" -echo " Agent ID: $AGENT_ID" -echo " Agent IP: $AGENT_IP_ADDRESS" -echo " Package Architecture: $PKG_ARCH" -echo " Package URL: $PACKAGE_URL" -echo "" - # Check for required tools -echo "๐Ÿ”ง Checking system requirements..." MISSING_TOOLS=() if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then @@ -162,129 +173,85 @@ if ! command -v systemctl >/dev/null 2>&1; then fi if [ ${#MISSING_TOOLS[@]} -ne 0 ]; then - echo "โŒ Missing required tools: ${MISSING_TOOLS[*]}" - echo " Please install missing tools and try again" - echo " On Debian/Ubuntu: sudo apt-get update && sudo apt-get install wget curl" + log_error "Missing required tools: ${MISSING_TOOLS[*]}" + log_info "On Debian/Ubuntu: sudo apt-get update && sudo apt-get install wget curl" exit 1 fi -echo "โœ… System requirements satisfied" - # Create temporary directory TEMP_DIR=$(mktemp -d) -echo "๐Ÿ“ Created temporary directory: $TEMP_DIR" # Download the .deb package -echo "" -echo "๐Ÿ“ฅ Downloading Regional Monitoring Agent package for $PKG_ARCH..." +log_info "Downloading package for $PKG_ARCH..." cd "$TEMP_DIR" -# Test if package exists first - Accept both 200 and 302 (redirect) as success -echo "๐Ÿ” Checking package availability..." +# Test if package exists first if command -v curl >/dev/null 2>&1; then HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -I "$PACKAGE_URL") if [ "$HTTP_STATUS" != "200" ] && [ "$HTTP_STATUS" != "302" ]; then - echo "โŒ Package not found at $PACKAGE_URL (HTTP $HTTP_STATUS)" - echo " Available packages should be:" - echo " - distributed-regional-check-agent_${PACKAGE_VERSION}_amd64.deb" - echo " - distributed-regional-check-agent_${PACKAGE_VERSION}_arm64.deb" - echo "" - echo " Please check the GitHub releases page:" - echo " https://github.com/operacle/Distributed-Regional-Monitoring/releases" + log_error "Package not found at $PACKAGE_URL (HTTP $HTTP_STATUS)" + log_info "Check: https://github.com/operacle/Distributed-Regional-Monitoring/releases" rm -rf "$TEMP_DIR" exit 1 fi - echo "โœ… Package found (HTTP $HTTP_STATUS), proceeding with download..." fi # Try wget first, then curl as fallback DOWNLOAD_SUCCESS=false if command -v wget >/dev/null 2>&1; then - echo "๐Ÿ“ฅ Downloading with wget..." if wget -q --show-progress --timeout=30 --tries=3 "$PACKAGE_URL" -O "$PACKAGE_NAME"; then DOWNLOAD_SUCCESS=true - echo "โœ… Package downloaded successfully using wget" fi fi if [ "$DOWNLOAD_SUCCESS" = false ] && command -v curl >/dev/null 2>&1; then - echo "๐Ÿ“ฅ Downloading with curl..." if curl -L --connect-timeout 30 --max-time 300 --retry 3 --retry-delay 2 -o "$PACKAGE_NAME" "$PACKAGE_URL" --progress-bar; then DOWNLOAD_SUCCESS=true - echo "โœ… Package downloaded successfully using curl" fi fi if [ "$DOWNLOAD_SUCCESS" = false ]; then - echo "โŒ Failed to download package from $PACKAGE_URL" - echo " Please check:" - echo " - Internet connection" - echo " - Package availability for $PKG_ARCH architecture" - echo " - GitHub repository access: https://github.com/operacle/Distributed-Regional-Monitoring/releases" - echo " - Firewall/proxy settings" - echo "" - echo " Available packages should be:" - echo " - distributed-regional-check-agent_${PACKAGE_VERSION}_amd64.deb" - echo " - distributed-regional-check-agent_${PACKAGE_VERSION}_arm64.deb" + log_error "Failed to download package from $PACKAGE_URL" + log_info "Check internet connection and package availability" rm -rf "$TEMP_DIR" exit 1 fi # Verify download was successful if [ ! -f "$PACKAGE_NAME" ] || [ ! -s "$PACKAGE_NAME" ]; then - echo "โŒ Downloaded package is empty or missing" - echo " File size: $(ls -lh "$PACKAGE_NAME" 2>/dev/null | awk '{print $5}' || echo 'file not found')" + log_error "Downloaded package is empty or missing" rm -rf "$TEMP_DIR" exit 1 fi # Verify package integrity -echo "" -echo "๐Ÿ” Verifying package..." if dpkg-deb --info "$PACKAGE_NAME" > /dev/null 2>&1; then - echo "โœ… Package verification successful" - - # Show package info - echo "๐Ÿ“ฆ Package Information:" - dpkg-deb --field "$PACKAGE_NAME" Package Version Architecture Description | head -4 + log_success "Package verified" else - echo "โŒ Package verification failed - corrupted download" - echo " File size: $(ls -lh "$PACKAGE_NAME" | awk '{print $5}')" - echo " Try downloading manually from: $PACKAGE_URL" + log_error "Package verification failed - corrupted download" rm -rf "$TEMP_DIR" exit 1 fi # Install the package -echo "" -echo "๐Ÿ“ฆ Installing Regional Monitoring Agent package..." +log_info "Installing package..." if dpkg -i "$PACKAGE_NAME" 2>/dev/null; then - echo "โœ… Package installed successfully" + log_success "Package installed" else - echo "โš ๏ธ Package installation had dependency issues, attempting to fix..." + log_warning "Fixing dependencies..." if apt-get update && apt-get install -f -y; then - echo "โœ… Dependencies fixed and package installed successfully" + log_success "Package installed with dependencies" else - echo "โŒ Failed to install package and fix dependencies" - echo " This might be due to:" - echo " - Missing system dependencies" - echo " - Architecture compatibility issues" - echo " - Package conflicts" - echo " - Insufficient disk space" - echo "" - echo " Manual resolution:" - echo " 1. Run: sudo apt-get update" - echo " 2. Run: sudo apt-get install -f" - echo " 3. Retry installation: sudo dpkg -i $PACKAGE_NAME" + log_error "Failed to install package" + log_info "Try: sudo apt-get update && sudo apt-get install -f" rm -rf "$TEMP_DIR" exit 1 fi fi # Configure the agent -echo "" -echo "โš™๏ธ Configuring Regional Monitoring Agent..." +log_info "Configuring agent..." # Ensure configuration directory exists mkdir -p /etc/regional-check-agent @@ -293,7 +260,6 @@ mkdir -p /etc/regional-check-agent cat > /etc/regional-check-agent/regional-check-agent.env << EOF # Distributed Regional Check Agent Configuration # Auto-generated on $(date) -# Architecture: $PKG_ARCH # Server Configuration PORT=8091 @@ -311,7 +277,7 @@ ENABLE_LOGGING=true POCKETBASE_ENABLED=true POCKETBASE_URL=$POCKETBASE_URL -# Regional Agent Configuration - Auto-configured +# Regional Agent Configuration REGION_NAME=$REGION_NAME AGENT_ID=$AGENT_ID AGENT_IP_ADDRESS=$AGENT_IP_ADDRESS @@ -323,112 +289,79 @@ MAX_RETRIES=3 REQUEST_TIMEOUT=10s EOF -echo "โœ… Configuration file created at /etc/regional-check-agent/regional-check-agent.env" - # Set proper permissions if id "regional-check-agent" &>/dev/null; then chown root:regional-check-agent /etc/regional-check-agent/regional-check-agent.env chmod 640 /etc/regional-check-agent/regional-check-agent.env - echo "โœ… Configuration file permissions set" else - echo "โš ๏ธ regional-check-agent user not found, using root permissions" chown root:root /etc/regional-check-agent/regional-check-agent.env chmod 600 /etc/regional-check-agent/regional-check-agent.env fi +log_success "Configuration complete" + # Enable and start the service -echo "" -echo "๐Ÿ”ง Starting Regional Monitoring Agent service..." +log_info "Starting service..." # Reload systemd daemon systemctl daemon-reload # Enable the service for auto-start if systemctl enable $SERVICE_NAME; then - echo "โœ… Service enabled for auto-start" + log_success "Service enabled" else - echo "โŒ Failed to enable service" - echo " Check systemd configuration" + log_error "Failed to enable service" rm -rf "$TEMP_DIR" exit 1 fi # Start the service if systemctl start $SERVICE_NAME; then - echo "โœ… Service started successfully" + log_success "Service started" else - echo "โŒ Failed to start service" - echo " Common issues:" - echo " - Configuration errors" - echo " - Port 8091 already in use" - echo " - Permission issues" - echo "" - echo " Troubleshooting:" - echo " - Check logs: sudo journalctl -u $SERVICE_NAME -f" - echo " - Check config: sudo nano /etc/regional-check-agent/regional-check-agent.env" - echo " - Manual start: sudo systemctl start $SERVICE_NAME" + log_error "Failed to start service" + log_info "Check logs: sudo journalctl -u $SERVICE_NAME -f" rm -rf "$TEMP_DIR" exit 1 fi # Wait a moment for service to initialize -echo "โณ Waiting for service to initialize..." sleep 5 -# Check service status -echo "" -echo "๐Ÿ“Š Service Status:" -systemctl status $SERVICE_NAME --no-pager -l --lines=5 - # Test health endpoint -echo "" -echo "๐Ÿฉบ Testing agent health endpoint..." HEALTH_CHECK_ATTEMPTS=3 -HEALTH_CHECK_DELAY=2 - for i in $(seq 1 $HEALTH_CHECK_ATTEMPTS); do if curl -s -f --connect-timeout 5 http://localhost:8091/health > /dev/null; then - echo "โœ… Agent health endpoint is responding" - HEALTH_OK=true + log_success "Health endpoint responding" break else if [ $i -lt $HEALTH_CHECK_ATTEMPTS ]; then - echo "โณ Health check attempt $i/$HEALTH_CHECK_ATTEMPTS failed, retrying in ${HEALTH_CHECK_DELAY}s..." - sleep $HEALTH_CHECK_DELAY + sleep 2 else - echo "โš ๏ธ Agent health endpoint not responding after $HEALTH_CHECK_ATTEMPTS attempts" - echo " This may be normal if the service is still starting up" - echo " Check status later with: curl http://localhost:8091/health" + log_warning "Health endpoint not responding (service may still be starting)" fi fi done # Cleanup rm -rf "$TEMP_DIR" + echo "" -echo "๐ŸŽ‰ Regional Monitoring Agent Installation Complete!" +echo "=============================================" +echo " Installation Complete!" +echo "=============================================" echo "" -echo "๐Ÿ“‹ Installation Summary:" -echo " Agent ID: $AGENT_ID" -echo " Region: $REGION_NAME" -echo " Architecture: $PKG_ARCH ($ARCH)" -echo " Status: $(systemctl is-active $SERVICE_NAME 2>/dev/null || echo 'unknown')" -echo " Health URL: http://localhost:8091/health" -echo " Service endpoint: http://localhost:8091/operation" -echo " Config file: /etc/regional-check-agent/regional-check-agent.env" +log_success "CheckCle Regional Monitoring Agent installed successfully" echo "" -echo "๐Ÿ“ Useful commands:" -echo " Check status: sudo systemctl status $SERVICE_NAME" -echo " View logs: sudo journalctl -u $SERVICE_NAME -f" -echo " Restart: sudo systemctl restart $SERVICE_NAME" -echo " Stop: sudo systemctl stop $SERVICE_NAME" -echo " Health check: curl http://localhost:8091/health" +echo "Agent Details:" +echo " Region: $REGION_NAME" +echo " Agent ID: $AGENT_ID" +echo " Status: $(systemctl is-active $SERVICE_NAME 2>/dev/null || echo 'unknown')" +echo " Health: http://localhost:8091/health" echo "" -echo "๐Ÿ”ง Troubleshooting:" -echo " If the service fails to start:" -echo " 1. Check logs: sudo journalctl -u $SERVICE_NAME -n 50" -echo " 2. Verify config: cat /etc/regional-check-agent/regional-check-agent.env" -echo " 3. Test connectivity: ping $(echo $POCKETBASE_URL | sed 's|https\?://||' | sed 's|/.*||')" -echo " 4. Check port availability: sudo netstat -tlnp | grep 8091" +echo "Management Commands:" +echo " Status: sudo systemctl status $SERVICE_NAME" +echo " Logs: sudo journalctl -u $SERVICE_NAME -f" +echo " Restart: sudo systemctl restart $SERVICE_NAME" echo "" -echo "โœจ The agent is now monitoring and reporting to your CheckCle dashboard!" \ No newline at end of file +log_success "Agent is now monitoring and reporting to your dashboard!" \ No newline at end of file From f7cf2fcc200a3340fa1961abc723727b55a4d209 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Thu, 31 Jul 2025 17:28:41 +0700 Subject: [PATCH 04/38] Fix: Set SSL initialize check with the current time. Closes: #100 - When adding a new SSL certificate, initialize with the current time. This ensures that the SSL check is triggered shortly after the certificate is added to the monitoring system. --- .../services/ssl/sslCertificateOperations.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/application/src/services/ssl/sslCertificateOperations.ts b/application/src/services/ssl/sslCertificateOperations.ts index d1e87a3..aa422b0 100644 --- a/application/src/services/ssl/sslCertificateOperations.ts +++ b/application/src/services/ssl/sslCertificateOperations.ts @@ -13,6 +13,8 @@ export const addSSLCertificate = async ( certificateData: AddSSLCertificateDto ): Promise => { try { + const currentTime = new Date().toISOString(); + // Prepare the data for saving to database // The Go service will handle the actual SSL checking const data = { @@ -23,14 +25,15 @@ export const addSSLCertificate = async ( cert_sans: "", cert_alg: "", serial_number: "", - valid_from: new Date().toISOString(), // Will be updated by Go service - valid_till: new Date().toISOString(), // Will be updated by Go service + valid_from: currentTime, // Will be updated by Go service + valid_till: currentTime, // Will be updated by Go service validity_days: 0, // Will be updated by Go service days_left: 0, // Will be updated by Go service warning_threshold: Number(certificateData.warning_threshold) || 30, expiry_threshold: Number(certificateData.expiry_threshold) || 7, notification_channel: certificateData.notification_channel || "", check_interval: Number(certificateData.check_interval) || 1, // New field + check_at: currentTime, // Set to current time to trigger immediate check }; // Save to database @@ -38,7 +41,7 @@ export const addSSLCertificate = async ( return record as unknown as SSLCertificate; } catch (error) { - // console.error("Error adding SSL certificate:", error); + console.error("Error adding SSL certificate:", error); throw error; } }; @@ -67,7 +70,7 @@ export const checkAndUpdateCertificate = async ( // Return the current certificate data return typedCertificate; } catch (error) { - // console.error("Error updating SSL certificate:", error); + console.error("Error updating SSL certificate:", error); throw error; } }; @@ -84,10 +87,10 @@ export const triggerImmediateCheck = async (certificateId: string): Promise => { await pb.collection("ssl_certificates").delete(id); return true; } catch (error) { - // console.error("Error deleting SSL certificate:", error); + console.error("Error deleting SSL certificate:", error); throw error; } }; @@ -112,10 +115,10 @@ export const deleteSSLCertificate = async (id: string): Promise => { */ export const refreshAllCertificates = async (): Promise<{ success: number; failed: number }> => { try { - const response = await pb.collection("ssl_certificates").getList(1, 200); + const response = await pb.collection("ssl_certificates").getList(1, 100); 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 +128,14 @@ export const refreshAllCertificates = async (): Promise<{ success: number; faile await checkCertificateAndNotify(cert); success++; } catch (error) { - // console.error(`Failed to refresh certificate ${cert.domain}:`, error); + console.error(`Failed to refresh certificate ${cert.domain}:`, error); failed++; } } return { success, failed }; } catch (error) { - // console.error("Error refreshing certificates:", error); + console.error("Error refreshing certificates:", error); throw error; } }; \ No newline at end of file From 914f1aba608a7e97cecf311c36c811034227064e Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Sat, 2 Aug 2025 16:53:15 +0700 Subject: [PATCH 05/38] feat: Support multiple notification channels - Update the Notification Settings dashboard to allow users to select multiple notification channels for alerts. - Add webhook to notification channels: Add webhook as a selectable option in the notification channel type dropdown. - Implement template type selection in the Add Template dialog. Based on the selected type. --- .../components/servers/EditServerDialog.tsx | 42 ++- .../services/ServiceMonitoringButton.tsx | 17 +- .../alerts-templates/AlertsTemplates.tsx | 99 ++++-- .../alerts-templates/TemplateDialog.tsx | 155 +++++++-- .../alerts-templates/TemplateList.tsx | 213 ++++++------ .../form/ServerTemplateFields.tsx | 211 ++++++++++++ .../form/ServiceTemplateFields.tsx | 133 ++++++++ .../form/SslTemplateFields.tsx | 79 +++++ .../settings/alerts-templates/hooks/index.ts | 2 +- .../alerts-templates/hooks/useTemplateForm.ts | 208 +++++++----- .../NotificationChannelDialog.tsx | 317 ++++++++++++++---- .../NotificationSettings.tsx | 3 +- application/src/pages/Login.tsx | 140 +++----- .../src/services/alertConfigService.ts | 33 +- .../handlers/serviceStatusHandlers.ts | 81 +---- .../service-status/resumeMonitoring.ts | 43 +-- .../src/services/notification/index.ts | 236 +------------ .../notification/templateProcessor.ts | 26 +- .../src/services/notificationService.ts | 194 ----------- .../serverNotificationTemplateService.ts | 103 ++++++ .../serviceNotificationTemplateService.ts | 93 +++++ .../sslNotificationTemplateService.ts | 87 +++++ application/src/services/templateService.ts | 180 +++++----- application/src/services/webhookService.ts | 66 ++++ 24 files changed, 1712 insertions(+), 1049 deletions(-) create mode 100644 application/src/components/settings/alerts-templates/form/ServerTemplateFields.tsx create mode 100644 application/src/components/settings/alerts-templates/form/ServiceTemplateFields.tsx create mode 100644 application/src/components/settings/alerts-templates/form/SslTemplateFields.tsx delete mode 100644 application/src/services/notificationService.ts create mode 100644 application/src/services/serverNotificationTemplateService.ts create mode 100644 application/src/services/serviceNotificationTemplateService.ts create mode 100644 application/src/services/sslNotificationTemplateService.ts create mode 100644 application/src/services/webhookService.ts diff --git a/application/src/components/servers/EditServerDialog.tsx b/application/src/components/servers/EditServerDialog.tsx index cce5755..3db001e 100644 --- a/application/src/components/servers/EditServerDialog.tsx +++ b/application/src/components/servers/EditServerDialog.tsx @@ -1,3 +1,4 @@ + import React, { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; @@ -12,7 +13,7 @@ import { pb } from "@/lib/pocketbase"; import { Server } from "@/types/server.types"; import { RefreshCw, X } from "lucide-react"; import { alertConfigService, AlertConfiguration } from "@/services/alertConfigService"; -import { templateService, NotificationTemplate } from "@/services/templateService"; +import { templateService, ServerNotificationTemplate } from "@/services/templateService"; import { serverThresholdService, ServerThreshold } from "@/services/serverThresholdService"; interface EditServerDialogProps { @@ -66,9 +67,9 @@ export const EditServerDialog: React.FC = ({ const [isSubmitting, setIsSubmitting] = useState(false); const [alertConfigs, setAlertConfigs] = useState([]); - const [templates, setTemplates] = useState([]); + const [templates, setTemplates] = useState([]); const [thresholds, setThresholds] = useState([]); - const [selectedTemplate, setSelectedTemplate] = useState(null); + const [selectedTemplate, setSelectedTemplate] = useState(null); const [selectedThreshold, setSelectedThreshold] = useState(null); const [loadingAlertConfigs, setLoadingAlertConfigs] = useState(false); const [loadingTemplates, setLoadingTemplates] = useState(false); @@ -78,7 +79,7 @@ export const EditServerDialog: React.FC = ({ // Initialize form data when server changes useEffect(() => { if (server) { - // console.log("Setting form data for server:", server); + // console.log("Setting form data for server:", server); // Parse comma-separated notification_id into array const notificationChannels = server.notification_id ? server.notification_id.split(',').map(id => id.trim()).filter(id => id) @@ -180,8 +181,9 @@ export const EditServerDialog: React.FC = ({ const loadTemplates = async () => { try { setLoadingTemplates(true); - const templateList = await templateService.getTemplates(); - setTemplates(templateList); + const templateList = await templateService.getTemplates('server'); + // Cast to ServerNotificationTemplate[] since we know we're getting server templates + setTemplates(templateList as ServerNotificationTemplate[]); } catch (error) { // console.error('Error loading templates:', error); toast({ @@ -621,27 +623,39 @@ export const EditServerDialog: React.FC = ({
- +

- {selectedTemplate.up_message || "No RAM threshold message defined"} + {selectedTemplate.ram_message || "No RAM message defined"}

- +

- {selectedTemplate.down_message || "No CPU threshold message defined"} + {selectedTemplate.cpu_message || "No CPU message defined"}

- +

- {selectedTemplate.incident_message || "No disk threshold message defined"} + {selectedTemplate.disk_message || "No disk message defined"}

- +

- {selectedTemplate.maintenance_message || "No network threshold message defined"} + {selectedTemplate.network_message || "No network message defined"} +

+
+
+ +

+ {selectedTemplate.up_message || "No up message defined"} +

+
+
+ +

+ {selectedTemplate.down_message || "No down message defined"}

diff --git a/application/src/components/services/ServiceMonitoringButton.tsx b/application/src/components/services/ServiceMonitoringButton.tsx index 3e2d3fc..8a0aed0 100644 --- a/application/src/components/services/ServiceMonitoringButton.tsx +++ b/application/src/components/services/ServiceMonitoringButton.tsx @@ -5,7 +5,6 @@ import { Play, Pause } from "lucide-react"; import { Service } from "@/types/service.types"; import { serviceService } from "@/services/serviceService"; import { useToast } from "@/hooks/use-toast"; -import { notificationService } from "@/services/notificationService"; interface ServiceMonitoringButtonProps { service: Service; @@ -34,16 +33,8 @@ export function ServiceMonitoringButton({ service, onStatusChange }: ServiceMoni if (onStatusChange) onStatusChange("paused"); - // Send notification for paused status (only here, not in pauseMonitoring.ts) - if (service.alerts !== "muted") { - // console.log("Sending pause notification from UI component"); - // IMPORTANT: Direct call to the notification service to ensure a message is sent - await notificationService.sendNotification({ - service: service, - status: "paused", - timestamp: new Date().toISOString(), - }); - } + // Notification handling removed - will be handled by backend + // console.log("Service paused - notifications will be handled by backend"); toast({ title: "Monitoring paused", @@ -51,7 +42,7 @@ export function ServiceMonitoringButton({ service, onStatusChange }: ServiceMoni }); } else { // Start/resume monitoring - // console.log(`Starting monitoring for service ${service.id} (${service.name})`); + // console.log(`Starting monitoring for service ${service.id} (${service.name})`); // First ensure we update the status in the database to not be paused anymore await serviceService.resumeMonitoring(service.id); @@ -100,4 +91,4 @@ export function ServiceMonitoringButton({ service, onStatusChange }: ServiceMoni )} ); -} +} \ No newline at end of file diff --git a/application/src/components/settings/alerts-templates/AlertsTemplates.tsx b/application/src/components/settings/alerts-templates/AlertsTemplates.tsx index b8226d4..de52b33 100644 --- a/application/src/components/settings/alerts-templates/AlertsTemplates.tsx +++ b/application/src/components/settings/alerts-templates/AlertsTemplates.tsx @@ -1,9 +1,10 @@ import React, { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { templateService } from "@/services/templateService"; +import { templateService, TemplateType } from "@/services/templateService"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus, RefreshCcw } from "lucide-react"; import { TemplateList } from "./TemplateList"; import { TemplateDialog } from "./TemplateDialog"; @@ -11,8 +12,10 @@ import { useToast } from "@/hooks/use-toast"; export const AlertsTemplates = () => { const { toast } = useToast(); + const [activeTab, setActiveTab] = useState('service'); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingTemplate, setEditingTemplate] = useState(null); + const [editingTemplateType, setEditingTemplateType] = useState(null); const { data: templates = [], @@ -20,17 +23,19 @@ export const AlertsTemplates = () => { error, refetch } = useQuery({ - queryKey: ['notification_templates'], - queryFn: templateService.getTemplates, + queryKey: ['notification_templates', activeTab], + queryFn: () => templateService.getTemplates(activeTab), }); - const handleAddTemplate = () => { + const handleAddTemplate = (templateType: TemplateType) => { setEditingTemplate(null); + setEditingTemplateType(templateType); setIsDialogOpen(true); }; - const handleEditTemplate = (id: string) => { + const handleEditTemplate = (id: string, templateType: TemplateType) => { setEditingTemplate(id); + setEditingTemplateType(templateType); setIsDialogOpen(true); }; @@ -51,33 +56,83 @@ export const AlertsTemplates = () => { Refresh - - {error ? ( -
-

Error loading templates

- -
- ) : ( - - )} + setActiveTab(value as TemplateType)}> + + Service Uptime + Server Monitoring + SSL Certificate + + + + {error ? ( +
+

Error loading service templates

+ +
+ ) : ( + handleEditTemplate(id, 'service')} + refetchTemplates={refetch} + templateType="service" + /> + )} +
+ + + {error ? ( +
+

Error loading server templates

+ +
+ ) : ( + handleEditTemplate(id, 'server')} + refetchTemplates={refetch} + templateType="server" + /> + )} +
+ + + {error ? ( +
+

Error loading SSL templates

+ +
+ ) : ( + handleEditTemplate(id, 'ssl')} + refetchTemplates={refetch} + templateType="ssl" + /> + )} +
+
{ refetch(); @@ -86,4 +141,4 @@ export const AlertsTemplates = () => { /> ); -}; +}; \ No newline at end of file diff --git a/application/src/components/settings/alerts-templates/TemplateDialog.tsx b/application/src/components/settings/alerts-templates/TemplateDialog.tsx index 4b9b13d..a1afeb7 100644 --- a/application/src/components/settings/alerts-templates/TemplateDialog.tsx +++ b/application/src/components/settings/alerts-templates/TemplateDialog.tsx @@ -1,19 +1,25 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; -import { Form } from "@/components/ui/form"; +import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useTemplateForm } from "./hooks/useTemplateForm"; -import { BasicTemplateFields } from "./form/BasicTemplateFields"; -import { MessagesTabContent } from "./form/MessagesTabContent"; -import { PlaceholdersTabContent } from "./form/PlaceholdersTabContent"; +import { ServerTemplateFields } from "./form/ServerTemplateFields"; +import { ServiceTemplateFields } from "./form/ServiceTemplateFields"; +import { SslTemplateFields } from "./form/SslTemplateFields"; import { Loader2, ChevronDown } from "lucide-react"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { TemplateType, templateTypeConfigs } from "@/services/templateService"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Textarea } from "@/components/ui/textarea"; interface TemplateDialogProps { open: boolean; templateId: string | null; + templateType: TemplateType | null; onOpenChange: (open: boolean) => void; onSuccess: () => void; } @@ -21,9 +27,12 @@ interface TemplateDialogProps { export const TemplateDialog: React.FC = ({ open, templateId, + templateType: initialTemplateType, onOpenChange, onSuccess, }) => { + const [selectedTemplateType, setSelectedTemplateType] = useState(initialTemplateType || 'service'); + const { form, isEditMode, @@ -32,28 +41,70 @@ export const TemplateDialog: React.FC = ({ onSubmit } = useTemplateForm({ templateId, + templateType: selectedTemplateType, open, onOpenChange, onSuccess }); - // For debugging purposes + // Update template type when prop changes or dialog opens useEffect(() => { - if (open) { - // console.log("Template dialog opened. Edit mode:", isEditMode, "Template ID:", templateId); - - // Log form values when they change - const subscription = form.watch((value) => { - // console.log("Current form values:", value); - }); - - return () => subscription.unsubscribe(); + if (initialTemplateType) { + setSelectedTemplateType(initialTemplateType); + } else if (open && !isEditMode) { + setSelectedTemplateType('service'); } - }, [open, isEditMode, templateId, form]); + }, [initialTemplateType, open, isEditMode]); + + // Handle template type change + const handleTemplateTypeChange = (newType: TemplateType) => { + if (!isEditMode) { + setSelectedTemplateType(newType); + form.setValue('templateType', newType); + } + }; + + const renderTemplateFields = () => { + switch (selectedTemplateType) { + case 'server': + return ; + case 'service': + return ; + case 'ssl': + return ; + default: + return null; + } + }; + + const renderPlaceholderGuide = () => { + const config = templateTypeConfigs[selectedTemplateType]; + if (!config) return null; + + return ( + + + Available Placeholders + + +

+ {config.description}. Use these placeholders in your messages: +

+
+ {config.placeholders.map((placeholder) => ( +
+ {placeholder} +
+ ))} +
+
+
+ ); + }; return ( - + {isEditMode ? "Edit Template" : "Add Template"} @@ -69,7 +120,69 @@ export const TemplateDialog: React.FC = ({
- + {/* Basic Fields */} +
+ ( + + Template Name + + + + + + )} + /> + + ( + + Template Type + + + + + + )} + /> + + ( + + Custom Placeholder + + + + + + )} + /> +
@@ -78,11 +191,11 @@ export const TemplateDialog: React.FC = ({ - + {renderTemplateFields()} - + {renderPlaceholderGuide()}
@@ -120,4 +233,4 @@ export const TemplateDialog: React.FC = ({
); -}; +}; \ No newline at end of file diff --git a/application/src/components/settings/alerts-templates/TemplateList.tsx b/application/src/components/settings/alerts-templates/TemplateList.tsx index e3c40d2..d820e96 100644 --- a/application/src/components/settings/alerts-templates/TemplateList.tsx +++ b/application/src/components/settings/alerts-templates/TemplateList.tsx @@ -1,17 +1,15 @@ import React, { useState } from "react"; -import { NotificationTemplate, templateService } from "@/services/templateService"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; import { Button } from "@/components/ui/button"; -import { Edit, Trash2 } from "lucide-react"; -import { +import { Badge } from "@/components/ui/badge"; +import { Trash2, Edit, MoreVertical } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, @@ -22,63 +20,75 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useToast } from "@/hooks/use-toast"; -import { Badge } from "@/components/ui/badge"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { templateService, AnyTemplate, TemplateType } from "@/services/templateService"; interface TemplateListProps { - templates: NotificationTemplate[]; + templates: AnyTemplate[]; isLoading: boolean; onEdit: (id: string) => void; refetchTemplates: () => void; + templateType: TemplateType; } -export const TemplateList: React.FC = ({ - templates, - isLoading, - onEdit, +export const TemplateList: React.FC = ({ + templates, + isLoading, + onEdit, refetchTemplates, + templateType }) => { const { toast } = useToast(); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [templateToDelete, setTemplateToDelete] = useState(null); - const [isDeleting, setIsDeleting] = useState(false); + const queryClient = useQueryClient(); + const [deleteTemplateId, setDeleteTemplateId] = useState(null); - const handleDeletePrompt = (template: NotificationTemplate) => { - setTemplateToDelete(template); - setDeleteDialogOpen(true); - }; - - const handleDeleteTemplate = async () => { - if (!templateToDelete) return; - - setIsDeleting(true); - try { - await templateService.deleteTemplate(templateToDelete.id); + // Delete mutation + const deleteMutation = useMutation({ + mutationFn: (id: string) => templateService.deleteTemplate(id, templateType), + onSuccess: () => { toast({ title: "Template deleted", - description: `Template "${templateToDelete.name}" has been removed.`, + description: "The template has been deleted successfully.", }); + queryClient.invalidateQueries({ queryKey: ['notification_templates', templateType] }); refetchTemplates(); - } catch (error) { - console.error("Error deleting template:", error); + }, + onError: (error) => { + // console.error("Error deleting template:", error); toast({ title: "Error", description: "Failed to delete template. Please try again.", variant: "destructive", }); - } finally { - setIsDeleting(false); - setDeleteDialogOpen(false); - setTemplateToDelete(null); + }, + }); + + const handleDelete = (id: string) => { + setDeleteTemplateId(id); + }; + + const confirmDelete = () => { + if (deleteTemplateId) { + deleteMutation.mutate(deleteTemplateId); + setDeleteTemplateId(null); } }; if (isLoading) { return ( -
-
-
-
-
+
+ {[...Array(3)].map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+ ))}
); } @@ -86,83 +96,86 @@ export const TemplateList: React.FC = ({ if (templates.length === 0) { return (
-

No templates found

-

- Create your first notification template to get started. -

+

No templates found. Create your first template to get started.

); } + const getTemplateTypeLabel = (type: TemplateType) => { + switch (type) { + case 'server': return 'Server'; + case 'service': return 'Service'; + case 'ssl': return 'SSL'; + default: return 'Unknown'; + } + }; + return ( <> -
- - - - Name - Type - Created - Actions - - - - {templates.map((template) => ( - - {template.name} - - {template.type} - - - {new Date(template.created).toLocaleDateString()} - - -
- - -
-
-
- ))} -
-
+
+ {templates.map((template) => ( +
+
+
+

{template.name}

+ {getTemplateTypeLabel(templateType)} +
+

+ Created: {new Date(template.created).toLocaleDateString()} + {template.updated !== template.created && + ` โ€ข Updated: ${new Date(template.updated).toLocaleDateString()}` + } +

+
+
+ + + + + + + handleDelete(template.id)} + className="text-destructive focus:text-destructive" + > + + Delete + + + +
+
+ ))}
- + setDeleteTemplateId(null)}> - Delete Template + Are you sure? - Are you sure you want to delete the template "{templateToDelete?.name}"? This action cannot be undone. + This action cannot be undone. This will permanently delete the template. - Cancel + Cancel { - e.preventDefault(); - handleDeleteTemplate(); - }} - disabled={isDeleting} + onClick={confirmDelete} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > - {isDeleting ? "Deleting..." : "Delete"} + Delete ); -}; +}; \ No newline at end of file diff --git a/application/src/components/settings/alerts-templates/form/ServerTemplateFields.tsx b/application/src/components/settings/alerts-templates/form/ServerTemplateFields.tsx new file mode 100644 index 0000000..259a2e4 --- /dev/null +++ b/application/src/components/settings/alerts-templates/form/ServerTemplateFields.tsx @@ -0,0 +1,211 @@ + +import React from "react"; +import { FormField, FormItem, FormLabel, FormControl, FormMessage, FormDescription } from "@/components/ui/form"; +import { Textarea } from "@/components/ui/textarea"; +import { Control } from "react-hook-form"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +interface ServerTemplateFieldsProps { + control: Control; +} + +export const ServerTemplateFields: React.FC = ({ control }) => { + return ( +
+ + + System Resource Messages + + +
+ ( + + CPU Alert Message + +