diff --git a/client/src/components/MiroFlowControl.tsx b/client/src/components/MiroFlowControl.tsx
index 871ef15..63d5043 100644
--- a/client/src/components/MiroFlowControl.tsx
+++ b/client/src/components/MiroFlowControl.tsx
@@ -73,6 +73,26 @@ export function MiroFlowControl({
},
});
+ const createDashboardMutation = useMutation({
+ mutationFn: async (dashboardData: {
+ dashboardTitle: string;
+ sqlQuery: string;
+ }) => {
+ const response = await apiRequest(
+ "POST",
+ "/api/superset/miroflow/create-dashboard",
+ {
+ ...dashboardData,
+ analysisId: mutation.data?.execution_id,
+ agent: selectedAgent,
+ task,
+ insights: mutation.data?.result,
+ }
+ );
+ return response.json();
+ },
+ });
+
const handleAnalyze = () => {
if (!task.trim()) {
return;
@@ -207,6 +227,46 @@ export function MiroFlowControl({
{mutation.data?.execution_id?.slice(0, 8)}...
+
+ {/* Create Dashboard Button */}
+
+
+
+ {createDashboardMutation.isSuccess && (
+
+
+ ✅ Dashboard criado! Atualizando...
+
+
+ )}
+
+ {createDashboardMutation.isError && (
+
+
+ ❌ Erro: {createDashboardMutation.error instanceof Error ? createDashboardMutation.error.message : "Erro ao criar dashboard"}
+
+
+ )}
+
)}
diff --git a/migrations/miroflow_dashboards.sql b/migrations/miroflow_dashboards.sql
new file mode 100644
index 0000000..33775ac
--- /dev/null
+++ b/migrations/miroflow_dashboards.sql
@@ -0,0 +1,39 @@
+-- MiroFlow Generated Dashboards
+-- Rastreia dashboards criados pelo MiroFlow baseado em análises
+
+CREATE TABLE IF NOT EXISTS miroflow_generated_dashboards (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+ -- Análise que criou este dashboard
+ analysis_id VARCHAR(255),
+ agent VARCHAR(50) NOT NULL CHECK (agent IN ('statistician', 'fiscal_auditor', 'researcher')),
+ task TEXT NOT NULL,
+ insights JSONB,
+
+ -- Dashboard no Superset
+ dashboard_id INT NOT NULL,
+ dashboard_title VARCHAR(255) NOT NULL,
+ dataset_name VARCHAR(255),
+
+ -- SQL sugerido/criado
+ sql_query TEXT,
+
+ -- Auditoria
+ created_by UUID,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ tenant_id INT,
+
+ -- Referência
+ superset_url VARCHAR(512),
+
+ -- Soft delete
+ deleted_at TIMESTAMP,
+
+ -- Constraints
+ CONSTRAINT unique_analysis_dashboard UNIQUE (analysis_id, dashboard_id) DEFERRABLE INITIALLY DEFERRED
+);
+
+CREATE INDEX idx_miroflow_dashboards_analysis ON miroflow_generated_dashboards(analysis_id);
+CREATE INDEX idx_miroflow_dashboards_created_at ON miroflow_generated_dashboards(created_at DESC);
+CREATE INDEX idx_miroflow_dashboards_dashboard_id ON miroflow_generated_dashboards(dashboard_id);
+CREATE INDEX idx_miroflow_dashboards_tenant ON miroflow_generated_dashboards(tenant_id);
diff --git a/server/superset/routes.ts b/server/superset/routes.ts
index 729b76e..78e516c 100644
--- a/server/superset/routes.ts
+++ b/server/superset/routes.ts
@@ -1,4 +1,5 @@
import type { Express, Request, Response } from "express";
+import { createMiroFlowBridge } from "./MiroFlowBridge";
const SUPERSET_HOST = process.env.SUPERSET_HOST || "localhost";
const SUPERSET_PORT = parseInt(process.env.SUPERSET_PORT || "8088", 10);
@@ -6,6 +7,8 @@ const SUPERSET_URL = `http://${SUPERSET_HOST}:${SUPERSET_PORT}`;
const ADMIN_USER = process.env.SUPERSET_ADMIN_USER || "admin";
const ADMIN_PASS = process.env.SUPERSET_ADMIN_PASSWORD || "arcadia2026";
+const miroflowBridge = createMiroFlowBridge();
+
// Cache do service token (válido por 50 min)
let cachedToken: string | null = null;
let tokenExpiry = 0;
@@ -105,4 +108,200 @@ export function registerSupersetRoutes(app: Express): void {
if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" });
res.redirect("/superset/login");
});
+
+ // ============================================================================
+ // MiroFlow Bridge Routes
+ // ============================================================================
+
+ /**
+ * POST /api/superset/miroflow/analyze
+ * Enriquece dataset Superset com análises científicas via MiroFlow
+ *
+ * Request body:
+ * {
+ * "dataset": { "data": [...], "columns": [...] },
+ * "config": { "type": "forecast|anomaly|exploratory", "horizon": 30, "confidence_level": 0.95 }
+ * }
+ *
+ * Response:
+ * {
+ * "success": true,
+ * "data": { ...enriched dataset with _miroflow_analysis, _anomaly_scores, etc },
+ * "analysis": { "summary": "...", "analysis_type": "..." },
+ * "execution_id": "uuid"
+ * }
+ */
+ app.post("/api/superset/miroflow/analyze", async (req: Request, res: Response) => {
+ await miroflowBridge.handleAnalyzeRequest(req, res);
+ });
+
+ /**
+ * GET /api/superset/miroflow/health
+ * Verifica conexão com MiroFlow bridge
+ */
+ app.get("/api/superset/miroflow/health", async (_req: Request, res: Response) => {
+ try {
+ const miroflowUrl = process.env.MIROFLOW_URL || "http://localhost:8006";
+ const response = await fetch(`${miroflowUrl}/health`, {
+ signal: AbortSignal.timeout(5_000),
+ });
+ const data = await response.json();
+ res.json({
+ online: response.ok,
+ miroflow: data,
+ bridge: "operational",
+ endpoints: {
+ analyze: "/api/superset/miroflow/analyze",
+ health: "/api/superset/miroflow/health",
+ },
+ });
+ } catch (err: any) {
+ res.json({
+ online: false,
+ error: err.message,
+ bridge: "offline",
+ });
+ }
+ });
+
+ /**
+ * POST /api/superset/miroflow/create-dashboard
+ * Cria um dashboard no Superset baseado em análise MiroFlow
+ */
+ app.post("/api/superset/miroflow/create-dashboard", async (req: Request, res: Response) => {
+ if (!req.isAuthenticated()) {
+ return res.status(401).json({ error: "Não autenticado" });
+ }
+
+ try {
+ const { dashboardTitle, sqlQuery, analysisId, agent, task, insights } = req.body;
+
+ if (!dashboardTitle || !sqlQuery || !analysisId) {
+ return res.status(400).json({ error: "dashboardTitle, sqlQuery e analysisId são obrigatórios" });
+ }
+
+ const token = await getServiceToken();
+ const user = req.user as any;
+
+ // 1. Criar dataset no Superset
+ const datasetResp = await fetch(`${SUPERSET_URL}/api/v1/datasets/`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ database: 1, // ID do banco "Arcádia Suite"
+ table_name: `miroflow_${analysisId.substring(0, 8)}`,
+ sql: sqlQuery,
+ }),
+ });
+
+ if (!datasetResp.ok) {
+ const err = await datasetResp.json().catch(() => ({ detail: "Erro ao criar dataset" }));
+ throw new Error(`Falha ao criar dataset: ${err.detail}`);
+ }
+
+ const dataset = await datasetResp.json();
+ const datasetId = dataset.id;
+
+ // 2. Criar chart automático
+ const chartResp = await fetch(`${SUPERSET_URL}/api/v1/chart/`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ datasource_id: datasetId,
+ datasource_type: "table",
+ chart_type: "table", // Começar com table, depois adicionar mais tipos
+ name: `${dashboardTitle} - Dados`,
+ description: `Criado via MiroFlow Analysis (${agent})`,
+ }),
+ });
+
+ if (!chartResp.ok) {
+ const err = await chartResp.json().catch(() => ({ detail: "Erro ao criar chart" }));
+ throw new Error(`Falha ao criar chart: ${err.detail}`);
+ }
+
+ const chart = await chartResp.json();
+ const chartId = chart.id;
+
+ // 3. Criar dashboard
+ const dashboardResp = await fetch(`${SUPERSET_URL}/api/v1/dashboard/`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ dashboard_title: dashboardTitle,
+ description: `Análise MiroFlow - ${agent} - ${new Date().toLocaleString('pt-BR')}`,
+ }),
+ });
+
+ if (!dashboardResp.ok) {
+ const err = await dashboardResp.json().catch(() => ({ detail: "Erro ao criar dashboard" }));
+ throw new Error(`Falha ao criar dashboard: ${err.detail}`);
+ }
+
+ const dashboard = await dashboardResp.json();
+ const dashboardId = dashboard.id;
+
+ // 4. Adicionar chart ao dashboard
+ const updateResp = await fetch(`${SUPERSET_URL}/api/v1/dashboard/${dashboardId}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ dashboard_title: dashboardTitle,
+ slices: [chartId],
+ }),
+ });
+
+ if (!updateResp.ok) {
+ throw new Error("Falha ao adicionar chart ao dashboard");
+ }
+
+ // 5. Salvar referência no banco Arcádia
+ const db = await import("../db/index.js");
+ await db.query(
+ `INSERT INTO miroflow_generated_dashboards
+ (analysis_id, agent, task, insights, dashboard_id, dashboard_title, dataset_name, sql_query, created_by, tenant_id, superset_url)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
+ [
+ analysisId,
+ agent || "unknown",
+ task || "",
+ JSON.stringify(insights || {}),
+ dashboardId,
+ dashboardTitle,
+ `miroflow_${analysisId.substring(0, 8)}`,
+ sqlQuery,
+ user.id,
+ user.tenantId || null,
+ `${SUPERSET_URL}/superset/dashboard/${dashboardId}/`,
+ ]
+ );
+
+ res.json({
+ success: true,
+ dashboardId,
+ dashboardUrl: `${SUPERSET_URL}/superset/dashboard/${dashboardId}/`,
+ message: `Dashboard "${dashboardTitle}" criado com sucesso!`,
+ });
+ } catch (err: any) {
+ console.error("[Superset] Erro ao criar dashboard:", err.message);
+ res.status(502).json({
+ error: err.message,
+ source: "superset_create_dashboard",
+ });
+ }
+ });
+
+ console.log("[Superset] MiroFlow Bridge registered at /api/superset/miroflow");
}