From f7bb18de80b1ae792ed90770251d4570df201205 Mon Sep 17 00:00:00 2001 From: Carlos Oliveira Date: Sat, 11 Apr 2026 12:33:20 -0300 Subject: [PATCH] feat: credenciais cloud na UI, perfis salvos e melhorias no Run Generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adiciona campos de credenciais por provider (GCS JSON, S3 keys, Azure conn string) - Salva/carrega/remove perfis de credenciais nomeados em credentials/profiles.json - Esconde bloco Format quando destination é database - Desativa Column Filters e botão Advanced (connection string) - Adiciona modal Help com referência de todos os campos do Run Generator - Endpoint /api/capabilities para detectar suporte a browse folder (Windows vs Docker) - Botão de pasta oculto automaticamente no Docker/Linux - Validação: bloqueia execução cloud sem bucket preenchido - Atualiza README com nova seção de credenciais e descrição expandida --- README.md | 31 ++++- src/dataforge/frontend/src/App.tsx | 175 ++++++++++++++++++++++++-- src/dataforge/frontend/vite.config.ts | 108 ++++++++++++++-- 3 files changed, 292 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 0e10221..792ee46 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dataforge -Ferramenta para geração de datasets sintéticos **relacionais** com integridade referencial garantida. Disponível via interface visual no navegador e via linha de comando (CLI). Ideal para testar pipelines de dados, popular bancos de desenvolvimento e criar fixtures para modelos dbt — sem usar dados sensíveis. +Ferramenta para geração de datasets sintéticos **relacionais** com integridade referencial garantida. Disponível via **interface visual no navegador** e via **linha de comando (CLI)**. Suporta múltiplos formatos de saída (CSV, JSON, Parquet, Avro), carga direta em bancos SQL (PostgreSQL, MySQL, SQLite) e upload automático para nuvem (GCS, S3, Azure). Ideal para testar pipelines de dados, popular bancos de desenvolvimento, criar fixtures para modelos dbt e gerar dados de demonstração — sem usar dados sensíveis. --- @@ -71,10 +71,12 @@ A interface visual roda em `http://localhost:5173` e é a forma principal de uso - **Save as Default** — salva o schema no servidor (`src/dataforge/schemas/`) para reutilização futura - **Run Generator** — executa o CLI diretamente da interface com configuração visual completa: - Formatos de saída (CSV, JSON, Parquet, Avro) e modo JSON (flat/nested) - - Destino: **local**, **nuvem** (GCS, S3, Azure) ou **banco de dados** (PostgreSQL, MySQL, SQLite) com teste de conexão e conexões salvas + - Destino: **local** (com seletor de pasta nativo no Windows), **nuvem** (GCS, S3, Azure) ou **banco de dados** (PostgreSQL, MySQL, SQLite) com teste de conexão e conexões salvas + - Credenciais cloud inseridas diretamente na UI (GCS JSON, S3 Access Key/Secret, Azure Connection String) com suporte a **perfis salvos** — salve e carregue credenciais por nome sem precisar de arquivos externos - Particionamento Hive-style por tabela - Modo recorrente, seed e incrementos de coluna - Logs de execução em tempo real com botão de parada + - Botão **? Help** com referência de todos os campos O diagrama é atualizado em tempo real e mostra as relações entre tabelas com setas representando FKs. @@ -361,12 +363,30 @@ docker compose run --rm cli generate -d ecommerce -f parquet --partition-by "ord ## Upload em nuvem -Coloque o arquivo de credenciais na pasta `credentials/` do projeto (ela é montada no container em `/app/credentials/`). +Há duas formas de fornecer credenciais cloud ao Dataforge: + +### Opção 1 — Interface Visual (recomendada) + +Na seção **Destination → Cloud** do Run Generator, insira as credenciais diretamente na UI: + +| Provider | Campos | +|----------|--------| +| Google Cloud Storage | JSON completo da Service Account | +| Amazon S3 | Access Key ID, Secret Access Key e Region | +| Azure Blob Storage | Connection String | + +Clique em **Save credentials** para salvar um perfil nomeado localmente (`credentials/profiles.json`). Perfis salvos aparecem no topo da seção Cloud e podem ser carregados com um clique. + +> **Nota:** o seletor de pasta (📁) no destino Local só funciona quando o Dataforge roda localmente no Windows. No Docker, digite o caminho manualmente (ex: `/app/output/dados`). + +### Opção 2 — Arquivo na pasta `credentials/` + +Coloque o arquivo de credenciais na pasta `credentials/` do projeto (ela é montada no container em `/app/credentials/`). As credenciais da UI têm prioridade; a pasta serve de fallback. ### Google Cloud Storage ```bash -# Usando arquivo de service account +# Via arquivo de service account (fallback) docker compose run --rm cli generate -d ecommerce -f parquet \ --upload gcs \ --bucket meu-bucket \ @@ -377,10 +397,11 @@ docker compose run --rm cli generate -d ecommerce -f parquet \ ### Amazon S3 ```bash -# Autenticação via variáveis de ambiente no docker-compose ou inline +# Via variáveis de ambiente docker compose run --rm \ -e AWS_ACCESS_KEY_ID=... \ -e AWS_SECRET_ACCESS_KEY=... \ + -e AWS_DEFAULT_REGION=us-east-1 \ cli generate -d hr -f csv \ --upload s3 \ --bucket meu-bucket \ diff --git a/src/dataforge/frontend/src/App.tsx b/src/dataforge/frontend/src/App.tsx index 948a43a..d2ee125 100644 --- a/src/dataforge/frontend/src/App.tsx +++ b/src/dataforge/frontend/src/App.tsx @@ -615,6 +615,43 @@ export default function App() { const [showRunPanel, setShowRunPanel] = useState(false); const [showRunHelp, setShowRunHelp] = useState(false); + const [canBrowseFolder, setCanBrowseFolder] = useState(false); + const [credProfiles, setCredProfiles] = useState<{ name: string; provider: string }[]>([]); + const [saveCredName, setSaveCredName] = useState(''); + const [showSaveCredInput, setShowSaveCredInput] = useState(false); + + React.useEffect(() => { + fetch('/api/capabilities').then(r => r.json()).then(d => setCanBrowseFolder(!!d.browseFolder)).catch(() => {}); + fetchCredProfiles(); + }, []); + + const fetchCredProfiles = () => { + fetch('/api/credential-profiles').then(r => r.json()).then(setCredProfiles).catch(() => {}); + }; + + const handleSaveCredProfile = async () => { + const name = saveCredName.trim(); + if (!name) return; + await fetch('/api/credential-profiles', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, provider: runConfig.uploadTarget, creds: runConfig.cloudCreds }), + }); + setSaveCredName(''); + setShowSaveCredInput(false); + fetchCredProfiles(); + }; + + const handleLoadCredProfile = async (name: string) => { + const res = await fetch(`/api/credential-profiles/${encodeURIComponent(name)}`); + const profile = await res.json(); + setRunConfig(r => ({ ...r, uploadTarget: profile.provider, cloudCreds: profile.creds })); + }; + + const handleDeleteCredProfile = async (name: string) => { + await fetch(`/api/credential-profiles/${encodeURIComponent(name)}`, { method: 'DELETE' }); + fetchCredProfiles(); + }; const [runConfig, setRunConfig] = useState<{ formats: string[], destination: 'local' | 'cloud' | 'database', @@ -634,6 +671,13 @@ export default function App() { tablesToInclude: string[], columnsFilter: string, increments: Array<{ table: string; column: string; step: string; unit: string }>, + cloudCreds: { + gcsJson: string, + s3AccessKey: string, + s3SecretKey: string, + s3Region: string, + azureConnStr: string, + }, }>({ formats: ['csv'], destination: 'local' as 'local' | 'cloud' | 'database', @@ -653,6 +697,13 @@ export default function App() { tablesToInclude: [], columnsFilter: '', increments: [], + cloudCreds: { + gcsJson: '', + s3AccessKey: '', + s3SecretKey: '', + s3Region: 'us-east-1', + azureConnStr: '', + }, }); const [runLogs, setRunLogs] = useState(''); const [isRunning, setIsRunning] = useState(false); @@ -688,6 +739,7 @@ export default function App() { tables: runConfig.tablesToInclude.length > 0 ? runConfig.tablesToInclude : undefined, columns: runConfig.columnsFilter.trim() ? runConfig.columnsFilter.trim().split('\n').filter(Boolean) : undefined, increments: runConfig.increments.filter(i => i.table && i.column && i.step !== ''), + cloudCreds: runConfig.destination === 'cloud' ? runConfig.cloudCreds : undefined, }) }); @@ -1297,7 +1349,7 @@ export default function App() {
setRunConfig(r => ({...r, outputDir: e.target.value}))} style={{ flex: 1, padding: '0.5rem' }} placeholder="e.g. output" /> - + }
)} @@ -1324,6 +1376,29 @@ export default function App() { {/* Cloud */} {runConfig.destination === 'cloud' && (
+ + {/* Saved credential profiles */} + {credProfiles.filter(p => p.provider === runConfig.uploadTarget).length > 0 && ( +
+ +
+ {credProfiles.filter(p => p.provider === runConfig.uploadTarget).map(p => ( +
+ + +
+ ))} +
+
+ )} + + {/* Provider */}
+ + {/* Bucket + Prefix */}
@@ -1342,9 +1419,76 @@ export default function App() { setRunConfig(r => ({...r, prefix: e.target.value}))} style={{ width: '100%', padding: '0.5rem' }} placeholder="e.g. datasets/" />
-

- Credentials auto-loaded from credentials/ -

+ + {/* Credentials — per provider */} +
+
+

Credentials

+ {showSaveCredInput ? ( +
+ setSaveCredName(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter') handleSaveCredProfile(); if (e.key === 'Escape') setShowSaveCredInput(false); }} + placeholder="Profile name..." + autoFocus + style={{ padding: '0.25rem 0.5rem', fontSize: '0.78rem', background: 'rgba(255,255,255,0.07)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: '5px', color: 'white', width: '130px' }} + /> + + +
+ ) : ( + + )} +
+ + {runConfig.uploadTarget === 'gcs' && ( +
+ +