mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-08 18:43:37 +08:00
The security-signaling surface failed open at three layers — every failure
mode read as a reassuring "0 attacks" / "0 installs":
- community-pulse edge function: supabase-js returns {data,error} without
throwing, and all five queries discarded `error` — a DB outage produced
real-looking zeros via the SUCCESS path, and the catch (also returning
zeros with HTTP 200) was unreachable for query failures. Every query now
destructures and throws; the catch serves the stale cache (marked
"stale": true) when one exists, else 503 {"error":"pulse_unavailable"}.
Success responses carry "status":"ok" so clients can distinguish
authoritative data from legacy backends. NOTE: the edge function deploys
out-of-band (supabase functions deploy community-pulse).
- gstack-security-dashboard: captures the HTTP status; non-200 / network
failure / error body / missing section → "unknown — backend error";
jq missing → "unknown — install jq" (the lossy grep fallback broke on
nested arrays and under-reported attacks as zero — removed); a 200
without the new marker shows figures with an "unverified (legacy
backend)" note. Also fixes a latent display bug: the TOTAL grep matched
the digit 7 inside "attacks_last_7_days" and misreported every count.
- gstack-community-dashboard: same class — curl || echo "{}" plus
grep || echo "0" printed "Weekly active installs: 0" on any failure.
Now "unknown — backend error (HTTP N)".
test/security-dashboard-fallback.test.ts pins the matrix (200+marker,
200-legacy, 503, network failure) x (jq present, jq absent) for both bins:
"unknown" states never render as 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
5.1 KiB
Bash
Executable File
121 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# gstack-community-dashboard — community usage stats from Supabase
|
|
#
|
|
# Calls the community-pulse edge function for aggregated stats:
|
|
# skill popularity, crash clusters, version distribution, retention.
|
|
#
|
|
# Env overrides (for testing):
|
|
# GSTACK_DIR — override auto-detected gstack root
|
|
# GSTACK_SUPABASE_URL — override Supabase project URL
|
|
# GSTACK_SUPABASE_ANON_KEY — override Supabase anon key
|
|
set -uo pipefail
|
|
|
|
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
|
|
# Source Supabase config if not overridden by env
|
|
if [ -z "${GSTACK_SUPABASE_URL:-}" ] && [ -f "$GSTACK_DIR/supabase/config.sh" ]; then
|
|
. "$GSTACK_DIR/supabase/config.sh"
|
|
fi
|
|
SUPABASE_URL="${GSTACK_SUPABASE_URL:-}"
|
|
ANON_KEY="${GSTACK_SUPABASE_ANON_KEY:-}"
|
|
|
|
if [ -z "$SUPABASE_URL" ] || [ -z "$ANON_KEY" ]; then
|
|
echo "gstack community dashboard"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "Supabase not configured yet. The community dashboard will be"
|
|
echo "available once the gstack Supabase project is set up."
|
|
echo ""
|
|
echo "For local analytics, run: gstack-analytics"
|
|
exit 0
|
|
fi
|
|
|
|
# ─── Fetch aggregated stats from edge function ────────────────
|
|
# HTTP status captured (#1947): a backend failure must read as "unknown",
|
|
# never as a healthy "Weekly active installs: 0".
|
|
TMPBODY="$(mktemp)"
|
|
trap 'rm -f "$TMPBODY"' EXIT
|
|
HTTP_CODE="$(curl -s --max-time 15 -w '%{http_code}' -o "$TMPBODY" \
|
|
"${SUPABASE_URL}/functions/v1/community-pulse" \
|
|
-H "apikey: ${ANON_KEY}" \
|
|
2>/dev/null || echo "000")"
|
|
DATA="$(cat "$TMPBODY" 2>/dev/null || echo "")"
|
|
|
|
echo "gstack community dashboard"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
|
|
if [ "$HTTP_CODE" != "200" ] || [ -z "$DATA" ] || ! printf '%s' "$DATA" | grep -q '"weekly_active"'; then
|
|
echo "Community stats: unknown — backend error (HTTP ${HTTP_CODE})"
|
|
echo ""
|
|
echo "For local analytics: gstack-analytics"
|
|
exit 0
|
|
fi
|
|
|
|
# ─── Weekly active installs ──────────────────────────────────
|
|
WEEKLY="$(echo "$DATA" | grep -o '"weekly_active":[0-9]*' | grep -o '[0-9]*' || echo "0")"
|
|
CHANGE="$(echo "$DATA" | grep -o '"change_pct":[0-9-]*' | grep -o '[0-9-]*' || echo "0")"
|
|
|
|
echo "Weekly active installs: ${WEEKLY}"
|
|
if ! printf '%s' "$DATA" | grep -q '"status":"ok"'; then
|
|
echo " (unverified — legacy backend response; deploy the latest community-pulse for verified figures)"
|
|
fi
|
|
if [ "$CHANGE" -gt 0 ] 2>/dev/null; then
|
|
echo " Change: +${CHANGE}%"
|
|
elif [ "$CHANGE" -lt 0 ] 2>/dev/null; then
|
|
echo " Change: ${CHANGE}%"
|
|
fi
|
|
echo ""
|
|
|
|
# ─── Skill popularity (top 10) ───────────────────────────────
|
|
echo "Top skills (last 7 days)"
|
|
echo "────────────────────────"
|
|
|
|
# Parse top_skills array from JSON
|
|
SKILLS="$(echo "$DATA" | grep -o '"top_skills":\[[^]]*\]' || echo "")"
|
|
if [ -n "$SKILLS" ] && [ "$SKILLS" != '"top_skills":[]' ]; then
|
|
# Parse each object — handle any key order (JSONB doesn't preserve order)
|
|
echo "$SKILLS" | grep -o '{[^}]*}' | while read -r OBJ; do
|
|
SKILL="$(echo "$OBJ" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}')"
|
|
COUNT="$(echo "$OBJ" | grep -o '"count":[0-9]*' | grep -o '[0-9]*')"
|
|
[ -n "$SKILL" ] && [ -n "$COUNT" ] && printf " /%-20s %s runs\n" "$SKILL" "$COUNT"
|
|
done
|
|
else
|
|
echo " No data yet"
|
|
fi
|
|
echo ""
|
|
|
|
# ─── Crash clusters ──────────────────────────────────────────
|
|
echo "Top crash clusters"
|
|
echo "──────────────────"
|
|
|
|
CRASHES="$(echo "$DATA" | grep -o '"crashes":\[[^]]*\]' || echo "")"
|
|
if [ -n "$CRASHES" ] && [ "$CRASHES" != '"crashes":[]' ]; then
|
|
echo "$CRASHES" | grep -o '{[^}]*}' | head -5 | while read -r OBJ; do
|
|
ERR="$(echo "$OBJ" | grep -o '"error_class":"[^"]*"' | awk -F'"' '{print $4}')"
|
|
C="$(echo "$OBJ" | grep -o '"total_occurrences":[0-9]*' | grep -o '[0-9]*')"
|
|
[ -n "$ERR" ] && printf " %-30s %s occurrences\n" "$ERR" "${C:-?}"
|
|
done
|
|
else
|
|
echo " No crashes reported"
|
|
fi
|
|
echo ""
|
|
|
|
# ─── Version distribution ────────────────────────────────────
|
|
echo "Version distribution (last 7 days)"
|
|
echo "───────────────────────────────────"
|
|
|
|
VERSIONS="$(echo "$DATA" | grep -o '"versions":\[[^]]*\]' || echo "")"
|
|
if [ -n "$VERSIONS" ] && [ "$VERSIONS" != '"versions":[]' ]; then
|
|
echo "$VERSIONS" | grep -o '{[^}]*}' | head -5 | while read -r OBJ; do
|
|
VER="$(echo "$OBJ" | grep -o '"version":"[^"]*"' | awk -F'"' '{print $4}')"
|
|
COUNT="$(echo "$OBJ" | grep -o '"count":[0-9]*' | grep -o '[0-9]*')"
|
|
[ -n "$VER" ] && [ -n "$COUNT" ] && printf " v%-15s %s events\n" "$VER" "$COUNT"
|
|
done
|
|
else
|
|
echo " No data yet"
|
|
fi
|
|
|
|
echo ""
|
|
echo "For local analytics: gstack-analytics"
|