From 4eff80c4dd1e511cdd4d2dc9bd92ff1b2e5d2da6 Mon Sep 17 00:00:00 2001 From: Pavel Zloi Date: Sat, 24 May 2025 18:04:37 +0300 Subject: [PATCH 1/7] Add entrypoint script to patch client --- README.md | 9 +++++---- config.js | 1 + entrypoint.sh | 11 +++++++++++ pm2.json | 2 +- watcher.js | 16 ++++++++-------- 5 files changed, 26 insertions(+), 13 deletions(-) create mode 100755 entrypoint.sh diff --git a/README.md b/README.md index 79bb693..63bece6 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Visit https://apulse.ybouane.com for a demo! - 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.js index 5b013d6..9790bf1 100644 --- a/config.js +++ b/config.js @@ -7,6 +7,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 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/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..358c572 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +STATUS_FILE=$(node - <<'NODE' +import config from './config.js'; +console.log(config.statusFile || './static/status.json'); +NODE +) +ESC_PATH=$(printf '%s\n' "$STATUS_FILE" | sed 's/[&/]/\\&/g') +sed -i -E "s|(fetch\(['\"])(\.\/status\.json)(['\"])|\1${ESC_PATH}\3|" static/client.js +exec node watcher.js + diff --git a/pm2.json b/pm2.json index ab53aa5..f8d7e07 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/watcher.js b/watcher.js index 658393f..b2f5ab5 100644 --- a/watcher.js +++ b/watcher.js @@ -1,17 +1,17 @@ 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); - } + try { + config = (await import('./config.js?refresh='+Date.now())).default; + statusFile = config.statusFile || './static/status.json'; + 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) => { From a377992ac1e92993ad65d3c99a1a8fe0cd68e4dd Mon Sep 17 00:00:00 2001 From: pasha Date: Sat, 24 May 2025 18:23:26 +0300 Subject: [PATCH 2/7] Dokerization --- .dockerignore | 138 ++++++++++++++++ .gitignore | 5 + Dockerfile | 6 + config.js => config.dist.js | 0 docker-compose.dist.yml | 18 +++ entrypoint.sh | 31 +++- nginx.conf | 15 ++ pm2.json | 2 +- static/client.js | 2 +- static/status.json | 306 +++++++++++++++++++++++++++++++++++- watcher.js | 17 +- 11 files changed, 521 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile rename config.js => config.dist.js (100%) create mode 100644 docker-compose.dist.yml create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a9f1a1a --- /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..986c3bc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM node:20-slim +WORKDIR /app +ADD . . +RUN ls -la +RUN chmod +x /app/entrypoint.sh +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/config.js b/config.dist.js similarity index 100% rename from config.js rename to config.dist.js diff --git a/docker-compose.dist.yml b/docker-compose.dist.yml new file mode 100644 index 0000000..c20e9ad --- /dev/null +++ b/docker-compose.dist.yml @@ -0,0 +1,18 @@ +services: + + watcher: + build: + context: . + volumes: + - ./apulse_data:/app/static + - ./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: + - "80:80" + restart: unless-stopped diff --git a/entrypoint.sh b/entrypoint.sh index 358c572..5065776 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,11 +1,28 @@ #!/bin/bash set -e -STATUS_FILE=$(node - <<'NODE' -import config from './config.js'; -console.log(config.statusFile || './static/status.json'); -NODE -) + +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 + ESC_PATH=$(printf '%s\n' "$STATUS_FILE" | sed 's/[&/]/\\&/g') -sed -i -E "s|(fetch\(['\"])(\.\/status\.json)(['\"])|\1${ESC_PATH}\3|" static/client.js -exec node watcher.js +sed -E "s|(fetch\(['\"])(\.\/status\.json)(['\"])|\1${ESC_PATH}\3|" -i static/client.js + +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 f8d7e07..9d9274f 100644 --- a/pm2.json +++ b/pm2.json @@ -1,6 +1,6 @@ { "apps": [{ "name" : "aPulse", - "script" : "./entrypoint.sh" + "script" : "./entrypoint.sh" }] } \ No newline at end of file diff --git a/static/client.js b/static/client.js index 7e88c1a..1759a52 100644 --- a/static/client.js +++ b/static/client.js @@ -5,7 +5,7 @@ document.addEventListener("DOMContentLoaded", async () => { let $main = document.querySelector('main'); const refreshStatus = async () => { try { - const response = await fetch('./status.json', {cache: "no-cache"}); + const response = await fetch('./static/status.json', {cache: "no-cache"}); if (!response.ok) { throw new Error(`Error fetching status.json: ${response.statusText}`); } diff --git a/static/status.json b/static/status.json index 9e26dfe..8d72d57 100644 --- a/static/status.json +++ b/static/status.json @@ -1 +1,305 @@ -{} \ No newline at end of file +{ + "sites": { + "google": { + "name": "Google", + "endpoints": { + "homepage": { + "name": "Homepage", + "link": "https://www.google.com", + "logs": [ + { + "t": 1748087741320, + "dur": 149.993578, + "dns": 0, + "tcp": 0, + "ttfb": 55.36531200000002, + "dll": 10.84602799999999, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748087757965, + "dur": 148.39908100000002, + "dns": 0, + "tcp": 0, + "ttfb": 52.42947699999999, + "dll": 17.866462000000013, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748088657969, + "dur": 114.20320800004993, + "dns": 0, + "tcp": 0, + "ttfb": 55.46503199997824, + "dll": 3.61165900004562, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748089557970, + "dur": 197.5670749999117, + "dns": 0, + "tcp": 0, + "ttfb": 63.359468000009656, + "dll": 79.4861919998657, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748090457973, + "dur": 103.08965699980035, + "dns": 0, + "tcp": 0, + "ttfb": 53.5293140001595, + "dll": 3.8372279996983707, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748091357976, + "dur": 138.84726900001988, + "dns": 0, + "tcp": 0, + "ttfb": 63.68227700004354, + "dll": 3.737294999882579, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748092257977, + "dur": 108.30177299957722, + "dns": 0, + "tcp": 0, + "ttfb": 57.426580000668764, + "dll": 3.4700069995597005, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748093157981, + "dur": 112.15920299943537, + "dns": 0, + "tcp": 0, + "ttfb": 60.26049399934709, + "dll": 4.292896999977529, + "err": "\"mustFind\" check failed" + }, + { + "t": 1748094057981, + "dur": 104.02492600027472, + "dns": 0, + "tcp": 0, + "ttfb": 53.809276999905705, + "dll": 3.9284429997205734, + "err": "\"mustFind\" check failed" + } + ] + } + } + }, + "traefik": { + "name": "Traefik", + "endpoints": { + "lb01-trefik-443": { + "name": "Reverse Proxy", + "link": "https://lb01", + "logs": [ + { + "t": 1748099643036, + "err": "TypeError: fetch failed", + "dur": 94.937342, + "ttfb": 94.937342 + } + ] + } + } + }, + "litellm": { + "name": "LiteLLM", + "endpoints": { + "lb01-litellm-4000": { + "name": "LiteLLM / API", + "link": "http://lb01:4000", + "logs": [ + { + "t": 1748099643132, + "dur": 12.401092000000006, + "dns": 0, + "tcp": 0, + "ttfb": 3.7235589999999945, + "dll": 4.344725000000011 + } + ] + }, + "lb01-litellm-15000": { + "name": "LiteLLM / GigaChat Adapter", + "link": "http://lb01:15000", + "logs": [ + { + "t": 1748099643146, + "dur": 4.7646939999999915, + "dns": 0, + "tcp": 0, + "ttfb": 2.1338580000000036, + "dll": 0.6840509999999824, + "err": "HTTP Status 404: Not Found" + } + ] + } + } + }, + "whisper": { + "name": "Whisper", + "endpoints": { + "gpu01-whisper-12000": { + "name": "Whisper / Load Balancer", + "link": "http://gpu01:12000", + "logs": [ + { + "t": 1748099643153, + "dur": 337.86044100000004, + "dns": 0, + "tcp": 0, + "ttfb": 147.73899999999998, + "dll": 4.780635000000018 + } + ] + }, + "gpu01-whisper-9311": { + "name": "Whisper Large v3 Turbo (gpu01 #1)", + "link": "http://gpu01:9311", + "logs": [ + { + "t": 1748099643491, + "dur": 3.554265999999984, + "dns": 0, + "tcp": 0, + "ttfb": 1.0672079999999937, + "dll": 0.9303069999999707 + } + ] + }, + "gpu01-whisper-9312": { + "name": "Whisper Large v3 Turbo (gpu01 #2)", + "link": "http://gpu01:9312", + "logs": [ + { + "t": 1748099643496, + "dur": 1.8227679999999964, + "dns": 0, + "tcp": 0, + "ttfb": 0.38554499999997915, + "dll": 0.6364889999999832 + } + ] + }, + "gpu02-whisper-9311": { + "name": "Whisper Large v3 Turbo (gpu02 #1)", + "link": "http://gpu02:9311", + "logs": [ + { + "t": 1748099643500, + "dur": 2.493159999999989, + "dns": 0, + "tcp": 0, + "ttfb": 0.8190170000000307, + "dll": 0.5700539999999705 + } + ] + }, + "gpu02-whisper-9312": { + "name": "Whisper Large v3 Turbo (gpu02 #2)", + "link": "http://gpu02:9312", + "logs": [ + { + "t": 1748099643504, + "dur": 1.6794530000000236, + "dns": 0, + "tcp": 0, + "ttfb": 0.2926729999999793, + "dll": 0.4940589999999929 + } + ] + } + } + }, + "ollama": { + "name": "Ollama", + "endpoints": { + "gpu02-ollama-11434": { + "name": "Ollama (gpu02)", + "link": "http://gpu02:11434", + "logs": [ + { + "t": 1748099643507, + "dur": 1.5874860000000126, + "dns": 0, + "tcp": 0, + "ttfb": 0.3414589999999862, + "dll": 0.568977000000018 + } + ] + } + } + }, + "fish-speech": { + "name": "Fish Speech", + "endpoints": { + "gpu02-fishspeech-13000": { + "name": "Fish Speech (gpu02)", + "link": "http://gpu02:13000", + "logs": [ + { + "t": 1748099643509, + "dur": 2.1904840000000263, + "dns": 0, + "tcp": 0, + "ttfb": 0.7611540000000332, + "dll": 0.7002239999999915, + "err": "HTTP Status 404: Not Found" + } + ] + } + } + } + }, + "config": { + "interval": 15, + "nDataPoints": 90, + "responseTimeGood": 300, + "responseTimeWarning": 600 + }, + "ui": [ + [ + "traefik", + [ + "lb01-trefik-443" + ] + ], + [ + "litellm", + [ + "lb01-litellm-4000", + "lb01-litellm-15000" + ] + ], + [ + "whisper", + [ + "gpu01-whisper-12000", + "gpu01-whisper-9311", + "gpu01-whisper-9312", + "gpu02-whisper-9311", + "gpu02-whisper-9312" + ] + ], + [ + "ollama", + [ + "gpu02-ollama-11434" + ] + ], + [ + "fish-speech", + [ + "gpu02-fishspeech-13000" + ] + ] + ], + "lastPulse": 1748099643513 +} \ No newline at end of file diff --git a/watcher.js b/watcher.js index b2f5ab5..d08721d 100644 --- a/watcher.js +++ b/watcher.js @@ -2,14 +2,13 @@ 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; - statusFile = config.statusFile || './static/status.json'; - 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 delay = async t=>new Promise(r=>setTimeout(r, t)); @@ -176,7 +175,7 @@ while(true) { endpoint_.link = endpoint.link || endpoint.url; endpoint_.logs = endpoint_.logs || []; let start; - + try { performance.clearResourceTimings(); start = performance.now(); From fe99e3351c9cc12a1c5a1ef4a7240ebc83a8e07c Mon Sep 17 00:00:00 2001 From: pasha Date: Sat, 24 May 2025 18:28:28 +0300 Subject: [PATCH 3/7] Tunes of comments --- .dockerignore | 2 +- README.md | 4 ++-- config.dist.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index a9f1a1a..0169800 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,7 +2,7 @@ static/status.json .idea -config.js +#config.js docker-compose.yml apulse_data diff --git a/README.md b/README.md index 63bece6..a67f7d9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Visit https://apulse.ybouane.com for a demo! [aPulse — Server Status Open Source NodeJS Tool](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... @@ -30,7 +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 + 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 diff --git a/config.dist.js b/config.dist.js index 9790bf1..042f603 100644 --- a/config.dist.js +++ b/config.dist.js @@ -7,7 +7,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 + statusFile : './static/status.json', // Path to status.json file 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 From bc1b47aa6ef4d95e497ab19bc226d399a8c704d5 Mon Sep 17 00:00:00 2001 From: pasha Date: Sat, 24 May 2025 18:35:15 +0300 Subject: [PATCH 4/7] Tunes of relative paths --- docker-compose.dist.yml | 4 ++-- static/client.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.dist.yml b/docker-compose.dist.yml index c20e9ad..858f99c 100644 --- a/docker-compose.dist.yml +++ b/docker-compose.dist.yml @@ -4,7 +4,7 @@ services: build: context: . volumes: - - ./apulse_data:/app/static + - ./apulse_data:/app/data - ./config.js:/app/config.js restart: unless-stopped @@ -14,5 +14,5 @@ services: - ./apulse_data:/usr/share/nginx/html - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - - "80:80" + - "8080:80" restart: unless-stopped diff --git a/static/client.js b/static/client.js index 1759a52..7e88c1a 100644 --- a/static/client.js +++ b/static/client.js @@ -5,7 +5,7 @@ document.addEventListener("DOMContentLoaded", async () => { let $main = document.querySelector('main'); const refreshStatus = async () => { try { - const response = await fetch('./static/status.json', {cache: "no-cache"}); + const response = await fetch('./status.json', {cache: "no-cache"}); if (!response.ok) { throw new Error(`Error fetching status.json: ${response.statusText}`); } From 678b02c8c2b6cb1d3a69b964281632edd642a2be Mon Sep 17 00:00:00 2001 From: pasha Date: Sat, 24 May 2025 18:58:12 +0300 Subject: [PATCH 5/7] showLinks paramer added --- Dockerfile | 1 - config.dist.js | 1 + static/client.js | 2 +- watcher.js | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 986c3bc..a8213b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ FROM node:20-slim WORKDIR /app ADD . . -RUN ls -la RUN chmod +x /app/entrypoint.sh ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/config.dist.js b/config.dist.js index 042f603..4dfc044 100644 --- a/config.dist.js +++ b/config.dist.js @@ -8,6 +8,7 @@ export default { 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/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/watcher.js b/watcher.js index d08721d..50de430 100644 --- a/watcher.js +++ b/watcher.js @@ -138,6 +138,7 @@ while(true) { nDataPoints : config.nDataPoints, responseTimeGood : config.responseTimeGood, responseTimeWarning : config.responseTimeWarning, + showLinks : config.showLinks, }; status.ui = []; From c6f02d05b92cb34078d89dea7ed691e00db2da6b Mon Sep 17 00:00:00 2001 From: pasha Date: Sat, 24 May 2025 19:45:03 +0300 Subject: [PATCH 6/7] Brushup --- entrypoint.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 5065776..a2a1d4c 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -21,8 +21,4 @@ else echo "statusFile is in ./static; skipping copy." fi -ESC_PATH=$(printf '%s\n' "$STATUS_FILE" | sed 's/[&/]/\\&/g') - -sed -E "s|(fetch\(['\"])(\.\/status\.json)(['\"])|\1${ESC_PATH}\3|" -i static/client.js - exec node watcher.js From 179b167314995f05f802f52c33de9b5fdb02481b Mon Sep 17 00:00:00 2001 From: pasha Date: Sat, 24 May 2025 20:09:53 +0300 Subject: [PATCH 7/7] Brushup --- static/status.json | 305 --------------------------------------------- 1 file changed, 305 deletions(-) delete mode 100644 static/status.json diff --git a/static/status.json b/static/status.json deleted file mode 100644 index 8d72d57..0000000 --- a/static/status.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "sites": { - "google": { - "name": "Google", - "endpoints": { - "homepage": { - "name": "Homepage", - "link": "https://www.google.com", - "logs": [ - { - "t": 1748087741320, - "dur": 149.993578, - "dns": 0, - "tcp": 0, - "ttfb": 55.36531200000002, - "dll": 10.84602799999999, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748087757965, - "dur": 148.39908100000002, - "dns": 0, - "tcp": 0, - "ttfb": 52.42947699999999, - "dll": 17.866462000000013, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748088657969, - "dur": 114.20320800004993, - "dns": 0, - "tcp": 0, - "ttfb": 55.46503199997824, - "dll": 3.61165900004562, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748089557970, - "dur": 197.5670749999117, - "dns": 0, - "tcp": 0, - "ttfb": 63.359468000009656, - "dll": 79.4861919998657, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748090457973, - "dur": 103.08965699980035, - "dns": 0, - "tcp": 0, - "ttfb": 53.5293140001595, - "dll": 3.8372279996983707, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748091357976, - "dur": 138.84726900001988, - "dns": 0, - "tcp": 0, - "ttfb": 63.68227700004354, - "dll": 3.737294999882579, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748092257977, - "dur": 108.30177299957722, - "dns": 0, - "tcp": 0, - "ttfb": 57.426580000668764, - "dll": 3.4700069995597005, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748093157981, - "dur": 112.15920299943537, - "dns": 0, - "tcp": 0, - "ttfb": 60.26049399934709, - "dll": 4.292896999977529, - "err": "\"mustFind\" check failed" - }, - { - "t": 1748094057981, - "dur": 104.02492600027472, - "dns": 0, - "tcp": 0, - "ttfb": 53.809276999905705, - "dll": 3.9284429997205734, - "err": "\"mustFind\" check failed" - } - ] - } - } - }, - "traefik": { - "name": "Traefik", - "endpoints": { - "lb01-trefik-443": { - "name": "Reverse Proxy", - "link": "https://lb01", - "logs": [ - { - "t": 1748099643036, - "err": "TypeError: fetch failed", - "dur": 94.937342, - "ttfb": 94.937342 - } - ] - } - } - }, - "litellm": { - "name": "LiteLLM", - "endpoints": { - "lb01-litellm-4000": { - "name": "LiteLLM / API", - "link": "http://lb01:4000", - "logs": [ - { - "t": 1748099643132, - "dur": 12.401092000000006, - "dns": 0, - "tcp": 0, - "ttfb": 3.7235589999999945, - "dll": 4.344725000000011 - } - ] - }, - "lb01-litellm-15000": { - "name": "LiteLLM / GigaChat Adapter", - "link": "http://lb01:15000", - "logs": [ - { - "t": 1748099643146, - "dur": 4.7646939999999915, - "dns": 0, - "tcp": 0, - "ttfb": 2.1338580000000036, - "dll": 0.6840509999999824, - "err": "HTTP Status 404: Not Found" - } - ] - } - } - }, - "whisper": { - "name": "Whisper", - "endpoints": { - "gpu01-whisper-12000": { - "name": "Whisper / Load Balancer", - "link": "http://gpu01:12000", - "logs": [ - { - "t": 1748099643153, - "dur": 337.86044100000004, - "dns": 0, - "tcp": 0, - "ttfb": 147.73899999999998, - "dll": 4.780635000000018 - } - ] - }, - "gpu01-whisper-9311": { - "name": "Whisper Large v3 Turbo (gpu01 #1)", - "link": "http://gpu01:9311", - "logs": [ - { - "t": 1748099643491, - "dur": 3.554265999999984, - "dns": 0, - "tcp": 0, - "ttfb": 1.0672079999999937, - "dll": 0.9303069999999707 - } - ] - }, - "gpu01-whisper-9312": { - "name": "Whisper Large v3 Turbo (gpu01 #2)", - "link": "http://gpu01:9312", - "logs": [ - { - "t": 1748099643496, - "dur": 1.8227679999999964, - "dns": 0, - "tcp": 0, - "ttfb": 0.38554499999997915, - "dll": 0.6364889999999832 - } - ] - }, - "gpu02-whisper-9311": { - "name": "Whisper Large v3 Turbo (gpu02 #1)", - "link": "http://gpu02:9311", - "logs": [ - { - "t": 1748099643500, - "dur": 2.493159999999989, - "dns": 0, - "tcp": 0, - "ttfb": 0.8190170000000307, - "dll": 0.5700539999999705 - } - ] - }, - "gpu02-whisper-9312": { - "name": "Whisper Large v3 Turbo (gpu02 #2)", - "link": "http://gpu02:9312", - "logs": [ - { - "t": 1748099643504, - "dur": 1.6794530000000236, - "dns": 0, - "tcp": 0, - "ttfb": 0.2926729999999793, - "dll": 0.4940589999999929 - } - ] - } - } - }, - "ollama": { - "name": "Ollama", - "endpoints": { - "gpu02-ollama-11434": { - "name": "Ollama (gpu02)", - "link": "http://gpu02:11434", - "logs": [ - { - "t": 1748099643507, - "dur": 1.5874860000000126, - "dns": 0, - "tcp": 0, - "ttfb": 0.3414589999999862, - "dll": 0.568977000000018 - } - ] - } - } - }, - "fish-speech": { - "name": "Fish Speech", - "endpoints": { - "gpu02-fishspeech-13000": { - "name": "Fish Speech (gpu02)", - "link": "http://gpu02:13000", - "logs": [ - { - "t": 1748099643509, - "dur": 2.1904840000000263, - "dns": 0, - "tcp": 0, - "ttfb": 0.7611540000000332, - "dll": 0.7002239999999915, - "err": "HTTP Status 404: Not Found" - } - ] - } - } - } - }, - "config": { - "interval": 15, - "nDataPoints": 90, - "responseTimeGood": 300, - "responseTimeWarning": 600 - }, - "ui": [ - [ - "traefik", - [ - "lb01-trefik-443" - ] - ], - [ - "litellm", - [ - "lb01-litellm-4000", - "lb01-litellm-15000" - ] - ], - [ - "whisper", - [ - "gpu01-whisper-12000", - "gpu01-whisper-9311", - "gpu01-whisper-9312", - "gpu02-whisper-9311", - "gpu02-whisper-9312" - ] - ], - [ - "ollama", - [ - "gpu02-ollama-11434" - ] - ], - [ - "fish-speech", - [ - "gpu02-fishspeech-13000" - ] - ] - ], - "lastPulse": 1748099643513 -} \ No newline at end of file