chore: sync estado completo do servidor — .env, compose, superset, routes

Inclui:
- .env real de produção (repo privado, auditoria total)
- docker-compose.prod.yml atualizado (LiteLLM/LLMFit, Superset)
- docker/superset/superset_config.py
- client/src/pages/BiWorkspace.tsx
- server/superset/routes.ts
- .gitignore: remove .env da lista de ignorados

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
root 2026-03-17 16:13:02 -03:00
parent 5571cb51e6
commit be22bbf77d
7 changed files with 322 additions and 9 deletions

20
.env Normal file
View File

@ -0,0 +1,20 @@
PGPASSWORD=afefb610090de1646944b537f3054715ad5755665c90036c2bcd258c9ab52dd8
SESSION_SECRET=092a26dcbebd6f7f5e7b9e020bdc4d4e966286c9780ef72e3426f437eb8d608b
SSO_SECRET=fbb87d1c5a14a6623d4e3dcc2b679bd68c46e59afb48dd6abd42b95cac945856
LITELLM_API_KEY=e396b827cbeb5e77d85cce4eb9da0521114f57a8e799f7aa8febad08d776c07e
WEBUI_SECRET_KEY=76c7fc044f9e8d6e5eb60b67c82832b67e84739da4827ce1fa7c101ae1a055df
DOMAIN=suite.onboardbi.com.br
OLLAMA_BASE_URL=http://ollama-ia1upsekrad96at5hq97e4qa:11434
LLMFIT_BASE_URL=http://llmfit:8000
LLMFIT_API_KEY=key-arcadia
# ── Superset BI ───────────────────────────────────────────────
SUPERSET_SECRET_KEY=421274b5ba360778a5398d528116a45ea962d1197e3dfc99f36a372ff1025a63
SUPERSET_ADMIN_USERNAME=admin
SUPERSET_ADMIN_PASSWORD=arcadia1337
SUPERSET_ADMIN_EMAIL=engenharia@ometas.com.br
COMPOSE_PROFILES=bi
SUPERSET_HOST=superset
SUPERSET_PORT=8088

3
.gitignore vendored
View File

@ -1,9 +1,6 @@
node_modules/ node_modules/
dist/ dist/
.DS_Store .DS_Store
.env
.env.local
.env.production
# Replit specific # Replit specific
.cache/ .cache/

View File

@ -2919,11 +2919,11 @@ export default function BiWorkspace() {
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-lg font-semibold text-[#1f334d]">MetaSet - Motor de BI</h2> <h2 className="text-lg font-semibold text-[#1f334d]">Superset - Motor de BI</h2>
<p className="text-sm text-gray-500">Acesso completo para criação manual de consultas SQL, gráficos e dashboards</p> <p className="text-sm text-gray-500">Acesso completo para criação manual de consultas SQL, gráficos e dashboards</p>
</div> </div>
<a <a
href="/api/bi/metaset/autologin" href="/api/bi/superset/autologin"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 bg-[#1f334d] text-white rounded-lg hover:bg-[#2a4466] transition-colors text-sm" className="inline-flex items-center gap-2 px-4 py-2 bg-[#1f334d] text-white rounded-lg hover:bg-[#2a4466] transition-colors text-sm"
@ -2934,9 +2934,9 @@ export default function BiWorkspace() {
</div> </div>
<div className="rounded-xl overflow-hidden border border-[#c89b3c]/20 bg-white shadow-sm" style={{ height: 'calc(100vh - 320px)' }}> <div className="rounded-xl overflow-hidden border border-[#c89b3c]/20 bg-white shadow-sm" style={{ height: 'calc(100vh - 320px)' }}>
<iframe <iframe
src="/api/bi/metaset/autologin" src="https://bi.onboardbi.com.br/superset/welcome/"
className="w-full h-full border-0" className="w-full h-full border-0"
title="MetaSet - Arcádia Insights" title="Superset - Arcádia Insights"
data-testid="iframe-metaset-advanced" data-testid="iframe-metaset-advanced"
allow="fullscreen" allow="fullscreen"
/> />

View File

@ -261,7 +261,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=coolify" - "traefik.docker.network=coolify"
- "traefik.http.routers.superset.entrypoints=https" - "traefik.http.routers.superset.entrypoints=https"
- "traefik.http.routers.superset.rule=Host(`bi.${DOMAIN}`)" - "traefik.http.routers.superset.rule=Host(`${SUPERSET_DOMAIN:-bi.onboardbi.com.br}`)"
- "traefik.http.routers.superset.tls=true" - "traefik.http.routers.superset.tls=true"
- "traefik.http.routers.superset.tls.certresolver=letsencrypt" - "traefik.http.routers.superset.tls.certresolver=letsencrypt"
- "traefik.http.services.superset.loadbalancer.server.port=8088" - "traefik.http.services.superset.loadbalancer.server.port=8088"

237
docker-compose.prod.yml.bak Normal file
View File

@ -0,0 +1,237 @@
# ─── Arcádia Suite — Produção (Coolify) ───────────────────────────────────────
# Este arquivo é usado pelo Coolify para deploy automático.
# NÃO inclui volumes de código-fonte — só artefatos de build.
# Configurar no Coolify: Environment Variables para todas as vars ${...}
name: arcadia-prod
services:
# ── Banco de dados com pgvector ─────────────────────────────────────────────
db:
image: pgvector/pgvector:pg16
restart: always
environment:
POSTGRES_DB: ${PGDATABASE:-arcadia}
POSTGRES_USER: ${PGUSER:-arcadia}
POSTGRES_PASSWORD: ${PGPASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
- ./docker/init-pgvector.sql:/docker-entrypoint-initdb.d/01-pgvector.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${PGUSER:-arcadia}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- arcadia-internal
# ── Redis ────────────────────────────────────────────────────────────────────
redis:
image: redis:7-alpine
restart: always
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- arcadia-internal
# ── App principal ─────────────────────────────────────────────────────────
app:
build:
context: .
dockerfile: Dockerfile
restart: always
environment:
NODE_ENV: production
PORT: 5000
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
REDIS_URL: redis://redis:6379
DOCKER_MODE: "true"
CONTABIL_PYTHON_URL: http://contabil:8003
BI_PYTHON_URL: http://bi:8004
AUTOMATION_PYTHON_URL: http://automation:8005
FISCO_PYTHON_URL: http://fisco:8002
PYTHON_SERVICE_URL: http://embeddings:8001
SESSION_SECRET: ${SESSION_SECRET}
SSO_SECRET: ${SSO_SECRET}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
LITELLM_BASE_URL: http://litellm:4000
LITELLM_API_KEY: ${LITELLM_API_KEY}
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
# ── Manus Agent — aponta para LiteLLM como gateway unificado ──────────
# LiteLLM roteia para Ollama (local), LLMFit (fine-tuned) ou externo
AI_INTEGRATIONS_OPENAI_BASE_URL: http://litellm:4000/v1
AI_INTEGRATIONS_OPENAI_API_KEY: ${LITELLM_API_KEY}
ports:
- "5000:5000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- arcadia-internal
- arcadia-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.arcadia.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.arcadia.tls=true"
- "traefik.http.routers.arcadia.tls.certresolver=letsencrypt"
# ── Microserviços Python ─────────────────────────────────────────────────────
contabil:
build:
context: .
dockerfile: Dockerfile.python
restart: always
environment:
SERVICE_NAME: contabil
SERVICE_PORT: 8003
CONTABIL_PORT: 8003
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
depends_on:
db:
condition: service_healthy
networks:
- arcadia-internal
bi:
build:
context: .
dockerfile: Dockerfile.python
restart: always
environment:
SERVICE_NAME: bi
SERVICE_PORT: 8004
BI_PORT: 8004
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
depends_on:
db:
condition: service_healthy
networks:
- arcadia-internal
automation:
build:
context: .
dockerfile: Dockerfile.python
restart: always
environment:
SERVICE_NAME: automation
SERVICE_PORT: 8005
AUTOMATION_PORT: 8005
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
depends_on:
db:
condition: service_healthy
networks:
- arcadia-internal
fisco:
build:
context: .
dockerfile: Dockerfile.python
restart: always
environment:
SERVICE_NAME: fisco
SERVICE_PORT: 8002
FISCO_PORT: 8002
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
depends_on:
db:
condition: service_healthy
networks:
- arcadia-internal
embeddings:
build:
context: .
dockerfile: Dockerfile.python
restart: always
environment:
SERVICE_NAME: embeddings
SERVICE_PORT: 8001
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
depends_on:
db:
condition: service_healthy
networks:
- arcadia-internal
# ── LiteLLM (gateway unificado de LLM — soberania dos dados) ─────────────────
# Roteia: LLMFit (fine-tuned) → Ollama (local) → externo (opt-in)
litellm:
image: ghcr.io/berriai/litellm:main-latest
restart: always
volumes:
- ./docker/litellm-config.yaml:/app/config.yaml
command: ["--config", "/app/config.yaml", "--port", "4000"]
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
LITELLM_MASTER_KEY: ${LITELLM_API_KEY}
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
# Ollama: se instalado no host use http://host-gateway:11434
# Se usar container Docker, mantém http://ollama:11434
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
# LLMFit: URL do serviço de modelos fine-tuned
LLMFIT_BASE_URL: ${LLMFIT_BASE_URL:-}
# Providers externos opcionais (soberania: só habilitados se configurados)
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
GROQ_API_KEY: ${GROQ_API_KEY:-}
depends_on:
db:
condition: service_healthy
networks:
- arcadia-internal
# ── Ollama (LLMs locais — soberania total) ────────────────────────────────────
# OPÇÃO A (padrão): Ollama como container Docker
# OPÇÃO B: Ollama no host → comente este serviço e defina
# OLLAMA_BASE_URL=http://host-gateway:11434 nas env vars
ollama:
image: ollama/ollama:latest
restart: always
volumes:
- ollama_models:/root/.ollama
networks:
- arcadia-internal
# Remova 'profiles: [ai]' para ativar por padrão no deploy
profiles: [ai]
# ── Open WebUI (interface para Ollama + LLMFit) ───────────────────────────────
open-webui:
image: ghcr.io/open-webui/open-webui:main
restart: always
environment:
# Pode apontar para LiteLLM para ter acesso a todos os modelos via WebUI
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
OPENAI_API_BASE_URL: http://litellm:4000/v1
OPENAI_API_KEY: ${LITELLM_API_KEY}
WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY}
volumes:
- open_webui_data:/app/backend/data
depends_on:
- litellm
networks:
- arcadia-internal
- arcadia-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.webui.rule=Host(`ai.${DOMAIN}`)"
- "traefik.http.routers.webui.tls=true"
- "traefik.http.routers.webui.tls.certresolver=letsencrypt"
- "traefik.http.services.webui.loadbalancer.server.port=8080"
profiles: [ai]
networks:
arcadia-internal:
driver: bridge
arcadia-public:
driver: bridge
volumes:
pgdata:
redis_data:
ollama_models:
open_webui_data:

View File

@ -11,9 +11,18 @@ ENABLE_CORS = True
CORS_OPTIONS = { CORS_OPTIONS = {
"supports_credentials": True, "supports_credentials": True,
"allow_headers": ["*"], "allow_headers": ["*"],
"resources": {r"/api/*": {"origins": "*"}}, "resources": {r"/*": {"origins": ["https://suite.onboardbi.com.br"]}},
} }
# Permitir embedding em iframe a partir do suite
TALISMAN_ENABLED = False
HTTP_HEADERS = {}
# Cookie de sessão acessível por todos os subdomínios de onboardbi.com.br
SESSION_COOKIE_DOMAIN = ".onboardbi.com.br"
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_SECURE = True
FEATURE_FLAGS = { FEATURE_FLAGS = {
"EMBEDDED_SUPERSET": True, "EMBEDDED_SUPERSET": True,
"ENABLE_TEMPLATE_PROCESSING": True, "ENABLE_TEMPLATE_PROCESSING": True,

View File

@ -147,5 +147,55 @@ export function registerSupersetRoutes(app: Express): void {
} }
}); });
// Autologin — faz login transparente no Superset e redireciona para /superset/
app.get("/api/bi/superset/autologin", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" });
// Passo 1: obter CSRF token da página de login
const loginPageResp = await fetch(`${SUPERSET_URL}/login/`);
const loginPageText = await loginPageResp.text();
const csrfMatch = loginPageText.match(/name="csrf_token"[\s\S]*?value="([^"]+)"/);
if (!csrfMatch) return res.status(502).json({ error: "CSRF token não encontrado no Superset" });
const csrfToken = csrfMatch[1];
const initialCookies = loginPageResp.headers.get("set-cookie") || "";
// Passo 2: fazer POST de login com as credenciais de admin
const formBody = new URLSearchParams({
username: ADMIN_USER,
password: ADMIN_PASS,
csrf_token: csrfToken,
});
const loginResp = await fetch(`${SUPERSET_URL}/login/`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Cookie: initialCookies,
},
body: formBody.toString(),
redirect: "manual",
});
// Passo 3: extrair o cookie de sessão e repassar ao browser
const setCookieRaw = loginResp.headers.get("set-cookie") || "";
const sessionMatch = setCookieRaw.match(/session=([^;]+)/);
if (sessionMatch) {
res.cookie("session", sessionMatch[1], {
domain: ".onboardbi.com.br",
secure: true,
path: "/",
httpOnly: true,
sameSite: "lax",
});
}
res.redirect("https://bi.onboardbi.com.br/superset/welcome/");
} catch (err: any) {
res.status(502).json({ error: err.message });
}
});
console.log("[Superset] Rotas registradas em /api/superset/*"); console.log("[Superset] Rotas registradas em /api/superset/*");
} }