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:
parent
5571cb51e6
commit
be22bbf77d
|
|
@ -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
|
||||||
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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/*");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue