diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..0169800
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,138 @@
+.git
+
+static/status.json
+.idea
+#config.js
+docker-compose.yml
+apulse_data
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/.gitignore b/.gitignore
index 9bf1fb8..3b9d516 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,9 @@
static/status.json
+.idea
+config.js
+docker-compose.yml
+apulse_data
+
# Logs
logs
*.log
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a8213b4
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,5 @@
+FROM node:20-slim
+WORKDIR /app
+ADD . .
+RUN chmod +x /app/entrypoint.sh
+ENTRYPOINT ["/app/entrypoint.sh"]
diff --git a/README.md b/README.md
index 79bb693..a67f7d9 100644
--- a/README.md
+++ b/README.md
@@ -7,13 +7,13 @@ Visit https://apulse.ybouane.com for a demo!
[
](https://apulse.ybouane.com)
# Features
-- Highly and easily configurable, edit the config.js file to add test endpoints and configure the watcher
+- Highly and easily configurable, copy config.js from config.dist.js, then edit file to add test endpoints and configure the watcher
- Supports sending outage notifications by: Telegram, Discord, Slack, SMS (Twilio API), Email (SendGrid API)
- Uses the Fetch API to test server-responses, you can configure GET, POST, PUT... requests and have full control over the fetch options.
- Check content for validity, HTTP status...
- Measures latency
- Minimal and easy to use dashboard
-- Easy to setup. Run the watcher.js script and open the static/index.html page to view the dashboard.
+- Easy to setup. Run the `entrypoint.sh` script and open the static/index.html page to view the dashboard.
- Auto-reload of the config file (no need to restart the watcher)
- No dependencies
@@ -30,6 +30,7 @@ export default {
verbose : true, // Whether or not to output pulse messages in the console
readableStatusJson : true, // Format status.json to be human readable
logsMaxDatapoints : 200, // Maximum datapoints history to keep (per endpoint)
+ statusFile : './static/status.json', // Path to status.json file
telegram : {}, // optional, tokens to send notifications through telegram
slack : {}, // optional, tokens to send notifications through slack
discord : {}, // optional, tokens to send notifications through discord
@@ -67,12 +68,12 @@ Clone the repo:
git clone https://github.com/ybouane/aPulse.git
```
-Either run the watcher.js script directly (you need to keep it running in the background)
+Either run the entrypoint.sh script directly (you need to keep it running in the background)
```shell
cd aPulse
```
```shell
-node watcher.js
+./entrypoint.sh
```
Or use a tool like PM2 (prefered method):
@@ -93,7 +94,7 @@ pm2 save
```
### Serving the status page
-The `watcher.js` script only takes care of running the status checks and updates the `status.json` file in the `static/` folder. If you want to view the final result, you simply need to serve the files in the `static/` folder. You can use Nginx with a config like:
+The `watcher.js` script only takes care of running the status checks and updates the file defined by `statusFile` (by default `static/status.json`). The `entrypoint.sh` script patches `static/client.js` with this path before starting `watcher.js`. If you want to view the final result, you simply need to serve the files in the `static/` folder (or wherever your dashboard points to). You can use Nginx with a config like:
```nginx
# Pulse
server {
diff --git a/config.js b/config.dist.js
similarity index 95%
rename from config.js
rename to config.dist.js
index 5b013d6..4dfc044 100644
--- a/config.js
+++ b/config.dist.js
@@ -7,6 +7,8 @@ export default {
verbose : true, // Whether or not to output pulse messages in the console
readableStatusJson : true, // Format status.json to be human readable
logsMaxDatapoints : 200, // Maximum datapoints history to keep (per endpoint)
+ statusFile : './static/status.json', // Path to status.json file
+ showLinks : true, // Display links on UI
telegram : { // optional, tokens to send notifications through telegram
botToken : '', // Contact @BotFather on telegram to create a bot
chatId : '',// Send a message to the bot, then visit https://api.telegram.org/bot/getUpdates to get the chatId
diff --git a/docker-compose.dist.yml b/docker-compose.dist.yml
new file mode 100644
index 0000000..858f99c
--- /dev/null
+++ b/docker-compose.dist.yml
@@ -0,0 +1,18 @@
+services:
+
+ watcher:
+ build:
+ context: .
+ volumes:
+ - ./apulse_data:/app/data
+ - ./config.js:/app/config.js
+ restart: unless-stopped
+
+ nginx:
+ image: nginx:alpine
+ volumes:
+ - ./apulse_data:/usr/share/nginx/html
+ - ./nginx.conf:/etc/nginx/conf.d/default.conf
+ ports:
+ - "8080:80"
+ restart: unless-stopped
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100755
index 0000000..a2a1d4c
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+set -e
+
+STATUS_FILE=$(node -e "
+ const fs = require('fs');
+ const path = require('path');
+
+ const configRaw = fs.readFileSync('./config.js', 'utf8');
+ const match = configRaw.match(/statusFile\s*:\s*['\"](.*?)['\"]/);
+ const result = match ? match[1] : './static/status.json';
+ console.log(result);
+")
+
+STATUS_DIR=$(dirname "$STATUS_FILE")
+
+if [ "$STATUS_DIR" != "./static" ]; then
+ echo "Copying static files to: $STATUS_DIR"
+ mkdir -p "$STATUS_DIR"
+ cp -r static/* "$STATUS_DIR"
+else
+ echo "statusFile is in ./static; skipping copy."
+fi
+
+exec node watcher.js
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..d471e9a
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,15 @@
+server {
+ listen 80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location /favicon.ico {
+ return 301 /favicon.png;
+ }
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+}
\ No newline at end of file
diff --git a/pm2.json b/pm2.json
index ab53aa5..9d9274f 100644
--- a/pm2.json
+++ b/pm2.json
@@ -1,6 +1,6 @@
{
"apps": [{
"name" : "aPulse",
- "script" : "./watcher.js"
+ "script" : "./entrypoint.sh"
}]
}
\ No newline at end of file
diff --git a/static/client.js b/static/client.js
index 7e88c1a..cc23e92 100644
--- a/static/client.js
+++ b/static/client.js
@@ -39,7 +39,7 @@ document.addEventListener("DOMContentLoaded", async () => {
$endpointName = document.createElement('h3');
$endpointName.innerText = endpoint.name;
- if(endpoint.link) {
+ if((config.showLinks ?? true) && endpoint.link) {
let $link = document.createElement('a');
$link.href = endpoint.link;
$link.target = '_blank';
diff --git a/static/status.json b/static/status.json
deleted file mode 100644
index 9e26dfe..0000000
--- a/static/status.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
\ No newline at end of file
diff --git a/watcher.js b/watcher.js
index 658393f..50de430 100644
--- a/watcher.js
+++ b/watcher.js
@@ -1,17 +1,16 @@
import {promises as fs, watchFile} from 'fs';
let config = (await import('./config.js')).default;
+let statusFile = config.statusFile || './static/status.json';
-watchFile('./config.js', async ()=>{ // Dynamically reload config and watch it for changes.
- try {
- config = (await import('./config.js?refresh='+Date.now())).default;
- console.log('Reloaded config file.')
- } catch(e) {
- console.error(e);
- }
+watchFile('./config.js', async () => { // Dynamically reload config and watch it for changes.
+ try {
+ config = (await import('./config.js?refresh=' + Date.now())).default;
+ console.log('Reloaded config file.')
+ } catch (e) {
+ console.error(e);
+ }
});
-const statusFile = './static/status.json';
-
const delay = async t=>new Promise(r=>setTimeout(r, t));
const handlize = s=>s.toLowerCase().replace(/[^a-z0-9]/g, ' ').trim().replace(/\s+/g, '-');
const checkContent = async (content, criterion, negate=false) => {
@@ -139,6 +138,7 @@ while(true) {
nDataPoints : config.nDataPoints,
responseTimeGood : config.responseTimeGood,
responseTimeWarning : config.responseTimeWarning,
+ showLinks : config.showLinks,
};
status.ui = [];
@@ -176,7 +176,7 @@ while(true) {
endpoint_.link = endpoint.link || endpoint.url;
endpoint_.logs = endpoint_.logs || [];
let start;
-
+
try {
performance.clearResourceTimings();
start = performance.now();