Initial commit - Resource Monitor with Ubuntu installer
This commit is contained in:
commit
17c366dcae
16 files changed
+3865
No files matched your search
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
```
|
||||||
@@ -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
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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! 🚀"
|
||||||
Generated
+2240
File diff suppressed because it is too large.
Load diff
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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();
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
Reference in new issue
Block a user