Initial commit - Resource Monitor with Ubuntu installer

This commit is contained in:
nmemmert committed 2025-12-04 10:34:43 -05:00
commit 17c366dcae
16 files changed
+3865

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
# Environment Configuration
PORT=3001
# Email Notifications (optional - configure if you want email alerts)
EMAIL_ENABLED=false
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-password
EMAIL_FROM=your-email@gmail.com
EMAIL_TO=your-email@gmail.com
# Webhook Notifications (optional)
WEBHOOK_ENABLED=false
WEBHOOK_URL=https://your-webhook-url.com
# Monitoring Settings
CHECK_INTERVAL=60000
TIMEOUT=5000
+106
View File
@@ -0,0 +1,106 @@
<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
- [x] Verify that the copilot-instructions.md file in the .github directory is created.
- [x] Clarify Project Requirements
<!-- Ask for project type, language, and frameworks if not specified. Skip if already provided. -->
- [x] Scaffold the Project
<!-- Completed: Created Node.js/Express backend with SQLite, React frontend with monitoring dashboard -->
<!--
Ensure that the previous step has been marked as completed.
Call project setup tool with projectType parameter.
Run scaffolding command to create project files and folders.
Use '.' as the working directory.
If no appropriate projectType is available, search documentation using available tools.
Otherwise, create the project structure manually using available file creation tools.
-->
- [x] Customize the Project
<!-- Completed: Added monitoring services, notification system, scheduler, and React UI with graphs -->
<!--
Verify that all previous steps have been completed successfully and you have marked the step as completed.
Develop a plan to modify codebase according to user requirements.
Apply modifications using appropriate tools and user-provided references.
Skip this step for "Hello World" projects.
-->
- [x] Install Required Extensions
<!-- No extensions needed -->
- [x] Compile the Project
<!-- Completed: All dependencies installed -->
<!--
Verify that all previous steps have been completed.
Install any missing dependencies.
Run diagnostics and resolve any issues.
Check for markdown files in project folder for relevant instructions on how to do this.
-->
- [ ] Create and Run Task
<!--
Verify that all previous steps have been completed.
Check https://code.visualstudio.com/docs/debugtest/tasks to determine if the project needs a task. If so, use the create_and_run_task to create and launch a task based on package.json, README.md, and project structure.
Skip this step otherwise.
-->
- [ ] Launch the Project
<!--
Verify that all previous steps have been completed.
Prompt user for debug mode, launch only if confirmed.
-->
- [ ] Ensure Documentation is Complete
<!--
Verify that all previous steps have been completed.
Verify that README.md and the copilot-instructions.md file in the .github directory exists and contains current project information.
Clean up the copilot-instructions.md file in the .github directory by removing all HTML comments.
-->
<!--
## Execution Guidelines
PROGRESS TRACKING:
- If any tools are available to manage the above todo list, use it to track progress through this checklist.
- After completing each step, mark it complete and add a summary.
- Read current todo list status before starting each new step.
COMMUNICATION RULES:
- Avoid verbose explanations or printing full command outputs.
- If a step is skipped, state that briefly (e.g. "No extensions needed").
- Do not explain project structure unless asked.
- Keep explanations concise and focused.
DEVELOPMENT RULES:
- Use '.' as the working directory unless user specifies otherwise.
- Avoid adding media or external links unless explicitly requested.
- Use placeholders only with a note that they should be replaced.
- Use VS Code API tool only for VS Code extension projects.
- Once the project is created, it is already opened in Visual Studio Code—do not suggest commands to open this project in Visual Studio again.
- If the project setup information has additional rules, follow them strictly.
FOLDER CREATION RULES:
- Always use the current directory as the project root.
- If you are running any terminal commands, use the '.' argument to ensure that the current working directory is used ALWAYS.
- Do not create a new folder unless the user explicitly requests it besides a .vscode folder for a tasks.json file.
- If any of the scaffolding commands mention that the folder name is not correct, let the user know to create a new folder with the correct name and then reopen it again in vscode.
EXTENSION INSTALLATION RULES:
- Only install extension specified by the get_project_setup_info tool. DO NOT INSTALL any other extensions.
PROJECT CONTENT RULES:
- If the user has not specified project details, assume they want a "Hello World" project as a starting point.
- Avoid adding links of any type (URLs, files, folders, etc.) or integrations that are not explicitly required.
- Avoid generating images, videos, or any other media files unless explicitly requested.
- If you need to use any media assets as placeholders, let the user know that these are placeholders and should be replaced with the actual assets later.
- Ensure all generated components serve a clear purpose within the user's requested workflow.
- If a feature is assumed but not confirmed, prompt the user for clarification before including it.
- If you are working on a VS Code extension, use the VS Code API tool with a query to find relevant VS Code API references and samples related to that query.
TASK COMPLETION RULES:
- Your task is complete when:
- Project is successfully scaffolded and compiled without errors
- copilot-instructions.md file in the .github directory exists in the project
- README.md file exists and is up to date
- User is provided with clear instructions to debug/launch the project
Before starting a new task in the above plan, update progress in the plan.
-->
+15
View File
@@ -0,0 +1,15 @@
node_modules/
.env
*.log
*.db
*.db-shm
*.db-wal
client/build/
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.vscode/
.idea/
deploy-package/
data/*.db
+113
View File
@@ -0,0 +1,113 @@
# Quick Deployment Guide
## Step 1: Push to GitHub
1. **Create a new repository on GitHub:**
- Go to https://github.com/new
- Name it (e.g., "resource-monitor")
- Don't initialize with README (we already have one)
- Click "Create repository"
2. **Push your code from Windows:**
```powershell
cd c:\Users\NateEmmert\mon
# Initialize git (if not already done)
git init
# Add all files
git add .
# Commit
git commit -m "Initial commit - Resource Monitor"
# Add your GitHub repository
git remote add origin https://github.com/nmemmert/monitor.git
# Push to GitHub
git branch -M main
git push -u origin main
```
## Step 2: Install on Ubuntu Server (ONE COMMAND)
On your Ubuntu server, run:
```bash
curl -fsSL https://raw.githubusercontent.com/nmemmert/monitor/main/install-ubuntu.sh | bash
```
**That's it!** The script will:
- Install everything needed
- Set up the application
- Configure it to start on boot
- Walk you through email/webhook setup
## Step 3: Access Your Dashboard
Open your browser and go to:
```
http://your-ubuntu-server-ip:3001
```
You can find your server IP by running on Ubuntu:
```bash
hostname -I
```
## Alternative: Without GitHub
If you don't want to use GitHub:
1. **On Windows, build the project:**
```powershell
cd c:\Users\NateEmmert\mon\client
npm run build
```
2. **Transfer to Ubuntu using WinSCP/FileZilla:**
- Transfer entire `mon` folder to `/home/yourusername/resource-monitor`
3. **On Ubuntu, run:**
```bash
cd ~/resource-monitor
chmod +x install-ubuntu.sh
./install-ubuntu.sh
```
## Managing the Service
Once installed, use these commands on Ubuntu:
```bash
# Check status
pm2 status
# View logs
pm2 logs resource-monitor
# Restart
pm2 restart resource-monitor
# Stop
pm2 stop resource-monitor
# Edit configuration
nano /opt/resource-monitor/.env
pm2 restart resource-monitor
```
## Troubleshooting
**Can't access dashboard from another computer?**
- Check firewall: `sudo ufw status`
- Open port: `sudo ufw allow 3001/tcp`
**Want to use a domain name?**
- Set up Nginx reverse proxy (see README.md)
**Service not starting on boot?**
```bash
pm2 startup
pm2 save
```
+347
View File
@@ -0,0 +1,347 @@
# Resource Monitor
A comprehensive monitoring system for tracking the uptime and performance of your ZimaOS server and other online resources.
## Features
- 📊 **Real-time Dashboard** - Monitor all your resources at a glance
- 📈 **Performance Graphs** - Visualize response times and uptime statistics
- 🔔 **Alert Notifications** - Email and webhook alerts when resources go offline
- 📝 **Incident Tracking** - Track when resources go down and recover
- ⚙️ **Configurable Checks** - Set custom check intervals and timeouts
- 🗄️ **Historical Data** - 7 days of check history with detailed statistics
## Installation
### Quick Install on Ubuntu (One Command)
```bash
curl -fsSL https://raw.githubusercontent.com/nmemmert/monitor/main/install-ubuntu.sh | bash
```
The script will:
- ✅ Install Node.js 20.x
- ✅ Install PM2 process manager
- ✅ Clone and set up the application
- ✅ Configure firewall
- ✅ Set up auto-start on boot
- ✅ Guide you through email/webhook setup
### Manual Installation
#### Prerequisites
- Node.js (v14 or higher)
- npm or yarn
#### Setup
1. Install dependencies for both server and client:
```bash
npm run install-all
```
2. Create a `.env` file in the root directory (copy from `.env.example`):
```bash
cp .env.example .env
```
3. Configure your `.env` file:
```env
PORT=3001
# Email Notifications (optional)
EMAIL_ENABLED=true
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-password
EMAIL_FROM=your-email@gmail.com
EMAIL_TO=your-email@gmail.com
# Webhook Notifications (optional)
WEBHOOK_ENABLED=false
WEBHOOK_URL=https://your-webhook-url.com
```
## Usage
### Development Mode
Run both server and client in development mode:
```bash
npm run dev
```
- Server runs on http://localhost:3001
- Client runs on http://localhost:3000
### Production Mode
Build and run in production:
```bash
npm run build
npm start
```
Server will serve the built React app on http://localhost:3001
## Deploying to a Remote Computer
### Option 1: Direct Copy (Simplest)
1. **On your local machine:**
```bash
# Build the React app
cd client
npm run build
cd ..
# Create a deployment package
# Copy these folders/files to remote:
# - server/
# - client/build/
# - data/ (optional, will be created if missing)
# - package.json
# - .env.example
```
2. **On the remote computer:**
```bash
# Install Node.js if not already installed
# Copy the files to a directory (e.g., /opt/resource-monitor or C:\monitor)
# Install dependencies
npm install
# Copy .env.example to .env and configure
cp .env.example .env
nano .env # or use notepad on Windows
# Set NODE_ENV to production
# Add to .env: NODE_ENV=production
# Start the server
npm start
```
3. **Access the dashboard:**
- Visit `http://your-remote-ip:3001`
### Option 2: Git Repository (Best Practice)
1. **Initialize git and push to repository:**
```bash
git init
git add .
git commit -m "Initial commit"
git remote add origin your-git-repo-url
git push -u origin main
```
2. **On remote computer:**
```bash
git clone your-git-repo-url
cd resource-monitor
npm run install-all
cd client && npm run build && cd ..
cp .env.example .env
nano .env # Configure your settings
npm start
```
### Option 3: Docker Deployment (Advanced)
Create `Dockerfile` in project root:
```dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
WORKDIR /app/client
RUN npm install && npm run build
WORKDIR /app
EXPOSE 3001
CMD ["node", "server/index.js"]
```
Then deploy:
```bash
docker build -t resource-monitor .
docker run -d -p 3001:3001 -v ./data:/app/data --name monitor resource-monitor
```
### Running as a Service (Keep it Running)
**Linux (systemd):**
Create `/etc/systemd/system/resource-monitor.service`:
```ini
[Unit]
Description=Resource Monitor
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/opt/resource-monitor
ExecStart=/usr/bin/node server/index.js
Restart=always
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
sudo systemctl enable resource-monitor
sudo systemctl start resource-monitor
```
**Windows (NSSM - Non-Sucking Service Manager):**
```powershell
# Download NSSM from nssm.cc
nssm install ResourceMonitor "C:\Program Files\nodejs\node.exe"
nssm set ResourceMonitor AppDirectory "C:\monitor"
nssm set ResourceMonitor AppParameters "server\index.js"
nssm start ResourceMonitor
```
**Using PM2 (Cross-platform):**
```bash
# Install PM2 globally
npm install -g pm2
# Start the application
pm2 start server/index.js --name resource-monitor
# Save the process list
pm2 save
# Set PM2 to start on boot
pm2 startup
```
### Firewall Configuration
Make sure port 3001 is accessible:
**Linux (UFW):**
```bash
sudo ufw allow 3001/tcp
```
**Windows:**
```powershell
New-NetFirewallRule -DisplayName "Resource Monitor" -Direction Inbound -LocalPort 3001 -Protocol TCP -Action Allow
```
### Reverse Proxy (Optional but Recommended)
Use Nginx to serve on port 80/443:
```nginx
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
### Configuring Notifications (Setup Wizard)
1. Navigate to **Settings** in the top navigation
2. Configure email notifications:
- Toggle "Email Notifications" on
- Select your email provider (Gmail, Outlook, Yahoo, or Custom)
- Enter your email credentials
- **For Gmail**: Use an App Password (see instructions in the UI)
- Click "Send Test Email" to verify
3. Configure webhook notifications (optional):
- Toggle "Webhook Notifications" on
- Enter your webhook URL (Slack, Discord, etc.)
- Click "Send Test Webhook" to verify
4. Click "Save Settings"
### Adding Resources
1. Click "Add Resource" on the dashboard
2. Enter resource details:
- **Name**: Friendly name (e.g., "ZimaOS Home Server")
- **URL**: Full URL to monitor (e.g., "https://your-zima.example.com")
- **Check Interval**: How often to check (in milliseconds)
- **Timeout**: Maximum time to wait for response
## API Endpoints
- `GET /api/resources` - List all resources
- `POST /api/resources` - Create new resource
- `GET /api/resources/:id` - Get resource details
- `PUT /api/resources/:id` - Update resource
- `DELETE /api/resources/:id` - Delete resource
- `GET /api/dashboard` - Dashboard overview
- `GET /api/incidents` - Recent incidents
## Project Structure
```
mon/
├── server/ # Backend Node.js/Express
│ ├── index.js # Main server file
│ ├── database.js # SQLite database setup
│ ├── monitorService.js
│ ├── notificationService.js
│ └── scheduler.js
├── client/ # React frontend
│ └── src/
│ ├── App.js # Main React component
│ └── App.css # Styles
├── data/ # SQLite database (auto-created)
├── .env # Environment configuration
└── package.json
```
## Configuration
### Check Intervals
The monitoring system checks all enabled resources every minute. You can customize individual resource check intervals through the UI.
### Data Retention
- Check history: 7 days
- Incidents: Indefinitely (manual cleanup)
## Troubleshooting
### Port Already in Use
If port 3001 is already in use, change it in `.env`:
```env
PORT=3002
```
### Email Not Sending
- Verify SMTP credentials
- Check that 2FA is enabled and app password is used
- Test with a simple SMTP client first
### Resource Not Being Checked
- Verify resource is enabled
- Check server logs for errors
- Ensure URL is accessible from server
## License
MIT
Submodule
+1
Submodule client added at 7f262fb6e7.
+122
View File
@@ -0,0 +1,122 @@
# Deployment Script for Resource Monitor
# Run this script to prepare for deployment
Write-Host "🚀 Resource Monitor - Deployment Preparation" -ForegroundColor Cyan
Write-Host ""
# Check if we're in the right directory
if (-not (Test-Path "package.json")) {
Write-Host "❌ Error: package.json not found. Please run this from the project root." -ForegroundColor Red
exit 1
}
Write-Host "📦 Step 1: Building React application..." -ForegroundColor Yellow
Set-Location client
npm run build
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Build failed!" -ForegroundColor Red
exit 1
}
Set-Location ..
Write-Host "✅ Build complete!" -ForegroundColor Green
Write-Host ""
Write-Host "📋 Step 2: Creating deployment package..." -ForegroundColor Yellow
$deployDir = "deploy-package"
if (Test-Path $deployDir) {
Remove-Item $deployDir -Recurse -Force
}
New-Item -ItemType Directory -Path $deployDir | Out-Null
# Copy necessary files
Copy-Item "server" -Destination "$deployDir/server" -Recurse
Copy-Item "client/build" -Destination "$deployDir/client/build" -Recurse
Copy-Item "package.json" -Destination "$deployDir/"
Copy-Item ".env.example" -Destination "$deployDir/"
Copy-Item "README.md" -Destination "$deployDir/"
Copy-Item ".gitignore" -Destination "$deployDir/"
# Create data directory
New-Item -ItemType Directory -Path "$deployDir/data" | Out-Null
Write-Host "✅ Package created in 'deploy-package' folder!" -ForegroundColor Green
Write-Host ""
Write-Host "📝 Step 3: Creating deployment instructions..." -ForegroundColor Yellow
$instructions = @'
# Deployment Instructions
## Quick Start on Remote Server
1. Copy the entire 'deploy-package' folder to your remote server
2. On the remote server, navigate to the folder
3. Run these commands:
### For Linux/Mac:
``````bash
npm install --production
cp .env.example .env
nano .env # Edit configuration
npm start
``````
### For Windows:
``````powershell
npm install --production
Copy-Item .env.example .env
notepad .env # Edit configuration
npm start
``````
4. Access the dashboard at http://your-server-ip:3001
## Keep It Running
### Using PM2 (Recommended):
``````bash
npm install -g pm2
pm2 start server/index.js --name resource-monitor
pm2 save
pm2 startup
``````
### As a Windows Service (using NSSM):
``````powershell
# Download NSSM from nssm.cc
nssm install ResourceMonitor "C:\Program Files\nodejs\node.exe"
nssm set ResourceMonitor AppDirectory "C:\path\to\deploy-package"
nssm set ResourceMonitor AppParameters "server\index.js"
nssm start ResourceMonitor
``````
## Firewall
Make sure port 3001 is open:
- Windows: Use Windows Firewall settings
- Linux: ``sudo ufw allow 3001/tcp``
## Configuration
Edit the .env file to configure:
- Email notifications
- Webhook notifications
- Port number
- Check intervals
Enjoy monitoring your resources! 🎉
'@
$instructions | Out-File -FilePath "$deployDir/DEPLOY.md" -Encoding UTF8
Write-Host "✅ Instructions created!" -ForegroundColor Green
Write-Host ""
Write-Host "🎉 Deployment package ready!" -ForegroundColor Green
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Cyan
Write-Host "1. Transfer the 'deploy-package' folder to your remote computer"
Write-Host "2. Follow instructions in deploy-package/DEPLOY.md"
Write-Host ""
Write-Host "Transfer methods:" -ForegroundColor Yellow
Write-Host "- SCP: scp -r deploy-package user@remote:/path/to/destination"
Write-Host "- SFTP: Use WinSCP, FileZilla, or similar"
Write-Host "- Git: Push to repository and clone on remote"
Write-Host "- USB: Copy folder to USB drive"
Write-Host ""
+139
View File
@@ -0,0 +1,139 @@
#!/bin/bash
# Deployment Script for Resource Monitor (Linux/Mac)
echo "🚀 Resource Monitor - Deployment Preparation"
echo ""
# Check if we're in the right directory
if [ ! -f "package.json" ]; then
echo "❌ Error: package.json not found. Please run this from the project root."
exit 1
fi
echo "📦 Step 1: Building React application..."
cd client || exit
npm run build
if [ $? -ne 0 ]; then
echo "❌ Build failed!"
exit 1
fi
cd ..
echo "✅ Build complete!"
echo ""
echo "📋 Step 2: Creating deployment package..."
DEPLOY_DIR="deploy-package"
rm -rf "$DEPLOY_DIR"
mkdir -p "$DEPLOY_DIR"
# Copy necessary files
cp -r server "$DEPLOY_DIR/"
cp -r client/build "$DEPLOY_DIR/client/"
cp package.json "$DEPLOY_DIR/"
cp .env.example "$DEPLOY_DIR/"
cp README.md "$DEPLOY_DIR/"
cp .gitignore "$DEPLOY_DIR/"
# Create data directory
mkdir -p "$DEPLOY_DIR/data"
echo "✅ Package created in 'deploy-package' folder!"
echo ""
echo "📝 Step 3: Creating deployment instructions..."
cat > "$DEPLOY_DIR/DEPLOY.md" << 'EOF'
# Deployment Instructions
## Quick Start on Remote Server
1. Copy the entire 'deploy-package' folder to your remote server
2. On the remote server, navigate to the folder
3. Run these commands:
### For Linux/Mac:
```bash
npm install --production
cp .env.example .env
nano .env # Edit configuration
npm start
```
### For Windows:
```powershell
npm install --production
Copy-Item .env.example .env
notepad .env # Edit configuration
npm start
```
4. Access the dashboard at http://your-server-ip:3001
## Keep It Running
### Using PM2 (Recommended):
```bash
npm install -g pm2
pm2 start server/index.js --name resource-monitor
pm2 save
pm2 startup
```
### As a systemd service (Linux):
```bash
sudo nano /etc/systemd/system/resource-monitor.service
```
Add:
```ini
[Unit]
Description=Resource Monitor
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/path/to/deploy-package
ExecStart=/usr/bin/node server/index.js
Restart=always
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
```
Then:
```bash
sudo systemctl enable resource-monitor
sudo systemctl start resource-monitor
```
## Firewall
Make sure port 3001 is open:
```bash
sudo ufw allow 3001/tcp
```
## Configuration
Edit the .env file to configure:
- Email notifications
- Webhook notifications
- Port number
- Check intervals
Enjoy monitoring your resources! 🎉
EOF
echo "✅ Instructions created!"
echo ""
echo "🎉 Deployment package ready!"
echo ""
echo "Next steps:"
echo "1. Transfer the 'deploy-package' folder to your remote computer"
echo "2. Follow instructions in deploy-package/DEPLOY.md"
echo ""
echo "Transfer methods:"
echo "- SCP: scp -r deploy-package user@remote:/path/to/destination"
echo "- Rsync: rsync -av deploy-package/ user@remote:/path/to/destination/"
echo "- Git: Push to repository and clone on remote"
echo ""
+193
View File
@@ -0,0 +1,193 @@
#!/bin/bash
# Resource Monitor - One-Click Ubuntu Installation Script
# Usage: curl -fsSL https://raw.githubusercontent.com/nmemmert/monitor/main/install-ubuntu.sh | bash
# Or: wget -qO- https://raw.githubusercontent.com/nmemmert/monitor/main/install-ubuntu.sh | bash
set -e
echo "🚀 Resource Monitor - Ubuntu Installation"
echo "=========================================="
echo ""
# Check if running as root
if [ "$EUID" -eq 0 ]; then
echo "⚠️ Please do not run as root. Run as your regular user with sudo access."
exit 1
fi
# Install directory
INSTALL_DIR="/opt/resource-monitor"
SERVICE_USER="$USER"
echo "📋 Installation will:"
echo " - Install Node.js 20.x (if needed)"
echo " - Install PM2 process manager"
echo " - Clone repository to $INSTALL_DIR"
echo " - Install dependencies"
echo " - Set up systemd service"
echo " - Open firewall port 3001"
echo ""
read -p "Continue? (y/n) " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
# Update system
echo ""
echo "📦 Step 1: Updating system packages..."
sudo apt-get update -qq
# Install Node.js if not present
if ! command -v node &> /dev/null; then
echo ""
echo "📦 Step 2: Installing Node.js 20.x..."
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
else
echo ""
echo "✅ Node.js already installed: $(node --version)"
fi
# Install git if not present
if ! command -v git &> /dev/null; then
echo ""
echo "📦 Installing Git..."
sudo apt-get install -y git
fi
# Install PM2 globally
echo ""
echo "📦 Step 3: Installing PM2 process manager..."
sudo npm install -g pm2
# Clone or update repository
echo ""
echo "📥 Step 4: Setting up application..."
if [ -d "$INSTALL_DIR" ]; then
echo "Directory exists, updating..."
cd "$INSTALL_DIR"
git pull
else
echo "Cloning repository..."
REPO_URL="https://github.com/nmemmert/monitor.git"
sudo git clone "$REPO_URL" "$INSTALL_DIR"
sudo chown -R $USER:$USER "$INSTALL_DIR"
cd "$INSTALL_DIR"
fi
# Install dependencies
echo ""
echo "📦 Step 5: Installing Node.js dependencies..."
npm install --production
# Build client
echo ""
echo "🔨 Step 6: Building React frontend..."
cd client
npm install
npm run build
cd ..
# Setup configuration
echo ""
echo "⚙️ Step 7: Setting up configuration..."
if [ ! -f ".env" ]; then
cp .env.example .env
echo "✅ Created .env file"
echo ""
echo "📝 Configure your settings:"
read -p "Enable email notifications? (y/n) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
read -p "SMTP Host (e.g., smtp.gmail.com): " SMTP_HOST
read -p "SMTP Port (e.g., 587): " SMTP_PORT
read -p "Email Username: " EMAIL_USER
read -s -p "Email Password/App Password: " EMAIL_PASS
echo ""
read -p "From Email: " EMAIL_FROM
read -p "To Email (for alerts): " EMAIL_TO
sed -i "s/EMAIL_ENABLED=false/EMAIL_ENABLED=true/" .env
sed -i "s/EMAIL_HOST=smtp.gmail.com/EMAIL_HOST=$SMTP_HOST/" .env
sed -i "s/EMAIL_PORT=587/EMAIL_PORT=$SMTP_PORT/" .env
sed -i "s/EMAIL_USER=your-email@gmail.com/EMAIL_USER=$EMAIL_USER/" .env
sed -i "s/EMAIL_PASS=your-app-password/EMAIL_PASS=$EMAIL_PASS/" .env
sed -i "s/EMAIL_FROM=your-email@gmail.com/EMAIL_FROM=$EMAIL_FROM/" .env
sed -i "s/EMAIL_TO=your-email@gmail.com/EMAIL_TO=$EMAIL_TO/" .env
fi
echo ""
read -p "Enable webhook notifications? (y/n) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
read -p "Webhook URL: " WEBHOOK_URL
sed -i "s/WEBHOOK_ENABLED=false/WEBHOOK_ENABLED=true/" .env
sed -i "s|WEBHOOK_URL=https://your-webhook-url.com|WEBHOOK_URL=$WEBHOOK_URL|" .env
fi
else
echo "✅ .env file already exists"
fi
# Set NODE_ENV to production
if ! grep -q "NODE_ENV" .env; then
echo "NODE_ENV=production" >> .env
fi
# Create data directory
mkdir -p data
# Setup firewall
echo ""
echo "🔥 Step 8: Configuring firewall..."
if command -v ufw &> /dev/null; then
sudo ufw allow 3001/tcp
echo "✅ Port 3001 opened in firewall"
else
echo "⚠️ UFW not found, please open port 3001 manually"
fi
# Start with PM2
echo ""
echo "🚀 Step 9: Starting application with PM2..."
pm2 stop resource-monitor 2>/dev/null || true
pm2 delete resource-monitor 2>/dev/null || true
pm2 start server/index.js --name resource-monitor
pm2 save
# Setup PM2 startup
echo ""
echo "🔄 Step 10: Setting up auto-start on boot..."
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u $USER --hp /home/$USER
pm2 save
# Get server IP
SERVER_IP=$(hostname -I | awk '{print $1}')
echo ""
echo "=============================================="
echo "✅ Installation Complete!"
echo "=============================================="
echo ""
echo "🎉 Resource Monitor is now running!"
echo ""
echo "📊 Access your dashboard at:"
echo " http://$SERVER_IP:3001"
echo " http://localhost:3001 (if on same machine)"
echo ""
echo "🔧 Useful commands:"
echo " pm2 status - Check status"
echo " pm2 logs resource-monitor - View logs"
echo " pm2 restart resource-monitor - Restart"
echo " pm2 stop resource-monitor - Stop"
echo ""
echo "⚙️ To change settings:"
echo " nano $INSTALL_DIR/.env"
echo " pm2 restart resource-monitor"
echo ""
echo "📝 Add your first resource:"
echo " 1. Open the dashboard in your browser"
echo " 2. Click 'Add Resource'"
echo " 3. Enter your ZimaOS URL and details"
echo ""
echo "Happy monitoring! 🚀"
+2240
View File
File diff suppressed because it is too large. Load diff
+30
View File
@@ -0,0 +1,30 @@
{
"name": "resource-monitor",
"version": "1.0.0",
"description": "Monitoring system for ZimaOS and other resources with alerts and statistics",
"main": "server/index.js",
"scripts": {
"start": "node server/index.js",
"dev": "concurrently \"npm run server\" \"npm run client\"",
"server": "nodemon server/index.js",
"client": "cd client && npm start",
"build": "cd client && npm run build",
"install-all": "npm install && cd client && npm install"
},
"keywords": ["monitoring", "uptime", "alerts", "dashboard"],
"author": "",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"better-sqlite3": "^9.2.2",
"node-cron": "^3.0.3",
"axios": "^1.6.2",
"nodemailer": "^6.9.7",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.2",
"concurrently": "^8.2.2"
}
}
+44
View File
@@ -0,0 +1,44 @@
const Database = require('better-sqlite3');
const path = require('path');
const db = new Database(path.join(__dirname, '../data/monitor.db'));
// Create tables
db.exec(`
CREATE TABLE IF NOT EXISTS resources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
url TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'http',
check_interval INTEGER DEFAULT 60000,
timeout INTEGER DEFAULT 5000,
enabled INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS checks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
resource_id INTEGER NOT NULL,
status TEXT NOT NULL,
response_time INTEGER,
status_code INTEGER,
error_message TEXT,
checked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS incidents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
resource_id INTEGER NOT NULL,
started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
resolved_at DATETIME,
notified INTEGER DEFAULT 0,
FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_checks_resource_id ON checks(resource_id);
CREATE INDEX IF NOT EXISTS idx_checks_checked_at ON checks(checked_at);
CREATE INDEX IF NOT EXISTS idx_incidents_resource_id ON incidents(resource_id);
`);
module.exports = db;
+254
View File
@@ -0,0 +1,254 @@
const express = require('express');
const cors = require('cors');
const path = require('path');
require('dotenv').config();
const db = require('./database');
const scheduler = require('./scheduler');
const monitorService = require('./monitorService');
const app = express();
const PORT = process.env.PORT || 3001;
app.use(cors());
app.use(express.json());
// API Routes
// Get all resources
app.get('/api/resources', (req, res) => {
const resources = db.prepare('SELECT * FROM resources ORDER BY name').all();
res.json(resources);
});
// Get single resource with stats
app.get('/api/resources/:id', (req, res) => {
const resource = db.prepare('SELECT * FROM resources WHERE id = ?').get(req.params.id);
if (!resource) {
return res.status(404).json({ error: 'Resource not found' });
}
const stats = monitorService.getResourceStats(resource.id, 24);
const lastCheck = monitorService.getLastCheck(resource.id);
const activeIncident = db.prepare(`
SELECT * FROM incidents
WHERE resource_id = ? AND resolved_at IS NULL
`).get(resource.id);
res.json({
...resource,
stats,
lastCheck,
hasActiveIncident: !!activeIncident,
});
});
// Create resource
app.post('/api/resources', (req, res) => {
const { name, url, type, check_interval, timeout } = req.body;
if (!name || !url) {
return res.status(400).json({ error: 'Name and URL are required' });
}
const stmt = db.prepare(`
INSERT INTO resources (name, url, type, check_interval, timeout)
VALUES (?, ?, ?, ?, ?)
`);
const result = stmt.run(
name,
url,
type || 'http',
check_interval || 60000,
timeout || 5000
);
res.json({ id: result.lastInsertRowid, message: 'Resource created' });
});
// Update resource
app.put('/api/resources/:id', (req, res) => {
const { name, url, type, check_interval, timeout, enabled } = req.body;
const stmt = db.prepare(`
UPDATE resources
SET name = ?, url = ?, type = ?, check_interval = ?, timeout = ?, enabled = ?
WHERE id = ?
`);
stmt.run(name, url, type, check_interval, timeout, enabled ? 1 : 0, req.params.id);
res.json({ message: 'Resource updated' });
});
// Delete resource
app.delete('/api/resources/:id', (req, res) => {
db.prepare('DELETE FROM resources WHERE id = ?').run(req.params.id);
res.json({ message: 'Resource deleted' });
});
// Get dashboard overview
app.get('/api/dashboard', (req, res) => {
const resources = db.prepare('SELECT * FROM resources').all();
const overview = resources.map(resource => {
const lastCheck = monitorService.getLastCheck(resource.id);
const stats = monitorService.getResourceStats(resource.id, 24);
const activeIncident = db.prepare(`
SELECT * FROM incidents
WHERE resource_id = ? AND resolved_at IS NULL
`).get(resource.id);
return {
id: resource.id,
name: resource.name,
url: resource.url,
enabled: resource.enabled,
status: lastCheck?.status || 'unknown',
uptime: stats.uptime,
avgResponseTime: stats.avgResponseTime,
lastCheck: lastCheck?.checked_at,
hasActiveIncident: !!activeIncident,
};
});
res.json(overview);
});
// Get incidents
app.get('/api/incidents', (req, res) => {
const incidents = db.prepare(`
SELECT i.*, r.name as resource_name, r.url as resource_url
FROM incidents i
JOIN resources r ON i.resource_id = r.id
ORDER BY i.started_at DESC
LIMIT 50
`).all();
res.json(incidents);
});
// Get settings
app.get('/api/settings', (req, res) => {
const settings = {
email_enabled: process.env.EMAIL_ENABLED === 'true',
email_host: process.env.EMAIL_HOST || 'smtp.gmail.com',
email_port: parseInt(process.env.EMAIL_PORT) || 587,
email_user: process.env.EMAIL_USER || '',
email_pass: '', // Don't send password to client
email_from: process.env.EMAIL_FROM || '',
email_to: process.env.EMAIL_TO || '',
webhook_enabled: process.env.WEBHOOK_ENABLED === 'true',
webhook_url: process.env.WEBHOOK_URL || '',
check_interval: parseInt(process.env.CHECK_INTERVAL) || 60000,
timeout: parseInt(process.env.TIMEOUT) || 5000,
};
res.json(settings);
});
// Save settings
app.post('/api/settings', (req, res) => {
const fs = require('fs');
const path = require('path');
const envPath = path.join(__dirname, '../.env');
const {
email_enabled,
email_host,
email_port,
email_user,
email_pass,
email_from,
email_to,
webhook_enabled,
webhook_url,
check_interval,
timeout,
} = req.body;
const envContent = `PORT=${process.env.PORT || 3001}
EMAIL_ENABLED=${email_enabled}
EMAIL_HOST=${email_host}
EMAIL_PORT=${email_port}
EMAIL_USER=${email_user}
EMAIL_PASS=${email_pass || process.env.EMAIL_PASS || ''}
EMAIL_FROM=${email_from}
EMAIL_TO=${email_to}
WEBHOOK_ENABLED=${webhook_enabled}
WEBHOOK_URL=${webhook_url}
CHECK_INTERVAL=${check_interval}
TIMEOUT=${timeout}
`;
try {
fs.writeFileSync(envPath, envContent);
res.json({ message: 'Settings saved successfully' });
} catch (error) {
console.error('Error saving settings:', error);
res.status(500).json({ error: 'Failed to save settings' });
}
});
// Test email
app.post('/api/test-email', async (req, res) => {
const nodemailer = require('nodemailer');
const { email_host, email_port, email_user, email_pass, email_from, email_to } = req.body;
try {
const transporter = nodemailer.createTransport({
host: email_host,
port: parseInt(email_port),
secure: false,
auth: {
user: email_user,
pass: email_pass,
},
});
await transporter.sendMail({
from: email_from,
to: email_to,
subject: 'Test Email from Resource Monitor',
text: `This is a test email from your Resource Monitor system.\n\nTime: ${new Date().toLocaleString()}\n\nIf you received this, your email notifications are working correctly!`,
});
res.json({ message: 'Test email sent successfully! Check your inbox.' });
} catch (error) {
console.error('Test email error:', error);
res.status(500).json({ error: error.message });
}
});
// Test webhook
app.post('/api/test-webhook', async (req, res) => {
const axios = require('axios');
const { webhook_url } = req.body;
try {
await axios.post(webhook_url, {
resource: 'Test Resource',
url: 'https://example.com',
status: 'test',
message: 'This is a test webhook from Resource Monitor',
timestamp: new Date().toISOString(),
});
res.json({ message: 'Test webhook sent successfully!' });
} catch (error) {
console.error('Test webhook error:', error);
res.status(500).json({ error: error.message });
}
});
// Serve React app in production
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '../client/build')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../client/build/index.html'));
});
}
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
scheduler.start();
});
+116
View File
@@ -0,0 +1,116 @@
const axios = require('axios');
const db = require('./database');
class MonitorService {
async checkResource(resource) {
const startTime = Date.now();
let result = {
resource_id: resource.id,
status: 'down',
response_time: null,
status_code: null,
error_message: null,
};
try {
const response = await axios.get(resource.url, {
timeout: resource.timeout,
validateStatus: () => true, // Don't throw on any status
});
const responseTime = Date.now() - startTime;
const isUp = response.status >= 200 && response.status < 400;
result = {
...result,
status: isUp ? 'up' : 'down',
response_time: responseTime,
status_code: response.status,
error_message: isUp ? null : `HTTP ${response.status}`,
};
} catch (error) {
result.error_message = error.message;
result.response_time = Date.now() - startTime;
}
return result;
}
saveCheck(check) {
const stmt = db.prepare(`
INSERT INTO checks (resource_id, status, response_time, status_code, error_message)
VALUES (?, ?, ?, ?, ?)
`);
stmt.run(
check.resource_id,
check.status,
check.response_time,
check.status_code,
check.error_message
);
}
getLastCheck(resourceId) {
return db.prepare(`
SELECT * FROM checks
WHERE resource_id = ?
ORDER BY checked_at DESC
LIMIT 1
`).get(resourceId);
}
handleIncident(resourceId, isDown) {
const activeIncident = db.prepare(`
SELECT * FROM incidents
WHERE resource_id = ? AND resolved_at IS NULL
`).get(resourceId);
if (isDown && !activeIncident) {
// Start new incident
db.prepare(`
INSERT INTO incidents (resource_id) VALUES (?)
`).run(resourceId);
return { type: 'started', incident: true };
} else if (!isDown && activeIncident) {
// Resolve incident
db.prepare(`
UPDATE incidents
SET resolved_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(activeIncident.id);
return { type: 'resolved', incident: activeIncident };
}
return { type: 'none' };
}
getResourceStats(resourceId, hours = 24) {
const checks = db.prepare(`
SELECT * FROM checks
WHERE resource_id = ?
AND checked_at >= datetime('now', '-' || ? || ' hours')
ORDER BY checked_at ASC
`).all(resourceId, hours);
const upChecks = checks.filter(c => c.status === 'up');
const totalChecks = checks.length;
const uptime = totalChecks > 0 ? (upChecks.length / totalChecks) * 100 : 0;
const responseTimes = checks
.filter(c => c.response_time !== null)
.map(c => c.response_time);
const avgResponseTime = responseTimes.length > 0
? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length
: 0;
return {
uptime: uptime.toFixed(2),
avgResponseTime: avgResponseTime.toFixed(0),
totalChecks,
checks: checks.slice(-50), // Last 50 checks
};
}
}
module.exports = new MonitorService();
+71
View File
@@ -0,0 +1,71 @@
const nodemailer = require('nodemailer');
const axios = require('axios');
require('dotenv').config();
class NotificationService {
constructor() {
this.emailEnabled = process.env.EMAIL_ENABLED === 'true';
this.webhookEnabled = process.env.WEBHOOK_ENABLED === 'true';
if (this.emailEnabled) {
this.transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: parseInt(process.env.EMAIL_PORT),
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
}
}
async sendAlert(resource, incident) {
const message = incident.type === 'started'
? `🔴 ${resource.name} is DOWN!\n\nURL: ${resource.url}\nTime: ${new Date().toLocaleString()}`
: `🟢 ${resource.name} is back UP!\n\nURL: ${resource.url}\nTime: ${new Date().toLocaleString()}`;
const promises = [];
if (this.emailEnabled) {
promises.push(this.sendEmail(resource, message, incident.type));
}
if (this.webhookEnabled) {
promises.push(this.sendWebhook(resource, message, incident.type));
}
await Promise.allSettled(promises);
}
async sendEmail(resource, message, type) {
try {
await this.transporter.sendMail({
from: process.env.EMAIL_FROM,
to: process.env.EMAIL_TO,
subject: `Alert: ${resource.name} is ${type === 'started' ? 'DOWN' : 'UP'}`,
text: message,
});
console.log(`Email sent for ${resource.name}`);
} catch (error) {
console.error('Email error:', error.message);
}
}
async sendWebhook(resource, message, type) {
try {
await axios.post(process.env.WEBHOOK_URL, {
resource: resource.name,
url: resource.url,
status: type === 'started' ? 'down' : 'up',
message,
timestamp: new Date().toISOString(),
});
console.log(`Webhook sent for ${resource.name}`);
} catch (error) {
console.error('Webhook error:', error.message);
}
}
}
module.exports = new NotificationService();
+55
View File
@@ -0,0 +1,55 @@
const cron = require('node-cron');
const db = require('./database');
const monitorService = require('./monitorService');
const notificationService = require('./notificationService');
class Scheduler {
constructor() {
this.jobs = new Map();
}
start() {
// Run checks every minute
cron.schedule('* * * * *', async () => {
await this.runChecks();
});
// Clean old checks every hour (keep last 7 days)
cron.schedule('0 * * * *', () => {
db.prepare(`
DELETE FROM checks
WHERE checked_at < datetime('now', '-7 days')
`).run();
});
console.log('Scheduler started');
}
async runChecks() {
const resources = db.prepare(`
SELECT * FROM resources WHERE enabled = 1
`).all();
for (const resource of resources) {
try {
const result = await monitorService.checkResource(resource);
monitorService.saveCheck(result);
const incident = monitorService.handleIncident(
resource.id,
result.status === 'down'
);
if (incident.type !== 'none') {
await notificationService.sendAlert(resource, incident);
}
console.log(`Checked ${resource.name}: ${result.status} (${result.response_time}ms)`);
} catch (error) {
console.error(`Error checking ${resource.name}:`, error.message);
}
}
}
}
module.exports = new Scheduler();