Produção
Subir, recarregar ao salvar, observar, estender com plugins e o que colocar na frente.
A linha de comando#
dataforge vitrine new meupainel # cria o projeto
dataforge vitrine dev # sobe recarregando ao salvar
dataforge vitrine run # sobe, sem recarregar
dataforge vitrine doctor # diz por que ela não sobe
dataforge vitrine dev --porta=8600 --host=0.0.0.0Sem argumento, ele procura main.df, app.df, painel.df e src/main.df, nessa ordem, e depois a entrada do forge.toml. A porta e o host da linha de comando vencem o que está escrito no arquivo — é o que permite trocar a porta sem editar o programa.
O doctor responde as perguntas de quem está vendo uma tela em branco, da causa mais provável para a menos: o módulo carrega, o Kiln está lá, existe um arquivo que sobe, ele compila, ele adota a Vitrine, ele chama V.subir, a porta está livre.
Para distribuir o projeto, dataforge pack.
Subir#
V.rodar(painel, porta := 8501) // uma página
V.subir(porta := 8501, recarregar := yes) // com hot reload
porta := V.servir(0) // em segundo plano
V.parar_servidor()V.servir(0) deixa o sistema escolher a porta e devolve qual foi — é o que torna um teste de integração independente de porta ocupada.
Hot reload#
Com recarregar := yes, salvar um .df do projeto reinicia o processo. Reiniciar, e não recarregar o módulo: o estado de um módulo recarregado pela metade produz erros que não existem no código, e depurar isso custa mais do que o segundo do reinício.
Configuração#
V.app("Painel",
icone := "📊",
descricao := "Vendas da Forja Ltda.",
tema := "escuro",
producao := yes,
validade_sessao := 1800,
limite_upload := 4194304)
V.configurar("atualizar_a_cada", 30)| Chave | Faz |
|---|---|
titulo · icone · descricao | aba do navegador e metadados |
tema | "claro", "escuro" ou um vault de cores |
modo_tema | "automatico" segue o sistema de quem abre |
producao | esconde o detalhe do erro e o diagnóstico |
validade_sessao | segundos até a sessão ociosa sair da memória |
limite_upload | bytes por arquivo enviado |
atualizar_a_cada | segundos entre recargas automáticas |
css · javascript | o seu, injetado na página |
manifesto | serve um manifesto PWA em /__vitrine__/manifesto.json |
https | marca o cookie de sessão como Secure |
Tema#
V.configurar("tema", {
"primaria": "#0F62FE",
"raio": "4px",
"largura": "1400px"
})Um tema é um vault de variáveis CSS. Mudar a cor primária muda o botão, o link, o foco, a borda do campo e a primeira série do gráfico ao mesmo tempo — e não em nove lugares. Um tema parcial completa o que falta a partir do claro e do escuro, para que quem trocou a primária não perca o modo escuro por isso.
Observabilidade#
V.registrar("consulta lenta", "aviso", {"ms": 1840})
V.logs(50, "erro")
V.metricas() // execuções, erros, média em ms, sessões, cache
V.saude() // o que um balanceador perguntaDuas rotas vêm prontas: GET /__vitrine__/saude e GET /__vitrine__/metricas. O log em memória tem teto de 2 000 linhas — sem teto, ele é um vazamento que só aparece depois de semanas no ar.
Middleware#
action so_de_dia(ctx):
given Time.hora() bigger 22:
V.aviso("O painel está fechado à noite.")
yield no // 'no' interrompe a página
yield yes
V.antes(so_de_dia)V.antes roda antes de toda página e pode interromper; V.depois recebe o contexto já montado. Para middleware de HTTP — CORS, limite de taxa, compressão —, use o do Kiln sobre V.montar().
Trabalho fora do pedido#
V.tarefa(enviar_email, destinatario) // roda numa thread, não espera
V.agendar(recalcular_totais, 3600) // de hora em horaV.tarefa é para o que a página não vai mostrar agora. Para um resultado que a página precisa, Arcane.Async, que tem await. O primeiro disparo de V.agendar é depois do primeiro intervalo — agendar algo "a cada hora" não deveria fazê-lo agora e de novo em uma hora.
Plugins#
action tema_da_empresa(app):
app.configurar("tema", {"primaria": "#7B1FA2"})
app.configurar("css", ".v-titulo { letter-spacing: -.03em }")
V.plugin("tema-empresa", tema_da_empresa)Um plugin é uma ação que recebe a aplicação e acrescenta algo. Registrar duas vezes o mesmo nome é erro, e não substituição silenciosa: quase sempre é um adopt duplicado, e descobrir isso por um comportamento que sumiu é caro.
O que colocar na frente#
Mais de um processo: a sessão num lugar comum#
Por padrão a sessão vive na memória do processo. Com dois processos atrás de um balanceador, o segundo pedido da mesma pessoa cai numa memória que nunca a viu: o contador volta a 1 e o login "cai". Dê às sessões um lugar que todos os processos veem:
V.app("Painel", sessoes_em := V.sessoes_em_banco("/dados/sessoes.db"))
// ou: V.sessoes_em_arquivos("/dados/sessoes")A página continua falando com um dicionário: a sessão é aberta no começo do pedido e gravada uma vez, no fim, só com o que mudou — inclusive itens.append(x), que não passa por definir. A gravação é por chave: duas abas mexendo em chaves diferentes não apagam uma à outra; na mesma chave, vence a última.
| Armazém | Quem vê |
|---|---|
| o padrão | este processo |
V.sessoes_em_banco(caminho) | todo processo que abre o mesmo SQLite — aceita também a conexão do Arcane.Database |
V.sessoes_em_arquivos(pasta) | todo processo que vê a mesma pasta |
um blueprint com carregar, gravar e apagar | o que ele decidir: Redis, Postgres… |
Atualização automática#
action acompanhar():
V.atualizar_a_cada(15)
V.metrica("Fila", tamanho_da_fila())A página se recarrega sozinha nesse intervalo — e não quando a aba está escondida, porque cobrar do servidor por uma página que ninguém está vendo é desperdício puro.
É o "tempo real" do framework, e ele é por pergunta e não por empurrão. O Kiln tem WebSocket e SSE (/docs/kiln/tempo-real) — a Vitrine é que não os usa: o modelo dela é reexecutar o programa inteiro, e empurrar um pedaço de tela exigiria saber qual pedaço mudou, que é exatamente o que este framework existe para não precisar saber.
Para um painel que muda a cada segundos, perguntar é suficiente e não quebra atrás de proxy nenhum. Para um fluxo de eventos contínuo — cotação, log ao vivo, progresso de um trabalho longo — é o Kiln direto que serve.