feat(geoportal): N8N webhook on sync completion + tile cache monitoring
- weekend-deep-sync.ts: fire webhook to N8N_WEBHOOK_URL after each sync cycle (N8N triggers tippecanoe PMTiles rebuild via SSH on host) - nginx tile-cache: add stub_status at /status, custom log format with cache status - Add tile-cache-stats.sh: shows HIT/MISS ratio, cache size, slow tiles - docker-compose: add N8N_WEBHOOK_URL env var Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,8 @@ services:
|
|||||||
- NOTIFICATION_CRON_SECRET=1547a198feca43af6c05622588c6d3b820bad5163b8c20175b2b5bbf8fc1a987
|
- NOTIFICATION_CRON_SECRET=1547a198feca43af6c05622588c6d3b820bad5163b8c20175b2b5bbf8fc1a987
|
||||||
# Weekend Deep Sync email reports (comma-separated for multiple recipients)
|
# Weekend Deep Sync email reports (comma-separated for multiple recipients)
|
||||||
- WEEKEND_SYNC_EMAIL=${WEEKEND_SYNC_EMAIL:-}
|
- WEEKEND_SYNC_EMAIL=${WEEKEND_SYNC_EMAIL:-}
|
||||||
|
# N8N webhook — triggers PMTiles rebuild after sync cycle
|
||||||
|
- N8N_WEBHOOK_URL=${N8N_WEBHOOK_URL:-}
|
||||||
# Portal-only users (comma-separated, redirected to /portal)
|
# Portal-only users (comma-separated, redirected to /portal)
|
||||||
- PORTAL_ONLY_USERS=dtiurbe,d.tiurbe
|
- PORTAL_ONLY_USERS=dtiurbe,d.tiurbe
|
||||||
# Address Book API (inter-service auth for external tools)
|
# Address Book API (inter-service auth for external tools)
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ proxy_cache_path /var/cache/nginx/tiles
|
|||||||
inactive=7d
|
inactive=7d
|
||||||
use_temp_path=off;
|
use_temp_path=off;
|
||||||
|
|
||||||
|
# Log format with cache status for monitoring (docker logs tile-cache | grep HIT/MISS)
|
||||||
|
log_format tiles '$remote_addr [$time_local] "$request" $status '
|
||||||
|
'cache=$upstream_cache_status size=$body_bytes_sent '
|
||||||
|
'time=$request_time';
|
||||||
|
|
||||||
server {
|
server {
|
||||||
|
access_log /var/log/nginx/access.log tiles;
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
@@ -19,6 +25,12 @@ server {
|
|||||||
add_header Content-Type text/plain;
|
add_header Content-Type text/plain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# nginx status (active connections, request counts) — for monitoring
|
||||||
|
location = /status {
|
||||||
|
access_log off;
|
||||||
|
stub_status on;
|
||||||
|
}
|
||||||
|
|
||||||
# Martin catalog endpoint (no cache)
|
# Martin catalog endpoint (no cache)
|
||||||
location = /catalog {
|
location = /catalog {
|
||||||
proxy_pass http://martin:3000/catalog;
|
proxy_pass http://martin:3000/catalog;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# tile-cache-stats.sh — Show tile cache hit/miss statistics
|
||||||
|
# Usage: ./scripts/tile-cache-stats.sh [MINUTES]
|
||||||
|
# Reads recent nginx logs from tile-cache container.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MINUTES="${1:-60}"
|
||||||
|
|
||||||
|
echo "=== Tile Cache Stats (last ${MINUTES}min) ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get nginx status (active connections)
|
||||||
|
echo "--- Connections ---"
|
||||||
|
curl -s "http://10.10.10.166:3010/status" 2>/dev/null || echo "(status endpoint unavailable)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Parse recent logs for cache hit/miss ratio
|
||||||
|
echo "--- Cache Performance ---"
|
||||||
|
docker logs tile-cache --since "${MINUTES}m" 2>/dev/null | \
|
||||||
|
grep -oP 'cache=\K\w+' | sort | uniq -c | sort -rn || echo "(no logs in timeframe)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- Cache Size ---"
|
||||||
|
docker exec tile-cache du -sh /var/cache/nginx/tiles/ 2>/dev/null || echo "(cannot read cache dir)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- Slowest Tiles (>1s) ---"
|
||||||
|
docker logs tile-cache --since "${MINUTES}m" 2>/dev/null | \
|
||||||
|
grep -oP 'time=\K[0-9.]+' | awk '$1 > 1.0 {print $1"s"}' | sort -rn | head -5 || echo "(none)"
|
||||||
@@ -401,6 +401,8 @@ export async function runWeekendDeepSync(): Promise<void> {
|
|||||||
console.log(
|
console.log(
|
||||||
`[weekend-sync] Ciclu complet #${state.completedCycles}! Reset pentru urmatorul ciclu.`,
|
`[weekend-sync] Ciclu complet #${state.completedCycles}! Reset pentru urmatorul ciclu.`,
|
||||||
);
|
);
|
||||||
|
// Notify N8N to rebuild PMTiles (overview tiles for geoportal)
|
||||||
|
await fireSyncWebhook(state.completedCycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
await saveState(state);
|
await saveState(state);
|
||||||
@@ -534,3 +536,28 @@ async function sendStatusEmail(
|
|||||||
console.warn(`[weekend-sync] Nu s-a putut trimite email: ${msg}`);
|
console.warn(`[weekend-sync] Nu s-a putut trimite email: ${msg}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* N8N Webhook — trigger PMTiles rebuild after sync cycle */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
async function fireSyncWebhook(cycle: number): Promise<void> {
|
||||||
|
const url = process.env.N8N_WEBHOOK_URL;
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
event: "weekend-sync-cycle-complete",
|
||||||
|
cycle,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
console.log(`[weekend-sync] Webhook trimis la N8N (ciclu #${cycle})`);
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
console.warn(`[weekend-sync] Webhook N8N esuat: ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user