From 4ddc5ceef5b63464b35a70ea47882bc6bad476db Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Fri, 28 Feb 2025 21:06:12 -0600 Subject: [PATCH 01/12] adding standalone mode --- web/next.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/next.config.ts b/web/next.config.ts index cbd7263..100d6e5 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -3,8 +3,8 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { reactStrictMode: true, async rewrites() { - // const backendUrl = process.env.BACKEND_URL || 'http://localhost:8090'; - const backendUrl = process.env.BACKEND_URL || 'http://172.233.208.210:8090'; + const backendUrl = process.env.BACKEND_URL || 'http://localhost:8090'; + // const backendUrl = process.env.BACKEND_URL || 'http://172.233.208.210:8090'; return [ { source: '/api/:path*', From e12937759259e9d1455dd8534459bda6b95b498f Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 00:14:02 -0600 Subject: [PATCH 02/12] WIP standalone build for web - wont start on cloud --- Dockerfile.build | 1 + cmd/cloud/main.go | 2 +- packaging/cloud/config/nginx.conf | 33 ++++ scripts/build-web.sh | 11 -- scripts/buildAll.sh | 3 +- scripts/buildServiceRadar.sh | 216 ++++++++++++++++++++++++++ scripts/setup-deb-cloud.sh | 122 ++++++++++----- scripts/setup-deb-web.sh | 242 ++++++++++++++++++++++++++++++ web/next.config.ts | 2 +- web/package-lock.json | 1 + web/package.json | 1 + 11 files changed, 582 insertions(+), 52 deletions(-) create mode 100644 packaging/cloud/config/nginx.conf delete mode 100755 scripts/build-web.sh create mode 100755 scripts/buildServiceRadar.sh create mode 100755 scripts/setup-deb-web.sh diff --git a/Dockerfile.build b/Dockerfile.build index bb1ac92..b22e7ba 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -9,6 +9,7 @@ RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ libsqlite3-dev \ make \ nodejs \ + iproute2 \ && rm -rf /var/lib/apt/lists/* # Verify installations diff --git a/cmd/cloud/main.go b/cmd/cloud/main.go index 2dd887e..e256c42 100644 --- a/cmd/cloud/main.go +++ b/cmd/cloud/main.go @@ -41,7 +41,7 @@ func run() error { apiServer := api.NewAPIServer( api.WithMetricsManager(server.GetMetricsManager()), api.WithSNMPManager(server.GetSNMPManager()), - api.WithAPIKey(cfg.APIKey), // Pass the API key from config + api.WithAPIKey(cfg.APIKey), ) server.SetAPIServer(apiServer) diff --git a/packaging/cloud/config/nginx.conf b/packaging/cloud/config/nginx.conf new file mode 100644 index 0000000..e338657 --- /dev/null +++ b/packaging/cloud/config/nginx.conf @@ -0,0 +1,33 @@ +server { + listen 80 default_server; + server_name _; + + access_log /var/log/nginx/serviceradar.access.log; + error_log /var/log/nginx/serviceradar.error.log; + + # API endpoints + location /api/ { + proxy_pass http://localhost:8090; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # WebSocket support (for Next.js if needed) + location /_next/webpack-hmr { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Main UI + location / { + proxy_pass http://localhost:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/scripts/build-web.sh b/scripts/build-web.sh deleted file mode 100755 index 5520319..0000000 --- a/scripts/build-web.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -e - -echo "Building web interface..." - -# Build web interface -cd ./web -npm install -npm run build - -echo "Web interface build complete." \ No newline at end of file diff --git a/scripts/buildAll.sh b/scripts/buildAll.sh index 48c3931..14b6b4b 100755 --- a/scripts/buildAll.sh +++ b/scripts/buildAll.sh @@ -3,9 +3,10 @@ VERSION=${VERSION:-1.0.19} +./scripts/setup-deb-agent.sh ./scripts/setup-deb-poller.sh +./scripts/setup-deb-web.sh ./scripts/setup-deb-dusk-checker.sh -./scripts/setup-deb-agent.sh ./scripts/setup-deb-snmp-checker.sh scp ./release-artifacts/serviceradar-poller_${VERSION}.deb duskadmin@192.168.2.22:~/ diff --git a/scripts/buildServiceRadar.sh b/scripts/buildServiceRadar.sh new file mode 100755 index 0000000..aa311d9 --- /dev/null +++ b/scripts/buildServiceRadar.sh @@ -0,0 +1,216 @@ +#!/bin/bash +# buildServiceradar.sh - Build and optionally install ServiceRadar components +set -e # Exit on any error + +# Default settings +VERSION=${VERSION:-1.0.20} +BUILD_TAGS=${BUILD_TAGS:-""} +BUILD_ALL=false +INSTALL=false +TARGET_HOST="" +COMPONENTS=() + +# Display usage information +usage() { + echo "Usage: $0 [options] [components]" + echo + echo "Options:" + echo " -h, --help Show this help message" + echo " -v, --version VERSION Set version number (default: $VERSION)" + echo " -t, --tags TAGS Set build tags" + echo " -a, --all Build all components" + echo " -i, --install Install packages after building" + echo " --host HOST Install to remote host (requires SSH access)" + echo + echo "Components:" + echo " cloud Build cloud API service" + echo " web Build web UI" + echo " poller Build poller service" + echo " agent Build agent service" + echo " dusk-checker Build dusk checker" + echo " snmp-checker Build SNMP checker" + echo + echo "Examples:" + echo " $0 --all Build all components" + echo " $0 cloud web Build cloud and web components" + echo " $0 --all --install Build and install all components locally" + echo " $0 cloud web --install --host user@server Build and install on remote host" + echo + exit 1 +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + usage + ;; + -v|--version) + VERSION="$2" + shift 2 + ;; + -t|--tags) + BUILD_TAGS="$2" + shift 2 + ;; + -a|--all) + BUILD_ALL=true + shift + ;; + -i|--install) + INSTALL=true + shift + ;; + --host) + TARGET_HOST="$2" + shift 2 + ;; + cloud|web|poller|agent|dusk-checker|snmp-checker) + COMPONENTS+=("$1") + shift + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + +# Check if we should build all components +if [ "$BUILD_ALL" = true ]; then + COMPONENTS=("cloud" "web" "poller" "agent" "dusk-checker" "snmp-checker") +fi + +# If no components specified, show usage +if [ ${#COMPONENTS[@]} -eq 0 ]; then + echo "Error: No components specified for building" + usage +fi + +# Export variables for sub-scripts +export VERSION +export BUILD_TAGS + +# Function to build a component +build_component() { + local component=$1 + echo "=========================================" + echo "Building $component component (version $VERSION)" + echo "=========================================" + + case $component in + cloud) + ./scripts/setup-deb-cloud.sh + ;; + web) + ./scripts/setup-deb-web.sh + ;; + poller) + ./scripts/setup-deb-poller.sh + ;; + agent) + ./scripts/setup-deb-agent.sh + ;; + dusk-checker) + ./scripts/setup-deb-dusk-checker.sh + ;; + snmp-checker) + ./scripts/setup-deb-snmp-checker.sh + ;; + *) + echo "Unknown component: $component" + return 1 + ;; + esac + + echo "Build of $component completed successfully" + return 0 +} + +# Function to install packages +install_packages() { + local install_cmd="sudo dpkg -i" + local prefix="./release-artifacts/" + local packages=() + + # Build list of package files + for component in "${COMPONENTS[@]}"; do + local package_name + + case $component in + cloud) + package_name="serviceradar-cloud_${VERSION}.deb" + ;; + web) + package_name="serviceradar-web_${VERSION}.deb" + ;; + poller) + package_name="serviceradar-poller_${VERSION}.deb" + ;; + agent) + package_name="serviceradar-agent_${VERSION}.deb" + ;; + dusk-checker) + package_name="serviceradar-dusk-checker_${VERSION}.deb" + ;; + snmp-checker) + package_name="serviceradar-snmp-checker_${VERSION}.deb" + ;; + *) + echo "Unknown component for installation: $component" + continue + ;; + esac + + # Add package to the list if it exists + if [ -f "${prefix}${package_name}" ]; then + packages+=("${prefix}${package_name}") + else + echo "Warning: Package file not found: ${prefix}${package_name}" + fi + done + + # If no packages to install, return + if [ ${#packages[@]} -eq 0 ]; then + echo "No packages found for installation" + return 1 + fi + + # Install locally or remotely + if [ -z "$TARGET_HOST" ]; then + echo "Installing packages locally..." + $install_cmd "${packages[@]}" + else + echo "Installing packages on $TARGET_HOST..." + + # Create temp directory on remote host + ssh "$TARGET_HOST" "mkdir -p ~/serviceradar-tmp" + + # Copy packages to remote host + for package in "${packages[@]}"; do + echo "Copying $package to $TARGET_HOST..." + scp "$package" "$TARGET_HOST:~/serviceradar-tmp/" + done + + # Install packages on remote host + ssh "$TARGET_HOST" "sudo dpkg -i ~/serviceradar-tmp/*.deb && rm -rf ~/serviceradar-tmp" + fi + + echo "Installation completed successfully" + return 0 +} + +# Create release-artifacts directory if it doesn't exist +mkdir -p ./release-artifacts + +# Build each component +for component in "${COMPONENTS[@]}"; do + build_component "$component" || exit 1 +done + +# Install packages if requested +if [ "$INSTALL" = true ]; then + install_packages || exit 1 +fi + +echo "All operations completed successfully!" diff --git a/scripts/setup-deb-cloud.sh b/scripts/setup-deb-cloud.sh index bfcbfa5..873e73f 100755 --- a/scripts/setup-deb-cloud.sh +++ b/scripts/setup-deb-cloud.sh @@ -12,29 +12,8 @@ PKG_ROOT="serviceradar-cloud_${VERSION}" mkdir -p "${PKG_ROOT}/DEBIAN" mkdir -p "${PKG_ROOT}/usr/local/bin" mkdir -p "${PKG_ROOT}/etc/serviceradar" +mkdir -p "${PKG_ROOT}/etc/nginx/conf.d" mkdir -p "${PKG_ROOT}/lib/systemd/system" -#mkdir -p "${PKG_ROOT}/usr/local/share/serviceradar-cloud/web" - -#echo "Building web interface..." - -# Build web interface if not already built -#if [ ! -d "web/dist" ]; then -# cd ./web -# npm install -# npm run build -# cd .. -#fi - -# Create a directory for the embedded content -#mkdir -p pkg/cloud/api/web -#cp -r web/dist pkg/cloud/api/web/ - -# Only copy web assets to package directory for container builds -# For non-container builds, they're embedded in the binary -#if [[ "$BUILD_TAGS" == *"containers"* ]]; then -# cp -r web/dist "${PKG_ROOT}/usr/local/share/serviceradar-cloud/web/" -# echo "Copied web assets for container build" -#fi echo "Building Go binary..." @@ -59,22 +38,55 @@ Version: ${VERSION} Section: utils Priority: optional Architecture: amd64 -Depends: systemd +Depends: systemd, nginx +Recommends: serviceradar-web Maintainer: Michael Freeman -Description: ServiceRadar cloud service with web interface - Provides centralized monitoring and web dashboard for ServiceRadar. +Description: ServiceRadar cloud API service + Provides centralized monitoring and API server for ServiceRadar monitoring system. + Includes Nginx configuration for API access. Config: /etc/serviceradar/cloud.json EOF # Create conffiles to mark configuration files cat > "${PKG_ROOT}/DEBIAN/conffiles" << EOF /etc/serviceradar/cloud.json +/etc/nginx/conf.d/serviceradar-cloud.conf +EOF + +# Create nginx configuration +cat > "${PKG_ROOT}/etc/nginx/conf.d/serviceradar-cloud.conf" << EOF +# ServiceRadar Cloud API - Nginx Configuration +# This is for API-only access. If you have the web UI installed, +# its configuration will take precedence. + +server { + listen 80; + server_name _; # Catch-all server name (use your domain if you have one) + + access_log /var/log/nginx/serviceradar-cloud.access.log; + error_log /var/log/nginx/serviceradar-cloud.error.log; + + # API endpoints + location /api/ { + proxy_pass http://localhost:8090; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + } + + # Root redirect to API documentation or status page + location = / { + return 200 'ServiceRadar API is running. Install serviceradar-web package for the UI.'; + add_header Content-Type text/plain; + } +} EOF # Create systemd service file cat > "${PKG_ROOT}/lib/systemd/system/serviceradar-cloud.service" << EOF [Unit] -Description=ServiceRadar Cloud Service +Description=ServiceRadar Cloud API Service After=network.target [Service] @@ -91,10 +103,8 @@ KillSignal=SIGTERM WantedBy=multi-user.target EOF -# Create default config only if we're creating a fresh package -if [ ! -f "/etc/serviceradar/cloud.json" ]; then - # Create default config file - cat > "${PKG_ROOT}/etc/serviceradar/cloud.json" << EOF +# Create default config file +cat > "${PKG_ROOT}/etc/serviceradar/cloud.json" << EOF { "listen_addr": ":8090", "grpc_addr": ":50052", @@ -131,13 +141,18 @@ if [ ! -f "/etc/serviceradar/cloud.json" ]; then ] } EOF -fi # Create postinst script cat > "${PKG_ROOT}/DEBIAN/postinst" << EOF #!/bin/bash set -e +# Check for Nginx +if ! command -v nginx >/dev/null 2>&1; then + echo "ERROR: Nginx is required but not installed. Please install nginx and try again." + exit 1 +fi + # Create serviceradar user if it doesn't exist if ! id -u serviceradar >/dev/null 2>&1; then useradd --system --no-create-home --shell /usr/sbin/nologin serviceradar @@ -152,17 +167,38 @@ mkdir -p /var/lib/serviceradar chown -R serviceradar:serviceradar /var/lib/serviceradar chmod 755 /var/lib/serviceradar -# Set permissions for web assets -#if [ -d "/usr/local/share/serviceradar-cloud/web" ]; then -# chown -R serviceradar:serviceradar /usr/local/share/serviceradar-cloud -# chmod -R 755 /usr/local/share/serviceradar-cloud -#fi +# Configure Nginx +# Only enable if serviceradar-web is not installed +if [ ! -f /etc/nginx/conf.d/serviceradar-web.conf ] && [ ! -f /etc/nginx/sites-enabled/serviceradar-web.conf ]; then + echo "Configuring Nginx for API-only access..." + + # Disable default site if it exists + if [ -f /etc/nginx/sites-enabled/default ]; then + rm -f /etc/nginx/sites-enabled/default + fi + + # Create symbolic link if Nginx uses sites-enabled pattern + if [ -d /etc/nginx/sites-enabled ]; then + ln -sf /etc/nginx/conf.d/serviceradar-cloud.conf /etc/nginx/sites-enabled/ + fi + + # Test and reload Nginx + nginx -t || { echo "Warning: Nginx configuration test failed. Please check your configuration."; } + systemctl reload nginx || systemctl restart nginx || echo "Warning: Failed to reload/restart Nginx." +else + echo "Detected serviceradar-web configuration, skipping API-only Nginx setup." +fi # Enable and start service systemctl daemon-reload systemctl enable serviceradar-cloud systemctl start serviceradar-cloud || echo "Failed to start service, please check the logs" +echo "ServiceRadar Cloud API service installed successfully!" +echo "API is running on port 8090" +echo "Accessible via Nginx at http://localhost/api/" +echo "For a complete UI experience, install the serviceradar-web package." + exit 0 EOF @@ -177,6 +213,16 @@ set -e systemctl stop serviceradar-cloud || true systemctl disable serviceradar-cloud || true +# Remove Nginx symlink if exists and if it's our configuration +if [ -f /etc/nginx/sites-enabled/serviceradar-cloud.conf ]; then + rm -f /etc/nginx/sites-enabled/serviceradar-cloud.conf + + # Reload Nginx if running + if systemctl is-active --quiet nginx; then + systemctl reload nginx || true + fi +fi + exit 0 EOF @@ -187,8 +233,8 @@ echo "Building Debian package..." # Create release-artifacts directory if it doesn't exist mkdir -p ./release-artifacts -# Build the package -dpkg-deb --build "${PKG_ROOT}" +# Build the package with root-owner-group to avoid ownership warnings +dpkg-deb --root-owner-group --build "${PKG_ROOT}" # Move the deb file to the release-artifacts directory mv "${PKG_ROOT}.deb" "./release-artifacts/" diff --git a/scripts/setup-deb-web.sh b/scripts/setup-deb-web.sh new file mode 100755 index 0000000..c2f3247 --- /dev/null +++ b/scripts/setup-deb-web.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# setup-deb-web.sh +set -e # Exit on any error + +echo "Setting up package structure for Next.js web interface..." + +VERSION=${VERSION:-1.0.20} + +# Create package directory structure +PKG_ROOT="serviceradar-web_${VERSION}" +mkdir -p "${PKG_ROOT}/DEBIAN" +mkdir -p "${PKG_ROOT}/usr/local/share/serviceradar-web" +mkdir -p "${PKG_ROOT}/lib/systemd/system" +mkdir -p "${PKG_ROOT}/etc/serviceradar" +mkdir -p "${PKG_ROOT}/etc/nginx/conf.d" + +echo "Building Next.js application..." + +# Build Next.js application +cd ./web + +# Ensure package.json contains the right scripts and dependencies +if ! grep -q '"next": ' package.json; then + echo "ERROR: This doesn't appear to be a Next.js app. Check your web directory." + exit 1 +fi + +# Install dependencies with pnpm +#npm install -g pnpm +npm install + +# Build the Next.js application +echo "Building Next.js application with standalone output..." +npm run build + +# Copy the Next.js standalone build +echo "Copying Next.js standalone build to package..." +cp -r .next/standalone/* "../${PKG_ROOT}/usr/local/share/serviceradar-web/" +cp -r .next/static "../${PKG_ROOT}/usr/local/share/serviceradar-web/.next/" + +# Ensure styled-jsx is properly included +mkdir -p "../${PKG_ROOT}/usr/local/share/serviceradar-web/node_modules/styled-jsx/dist" +cp -r node_modules/styled-jsx/dist/* "../${PKG_ROOT}/usr/local/share/serviceradar-web/node_modules/styled-jsx/dist/" +cp node_modules/styled-jsx/package.json "../${PKG_ROOT}/usr/local/share/serviceradar-web/node_modules/styled-jsx/" + +# Copy public files if they exist +if [ -d "public" ]; then + cp -r public "../${PKG_ROOT}/usr/local/share/serviceradar-web/" +fi + +# Cleanup temp directory +rm -rf "$TEMP_DIR" + +cd .. + +echo "Creating package files..." + +# Create default config file +cat > "${PKG_ROOT}/etc/serviceradar/web.json" << EOF +{ + "port": 3000, + "host": "0.0.0.0", + "api_url": "http://localhost:8090" +} +EOF + +# Create Nginx configuration +cat > "${PKG_ROOT}/etc/nginx/conf.d/serviceradar-web.conf" << EOF +# ServiceRadar Web Interface - Nginx Configuration +server { + listen 80; + server_name _; # Catch-all server name (use your domain if you have one) + + access_log /var/log/nginx/serviceradar-web.access.log; + error_log /var/log/nginx/serviceradar-web.error.log; + + # API proxy (assumes serviceradar-cloud package is installed) + location /api/ { + proxy_pass http://localhost:8090; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + } + + # Support for Next.js WebSockets (if used) + location /_next/webpack-hmr { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Main app - proxy all requests to Next.js + location / { + proxy_pass http://localhost:3000; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + } +} +EOF + +# Create control file +cat > "${PKG_ROOT}/DEBIAN/control" << EOF +Package: serviceradar-web +Version: ${VERSION} +Section: utils +Priority: optional +Architecture: amd64 +Depends: systemd, nodejs (>= 16.0.0), nginx +Recommends: serviceradar-cloud +Maintainer: Michael Freeman +Description: ServiceRadar web interface + Next.js web interface for the ServiceRadar monitoring system. + Includes Nginx configuration for integrated API and UI access. +Config: /etc/serviceradar/web.json +EOF + +# Create conffiles to mark configuration files +cat > "${PKG_ROOT}/DEBIAN/conffiles" << EOF +/etc/serviceradar/web.json +/etc/nginx/conf.d/serviceradar-web.conf +EOF + +# Create systemd service file +cat > "${PKG_ROOT}/lib/systemd/system/serviceradar-web.service" << EOF +[Unit] +Description=ServiceRadar Web Interface +After=network.target + +[Service] +Type=simple +User=serviceradar +WorkingDirectory=/usr/local/share/serviceradar-web +Environment=NODE_ENV=production +Environment=PORT=3000 +ExecStart=/usr/bin/node server.js +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF + +# Create postinst script +cat > "${PKG_ROOT}/DEBIAN/postinst" << EOF +#!/bin/bash +set -e + +# Check for Nginx +if ! command -v nginx >/dev/null 2>&1; then + echo "ERROR: Nginx is required but not installed. Please install nginx and try again." + exit 1 +fi + +# Create serviceradar user if it doesn't exist +if ! id -u serviceradar >/dev/null 2>&1; then + useradd --system --no-create-home --shell /usr/sbin/nologin serviceradar +fi + +# Install Node.js if not already installed +if ! command -v node >/dev/null 2>&1; then + echo "Installing Node.js..." + curl -fsSL https://deb.nodesource.com/setup_18.x | bash - + apt-get install -y nodejs +fi + +# Set permissions +chown -R serviceradar:serviceradar /usr/local/share/serviceradar-web +chown -R serviceradar:serviceradar /etc/serviceradar/web.json +chmod 755 /usr/local/share/serviceradar-web +chmod 644 /etc/serviceradar/web.json + +# Configure Nginx +if [ -f /etc/nginx/sites-enabled/default ]; then + echo "Disabling default Nginx site..." + rm -f /etc/nginx/sites-enabled/default +fi + +# Create symbolic link if Nginx uses sites-enabled pattern +if [ -d /etc/nginx/sites-enabled ]; then + ln -sf /etc/nginx/conf.d/serviceradar-web.conf /etc/nginx/sites-enabled/ +fi + +# Test and reload Nginx +echo "Testing Nginx configuration..." +nginx -t || { echo "Warning: Nginx configuration test failed. Please check your configuration."; } +systemctl reload nginx || systemctl restart nginx || echo "Warning: Failed to reload/restart Nginx." + +# Enable and start service +systemctl daemon-reload +systemctl enable serviceradar-web +systemctl start serviceradar-web || echo "Failed to start service, please check the logs" + +echo "ServiceRadar Web Interface installed successfully!" +echo "Web UI is running on port 3000" +echo "Nginx configured as reverse proxy - you can access the UI at http://localhost/" +echo "Note: For full functionality, install the serviceradar-cloud package" + +exit 0 +EOF + +chmod 755 "${PKG_ROOT}/DEBIAN/postinst" + +# Create prerm script +cat > "${PKG_ROOT}/DEBIAN/prerm" << EOF +#!/bin/bash +set -e + +# Stop and disable service +systemctl stop serviceradar-web || true +systemctl disable serviceradar-web || true + +# Remove Nginx symlink if exists +if [ -f /etc/nginx/sites-enabled/serviceradar-web.conf ]; then + rm -f /etc/nginx/sites-enabled/serviceradar-web.conf +fi + +# Reload Nginx if running +if systemctl is-active --quiet nginx; then + systemctl reload nginx || true +fi + +exit 0 +EOF + +chmod 755 "${PKG_ROOT}/DEBIAN/prerm" + +echo "Building Debian package..." + +# Create release-artifacts directory if it doesn't exist +mkdir -p ./release-artifacts + +# Build the package with root-owner-group to avoid ownership warnings +dpkg-deb --root-owner-group --build "${PKG_ROOT}" + +# Move the deb file to the release-artifacts directory +mv "${PKG_ROOT}.deb" "./release-artifacts/" + +echo "Package built: release-artifacts/${PKG_ROOT}.deb" \ No newline at end of file diff --git a/web/next.config.ts b/web/next.config.ts index 100d6e5..1871713 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -2,9 +2,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { reactStrictMode: true, + output: "standalone", async rewrites() { const backendUrl = process.env.BACKEND_URL || 'http://localhost:8090'; - // const backendUrl = process.env.BACKEND_URL || 'http://172.233.208.210:8090'; return [ { source: '/api/:path*', diff --git a/web/package-lock.json b/web/package-lock.json index 7e7ddfd..1a8d630 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -14,6 +14,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "recharts": "^2.15.1", + "styled-jsx": "^5.1.6", "tailwindcss-animate": "^1.0.7", "xlsx": "^0.18.5" }, diff --git a/web/package.json b/web/package.json index a4885cc..776c6cf 100644 --- a/web/package.json +++ b/web/package.json @@ -15,6 +15,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "recharts": "^2.15.1", + "styled-jsx": "^5.1.6", "tailwindcss-animate": "^1.0.7", "xlsx": "^0.18.5" }, From 27fe3a5dbfe68ae2e166b37b609bd8e563f6c768 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 09:04:57 -0600 Subject: [PATCH 03/12] fixed next build --- scripts/setup-deb-web.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup-deb-web.sh b/scripts/setup-deb-web.sh index c2f3247..246a4be 100755 --- a/scripts/setup-deb-web.sh +++ b/scripts/setup-deb-web.sh @@ -36,7 +36,7 @@ npm run build # Copy the Next.js standalone build echo "Copying Next.js standalone build to package..." cp -r .next/standalone/* "../${PKG_ROOT}/usr/local/share/serviceradar-web/" -cp -r .next/static "../${PKG_ROOT}/usr/local/share/serviceradar-web/.next/" +cp -r .next/standalone/.next "../${PKG_ROOT}/usr/local/share/serviceradar-web/" # Ensure styled-jsx is properly included mkdir -p "../${PKG_ROOT}/usr/local/share/serviceradar-web/node_modules/styled-jsx/dist" From aa929434736e15f7f5183bdc0fa1686b92d2d40f Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 09:46:55 -0600 Subject: [PATCH 04/12] removing extra nginx config, might need it tho --- scripts/setup-deb-cloud.sh | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/scripts/setup-deb-cloud.sh b/scripts/setup-deb-cloud.sh index 873e73f..597ef48 100755 --- a/scripts/setup-deb-cloud.sh +++ b/scripts/setup-deb-cloud.sh @@ -50,37 +50,6 @@ EOF # Create conffiles to mark configuration files cat > "${PKG_ROOT}/DEBIAN/conffiles" << EOF /etc/serviceradar/cloud.json -/etc/nginx/conf.d/serviceradar-cloud.conf -EOF - -# Create nginx configuration -cat > "${PKG_ROOT}/etc/nginx/conf.d/serviceradar-cloud.conf" << EOF -# ServiceRadar Cloud API - Nginx Configuration -# This is for API-only access. If you have the web UI installed, -# its configuration will take precedence. - -server { - listen 80; - server_name _; # Catch-all server name (use your domain if you have one) - - access_log /var/log/nginx/serviceradar-cloud.access.log; - error_log /var/log/nginx/serviceradar-cloud.error.log; - - # API endpoints - location /api/ { - proxy_pass http://localhost:8090; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - - # Root redirect to API documentation or status page - location = / { - return 200 'ServiceRadar API is running. Install serviceradar-web package for the UI.'; - add_header Content-Type text/plain; - } -} EOF # Create systemd service file From 1889ea26aa5864ebf19c8c2943f2716a26a652fd Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 16:13:42 -0600 Subject: [PATCH 05/12] fetching most data properly just not sparkline or snmp --- cmd/cloud/main.go | 1 - pkg/cloud/api/server.go | 23 +- pkg/cloud/types.go | 1 - pkg/http/middleware.go | 3 + scripts/setup-deb-cloud.sh | 42 +-- scripts/setup-deb-web.sh | 30 +- web/next.config.ts | 6 + web/package-lock.json | 313 +++++++++++++++++- web/package.json | 1 + web/src/app/layout.tsx | 6 +- web/src/app/nodes/page.js | 57 +++- web/src/app/page.tsx | 13 +- .../service/[nodeid]/[servicename]/page.js | 18 +- web/src/components/Dashboard.jsx | 31 +- web/src/components/NodeList.jsx | 59 ++-- web/src/components/SNMPDashboard.jsx | 229 ++++--------- web/src/components/ServiceSparkline.jsx | 29 +- web/src/middleware.ts | 7 +- 18 files changed, 540 insertions(+), 329 deletions(-) diff --git a/cmd/cloud/main.go b/cmd/cloud/main.go index e256c42..447e049 100644 --- a/cmd/cloud/main.go +++ b/cmd/cloud/main.go @@ -41,7 +41,6 @@ func run() error { apiServer := api.NewAPIServer( api.WithMetricsManager(server.GetMetricsManager()), api.WithSNMPManager(server.GetSNMPManager()), - api.WithAPIKey(cfg.APIKey), ) server.SetAPIServer(apiServer) diff --git a/pkg/cloud/api/server.go b/pkg/cloud/api/server.go index ec1baa0..57df181 100644 --- a/pkg/cloud/api/server.go +++ b/pkg/cloud/api/server.go @@ -6,10 +6,10 @@ import ( "encoding/json" "log" "net/http" + "os" "time" "github.com/carverauto/serviceradar/pkg/checker/snmp" - "github.com/carverauto/serviceradar/pkg/db" srHttp "github.com/carverauto/serviceradar/pkg/http" "github.com/carverauto/serviceradar/pkg/metrics" "github.com/gorilla/mux" @@ -19,14 +19,13 @@ func NewAPIServer(options ...func(server *APIServer)) *APIServer { s := &APIServer{ nodes: make(map[string]*NodeStatus), router: mux.NewRouter(), - apiKey: "", // Default empty API key } for _, o := range options { o(s) } - s.setupRoutes(s.apiKey) + s.setupRoutes() return s } @@ -43,23 +42,13 @@ func WithSNMPManager(m snmp.SNMPManager) func(server *APIServer) { } } -func WithAPIKey(apiKey string) func(server *APIServer) { - return func(server *APIServer) { - server.apiKey = apiKey - } -} - -func WithDB(db db.Service) func(server *APIServer) { - return func(server *APIServer) { - server.db = db - } -} - -func (s *APIServer) setupRoutes(apiKey string) { +func (s *APIServer) setupRoutes() { // Create a middleware chain middlewareChain := func(next http.Handler) http.Handler { + log.Printf("SERVER API_KEY: %s", os.Getenv("API_KEY")) + // Order matters: first API key check, then CORS headers - return srHttp.CommonMiddleware(srHttp.APIKeyMiddleware(apiKey)(next)) + return srHttp.CommonMiddleware(srHttp.APIKeyMiddleware(os.Getenv("API_KEY"))(next)) } // Add middleware to router diff --git a/pkg/cloud/types.go b/pkg/cloud/types.go index 591230d..d18f5c4 100644 --- a/pkg/cloud/types.go +++ b/pkg/cloud/types.go @@ -31,7 +31,6 @@ type Config struct { Metrics Metrics `json:"metrics"` SNMP snmp.Config `json:"snmp"` Security *models.SecurityConfig `json:"security"` - APIKey string `json:"api_key,omitempty"` } type Server struct { diff --git a/pkg/http/middleware.go b/pkg/http/middleware.go index a8ebb80..e845455 100644 --- a/pkg/http/middleware.go +++ b/pkg/http/middleware.go @@ -49,6 +49,9 @@ func APIKeyMiddleware(apiKeyParam string) func(next http.Handler) http.Handler { return } + // print out the full request header + log.Printf("Request Header: %v", r.Header) + // Check API key in header or query parameter requestKey := r.Header.Get("X-API-Key") if requestKey == "" { diff --git a/scripts/setup-deb-cloud.sh b/scripts/setup-deb-cloud.sh index 597ef48..8d188ba 100755 --- a/scripts/setup-deb-cloud.sh +++ b/scripts/setup-deb-cloud.sh @@ -1,5 +1,5 @@ #!/bin/bash -# setup-deb-cloud.sh +# setup-deb-cloud.sh - UPDATED set -e # Exit on any error echo "Setting up package structure..." @@ -61,6 +61,7 @@ After=network.target [Service] Type=simple User=serviceradar +EnvironmentFile=/etc/serviceradar/api.env ExecStart=/usr/local/bin/serviceradar-cloud -config /etc/serviceradar/cloud.json Restart=always RestartSec=10 @@ -136,26 +137,14 @@ mkdir -p /var/lib/serviceradar chown -R serviceradar:serviceradar /var/lib/serviceradar chmod 755 /var/lib/serviceradar -# Configure Nginx -# Only enable if serviceradar-web is not installed -if [ ! -f /etc/nginx/conf.d/serviceradar-web.conf ] && [ ! -f /etc/nginx/sites-enabled/serviceradar-web.conf ]; then - echo "Configuring Nginx for API-only access..." - - # Disable default site if it exists - if [ -f /etc/nginx/sites-enabled/default ]; then - rm -f /etc/nginx/sites-enabled/default - fi - - # Create symbolic link if Nginx uses sites-enabled pattern - if [ -d /etc/nginx/sites-enabled ]; then - ln -sf /etc/nginx/conf.d/serviceradar-cloud.conf /etc/nginx/sites-enabled/ - fi - - # Test and reload Nginx - nginx -t || { echo "Warning: Nginx configuration test failed. Please check your configuration."; } - systemctl reload nginx || systemctl restart nginx || echo "Warning: Failed to reload/restart Nginx." -else - echo "Detected serviceradar-web configuration, skipping API-only Nginx setup." +# Generate API key if it doesn't exist +if [ ! -f "/etc/serviceradar/api.env" ]; then + echo "Generating API key..." + API_KEY=\$(openssl rand -hex 32) + echo "API_KEY=\$API_KEY" > /etc/serviceradar/api.env + chmod 600 /etc/serviceradar/api.env + chown serviceradar:serviceradar /etc/serviceradar/api.env + echo "API key generated and stored in /etc/serviceradar/api.env" fi # Enable and start service @@ -182,17 +171,6 @@ set -e systemctl stop serviceradar-cloud || true systemctl disable serviceradar-cloud || true -# Remove Nginx symlink if exists and if it's our configuration -if [ -f /etc/nginx/sites-enabled/serviceradar-cloud.conf ]; then - rm -f /etc/nginx/sites-enabled/serviceradar-cloud.conf - - # Reload Nginx if running - if systemctl is-active --quiet nginx; then - systemctl reload nginx || true - fi -fi - -exit 0 EOF chmod 755 "${PKG_ROOT}/DEBIAN/prerm" diff --git a/scripts/setup-deb-web.sh b/scripts/setup-deb-web.sh index 246a4be..a9199a8 100755 --- a/scripts/setup-deb-web.sh +++ b/scripts/setup-deb-web.sh @@ -1,5 +1,5 @@ #!/bin/bash -# setup-deb-web.sh +# setup-deb-web.sh - UPDATED set -e # Exit on any error echo "Setting up package structure for Next.js web interface..." @@ -25,8 +25,7 @@ if ! grep -q '"next": ' package.json; then exit 1 fi -# Install dependencies with pnpm -#npm install -g pnpm +# Install dependencies with npm npm install # Build the Next.js application @@ -38,19 +37,15 @@ echo "Copying Next.js standalone build to package..." cp -r .next/standalone/* "../${PKG_ROOT}/usr/local/share/serviceradar-web/" cp -r .next/standalone/.next "../${PKG_ROOT}/usr/local/share/serviceradar-web/" -# Ensure styled-jsx is properly included -mkdir -p "../${PKG_ROOT}/usr/local/share/serviceradar-web/node_modules/styled-jsx/dist" -cp -r node_modules/styled-jsx/dist/* "../${PKG_ROOT}/usr/local/share/serviceradar-web/node_modules/styled-jsx/dist/" -cp node_modules/styled-jsx/package.json "../${PKG_ROOT}/usr/local/share/serviceradar-web/node_modules/styled-jsx/" +# Make sure static files are copied +mkdir -p "../${PKG_ROOT}/usr/local/share/serviceradar-web/.next/static" +cp -r .next/static "../${PKG_ROOT}/usr/local/share/serviceradar-web/.next/" # Copy public files if they exist if [ -d "public" ]; then cp -r public "../${PKG_ROOT}/usr/local/share/serviceradar-web/" fi -# Cleanup temp directory -rm -rf "$TEMP_DIR" - cd .. echo "Creating package files..." @@ -93,7 +88,7 @@ server { # Main app - proxy all requests to Next.js location / { - proxy_pass http://localhost:3000; + proxy_pass http://127.0.0.1:3000; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; @@ -136,6 +131,7 @@ User=serviceradar WorkingDirectory=/usr/local/share/serviceradar-web Environment=NODE_ENV=production Environment=PORT=3000 +EnvironmentFile=/etc/serviceradar/api.env ExecStart=/usr/bin/node server.js Restart=always RestartSec=10 @@ -173,6 +169,17 @@ chown -R serviceradar:serviceradar /etc/serviceradar/web.json chmod 755 /usr/local/share/serviceradar-web chmod 644 /etc/serviceradar/web.json +# Check for API key from cloud package +if [ ! -f "/etc/serviceradar/api.env" ]; then + echo "WARNING: API key file not found. The serviceradar-cloud package should be installed first." + echo "Creating a temporary API key file..." + API_KEY=\$(openssl rand -hex 32) + echo "API_KEY=\$API_KEY" > /etc/serviceradar/api.env + chmod 600 /etc/serviceradar/api.env + chown serviceradar:serviceradar /etc/serviceradar/api.env + echo "For proper functionality, please reinstall serviceradar-cloud package." +fi + # Configure Nginx if [ -f /etc/nginx/sites-enabled/default ]; then echo "Disabling default Nginx site..." @@ -197,7 +204,6 @@ systemctl start serviceradar-web || echo "Failed to start service, please check echo "ServiceRadar Web Interface installed successfully!" echo "Web UI is running on port 3000" echo "Nginx configured as reverse proxy - you can access the UI at http://localhost/" -echo "Note: For full functionality, install the serviceradar-cloud package" exit 0 EOF diff --git a/web/next.config.ts b/web/next.config.ts index 1871713..bae678c 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -16,6 +16,12 @@ const nextConfig: NextConfig = { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8090', NEXT_PUBLIC_BACKEND_URL: process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8090/', }, + serverRuntimeConfig: { + // Will only be available on the server side + apiKey: process.env.API_KEY || '', + } }; +console.log('Next.js configuration loaded with API_KEY length:', process.env.API_KEY) + export default nextConfig; \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 1a8d630..69c2a29 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,6 +11,7 @@ "lodash": "^4.17.21", "lucide-react": "^0.476.0", "next": "15.1.7", + "next-runtime-env": "^3.2.2", "react": "^19.0.0", "react-dom": "^19.0.0", "recharts": "^2.15.1", @@ -812,6 +813,22 @@ "node": ">= 10" } }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.24.tgz", + "integrity": "sha512-9KuS+XUXM3T6v7leeWU0erpJ6NsFIwiTFD5nzNg8J5uo/DMIPvCp3L1Ao5HjbHX0gkWPB1VrKoo/Il4F0cGK2Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-win32-x64-msvc": { "version": "15.1.7", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.7.tgz", @@ -3277,7 +3294,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -4256,6 +4272,301 @@ } } }, + "node_modules/next-runtime-env": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/next-runtime-env/-/next-runtime-env-3.2.2.tgz", + "integrity": "sha512-S5S6NxIf3XeaVc9fLBN2L5Jzu+6dLYCXeOaPQa1RzKRYlG2BBayxXOj6A4VsciocyNkJMazW1VAibtbb1/ZjAw==", + "license": "MIT", + "dependencies": { + "next": "^14", + "react": "^18" + }, + "peerDependencies": { + "next": "^14", + "react": "^18" + } + }, + "node_modules/next-runtime-env/node_modules/@next/env": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.24.tgz", + "integrity": "sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==", + "license": "MIT" + }, + "node_modules/next-runtime-env/node_modules/@next/swc-darwin-arm64": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.24.tgz", + "integrity": "sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@next/swc-darwin-x64": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.24.tgz", + "integrity": "sha512-lXR2WQqUtu69l5JMdTwSvQUkdqAhEWOqJEYUQ21QczQsAlNOW2kWZCucA6b3EXmPbcvmHB1kSZDua/713d52xg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.24.tgz", + "integrity": "sha512-nxvJgWOpSNmzidYvvGDfXwxkijb6hL9+cjZx1PVG6urr2h2jUqBALkKjT7kpfurRWicK6hFOvarmaWsINT1hnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.24.tgz", + "integrity": "sha512-PaBgOPhqa4Abxa3y/P92F3kklNPsiFjcjldQGT7kFmiY5nuFn8ClBEoX8GIpqU1ODP2y8P6hio6vTomx2Vy0UQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.24.tgz", + "integrity": "sha512-vEbyadiRI7GOr94hd2AB15LFVgcJZQWu7Cdi9cWjCMeCiUsHWA0U5BkGPuoYRnTxTn0HacuMb9NeAmStfBCLoQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.24.tgz", + "integrity": "sha512-df0FC9ptaYsd8nQCINCzFtDWtko8PNRTAU0/+d7hy47E0oC17tI54U/0NdGk7l/76jz1J377dvRjmt6IUdkpzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.24.tgz", + "integrity": "sha512-ZEntbLjeYAJ286eAqbxpZHhDFYpYjArotQ+/TW9j7UROh0DUmX7wYDGtsTPpfCV8V+UoqHBPU7q9D4nDNH014Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.24.tgz", + "integrity": "sha512-cXcJ2+x0fXQ2CntaE00d7uUH+u1Bfp/E0HsNQH79YiLaZE5Rbm7dZzyAYccn3uICM7mw+DxoMqEfGXZtF4Fgaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/next-runtime-env/node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/next-runtime-env/node_modules/next": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.24.tgz", + "integrity": "sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ==", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.24", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.24", + "@next/swc-darwin-x64": "14.2.24", + "@next/swc-linux-arm64-gnu": "14.2.24", + "@next/swc-linux-arm64-musl": "14.2.24", + "@next/swc-linux-x64-gnu": "14.2.24", + "@next/swc-linux-x64-musl": "14.2.24", + "@next/swc-win32-arm64-msvc": "14.2.24", + "@next/swc-win32-ia32-msvc": "14.2.24", + "@next/swc-win32-x64-msvc": "14.2.24" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-runtime-env/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/next-runtime-env/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/next-runtime-env/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/next-runtime-env/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/next-runtime-env/node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", diff --git a/web/package.json b/web/package.json index 776c6cf..71bf7fb 100644 --- a/web/package.json +++ b/web/package.json @@ -12,6 +12,7 @@ "lodash": "^4.17.21", "lucide-react": "^0.476.0", "next": "15.1.7", + "next-runtime-env": "^3.2.2", "react": "^19.0.0", "react-dom": "^19.0.0", "recharts": "^2.15.1", diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 729f09a..7d1968b 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -1,7 +1,9 @@ +// app/layout.tsx import './globals.css'; import { Inter } from 'next/font/google'; import { Providers } from './providers'; import { ReactNode } from 'react'; +import { PublicEnvScript } from 'next-runtime-env'; const inter = Inter({ subsets: ['latin'] }); @@ -10,15 +12,15 @@ export const metadata = { description: 'Monitor your network services', }; -// Define the props type for RootLayout interface RootLayoutProps { - children: ReactNode; // Explicitly type children + children: ReactNode; } export default function RootLayout({ children }: RootLayoutProps) { return ( + diff --git a/web/src/app/nodes/page.js b/web/src/app/nodes/page.js index 64e270a..eacf7ae 100644 --- a/web/src/app/nodes/page.js +++ b/web/src/app/nodes/page.js @@ -2,26 +2,21 @@ import { Suspense } from 'react'; import NodeList from '../../components/NodeList'; -// Async function to fetch data on the server with API key authentication +// Server component that fetches data async function fetchNodes() { try { // When running on the server, use the full backend URL - const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL; - const apiKey = process.env.API_KEY; + const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8090'; + const apiKey = process.env.API_KEY || ''; const response = await fetch(`${backendUrl}/api/nodes`, { headers: { 'X-API-Key': apiKey }, - cache: 'no-store' // Don't cache this request + cache: 'no-store', // For real-time data }); if (!response.ok) { - console.error('Nodes API fetch failed:', { - status: response.status, - statusText: response.statusText - }); - throw new Error(`Nodes API request failed: ${response.status}`); } @@ -32,15 +27,55 @@ async function fetchNodes() { } } +// Fetch metrics for a specific node and service +async function fetchMetricsForService(nodeId, serviceName) { + try { + const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8090'; + const apiKey = process.env.API_KEY || ''; + + const response = await fetch(`${backendUrl}/api/nodes/${nodeId}/metrics`, { + headers: { + 'X-API-Key': apiKey + }, + cache: 'no-store', + }); + + if (!response.ok) { + return []; + } + + const allMetrics = await response.json(); + // Filter the metrics for this specific service + return allMetrics.filter(m => m.service_name === serviceName); + } catch (error) { + console.error(`Error fetching metrics for ${nodeId}/${serviceName}:`, error); + return []; + } +} + export default async function NodesPage() { - const initialNodes = await fetchNodes(); + // Fetch all nodes first + const nodes = await fetchNodes(); + + // Fetch metrics for ICMP services + const serviceMetrics = {}; + + for (const node of nodes) { + const icmpServices = node.services?.filter(s => s.type === 'icmp') || []; + + for (const service of icmpServices) { + const metrics = await fetchMetricsForService(node.node_id, service.name); + const key = `${node.node_id}-${service.name}`; + serviceMetrics[key] = metrics; + } + } return (
Loading nodes...
}> - + ); diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 43d92b2..3015d3b 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -1,17 +1,19 @@ -// src/app/page.tsx +// src/app/page.tsx (Server Component) import { Suspense } from 'react'; import Dashboard from '../components/Dashboard'; +// This runs only on the server async function fetchStatus() { try { - // When running on the server, use the full backend URL - const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL; + // Direct server-to-server call with API key + const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8090'; const apiKey = process.env.API_KEY || ''; const response = await fetch(`${backendUrl}/api/status`, { headers: { 'X-API-Key': apiKey - } + }, + cache: 'no-store' // For fresh data on each request }); if (!response.ok) { @@ -25,13 +27,16 @@ async function fetchStatus() { } } +// Server Component export default async function HomePage() { + // Data fetching happens server-side const initialData = await fetchStatus(); return (

Dashboard

Loading dashboard...
}> + {/* Pass pre-fetched data to client component */} diff --git a/web/src/app/service/[nodeid]/[servicename]/page.js b/web/src/app/service/[nodeid]/[servicename]/page.js index b1175eb..d9811e7 100644 --- a/web/src/app/service/[nodeid]/[servicename]/page.js +++ b/web/src/app/service/[nodeid]/[servicename]/page.js @@ -9,9 +9,10 @@ async function fetchServiceData(nodeId, serviceName) { const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8090'; const apiKey = process.env.API_KEY || ''; + // Fetch node info const nodesResponse = await fetch(`${backendUrl}/api/nodes`, { headers: { 'X-API-Key': apiKey }, - cache: 'no-store', // Prevent caching on the server + cache: 'no-store', }); if (!nodesResponse.ok) { @@ -19,42 +20,46 @@ async function fetchServiceData(nodeId, serviceName) { } const nodes = await nodesResponse.json(); - const node = nodes.find((n) => n.node_id === nodeId); + if (!node) return { error: 'Node not found' }; const service = node.services?.find((s) => s.name === serviceName); if (!service) return { error: 'Service not found' }; + // Fetch metrics let metrics = []; try { const metricsResponse = await fetch(`${backendUrl}/api/nodes/${nodeId}/metrics`, { headers: { 'X-API-Key': apiKey }, - next: { revalidate: 30 }, + cache: 'no-store', }); + if (!metricsResponse.ok) { - console.error(`Metrics API failed: ${metricsResponse.status} - ${await metricsResponse.text()}`); + console.error(`Metrics API failed: ${metricsResponse.status}`); } else { metrics = await metricsResponse.json(); } } catch (metricsError) { console.error('Error fetching metrics data:', metricsError); } + const serviceMetrics = metrics.filter((m) => m.service_name === serviceName); + // Fetch SNMP data if needed let snmpData = []; if (service.type === 'snmp') { try { const end = new Date(); const start = new Date(); - start.setHours(end.getHours() - 1); + start.setHours(end.getHours() - 24); // Get 24h of data for initial load const snmpUrl = `${backendUrl}/api/nodes/${nodeId}/snmp?start=${start.toISOString()}&end=${end.toISOString()}`; console.log("Fetching SNMP from:", snmpUrl); const snmpResponse = await fetch(snmpUrl, { headers: { 'X-API-Key': apiKey }, - next: { revalidate: 30 }, + cache: 'no-store', }); if (!snmpResponse.ok) { @@ -62,6 +67,7 @@ async function fetchServiceData(nodeId, serviceName) { console.error(`SNMP API failed: ${snmpResponse.status} - ${errorText}`); throw new Error(`SNMP API request failed: ${snmpResponse.status} - ${errorText}`); } + snmpData = await snmpResponse.json(); console.log("SNMP data fetched:", snmpData.length); } catch (snmpError) { diff --git a/web/src/components/Dashboard.jsx b/web/src/components/Dashboard.jsx index 22090e5..68b796c 100644 --- a/web/src/components/Dashboard.jsx +++ b/web/src/components/Dashboard.jsx @@ -1,31 +1,16 @@ -// src/components/Dashboard.jsx +// src/components/Dashboard.jsx - Client Component 'use client'; import React from 'react'; -import { useAPIData } from '@/lib/api'; function Dashboard({ initialData = null }) { - // Use improved API client with caching - refresh every 30 seconds instead of 10 - const { data: systemStatus, error, isLoading } = useAPIData('/api/status', initialData, 10000); + // No data fetching here - just use the data passed from server component - if (isLoading && !systemStatus) { - return ( -
- {[...Array(3)].map((_, i) => ( -
-
-
-
- ))} -
- ); - } - - if (error) { + if (!initialData) { return (

Error Loading Dashboard

-

{error}

+

Could not load dashboard data

); } @@ -38,7 +23,7 @@ function Dashboard({ initialData = null }) { Total Nodes

- {systemStatus?.total_nodes || 0} + {initialData?.total_nodes || 0}

@@ -48,7 +33,7 @@ function Dashboard({ initialData = null }) { Healthy Nodes

- {systemStatus?.healthy_nodes || 0} + {initialData?.healthy_nodes || 0}

@@ -58,8 +43,8 @@ function Dashboard({ initialData = null }) { Last Update

- {systemStatus?.last_update - ? new Date(systemStatus.last_update).toLocaleTimeString() + {initialData?.last_update + ? new Date(initialData.last_update).toLocaleTimeString() : 'N/A'}

diff --git a/web/src/components/NodeList.jsx b/web/src/components/NodeList.jsx index d5cf4e8..b8d2d65 100644 --- a/web/src/components/NodeList.jsx +++ b/web/src/components/NodeList.jsx @@ -1,10 +1,9 @@ // src/components/NodeList.jsx 'use client'; -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import ServiceSparkline from "./ServiceSparkline"; -import { useAPIData } from '@/lib/api'; function NodeList({ initialNodes = [] }) { const router = useRouter(); @@ -13,8 +12,23 @@ function NodeList({ initialNodes = [] }) { const [nodesPerPage] = useState(10); const [sortBy, setSortBy] = useState('name'); const [sortOrder, setSortOrder] = useState('asc'); + // Use initialNodes directly instead of fetching + const [nodes, setNodes] = useState(initialNodes); - const { data: nodes, error, isLoading } = useAPIData('/api/nodes', initialNodes, 10000); + // Add auto-refresh functionality + useEffect(() => { + // Update from new props when initialNodes changes + setNodes(initialNodes); + }, [initialNodes]); + + // Optional: Add page refresh + useEffect(() => { + const interval = setInterval(() => { + router.refresh(); // Trigger server-side refetch + }, 30000); // Every 30 seconds + + return () => clearInterval(interval); + }, [router]); const sortNodesByName = useCallback((a, b) => { const aMatch = a.node_id.match(/(\d+)$/); @@ -95,39 +109,6 @@ function NodeList({ initialNodes = [] }) { setSortOrder((prev) => (prev === 'asc' ? 'desc' : 'asc')); }, []); - // Error State - if (error) { - return ( -
-

Error Loading Nodes

-

{error}

-
- ); - } - - // Loading State - if (isLoading && (!nodes || nodes.length === 0)) { - return ( -
-
-
-
-
-
- {[...Array(6)].map((_, i) => ( -
-
-
-
-
-
-
- ))} -
-
- ); - } - // Regular Component Content return (
@@ -168,7 +149,7 @@ function NodeList({ initialNodes = [] }) {
{/* Content placeholder when no nodes are found */} - {sortedNodes.length === 0 && !isLoading && ( + {sortedNodes.length === 0 && (

No nodes found

@@ -286,11 +267,13 @@ function NodeList({ initialNodes = [] }) {

))} - {new Date(node.last_update).toLocaleString()} diff --git a/web/src/components/SNMPDashboard.jsx b/web/src/components/SNMPDashboard.jsx index d4275fa..105f901 100644 --- a/web/src/components/SNMPDashboard.jsx +++ b/web/src/components/SNMPDashboard.jsx @@ -1,19 +1,32 @@ -import React, {useCallback, useEffect, useRef, useState} from 'react'; +// src/components/SNMPDashboard.jsx +'use client'; + +import React, {useCallback, useState, useEffect} from 'react'; import {CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; +import { useRouter } from 'next/navigation'; -const SNMPDashboard = ({ nodeId, serviceName }) => { - const [snmpData, setSNMPData] = useState([]); +const SNMPDashboard = ({ nodeId, serviceName, initialData = [] }) => { + const router = useRouter(); + const [snmpData, setSNMPData] = useState(initialData); const [processedData, setProcessedData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); const [timeRange, setTimeRange] = useState('1h'); const [selectedMetric, setSelectedMetric] = useState(null); const [availableMetrics, setAvailableMetrics] = useState([]); - // Use refs to prevent state updates during render cycles - const dataRef = useRef(snmpData); - const fetchingRef = useRef(false); - const timerId = useRef(null); + // Add auto-refresh + useEffect(() => { + const refreshInterval = 30000; // 30 seconds + const timer = setInterval(() => { + router.refresh(); // Trigger a server-side refresh + }, refreshInterval); + + return () => clearInterval(timer); + }, [router]); + + // Update data when initialData changes + useEffect(() => { + setSNMPData(initialData); + }, [initialData]); // Process SNMP counter data to show rates instead of raw values const processCounterData = useCallback((data) => { @@ -51,131 +64,20 @@ const SNMPDashboard = ({ nodeId, serviceName }) => { } }, []); - // The main data fetching function - separated to avoid re-creation on every render - const fetchSNMPData = useCallback(async () => { - // Prevent concurrent fetches - if (fetchingRef.current) return; - fetchingRef.current = true; - - try { - // Only show loading on initial fetch - if (!dataRef.current.length) { - setLoading(true); - } - - const end = new Date(); - const start = new Date(); - - switch (timeRange) { - case '1h': - start.setHours(end.getHours() - 1); - break; - case '6h': - start.setHours(end.getHours() - 6); - break; - case '24h': - start.setHours(end.getHours() - 24); - break; - default: - start.setHours(end.getHours() - 1); - } - - // Directly use fetch to avoid any potential issues with the cache - const response = await fetch( - `/api/nodes/${nodeId}/snmp?start=${start.toISOString()}&end=${end.toISOString()}`, - { - headers: { - 'X-API-Key': process.env.NEXT_PUBLIC_API_KEY || '', - 'Cache-Control': 'no-cache' - }, - cache: 'no-store' - } - ); - - if (!response.ok) { - throw new Error(`Server returned ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - - // Handle empty or invalid data - if (!data || !Array.isArray(data)) { - console.warn("Received invalid SNMP data format"); - fetchingRef.current = false; - return; - } - - // Don't update state if component is unmounting or not mounted - if (!dataRef.current) return; - + // Initialize metrics and selection + useEffect(() => { + if (snmpData.length > 0) { // Extract unique OID names - const metrics = [...new Set(data.map(item => item.oid_name))]; + const metrics = [...new Set(snmpData.map(item => item.oid_name))]; - // Update our state safely - setSNMPData(data); - dataRef.current = data; setAvailableMetrics(metrics); + // Set default selected metric if (!selectedMetric && metrics.length > 0) { setSelectedMetric(metrics[0]); } - - setLoading(false); - setError(null); - } catch (err) { - console.error('Error fetching SNMP data:', err); - - // Only show error if we don't have any data yet - if (!dataRef.current.length) { - setError(err.message || "Failed to fetch SNMP data"); - setLoading(false); - } - } finally { - fetchingRef.current = false; } - }, [nodeId, timeRange, selectedMetric]); - - // Initial data load - useEffect(() => { - // Reset state when parameters change - setSNMPData([]); - dataRef.current = []; - setLoading(true); - setError(null); - - fetchSNMPData().catch(err => console.error("Initial fetch error:", err)); - - return () => { - // Clear any pending timers on unmount - if (timerId.current) clearTimeout(timerId.current); - }; - }, [fetchSNMPData, nodeId, timeRange]); - - // Set up polling - in a separate effect to avoid interfering with data fetching - useEffect(() => { - const pollInterval = 30000; // 30 seconds - - // Set up polling with manual setTimeout instead of setInterval - const pollData = () => { - fetchSNMPData() - .catch(err => console.error("Poll error:", err)) - .finally(() => { - // Only schedule next poll if component is still mounted - if (dataRef.current !== null) { - timerId.current = setTimeout(pollData, pollInterval); - } - }); - }; - - // Start polling - timerId.current = setTimeout(pollData, pollInterval); - - // Clean up on unmount - return () => { - dataRef.current = null; // Signal that we're unmounting - if (timerId.current) clearTimeout(timerId.current); - }; - }, [fetchSNMPData]); + }, [snmpData, selectedMetric]); // Process metric data when selected metric changes useEffect(() => { @@ -190,6 +92,42 @@ const SNMPDashboard = ({ nodeId, serviceName }) => { } }, [selectedMetric, snmpData, processCounterData]); + // Filter data based on time range + useEffect(() => { + if (snmpData.length > 0 && selectedMetric) { + try { + const end = new Date(); + const start = new Date(); + + switch (timeRange) { + case '1h': + start.setHours(end.getHours() - 1); + break; + case '6h': + start.setHours(end.getHours() - 6); + break; + case '24h': + start.setHours(end.getHours() - 24); + break; + default: + start.setHours(end.getHours() - 1); + } + + // Filter by time range + const timeFilteredData = snmpData.filter(item => { + const timestamp = new Date(item.timestamp); + return timestamp >= start && timestamp <= end; + }); + + const metricData = timeFilteredData.filter(item => item.oid_name === selectedMetric); + const processed = processCounterData(metricData); + setProcessedData(processed); + } catch (err) { + console.error('Error processing time-filtered data:', err); + } + } + }, [timeRange, selectedMetric, snmpData, processCounterData]); + const formatRate = (rate) => { if (rate === undefined || rate === null || isNaN(rate)) return "N/A"; @@ -205,41 +143,20 @@ const SNMPDashboard = ({ nodeId, serviceName }) => { } }; - // Error state - if (error) { - return ( -
-

Error Loading SNMP Data

-

{error}

- -
- ); - } - - // Loading state - if (loading) { + // Empty data state + if (!snmpData.length) { return ( -
-
- Loading SNMP data... -
+
+

+ No SNMP Data Available +

+

+ No metrics found for this service. +

); } - // Empty data state if (!processedData.length) { return (
diff --git a/web/src/components/ServiceSparkline.jsx b/web/src/components/ServiceSparkline.jsx index 33e226a..6713ec8 100644 --- a/web/src/components/ServiceSparkline.jsx +++ b/web/src/components/ServiceSparkline.jsx @@ -1,22 +1,15 @@ 'use client'; -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useMemo } from 'react'; import { LineChart, Line, YAxis, ResponsiveContainer } from 'recharts'; import { TrendingUp, TrendingDown, Minus } from 'lucide-react'; import _ from 'lodash'; -import { useAPIData } from '../lib/api'; const MAX_POINTS = 100; -const POLLING_INTERVAL = 10000; // 10 seconds const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] }) => { - const { data: metrics, error, isLoading } = useAPIData( - `/api/nodes/${nodeId}/metrics`, - initialMetrics, - POLLING_INTERVAL - ); - - const [errorState, setError] = useState(error); // Handle initial error state + // Use initialMetrics directly instead of fetching via API + const [metrics] = useState(initialMetrics); const processedMetrics = useMemo(() => { if (!metrics || metrics.length === 0) return []; @@ -55,18 +48,8 @@ const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] return changePct > 0 ? 'up' : 'down'; }, [processedMetrics]); - useEffect(() => { - setError(error); // Sync error state with useAPIData - }, [error]); - - if (isLoading && !processedMetrics.length) { - return
-
-
; - } - - if (errorState) { - return
Error
; + if (processedMetrics.length === 0) { + return
No data
; } const latestValue = processedMetrics[processedMetrics.length - 1]?.value || 0; @@ -98,6 +81,6 @@ const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] ); }); -ServiceSparkline.displayName = 'ServiceSparkline'; // Helpful for debugging +ServiceSparkline.displayName = 'ServiceSparkline'; export default ServiceSparkline; \ No newline at end of file diff --git a/web/src/middleware.ts b/web/src/middleware.ts index 26f9488..3207790 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -1,12 +1,13 @@ // src/middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; +import { env } from 'next-runtime-env'; export function middleware(request: NextRequest) { // Only apply to api routes if (request.nextUrl.pathname.startsWith('/api/')) { - // Use the API key from environment - const apiKey = process.env.API_KEY || ''; + // Get API key using next-runtime-env + const apiKey = env('API_KEY') || ''; // Clone the request headers const requestHeaders = new Headers(request.headers); @@ -14,6 +15,8 @@ export function middleware(request: NextRequest) { // Add the API key header requestHeaders.set('X-API-Key', apiKey); + console.log(`[Middleware] Adding API key to request: ${request.nextUrl.pathname}`); + // Return a new response with the API key header return NextResponse.next({ request: { From 5be577308b0a58f3ee82aa8d66492efa17f723c9 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 16:17:15 -0600 Subject: [PATCH 06/12] SNMP data is back --- web/src/components/SNMPDashboard.jsx | 76 ++++++++++++------------- web/src/components/ServiceSparkline.jsx | 14 ++++- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/web/src/components/SNMPDashboard.jsx b/web/src/components/SNMPDashboard.jsx index 105f901..b374727 100644 --- a/web/src/components/SNMPDashboard.jsx +++ b/web/src/components/SNMPDashboard.jsx @@ -1,4 +1,3 @@ -// src/components/SNMPDashboard.jsx 'use client'; import React, {useCallback, useState, useEffect} from 'react'; @@ -13,21 +12,6 @@ const SNMPDashboard = ({ nodeId, serviceName, initialData = [] }) => { const [selectedMetric, setSelectedMetric] = useState(null); const [availableMetrics, setAvailableMetrics] = useState([]); - // Add auto-refresh - useEffect(() => { - const refreshInterval = 30000; // 30 seconds - const timer = setInterval(() => { - router.refresh(); // Trigger a server-side refresh - }, refreshInterval); - - return () => clearInterval(timer); - }, [router]); - - // Update data when initialData changes - useEffect(() => { - setSNMPData(initialData); - }, [initialData]); - // Process SNMP counter data to show rates instead of raw values const processCounterData = useCallback((data) => { if (!data || data.length < 2) return data || []; @@ -64,38 +48,36 @@ const SNMPDashboard = ({ nodeId, serviceName, initialData = [] }) => { } }, []); + // Set up auto-refresh from server + useEffect(() => { + const refreshInterval = 30000; // 30 seconds + const timer = setInterval(() => { + router.refresh(); // Trigger a server-side refresh + }, refreshInterval); + + return () => clearInterval(timer); + }, [router]); + // Initialize metrics and selection useEffect(() => { - if (snmpData.length > 0) { - // Extract unique OID names - const metrics = [...new Set(snmpData.map(item => item.oid_name))]; + if (initialData.length > 0) { + setSNMPData(initialData); + // Extract unique OID names + const metrics = [...new Set(initialData.map(item => item.oid_name))]; setAvailableMetrics(metrics); - // Set default selected metric if (!selectedMetric && metrics.length > 0) { setSelectedMetric(metrics[0]); } } - }, [snmpData, selectedMetric]); + }, [initialData, selectedMetric]); // Process metric data when selected metric changes useEffect(() => { if (snmpData.length > 0 && selectedMetric) { try { - const metricData = snmpData.filter(item => item.oid_name === selectedMetric); - const processed = processCounterData(metricData); - setProcessedData(processed); - } catch (err) { - console.error('Error processing metric data:', err); - } - } - }, [selectedMetric, snmpData, processCounterData]); - - // Filter data based on time range - useEffect(() => { - if (snmpData.length > 0 && selectedMetric) { - try { + // Filter by time range const end = new Date(); const start = new Date(); @@ -119,14 +101,26 @@ const SNMPDashboard = ({ nodeId, serviceName, initialData = [] }) => { return timestamp >= start && timestamp <= end; }); + // Filter by selected metric const metricData = timeFilteredData.filter(item => item.oid_name === selectedMetric); + + // Process the data const processed = processCounterData(metricData); setProcessedData(processed); } catch (err) { - console.error('Error processing time-filtered data:', err); + console.error('Error processing metric data:', err); } } - }, [timeRange, selectedMetric, snmpData, processCounterData]); + }, [selectedMetric, snmpData, timeRange, processCounterData]); + + // When time range changes, refresh the page to get new data from server + const handleTimeRangeChange = (range) => { + setTimeRange(range); + // For significant time range changes, refresh data from server + if (range === '24h' || (timeRange === '24h' && range !== '24h')) { + router.refresh(); + } + }; const formatRate = (rate) => { if (rate === undefined || rate === null || isNaN(rate)) return "N/A"; @@ -144,7 +138,7 @@ const SNMPDashboard = ({ nodeId, serviceName, initialData = [] }) => { }; // Empty data state - if (!snmpData.length) { + if (!initialData.length) { return (

@@ -157,11 +151,11 @@ const SNMPDashboard = ({ nodeId, serviceName, initialData = [] }) => { ); } - if (!processedData.length) { + if (!processedData.length && selectedMetric) { return (

- No SNMP Data Available + No Data Available

No metrics found for the selected time range and OID. @@ -174,7 +168,7 @@ const SNMPDashboard = ({ nodeId, serviceName, initialData = [] }) => { {['1h', '6h', '24h'].map((range) => (

+ }>

diff --git a/web/src/components/NodeList.jsx b/web/src/components/NodeList.jsx index b8d2d65..c3a62f7 100644 --- a/web/src/components/NodeList.jsx +++ b/web/src/components/NodeList.jsx @@ -1,33 +1,32 @@ -// src/components/NodeList.jsx 'use client'; -import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import React, { useState, useMemo, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import ServiceSparkline from "./ServiceSparkline"; +import { useEffect } from 'react'; -function NodeList({ initialNodes = [] }) { +function NodeList({ initialNodes = [], serviceMetrics = {} }) { const router = useRouter(); const [searchTerm, setSearchTerm] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [nodesPerPage] = useState(10); const [sortBy, setSortBy] = useState('name'); const [sortOrder, setSortOrder] = useState('asc'); - // Use initialNodes directly instead of fetching const [nodes, setNodes] = useState(initialNodes); - // Add auto-refresh functionality + // Update from new props when initialNodes changes useEffect(() => { - // Update from new props when initialNodes changes setNodes(initialNodes); }, [initialNodes]); - // Optional: Add page refresh + // Set up auto-refresh useEffect(() => { - const interval = setInterval(() => { - router.refresh(); // Trigger server-side refetch - }, 30000); // Every 30 seconds + const refreshInterval = 30000; // 30 seconds + const timer = setInterval(() => { + router.refresh(); // Trigger a server-side refresh + }, refreshInterval); - return () => clearInterval(interval); + return () => clearInterval(timer); }, [router]); const sortNodesByName = useCallback((a, b) => { @@ -109,7 +108,6 @@ function NodeList({ initialNodes = [] }) { setSortOrder((prev) => (prev === 'asc' ? 'desc' : 'asc')); }, []); - // Regular Component Content return (
{/* Header row */} @@ -159,63 +157,23 @@ function NodeList({ initialNodes = [] }) { )} {/* Main content */} - {renderTableView()} - - {/* Pagination */} - {pageCount > 1 && ( -
- {[...Array(pageCount)].map((_, i) => ( - - ))} -
- )} -
- ); - - function renderTableView() { - return (
- - - - - @@ -224,11 +182,7 @@ function NodeList({ initialNodes = [] }) { {currentNodes.map((node) => ( - @@ -283,8 +229,27 @@ function NodeList({ initialNodes = [] }) {
+ Status + Node + Services + ICMP Response Time + Last Update
-
+
{node.node_id} @@ -238,20 +192,13 @@ function NodeList({ initialNodes = [] }) { {node.services?.map((service, idx) => (
- handleServiceClick(node.node_id, service.name) - } + className="inline-flex items-center gap-1 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 p-1 rounded transition-colors" + onClick={() => handleServiceClick(node.node_id, service.name)} > - + - {service.name} - + {service.name} +
))} @@ -259,23 +206,22 @@ function NodeList({ initialNodes = [] }) {
{node.services ?.filter((service) => service.type === 'icmp') - .map((service, idx) => ( -
- -
- ))} + .map((service, idx) => { + const metricKey = `${node.node_id}-${service.name}`; + const metricsForService = serviceMetrics[metricKey] || []; + + return ( +
+ +
+ ); + })}
+ {new Date(node.last_update).toLocaleString()}
- ); - } + + {/* Pagination */} + {pageCount > 1 && ( +
+ {[...Array(pageCount)].map((_, i) => ( + + ))} +
+ )} +
+ ); } export default NodeList; \ No newline at end of file diff --git a/web/src/components/ServiceSparkline.jsx b/web/src/components/ServiceSparkline.jsx index 5cbd467..263e54a 100644 --- a/web/src/components/ServiceSparkline.jsx +++ b/web/src/components/ServiceSparkline.jsx @@ -10,6 +10,7 @@ import { useEffect } from 'react'; const MAX_POINTS = 100; const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] }) => { + console.log(`ServiceSparkline rendering for ${nodeId}/${serviceName} with ${initialMetrics.length} metrics`); const router = useRouter(); // Use the initialMetrics that were passed in from the server const [metrics] = useState(initialMetrics); @@ -24,7 +25,12 @@ const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] }, [router]); const processedMetrics = useMemo(() => { - if (!metrics || metrics.length === 0) return []; + if (!metrics || metrics.length === 0) { + console.log(`No metrics available for ${nodeId}/${serviceName}`); + return []; + } + + console.log(`Processing ${metrics.length} metrics for ${nodeId}/${serviceName}`); const serviceMetrics = metrics .filter((m) => m.service_name === serviceName) @@ -35,12 +41,14 @@ const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] .sort((a, b) => a.timestamp - b.timestamp) .slice(-MAX_POINTS); // Limit to recent points + console.log(`Filtered to ${serviceMetrics.length} service-specific metrics`); + if (serviceMetrics.length < 5) return serviceMetrics; // Downsample for performance const step = Math.max(1, Math.floor(serviceMetrics.length / 20)); return serviceMetrics.filter((_, i) => i % step === 0 || i === serviceMetrics.length - 1); - }, [metrics, serviceName]); + }, [metrics, serviceName, nodeId]); const trend = useMemo(() => { if (processedMetrics.length < 5) return 'neutral'; From e893fb2ff56153105d30c4ce46ff2d127a0a2a63 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 16:39:26 -0600 Subject: [PATCH 08/12] cleanup --- pkg/cloud/server.go | 16 ---------------- web/src/components/ServiceSparkline.jsx | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/pkg/cloud/server.go b/pkg/cloud/server.go index d40e93f..508e126 100644 --- a/pkg/cloud/server.go +++ b/pkg/cloud/server.go @@ -44,9 +44,6 @@ func NewServer(_ context.Context, config *Config) (*Server, error) { config.Metrics.MaxNodes = 10000 } - // log the config.Metrics - log.Printf("Metrics config: %+v", config.Metrics) - metricsManager := metrics.NewManager(models.MetricsConfig{ Enabled: config.Metrics.Enabled, Retention: config.Metrics.Retention, @@ -336,13 +333,6 @@ func (s *Server) SetAPIServer(apiServer api.Service) { return nil, fmt.Errorf("failed to get node history: %w", err) } - // debug points - log.Printf("Fetched %d history points for node: %s", len(points), nodeID) - // log first 20 points - for i := 0; i < 20 && i < len(points); i++ { - log.Printf("Point %d: %v", i, points[i]) - } - apiPoints := make([]api.NodeHistoryPoint, len(points)) for i, p := range points { apiPoints[i] = api.NodeHistoryPoint{ @@ -356,8 +346,6 @@ func (s *Server) SetAPIServer(apiServer api.Service) { } func (s *Server) checkInitialStates() { - log.Printf("Checking initial states of all nodes") - likeConditions := make([]string, 0, len(s.pollerPatterns)) args := make([]interface{}, 0, len(s.pollerPatterns)) @@ -562,8 +550,6 @@ func (s *Server) processSNMPMetrics(nodeID string, details json.RawMessage, time Metadata: metadata, } - log.Printf("Storing SNMP metric %s for node %s, value: %s", oidName, nodeID, valueStr) - // Store in database if err := s.db.StoreMetric(nodeID, metric); err != nil { log.Printf("Error storing SNMP metric %s for node %s: %v", oidName, nodeID, err) @@ -593,8 +579,6 @@ func (*Server) processSweepData(svc *api.ServiceStatus, now time.Time) error { return fmt.Errorf("%w: %w", errInvalidSweepData, err) } - log.Printf("Received sweep data with timestamp: %v", time.Unix(sweepData.LastSweep, 0).Format(time.RFC3339)) - // If LastSweep is not set or is invalid (0 or negative), use current time if sweepData.LastSweep > now.Add(oneDay).Unix() { log.Printf("Invalid or missing LastSweep timestamp (%d), using current time", sweepData.LastSweep) diff --git a/web/src/components/ServiceSparkline.jsx b/web/src/components/ServiceSparkline.jsx index 263e54a..d70fd64 100644 --- a/web/src/components/ServiceSparkline.jsx +++ b/web/src/components/ServiceSparkline.jsx @@ -19,7 +19,7 @@ const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] useEffect(() => { const interval = setInterval(() => { router.refresh(); // This triggers a server-side refresh - }, 30000); // Refresh every 30 seconds + }, 10000); // Refresh every 30 seconds return () => clearInterval(interval); }, [router]); From 1b041388cd7c36a3aee35c2148ff6418989411f1 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 16:47:19 -0600 Subject: [PATCH 09/12] sparklines are refreshing now --- web/src/components/NodeList.jsx | 61 +++++++------------------ web/src/components/ServiceSparkline.jsx | 19 ++++---- 2 files changed, 27 insertions(+), 53 deletions(-) diff --git a/web/src/components/NodeList.jsx b/web/src/components/NodeList.jsx index c3a62f7..692737d 100644 --- a/web/src/components/NodeList.jsx +++ b/web/src/components/NodeList.jsx @@ -1,9 +1,8 @@ 'use client'; -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import ServiceSparkline from "./ServiceSparkline"; -import { useEffect } from 'react'; function NodeList({ initialNodes = [], serviceMetrics = {} }) { const router = useRouter(); @@ -14,16 +13,16 @@ function NodeList({ initialNodes = [], serviceMetrics = {} }) { const [sortOrder, setSortOrder] = useState('asc'); const [nodes, setNodes] = useState(initialNodes); - // Update from new props when initialNodes changes + // Update nodes when initialNodes changes useEffect(() => { setNodes(initialNodes); }, [initialNodes]); // Set up auto-refresh useEffect(() => { - const refreshInterval = 30000; // 30 seconds + const refreshInterval = 10000; // 10 seconds (sync with ServiceSparkline) const timer = setInterval(() => { - router.refresh(); // Trigger a server-side refresh + router.refresh(); // Trigger server-side re-fetch of nodes/page.js }, refreshInterval); return () => clearInterval(timer); @@ -66,9 +65,7 @@ function NodeList({ initialNodes = [], serviceMetrics = {} }) { sortedResults.sort((a, b) => b.is_healthy === a.is_healthy ? sortNodesByName(a, b) - : b.is_healthy - ? 1 - : -1 + : b.is_healthy ? 1 : -1 ); break; case 'name': @@ -117,19 +114,14 @@ function NodeList({ initialNodes = [], serviceMetrics = {} }) { setSearchTerm(e.target.value)} /> @@ -161,21 +151,11 @@ function NodeList({ initialNodes = [], serviceMetrics = {} }) { - - - - - + + + + + @@ -184,9 +164,7 @@ function NodeList({ initialNodes = [], serviceMetrics = {} }) { - +
- Status - - Node - - Services - - ICMP Response Time - - Last Update - StatusNodeServicesICMP Response TimeLast Update
- {node.node_id} - {node.node_id}
{node.services?.map((service, idx) => ( @@ -196,9 +174,7 @@ function NodeList({ initialNodes = [], serviceMetrics = {} }) { onClick={() => handleServiceClick(node.node_id, service.name)} > - - {service.name} - + {service.name}
))} @@ -209,7 +185,6 @@ function NodeList({ initialNodes = [], serviceMetrics = {} }) { .map((service, idx) => { const metricKey = `${node.node_id}-${service.name}`; const metricsForService = serviceMetrics[metricKey] || []; - return (
setCurrentPage(i + 1)} - className={`px-3 py-1 rounded transition-colors ${ - currentPage === i + 1 - ? 'bg-blue-500 text-white' - : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100' - }`} + className={`px-3 py-1 rounded transition-colors ${currentPage === i + 1 ? 'bg-blue-500 text-white' : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100'}`} > {i + 1} diff --git a/web/src/components/ServiceSparkline.jsx b/web/src/components/ServiceSparkline.jsx index d70fd64..1e5ca16 100644 --- a/web/src/components/ServiceSparkline.jsx +++ b/web/src/components/ServiceSparkline.jsx @@ -1,25 +1,28 @@ 'use client'; -import React, { useState, useMemo } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { LineChart, Line, YAxis, ResponsiveContainer } from 'recharts'; import { TrendingUp, TrendingDown, Minus } from 'lucide-react'; import _ from 'lodash'; import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; const MAX_POINTS = 100; +const REFRESH_INTERVAL = 10000; // 10 seconds const ServiceSparkline = React.memo(({ nodeId, serviceName, initialMetrics = [] }) => { - console.log(`ServiceSparkline rendering for ${nodeId}/${serviceName} with ${initialMetrics.length} metrics`); const router = useRouter(); - // Use the initialMetrics that were passed in from the server - const [metrics] = useState(initialMetrics); + const [metrics, setMetrics] = useState(initialMetrics); - // Set up periodic refresh to get new data from server + // Update metrics when initialMetrics changes from server + useEffect(() => { + setMetrics(initialMetrics); + }, [initialMetrics]); + + // Set up periodic refresh to trigger server-side data update useEffect(() => { const interval = setInterval(() => { - router.refresh(); // This triggers a server-side refresh - }, 10000); // Refresh every 30 seconds + router.refresh(); // Triggers server-side re-fetch of nodes/page.js + }, REFRESH_INTERVAL); return () => clearInterval(interval); }, [router]); From 767347b93e64d39246cd54661d70607fd4f331b7 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 21:02:35 -0600 Subject: [PATCH 10/12] linter fix --- pkg/cloud/api/types.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/cloud/api/types.go b/pkg/cloud/api/types.go index 50965fc..a0a5736 100644 --- a/pkg/cloud/api/types.go +++ b/pkg/cloud/api/types.go @@ -6,7 +6,6 @@ import ( "time" "github.com/carverauto/serviceradar/pkg/checker/snmp" - "github.com/carverauto/serviceradar/pkg/db" "github.com/carverauto/serviceradar/pkg/metrics" "github.com/carverauto/serviceradar/pkg/models" "github.com/gorilla/mux" @@ -55,7 +54,5 @@ type APIServer struct { nodeHistoryHandler func(nodeID string) ([]NodeHistoryPoint, error) metricsManager metrics.MetricCollector snmpManager snmp.SNMPManager - db db.Service knownPollers []string - apiKey string } From 33ebdf29935fb586f3d03cf45b7ac489203bdb9e Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 21:04:52 -0600 Subject: [PATCH 11/12] cleaning up debug logs --- pkg/cloud/api/server.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/cloud/api/server.go b/pkg/cloud/api/server.go index 57df181..dfd6365 100644 --- a/pkg/cloud/api/server.go +++ b/pkg/cloud/api/server.go @@ -45,8 +45,6 @@ func WithSNMPManager(m snmp.SNMPManager) func(server *APIServer) { func (s *APIServer) setupRoutes() { // Create a middleware chain middlewareChain := func(next http.Handler) http.Handler { - log.Printf("SERVER API_KEY: %s", os.Getenv("API_KEY")) - // Order matters: first API key check, then CORS headers return srHttp.CommonMiddleware(srHttp.APIKeyMiddleware(os.Getenv("API_KEY"))(next)) } @@ -162,8 +160,6 @@ func (s *APIServer) getNodeHistory(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) nodeID := vars["id"] - log.Printf("Getting node history for: %s", nodeID) - if s.nodeHistoryHandler == nil { http.Error(w, "History handler not configured", http.StatusInternalServerError) return @@ -177,8 +173,6 @@ func (s *APIServer) getNodeHistory(w http.ResponseWriter, r *http.Request) { return } - log.Printf("Fetched %d history points for node: %s", len(points), nodeID) - if err := s.encodeJSONResponse(w, points); err != nil { log.Printf("Error encoding history response: %v", err) http.Error(w, "Error encoding response", http.StatusInternalServerError) @@ -280,7 +274,6 @@ func (s *APIServer) getNode(w http.ResponseWriter, r *http.Request) { node, exists := s.getNodeByID(nodeID) if !exists { - log.Printf("Node %s not found", nodeID) http.Error(w, "Node not found", http.StatusNotFound) return From 4f8010d2fbdf7bacb52f6160a86f471f6fe80546 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 1 Mar 2025 21:06:23 -0600 Subject: [PATCH 12/12] removing debug --- pkg/http/middleware.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/http/middleware.go b/pkg/http/middleware.go index e845455..a8ebb80 100644 --- a/pkg/http/middleware.go +++ b/pkg/http/middleware.go @@ -49,9 +49,6 @@ func APIKeyMiddleware(apiKeyParam string) func(next http.Handler) http.Handler { return } - // print out the full request header - log.Printf("Request Header: %v", r.Header) - // Check API key in header or query parameter requestKey := r.Header.Get("X-API-Key") if requestKey == "" {