docs: add project documentation, logging config, status script
- docs/VEZA_PROJECT_DOCUMENTATION.md - config/logging.toml - status.sh utility script Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
20a16f7cbe
commit
d5bfe4a558
3 changed files with 1533 additions and 0 deletions
150
config/logging.toml
Normal file
150
config/logging.toml
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# VEZA — Centralized Logging Configuration
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# Single source of truth for ALL service logging.
|
||||
# Read by: Go backend, Rust stream server, Frontend (via env vars)
|
||||
#
|
||||
# Override any value via environment variable:
|
||||
# LOG_LEVEL=DEBUG LOG_DIR=/tmp/veza-logs make dev
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ── Global defaults ──────────────────────────────────────────────────────────
|
||||
|
||||
[global]
|
||||
# Log level: TRACE, DEBUG, INFO, WARN, ERROR (case-insensitive, Go & Rust compatible)
|
||||
level = "INFO"
|
||||
|
||||
# Base directory for all log files
|
||||
# Created automatically with 0755 permissions if it doesn't exist.
|
||||
# Fallback: ./logs (if /var/log/veza is not writable)
|
||||
dir = "/var/log/veza"
|
||||
|
||||
# Format: "json" (production/staging) or "text" (development)
|
||||
# Auto-detected from APP_ENV if not set: production/staging → json, else → text
|
||||
format = "auto"
|
||||
|
||||
# ── File rotation (lumberjack for Go, tracing-appender for Rust) ─────────────
|
||||
|
||||
[rotation]
|
||||
# Maximum size of a single log file before rotation (megabytes)
|
||||
max_size_mb = 100
|
||||
|
||||
# Maximum number of rotated files to retain
|
||||
max_backups = 10
|
||||
|
||||
# Maximum age of rotated files before deletion (days)
|
||||
max_age_days = 30
|
||||
|
||||
# Compress rotated files with gzip
|
||||
compress = true
|
||||
|
||||
# Rust rotation strategy: "hourly" or "daily"
|
||||
# Hourly if max_size_mb ≤ 100, daily otherwise
|
||||
rust_rotation = "hourly"
|
||||
|
||||
# Maximum rotated files for Rust (tracing-appender)
|
||||
rust_max_files = 5
|
||||
|
||||
# ── Go Backend API ───────────────────────────────────────────────────────────
|
||||
|
||||
[backend]
|
||||
# Module name — determines log file names: {module}.log, {module}-error.log
|
||||
module = "backend-api"
|
||||
|
||||
# Additional module loggers (each gets its own .log + -error.log pair)
|
||||
# These log database queries, redis commands, rabbitmq events separately
|
||||
modules = ["db", "rabbitmq"]
|
||||
|
||||
# Slow request detection threshold (milliseconds)
|
||||
# Requests slower than this are logged at WARN level
|
||||
slow_request_threshold_ms = 1000
|
||||
|
||||
# Sampling (production only — prevents log spam under high load)
|
||||
# Initial: first N messages per second are always logged
|
||||
# Thereafter: 1 in N subsequent messages are logged
|
||||
sampling_initial = 100
|
||||
sampling_thereafter = 100
|
||||
|
||||
# Async buffered writes
|
||||
# Buffer size in KB — reduces syscalls by batching writes
|
||||
buffer_size_kb = 256
|
||||
|
||||
# Flush interval in milliseconds — maximum delay before buffer is flushed
|
||||
flush_interval_ms = 100
|
||||
|
||||
# ── Rust Stream Server ───────────────────────────────────────────────────────
|
||||
|
||||
[stream]
|
||||
# Module name — determines log file prefix: {module}.YYYY-MM-DD-HH
|
||||
module = "stream"
|
||||
|
||||
# Include source file and line number in log entries
|
||||
include_source = true
|
||||
|
||||
# Include thread IDs in log entries
|
||||
include_thread_ids = true
|
||||
|
||||
# ── Frontend (React) ────────────────────────────────────────────────────────
|
||||
|
||||
[frontend]
|
||||
# Frontend log level (VITE_LOG_LEVEL)
|
||||
# Defaults: DEBUG in development, WARN in production
|
||||
level = "auto"
|
||||
|
||||
# Backend endpoint for forwarding frontend errors
|
||||
# Uses navigator.sendBeacon() for non-blocking delivery
|
||||
endpoint = "/api/v1/logs/frontend"
|
||||
|
||||
# Enable Sentry error tracking (requires VITE_SENTRY_DSN env var)
|
||||
sentry_enabled = false
|
||||
|
||||
# ── Log Aggregation (optional — Loki/Grafana) ───────────────────────────────
|
||||
|
||||
[aggregation]
|
||||
# Enable centralized log aggregation (Loki-compatible)
|
||||
enabled = false
|
||||
|
||||
# Aggregation endpoint URL
|
||||
# endpoint = "http://loki:3100/loki/api/v1/push"
|
||||
|
||||
# Batch size — number of log entries per HTTP push
|
||||
batch_size = 100
|
||||
|
||||
# Flush interval (seconds) — maximum delay between pushes
|
||||
flush_interval_s = 5
|
||||
|
||||
# HTTP timeout for push requests (seconds)
|
||||
timeout_s = 10
|
||||
|
||||
# Static labels applied to all log entries
|
||||
# labels = "app=veza,env=production"
|
||||
|
||||
# ── File permissions ─────────────────────────────────────────────────────────
|
||||
|
||||
[permissions]
|
||||
# Directory permissions (octal)
|
||||
dir_mode = "0755"
|
||||
|
||||
# File permissions (octal) — Go files
|
||||
file_mode = "0640"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Environment Variable Overrides (highest priority)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# LOG_LEVEL → global.level
|
||||
# LOG_DIR → global.dir
|
||||
# LOG_FORMAT → global.format
|
||||
# SLOW_REQUEST_THRESHOLD_MS → backend.slow_request_threshold_ms
|
||||
# LOG_AGGREGATION_ENABLED → aggregation.enabled
|
||||
# LOG_AGGREGATION_ENDPOINT → aggregation.endpoint
|
||||
# LOG_AGGREGATION_BATCH_SIZE → aggregation.batch_size
|
||||
# LOG_AGGREGATION_FLUSH_INTERVAL → aggregation.flush_interval_s
|
||||
# LOG_AGGREGATION_TIMEOUT → aggregation.timeout_s
|
||||
# LOG_AGGREGATION_LABELS → aggregation.labels
|
||||
# VITE_LOG_LEVEL → frontend.level
|
||||
# VITE_SENTRY_DSN → frontend.sentry_enabled (if set)
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
1059
docs/VEZA_PROJECT_DOCUMENTATION.md
Normal file
1059
docs/VEZA_PROJECT_DOCUMENTATION.md
Normal file
File diff suppressed because it is too large
Load diff
324
status.sh
Executable file
324
status.sh
Executable file
|
|
@ -0,0 +1,324 @@
|
|||
#!/usr/bin/env bash
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# VEZA — Project Status Dashboard
|
||||
# Usage: ./status.sh (full report)
|
||||
# ./status.sh --quick (infra + services only, no tests)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
set -uo pipefail
|
||||
|
||||
# ── Colors & symbols ─────────────────────────────────────────────────────────
|
||||
R=$'\033[0;31m' G=$'\033[0;32m' Y=$'\033[0;33m' B=$'\033[0;34m'
|
||||
M=$'\033[0;35m' C=$'\033[0;36m' W=$'\033[1;37m' D=$'\033[0;90m'
|
||||
NC=$'\033[0m' BOLD=$'\033[1m' DIM=$'\033[2m'
|
||||
|
||||
OK="${G}✓${NC}" FAIL="${R}✗${NC}" WARN="${Y}⚠${NC}" SKIP="${D}○${NC}"
|
||||
SPIN="${C}…${NC}"
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
QUICK=false
|
||||
[[ "${1:-}" == "--quick" || "${1:-}" == "-q" ]] && QUICK=true
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
hr() { echo -e "${D}$(printf '─%.0s' {1..72})${NC}"; }
|
||||
hdr() { echo -e "\n${BOLD}${C}▸ $1${NC}"; hr; }
|
||||
ok() { echo -e " ${OK} $1"; }
|
||||
ko() { echo -e " ${FAIL} $1"; }
|
||||
wr() { echo -e " ${WARN} $1"; }
|
||||
sk() { echo -e " ${SKIP} ${D}$1${NC}"; }
|
||||
kv() { printf " %-24s " "$1"; echo -e "$2"; }
|
||||
|
||||
check_port() {
|
||||
local port="$1"
|
||||
if ss -tlnp 2>/dev/null | grep -q ":${port} " || nc -z 127.0.0.1 "$port" 2>/dev/null; then
|
||||
echo "up"
|
||||
else
|
||||
echo "down"
|
||||
fi
|
||||
}
|
||||
|
||||
http_check() {
|
||||
local url="$1" timeout="${2:-3}"
|
||||
curl -s -o /dev/null -w '%{http_code}' --max-time "$timeout" "$url" 2>/dev/null || echo "000"
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# HEADER
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
echo
|
||||
echo -e "${BOLD}${M} ╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BOLD}${M} ║ VEZA — Project Status Dashboard ║${NC}"
|
||||
echo -e "${BOLD}${M} ╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo -e " ${D}$(date '+%Y-%m-%d %H:%M:%S') • $(hostname)${NC}"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# GIT & VERSION
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
hdr "GIT & VERSION"
|
||||
|
||||
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
|
||||
commit=$(git rev-parse --short HEAD 2>/dev/null || echo "?")
|
||||
commit_msg=$(git log -1 --format='%s' 2>/dev/null | head -c 60 || echo "")
|
||||
tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
|
||||
ahead=$(git rev-list "${tag}..HEAD" --count 2>/dev/null || echo "?")
|
||||
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
kv "Branch:" "${W}${branch}${NC}"
|
||||
kv "Latest tag:" "${G}${tag}${NC} ${D}(+${ahead} commits)${NC}"
|
||||
kv "HEAD:" "${commit} — ${DIM}${commit_msg}${NC}"
|
||||
|
||||
if [[ "$dirty" -gt 0 ]]; then
|
||||
staged=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
|
||||
unstaged=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
|
||||
untracked=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ')
|
||||
kv "Working tree:" "${Y}${dirty} changes${NC} ${D}(staged:${staged} modified:${unstaged} untracked:${untracked})${NC}"
|
||||
else
|
||||
kv "Working tree:" "${G}clean${NC}"
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# TOOLCHAIN
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
hdr "TOOLCHAIN"
|
||||
|
||||
go_ver=$(go version 2>/dev/null | awk '{print $3}' | sed 's/go//' || echo "not installed")
|
||||
rust_ver=$(rustc --version 2>/dev/null | awk '{print $2}' || echo "not installed")
|
||||
node_ver=$(node --version 2>/dev/null | sed 's/v//' || echo "not installed")
|
||||
npm_ver=$(npm --version 2>/dev/null || echo "?")
|
||||
docker_ver=$(docker --version 2>/dev/null | awk '{print $3}' | tr -d ',' || echo "not installed")
|
||||
|
||||
kv "Go:" "${go_ver}"
|
||||
kv "Rust:" "${rust_ver}"
|
||||
kv "Node:" "${node_ver} ${D}(npm ${npm_ver})${NC}"
|
||||
kv "Docker:" "${docker_ver}"
|
||||
|
||||
air_ok=$(command -v air >/dev/null 2>&1 && echo "${G}yes${NC}" || echo "${D}no${NC}")
|
||||
cargo_watch_ok=$(command -v cargo-watch >/dev/null 2>&1 && echo "${G}yes${NC}" || echo "${D}no${NC}")
|
||||
kv "Hot reload:" "air=${air_ok} cargo-watch=${cargo_watch_ok}"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# DOCKER INFRASTRUCTURE
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
hdr "DOCKER INFRASTRUCTURE"
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
ko "Docker daemon is not running"
|
||||
else
|
||||
for name in postgres redis rabbitmq clamav minio elasticsearch; do
|
||||
container="veza_${name}"
|
||||
status=$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || echo "absent")
|
||||
health=$(docker inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null || echo "n/a")
|
||||
label=$(printf '%-16s' "$name")
|
||||
|
||||
case "$status" in
|
||||
running)
|
||||
if [[ "$health" == "healthy" ]]; then
|
||||
ok "${label} ${G}running${NC} ${G}healthy${NC}"
|
||||
elif [[ "$health" == "unhealthy" ]]; then
|
||||
wr "${label} ${G}running${NC} ${R}unhealthy${NC}"
|
||||
else
|
||||
ok "${label} ${G}running${NC} ${D}(no healthcheck)${NC}"
|
||||
fi ;;
|
||||
exited) ko "${label} ${R}exited${NC}" ;;
|
||||
*) sk "${label} ${status}" ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# APPLICATION SERVICES
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
hdr "APPLICATION SERVICES"
|
||||
|
||||
declare -A SVC_PORTS=( [backend-api]=18080 [stream-server]=18082 [frontend]=5173 )
|
||||
declare -A SVC_HEALTH=( [backend-api]="/api/v1/health" [stream-server]="/health" [frontend]="/" )
|
||||
|
||||
for svc in backend-api stream-server frontend; do
|
||||
port="${SVC_PORTS[$svc]}"
|
||||
endpoint="${SVC_HEALTH[$svc]}"
|
||||
url="http://localhost:${port}${endpoint}"
|
||||
label=$(printf '%-16s' "$svc")
|
||||
|
||||
state=$(check_port "$port")
|
||||
if [[ "$state" == "up" ]]; then
|
||||
code=$(http_check "$url" 3)
|
||||
if [[ "$code" =~ ^2 ]]; then
|
||||
ok "${label} ${G}:${port}${NC} HTTP ${G}${code}${NC}"
|
||||
elif [[ "$code" == "000" ]]; then
|
||||
wr "${label} ${Y}:${port}${NC} ${Y}port open, no HTTP${NC}"
|
||||
else
|
||||
wr "${label} ${G}:${port}${NC} HTTP ${Y}${code}${NC}"
|
||||
fi
|
||||
else
|
||||
ko "${label} ${R}:${port} not listening${NC}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Rate limiting probe (only if backend is up)
|
||||
if [[ "$(check_port 18080)" == "up" ]]; then
|
||||
got429=false
|
||||
for _ in $(seq 1 8); do
|
||||
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 \
|
||||
-X POST http://localhost:18080/api/v1/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"probe@test.invalid","password":"x"}' 2>/dev/null) || c="000"
|
||||
[[ "$c" == "429" ]] && got429=true && break
|
||||
done
|
||||
if $got429; then
|
||||
kv "Rate limiting:" "${Y}ACTIVE${NC} — use ${W}make dev-e2e${NC} for tests"
|
||||
else
|
||||
kv "Rate limiting:" "${G}disabled (test mode)${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# DATABASE
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
hdr "DATABASE"
|
||||
|
||||
pg_port="${PORT_POSTGRES:-15432}"
|
||||
if [[ "$(check_port "$pg_port")" == "up" ]]; then
|
||||
pg_url="postgres://veza:${DB_PASSWORD:-password}@localhost:${pg_port}/veza?sslmode=disable"
|
||||
|
||||
tbl_count=$(psql "$pg_url" -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema='public'" 2>/dev/null | tr -d ' ' || echo "?")
|
||||
kv "Tables:" "${tbl_count}"
|
||||
|
||||
mig_count=$(find veza-backend-api/migrations -maxdepth 1 -name '*.sql' ! -name '*_down*' 2>/dev/null | wc -l | tr -d ' ')
|
||||
latest_mig=$(find veza-backend-api/migrations -maxdepth 1 -name '*.sql' ! -name '*_down*' -printf '%f\n' 2>/dev/null | sort -n | tail -1 || echo "none")
|
||||
kv "Migrations:" "${mig_count} files — latest: ${D}${latest_mig}${NC}"
|
||||
|
||||
for tbl in users tracks playlists products; do
|
||||
cnt=$(psql "$pg_url" -t -c "SELECT count(*) FROM ${tbl}" 2>/dev/null | tr -d ' ' || echo "—")
|
||||
kv " ${tbl}:" "${cnt} rows"
|
||||
done
|
||||
else
|
||||
sk "PostgreSQL not reachable on :${pg_port}"
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# CODEBASE STATS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
hdr "CODEBASE"
|
||||
|
||||
go_files=$(find veza-backend-api -name '*.go' ! -path '*/vendor/*' 2>/dev/null | wc -l | tr -d ' ')
|
||||
rs_files=$(find veza-stream-server -name '*.rs' 2>/dev/null | wc -l | tr -d ' ')
|
||||
ts_files=$(find apps/web/src \( -name '*.ts' -o -name '*.tsx' \) 2>/dev/null | wc -l | tr -d ' ')
|
||||
e2e_files=$(find tests/e2e -name '*.spec.ts' 2>/dev/null | wc -l | tr -d ' ')
|
||||
e2e_tests=$(grep -rch "test(" tests/e2e/*.spec.ts 2>/dev/null | awk '{s+=$1}END{print s+0}')
|
||||
|
||||
kv "Go (backend):" "${go_files} files"
|
||||
kv "Rust (stream):" "${rs_files} files"
|
||||
kv "TS/TSX (web):" "${ts_files} files"
|
||||
kv "E2E specs:" "${e2e_files} files — ${W}~${e2e_tests} tests${NC}"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# TESTS (skip with --quick)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
if $QUICK; then
|
||||
hdr "TESTS ${D}(skipped — run without --quick)${NC}"
|
||||
else
|
||||
hdr "TESTS"
|
||||
|
||||
# ── Go tests ──
|
||||
echo -ne " ${SPIN} Running Go tests…\r"
|
||||
go_out=$(cd veza-backend-api && go test ./internal/handlers/... ./internal/services/... -short -count=1 -timeout 60s 2>&1) || true
|
||||
go_pass=$(echo "$go_out" | grep -c '^ok' || true)
|
||||
go_fail=$(echo "$go_out" | grep -c '^FAIL' || true)
|
||||
go_skip=$(echo "$go_out" | grep -c '\[no test files\]' || true)
|
||||
printf '\r\033[2K'
|
||||
if [[ "$go_fail" -eq 0 ]]; then
|
||||
ok "Go: ${G}${go_pass} passed${NC} ${D}${go_skip} skipped${NC}"
|
||||
else
|
||||
ko "Go: ${G}${go_pass} passed${NC} ${R}${go_fail} FAILED${NC} ${D}${go_skip} skipped${NC}"
|
||||
fi
|
||||
|
||||
# ── Rust tests ──
|
||||
echo -ne " ${SPIN} Running Rust tests…\r"
|
||||
rust_out=$(cd veza-stream-server && cargo test --lib -q 2>&1) || true
|
||||
rust_summary=$(echo "$rust_out" | grep -E 'test result:' | head -1 || echo "")
|
||||
printf '\r\033[2K'
|
||||
if echo "$rust_summary" | grep -q "0 failed" 2>/dev/null; then
|
||||
ok "Rust: ${G}${rust_summary}${NC}"
|
||||
elif [[ -n "$rust_summary" ]]; then
|
||||
ko "Rust: ${R}${rust_summary}${NC}"
|
||||
else
|
||||
rust_pass=$(echo "$rust_out" | grep -oP '\d+ passed' | head -1 || echo "? passed")
|
||||
ok "Rust: ${G}${rust_pass}${NC}"
|
||||
fi
|
||||
|
||||
# ── Frontend unit tests ──
|
||||
echo -ne " ${SPIN} Running frontend tests…\r"
|
||||
web_out=$(cd apps/web && npx vitest run --reporter=verbose 2>&1 | tail -30) || true
|
||||
web_summary=$(echo "$web_out" | grep -iE 'Tests\s+\d|test files' | tail -1 || echo "")
|
||||
printf '\r\033[2K'
|
||||
if [[ -n "$web_summary" ]]; then
|
||||
if echo "$web_summary" | grep -qi "failed"; then
|
||||
ko "Web: ${web_summary}"
|
||||
else
|
||||
ok "Web: ${web_summary}"
|
||||
fi
|
||||
else
|
||||
sk "Web: could not parse results"
|
||||
fi
|
||||
|
||||
# ── E2E last run ──
|
||||
last_run="tests/e2e/test-results/.last-run.json"
|
||||
if [[ -f "$last_run" ]]; then
|
||||
pw_status=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('status','?'))" "$last_run" 2>/dev/null || echo "?")
|
||||
kv "E2E last run:" "${pw_status}"
|
||||
else
|
||||
sk "E2E: no previous run — use ${W}npm run e2e${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# VERSIONS ROADMAP
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
hdr "ROADMAP"
|
||||
|
||||
roadmap="VEZA_VERSIONS_ROADMAP.md"
|
||||
if [[ -f "$roadmap" ]]; then
|
||||
done_count=$(grep -c '✅ DONE' "$roadmap" || true)
|
||||
todo_count=$(grep -c '⏳ TODO' "$roadmap" || true)
|
||||
progress_count=$(grep -cE '🔄|🔨' "$roadmap" || true)
|
||||
|
||||
kv "Done:" "${G}${done_count}${NC} versions"
|
||||
[[ "$progress_count" -gt 0 ]] && kv "In progress:" "${Y}${progress_count}${NC} versions"
|
||||
[[ "$todo_count" -gt 0 ]] && kv "Todo:" "${D}${todo_count}${NC} versions"
|
||||
|
||||
# Extract next TODO version name (format: "## vX.Y.Z — Name")
|
||||
next_line=$(grep -B5 '⏳ TODO' "$roadmap" | grep -oP 'v\d+\.\d+\.\d+\s*[—–-]\s*.*' | head -1 || true)
|
||||
[[ -n "$next_line" ]] && kv "Next up:" "${W}${next_line}${NC}"
|
||||
else
|
||||
sk "Roadmap file not found"
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# HEALTH SUMMARY
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
echo
|
||||
hr
|
||||
|
||||
total_ok=0; total_ko=0
|
||||
|
||||
for svc in postgres redis rabbitmq; do
|
||||
container="veza_${svc}"
|
||||
s=$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || echo "absent")
|
||||
h=$(docker inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null || echo "n/a")
|
||||
if [[ "$s" == "running" && "$h" == "healthy" ]]; then ((total_ok++)); else ((total_ko++)) || true; fi
|
||||
done
|
||||
|
||||
for port in 18080 18082 5173; do
|
||||
if [[ "$(check_port "$port")" == "up" ]]; then ((total_ok++)); else ((total_ko++)) || true; fi
|
||||
done
|
||||
|
||||
if [[ "$total_ko" -eq 0 ]]; then
|
||||
echo -e " ${BOLD}${G}ALL SYSTEMS OPERATIONAL${NC} (${total_ok}/6 services up)"
|
||||
elif [[ "$total_ko" -le 2 ]]; then
|
||||
echo -e " ${BOLD}${Y}PARTIAL${NC} — ${total_ok} up, ${total_ko} down"
|
||||
else
|
||||
echo -e " ${BOLD}${R}DEGRADED${NC} — ${total_ok} up, ${total_ko} down"
|
||||
fi
|
||||
|
||||
echo
|
||||
Loading…
Reference in a new issue