diff --git a/apps/visualizer/.env.example b/apps/visualizer/.env.example index 9e85877..aae6f87 100644 --- a/apps/visualizer/.env.example +++ b/apps/visualizer/.env.example @@ -1,12 +1,15 @@ # Default: UI reads ticks from the same-origin API proxy at `/api`. VITE_API_URL=/api -# Fallback (optional): query Hasura directly (not recommended in browser). -VITE_HASURA_URL=http://localhost:8080/v1/graphql -# Optional (only if you intentionally query Hasura directly from the browser): +# Hasura GraphQL endpoint (supports subscriptions via WS). +# On VPS, `trade-frontend` proxies Hasura at the same origin under `/graphql`. +VITE_HASURA_URL=/graphql +# Optional explicit WS URL; when omitted the app derives it from `VITE_HASURA_URL`. +# Can be absolute (wss://...) or a same-origin path (e.g. /graphql-ws). +# VITE_HASURA_WS_URL=/graphql-ws +# Optional auth (only if Hasura is not configured with `HASURA_GRAPHQL_UNAUTHORIZED_ROLE=public`): # VITE_HASURA_AUTH_TOKEN=YOUR_JWT -# VITE_HASURA_ADMIN_SECRET=devsecret -VITE_SYMBOL=PUMP-PERP +VITE_SYMBOL=SOL-PERP # Optional: filter by source (leave empty for all) # VITE_SOURCE=drift_oracle VITE_POLL_MS=1000 diff --git a/apps/visualizer/__r b/apps/visualizer/__r new file mode 100755 index 0000000..f586fef --- /dev/null +++ b/apps/visualizer/__r @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +SSH_TUNNEL_TARGET="${SSH_TUNNEL_TARGET:-mevnode_bot}" +K8S_NAMESPACE="${K8S_NAMESPACE:-trade-staging}" + +SSH_FRONTEND_LOCAL_PORT="${SSH_FRONTEND_LOCAL_PORT:-13081}" +SSH_HASURA_LOCAL_PORT="${SSH_HASURA_LOCAL_PORT:-18080}" + +SSH_FRONTEND_REMOTE_HOST="${SSH_FRONTEND_REMOTE_HOST:-127.0.0.1}" +SSH_FRONTEND_REMOTE_PORT="${SSH_FRONTEND_REMOTE_PORT:-30081}" +SSH_HASURA_REMOTE_PORT="${SSH_HASURA_REMOTE_PORT:-8080}" + +VITE_DEV_PORT="${VITE_DEV_PORT:-5174}" + +SSH_PID="" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + exit 1 + fi +} + +wait_for_listen() { + local port="$1" + local label="$2" + local tries="${3:-80}" + local delay="${4:-0.25}" + local i + + for ((i = 0; i < tries; i += 1)); do + if ss -ltnH | awk '{print $4}' | grep -Eq "(^|[:])${port}$"; then + return 0 + fi + sleep "${delay}" + done + + echo "Timed out waiting for ${label} on port ${port}" >&2 + return 1 +} + +get_cluster_ip() { + local service="$1" + ssh \ + -o BatchMode=yes \ + -o StrictHostKeyChecking=accept-new \ + -T "${SSH_TUNNEL_TARGET}" \ + "kubectl -n ${K8S_NAMESPACE} get svc ${service} -o jsonpath='{.spec.clusterIP}'" +} + +cleanup() { + local code=$? + trap - EXIT INT TERM + + if [[ -n "${SSH_PID}" ]] && kill -0 "${SSH_PID}" 2>/dev/null; then + kill "${SSH_PID}" 2>/dev/null || true + wait "${SSH_PID}" 2>/dev/null || true + fi + + exit "${code}" +} + +trap cleanup EXIT INT TERM + +require_cmd ssh +require_cmd npm +require_cmd ss + +HASURA_CLUSTER_IP="${HASURA_CLUSTER_IP:-$(get_cluster_ip hasura)}" + +if [[ -z "${HASURA_CLUSTER_IP}" ]]; then + echo "Could not resolve ClusterIP for hasura in namespace ${K8S_NAMESPACE}" >&2 + exit 1 +fi + +echo "SSH target: ${SSH_TUNNEL_TARGET}" +echo "Namespace: ${K8S_NAMESPACE}" +echo "hasura ClusterIP: ${HASURA_CLUSTER_IP}" + +echo "Starting SSH tunnel..." +ssh \ + -o StrictHostKeyChecking=accept-new \ + -o ExitOnForwardFailure=yes \ + -o ServerAliveInterval=30 \ + -o ServerAliveCountMax=3 \ + -N \ + -L "${SSH_FRONTEND_LOCAL_PORT}:${SSH_FRONTEND_REMOTE_HOST}:${SSH_FRONTEND_REMOTE_PORT}" \ + -L "${SSH_HASURA_LOCAL_PORT}:${HASURA_CLUSTER_IP}:${SSH_HASURA_REMOTE_PORT}" \ + "${SSH_TUNNEL_TARGET}" & +SSH_PID=$! + +wait_for_listen "${SSH_FRONTEND_LOCAL_PORT}" "frontend SSH tunnel" +wait_for_listen "${SSH_HASURA_LOCAL_PORT}" "Hasura SSH tunnel" + +echo "Starting Vite on :${VITE_DEV_PORT}..." +cd "${SCRIPT_DIR}" + +export VITE_DEV_PORT +export VITE_HASURA_URL="${VITE_HASURA_URL:-/graphql}" +export VITE_HASURA_WS_URL="${VITE_HASURA_WS_URL:-ws://127.0.0.1:${SSH_HASURA_LOCAL_PORT}/v1/graphql}" +export FRONTEND_PROXY_TARGET="${FRONTEND_PROXY_TARGET:-http://127.0.0.1:${SSH_FRONTEND_LOCAL_PORT}}" +export GRAPHQL_PROXY_TARGET="${GRAPHQL_PROXY_TARGET:-http://127.0.0.1:${SSH_HASURA_LOCAL_PORT}}" +export GRAPHQL_PROXY_PATH="${GRAPHQL_PROXY_PATH:-/v1/graphql}" + +echo "Frontend auth proxy: ${FRONTEND_PROXY_TARGET}" +echo "GraphQL proxy: ${GRAPHQL_PROXY_TARGET}${GRAPHQL_PROXY_PATH}" +echo "Open: http://localhost:${VITE_DEV_PORT}/" + +exec npm run dev diff --git a/apps/visualizer/__start b/apps/visualizer/__start new file mode 100644 index 0000000..16e027f --- /dev/null +++ b/apps/visualizer/__start @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +cd "${SCRIPT_DIR}" + +if [[ -n "${SSH_TUNNEL_PORT:-}" ]]; then + export FRONTEND_PROXY_TARGET="${FRONTEND_PROXY_TARGET:-http://127.0.0.1:${SSH_TUNNEL_PORT}}" + export GRAPHQL_PROXY_TARGET="${GRAPHQL_PROXY_TARGET:-http://127.0.0.1:${SSH_TUNNEL_PORT}}" +else + export FRONTEND_PROXY_TARGET="${FRONTEND_PROXY_TARGET:-https://trade.mpabi.pl}" + export GRAPHQL_PROXY_TARGET="${GRAPHQL_PROXY_TARGET:-https://trade.mpabi.pl}" +fi + +export VITE_HASURA_URL="${VITE_HASURA_URL:-/graphql}" +export VITE_HASURA_WS_URL="${VITE_HASURA_WS_URL:-/graphql-ws}" + +if [[ -z "${API_PROXY_BASIC_AUTH:-}" && -z "${API_PROXY_BASIC_AUTH_FILE:-}" ]]; then + if [[ -f "${ROOT_DIR}/tokens/frontend.json" ]]; then + export API_PROXY_BASIC_AUTH_FILE="tokens/frontend.json" + else + echo "Missing basic auth config for VPS proxy." + echo "Set API_PROXY_BASIC_AUTH='USER:PASS' or create tokens/frontend.json" >&2 + fi +fi + +npm run dev diff --git a/apps/visualizer/diagram.html b/apps/visualizer/diagram.html new file mode 100644 index 0000000..f810b49 --- /dev/null +++ b/apps/visualizer/diagram.html @@ -0,0 +1,12 @@ + + +
+ + +