feat: enhance time synchronization and Jitsi configuration handling

- Implemented a comprehensive time synchronization mechanism that applies GMT offsets based on user configuration, ensuring accurate timestamps across the application.
- Updated the Jitsi configuration to include SSH settings, allowing for better integration with Docker setups.
- Refactored the backend queries and mutations to handle the new SSH configuration fields, ensuring secure and flexible server management.
- Enhanced error handling and logging for time synchronization processes, providing clearer feedback for users and developers.
This commit is contained in:
2025-11-22 18:18:16 -03:00
parent 54089f5eca
commit c056506ce5
17 changed files with 1765 additions and 257 deletions

View File

@@ -1,52 +1,90 @@
<script lang="ts">
import { useQuery, useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
import { useQuery, useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
const client = useConvexClient();
const currentUser = useQuery(api.auth.getCurrentUser, {});
const configAtual = useQuery(api.configuracaoJitsi.obterConfigJitsi, {});
let domain = $state("");
let appId = $state("sgse-app");
let roomPrefix = $state("sgse");
// Query condicional para configuração completa
const configCompletaQuery = $derived(
configAtual?.data?._id ? { configId: configAtual.data._id } : null
);
const configCompleta = useQuery(
api.configuracaoJitsi.obterConfigJitsiCompleta,
configCompletaQuery ? configCompletaQuery : 'skip'
);
let domain = $state('');
let appId = $state('sgse-app');
let roomPrefix = $state('sgse');
let useHttps = $state(false);
let acceptSelfSignedCert = $state(false);
// Campos SSH/Docker
let sshHost = $state('');
let sshPort = $state(22);
let sshUsername = $state('');
let sshPassword = $state('');
let sshKeyPath = $state('');
let dockerComposePath = $state('');
let jitsiConfigPath = $state('~/.jitsi-meet-cfg');
let mostrarConfigSSH = $state(false);
let processando = $state(false);
let testando = $state(false);
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(null);
let testandoSSH = $state(false);
let aplicandoServidor = $state(false);
let mensagem = $state<{ tipo: 'success' | 'error'; texto: string; detalhes?: string } | null>(
null
);
function mostrarMensagem(tipo: "success" | "error", texto: string) {
mensagem = { tipo, texto };
function mostrarMensagem(tipo: 'success' | 'error', texto: string, detalhes?: string) {
mensagem = { tipo, texto, detalhes };
setTimeout(() => {
mensagem = null;
}, 5000);
}, 8000);
}
// Carregar config existente
$effect(() => {
if (configAtual?.data) {
domain = configAtual.data.domain || "";
appId = configAtual.data.appId || "sgse-app";
roomPrefix = configAtual.data.roomPrefix || "sgse";
domain = configAtual.data.domain || '';
appId = configAtual.data.appId || 'sgse-app';
roomPrefix = configAtual.data.roomPrefix || 'sgse';
useHttps = configAtual.data.useHttps ?? false;
acceptSelfSignedCert = configAtual.data.acceptSelfSignedCert ?? false;
} else if (configAtual === null) {
// Se não há configuração, resetar para valores padrão
domain = "";
appId = "sgse-app";
roomPrefix = "sgse";
domain = '';
appId = 'sgse-app';
roomPrefix = 'sgse';
useHttps = false;
acceptSelfSignedCert = false;
}
});
// Carregar configurações SSH/Docker
$effect(() => {
if (configCompleta?.data) {
sshHost = configCompleta.data.sshHost || '';
sshPort = configCompleta.data.sshPort || 22;
sshUsername = configCompleta.data.sshUsername || '';
sshPassword = ''; // Sempre limpar senha por segurança
sshKeyPath = configCompleta.data.sshKeyPath || '';
dockerComposePath = configCompleta.data.dockerComposePath || '';
jitsiConfigPath = configCompleta.data.jitsiConfigPath || '~/.jitsi-meet-cfg';
mostrarConfigSSH = !!(configCompleta.data.sshHost || configCompleta.data.sshUsername);
}
});
// Ativar HTTPS automaticamente se domínio contém porta 8443
$effect(() => {
if (domain.includes(":8443")) {
if (domain.includes(':8443')) {
useHttps = true;
// Para localhost com porta 8443, geralmente é certificado autoassinado
if (domain.includes("localhost")) {
if (domain.includes('localhost')) {
acceptSelfSignedCert = true;
}
}
@@ -55,22 +93,19 @@
async function salvarConfiguracao() {
// Validação de campos obrigatórios
if (!domain?.trim() || !appId?.trim() || !roomPrefix?.trim()) {
mostrarMensagem("error", "Preencha todos os campos obrigatórios");
mostrarMensagem('error', 'Preencha todos os campos obrigatórios');
return;
}
// Validação de roomPrefix (apenas letras, números e hífens)
const roomPrefixRegex = /^[a-zA-Z0-9-]+$/;
if (!roomPrefixRegex.test(roomPrefix.trim())) {
mostrarMensagem(
"error",
"Prefixo de sala deve conter apenas letras, números e hífens"
);
mostrarMensagem('error', 'Prefixo de sala deve conter apenas letras, números e hífens');
return;
}
if (!currentUser?.data) {
mostrarMensagem("error", "Usuário não autenticado");
mostrarMensagem('error', 'Usuário não autenticado');
return;
}
@@ -82,18 +117,26 @@
roomPrefix: roomPrefix.trim(),
useHttps,
acceptSelfSignedCert,
configuradoPorId: currentUser.data._id as Id<"usuarios">,
configuradoPorId: currentUser.data._id as Id<'usuarios'>,
// Configurações SSH/Docker (opcionais)
sshHost: sshHost.trim() || undefined,
sshPort: sshPort || undefined,
sshUsername: sshUsername.trim() || undefined,
sshPassword: sshPassword.trim() || undefined,
sshKeyPath: sshKeyPath.trim() || undefined,
dockerComposePath: dockerComposePath.trim() || undefined,
jitsiConfigPath: jitsiConfigPath.trim() || undefined
});
if (resultado.sucesso) {
mostrarMensagem("success", "Configuração salva com sucesso!");
mostrarMensagem('success', 'Configuração salva com sucesso!');
} else {
mostrarMensagem("error", resultado.erro);
mostrarMensagem('error', resultado.erro);
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("Erro ao salvar configuração:", error);
mostrarMensagem("error", errorMessage || "Erro ao salvar configuração");
console.error('Erro ao salvar configuração:', error);
mostrarMensagem('error', errorMessage || 'Erro ao salvar configuração');
} finally {
processando = false;
}
@@ -101,7 +144,7 @@
async function testarConexao() {
if (!domain?.trim()) {
mostrarMensagem("error", "Preencha o domínio antes de testar");
mostrarMensagem('error', 'Preencha o domínio antes de testar');
return;
}
@@ -110,45 +153,128 @@
const resultado = await client.action(api.configuracaoJitsi.testarConexaoJitsi, {
domain: domain.trim(),
useHttps,
acceptSelfSignedCert,
acceptSelfSignedCert
});
if (resultado.sucesso) {
const mensagemSucesso = resultado.aviso
const mensagemSucesso = resultado.aviso
? `Conexão testada com sucesso! ${resultado.aviso}`
: "Conexão testada com sucesso! Servidor Jitsi está acessível.";
mostrarMensagem("success", mensagemSucesso);
: 'Conexão testada com sucesso! Servidor Jitsi está acessível.';
mostrarMensagem('success', mensagemSucesso);
} else {
mostrarMensagem("error", `Erro ao testar conexão: ${resultado.erro}`);
mostrarMensagem('error', `Erro ao testar conexão: ${resultado.erro}`);
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("Erro ao testar conexão:", error);
mostrarMensagem(
"error",
errorMessage || "Erro ao conectar com o servidor Jitsi"
);
console.error('Erro ao testar conexão:', error);
mostrarMensagem('error', errorMessage || 'Erro ao conectar com o servidor Jitsi');
} finally {
testando = false;
}
}
const statusConfig = $derived(
configAtual?.data?.ativo ? "Configurado" : "Não configurado"
);
async function testarConexaoSSH() {
if (!sshHost?.trim() || !sshUsername?.trim()) {
mostrarMensagem('error', 'Preencha Host e Usuário SSH antes de testar');
return;
}
if (!sshPassword?.trim() && !sshKeyPath?.trim()) {
mostrarMensagem('error', 'Preencha a senha SSH ou o caminho da chave antes de testar');
return;
}
testandoSSH = true;
try {
const resultado = await client.action(api.actions.jitsiServer.testarConexaoSSH, {
sshHost: sshHost.trim(),
sshPort: sshPort || 22,
sshUsername: sshUsername.trim(),
sshPassword: sshPassword.trim() || undefined,
sshKeyPath: sshKeyPath.trim() || undefined
});
if (resultado.sucesso) {
mostrarMensagem('success', resultado.mensagem);
} else {
mostrarMensagem('error', `Erro ao testar SSH: ${resultado.erro}`);
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('Erro ao testar SSH:', error);
mostrarMensagem('error', errorMessage || 'Erro ao conectar via SSH');
} finally {
testandoSSH = false;
}
}
async function aplicarConfiguracaoServidor() {
if (!configAtual?.data?._id) {
mostrarMensagem('error', 'Salve a configuração básica antes de aplicar no servidor');
return;
}
if (!sshHost?.trim() || !sshUsername?.trim()) {
mostrarMensagem('error', 'Configure o acesso SSH antes de aplicar no servidor');
return;
}
// Senha SSH é necessária para aplicar (pode ser a armazenada ou uma nova)
if (!sshPassword?.trim() && !sshKeyPath?.trim() && !configCompleta?.data?.sshPasswordHash) {
mostrarMensagem(
'error',
'Forneça a senha SSH ou o caminho da chave para aplicar a configuração'
);
return;
}
if (
!confirm(
'Deseja aplicar essas configurações no servidor Jitsi Docker? Os containers serão reiniciados.'
)
) {
return;
}
aplicandoServidor = true;
try {
const resultado = await client.action(api.actions.jitsiServer.aplicarConfiguracaoServidor, {
configId: configAtual.data._id,
sshPassword: sshPassword.trim() || undefined
});
if (resultado.sucesso) {
mostrarMensagem('success', resultado.mensagem, resultado.detalhes);
// Limpar senha após uso
sshPassword = '';
} else {
mostrarMensagem('error', `Erro ao aplicar configuração: ${resultado.erro}`);
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('Erro ao aplicar configuração:', error);
mostrarMensagem('error', errorMessage || 'Erro ao aplicar configuração no servidor');
} finally {
aplicandoServidor = false;
}
}
const statusConfig = $derived(configAtual?.data?.ativo ? 'Configurado' : 'Não configurado');
const configuradoNoServidor = $derived(configCompleta?.data?.configuradoNoServidor ?? false);
const isLoading = $derived(configAtual === undefined);
const hasError = $derived(configAtual === null && !isLoading);
</script>
<div class="container mx-auto px-4 py-6 max-w-4xl">
<div class="container mx-auto max-w-4xl px-4 py-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="p-3 bg-primary/10 rounded-xl">
<div class="bg-primary/10 rounded-xl p-3">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8 text-primary"
class="text-primary h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -162,7 +288,7 @@
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Configurações do Jitsi Meet</h1>
<h1 class="text-base-content text-3xl font-bold">Configurações do Jitsi Meet</h1>
<p class="text-base-content/60 mt-1">
Configurar servidor Jitsi para chamadas de vídeo e áudio
</p>
@@ -174,16 +300,16 @@
{#if mensagem}
<div
class="alert mb-6"
class:alert-success={mensagem.tipo === "success"}
class:alert-error={mensagem.tipo === "error"}
class:alert-success={mensagem.tipo === 'success'}
class:alert-error={mensagem.tipo === 'error'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
{#if mensagem.tipo === "success"}
{#if mensagem.tipo === 'success'}
<path
stroke-linecap="round"
stroke-linejoin="round"
@@ -199,7 +325,16 @@
/>
{/if}
</svg>
<span>{mensagem.texto}</span>
<div class="flex-1">
<span>{mensagem.texto}</span>
{#if mensagem.detalhes}
<details class="mt-2">
<summary class="cursor-pointer text-sm opacity-75">Detalhes</summary>
<pre
class="bg-base-200 mt-2 rounded p-2 font-mono text-xs whitespace-pre-wrap">{mensagem.detalhes}</pre>
</details>
{/if}
</div>
</div>
{/if}
@@ -213,16 +348,12 @@
<!-- Status -->
{#if !isLoading}
<div
class="alert {configAtual?.data?.ativo
? 'alert-success'
: 'alert-warning'} mb-6"
>
<div class="alert {configAtual?.data?.ativo ? 'alert-success' : 'alert-warning'} mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
class="h-6 w-6 shrink-0 stroke-current"
>
{#if configAtual?.data?.ativo}
<path
@@ -244,9 +375,7 @@
<strong>Status:</strong>
{statusConfig}
{#if configAtual?.data?.testadoEm}
- Última conexão testada em {new Date(
configAtual.data.testadoEm
).toLocaleString("pt-BR")}
- Última conexão testada em {new Date(configAtual.data.testadoEm).toLocaleString('pt-BR')}
{/if}
</span>
</div>
@@ -258,7 +387,7 @@
<div class="card-body">
<h2 class="card-title mb-4">Dados do Servidor Jitsi</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<!-- Domínio -->
<div class="form-control md:col-span-2">
<label class="label" for="jitsi-domain">
@@ -308,30 +437,25 @@
class="input input-bordered"
/>
<div class="label">
<span class="label-text-alt"
>Apenas letras, números e hífens</span
>
<span class="label-text-alt">Apenas letras, números e hífens</span>
</div>
</div>
</div>
<!-- Opções de Segurança -->
<div class="divider"></div>
<h3 class="font-bold mb-2">Configurações de Segurança</h3>
<h3 class="mb-2 font-bold">Configurações de Segurança</h3>
<div class="flex flex-col gap-4">
<div class="form-control">
<label class="label cursor-pointer gap-3">
<input
type="checkbox"
bind:checked={useHttps}
class="checkbox checkbox-primary"
/>
<input type="checkbox" bind:checked={useHttps} class="checkbox checkbox-primary" />
<span class="label-text font-medium">Usar HTTPS</span>
</label>
<div class="label">
<span class="label-text-alt"
>Ativado automaticamente se domínio contém :8443. Desmarque para usar HTTP (não recomendado para produção)</span
>Ativado automaticamente se domínio contém :8443. Desmarque para usar HTTP (não
recomendado para produção)</span
>
</div>
</div>
@@ -347,14 +471,228 @@
</label>
<div class="label">
<span class="label-text-alt text-warning"
>Habilitar apenas para desenvolvimento local com certificados autoassinados. Em produção, use certificados válidos.</span
>Habilitar apenas para desenvolvimento local com certificados autoassinados. Em
produção, use certificados válidos.</span
>
</div>
</div>
</div>
<!-- Configurações SSH/Docker -->
<div class="divider"></div>
<div class="mb-2 flex items-center justify-between">
<h3 class="font-bold">Configuração SSH/Docker (Opcional)</h3>
<label class="label cursor-pointer gap-2">
<span class="label-text text-sm">Configurar servidor via SSH</span>
<input type="checkbox" bind:checked={mostrarConfigSSH} class="checkbox checkbox-sm" />
</label>
</div>
{#if mostrarConfigSSH}
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
<!-- SSH Host -->
<div class="form-control">
<label class="label" for="ssh-host">
<span class="label-text font-medium">Host SSH *</span>
</label>
<input
id="ssh-host"
type="text"
bind:value={sshHost}
placeholder="192.168.1.100 ou servidor.local"
class="input input-bordered"
/>
<div class="label">
<span class="label-text-alt">Endereço do servidor Docker</span>
</div>
</div>
<!-- SSH Port -->
<div class="form-control">
<label class="label" for="ssh-port">
<span class="label-text font-medium">Porta SSH</span>
</label>
<input
id="ssh-port"
type="number"
bind:value={sshPort}
placeholder="22"
min="1"
max="65535"
class="input input-bordered"
/>
</div>
<!-- SSH Username -->
<div class="form-control">
<label class="label" for="ssh-username">
<span class="label-text font-medium">Usuário SSH *</span>
</label>
<input
id="ssh-username"
type="text"
bind:value={sshUsername}
placeholder="usuario"
class="input input-bordered"
/>
</div>
<!-- SSH Password ou Key Path -->
<div class="form-control">
<label class="label" for="ssh-password">
<span class="label-text font-medium">Senha SSH</span>
</label>
<input
id="ssh-password"
type="password"
bind:value={sshPassword}
placeholder="Deixe vazio para manter senha salva"
class="input input-bordered"
/>
<div class="label">
<span class="label-text-alt">Ou use caminho da chave SSH abaixo</span>
</div>
</div>
<!-- SSH Key Path -->
<div class="form-control md:col-span-2">
<label class="label" for="ssh-key-path">
<span class="label-text font-medium">Caminho da Chave SSH</span>
</label>
<input
id="ssh-key-path"
type="text"
bind:value={sshKeyPath}
placeholder="/home/usuario/.ssh/id_rsa"
class="input input-bordered"
/>
<div class="label">
<span class="label-text-alt">Caminho no servidor SSH para a chave privada</span>
</div>
</div>
<!-- Docker Compose Path -->
<div class="form-control">
<label class="label" for="docker-compose-path">
<span class="label-text font-medium">Caminho Docker Compose</span>
</label>
<input
id="docker-compose-path"
type="text"
bind:value={dockerComposePath}
placeholder="/home/usuario/jitsi-docker"
class="input input-bordered"
/>
<div class="label">
<span class="label-text-alt">Diretório com docker-compose.yml</span>
</div>
</div>
<!-- Jitsi Config Path -->
<div class="form-control">
<label class="label" for="jitsi-config-path">
<span class="label-text font-medium">Caminho Config Jitsi</span>
</label>
<input
id="jitsi-config-path"
type="text"
bind:value={jitsiConfigPath}
placeholder="~/.jitsi-meet-cfg"
class="input input-bordered"
/>
<div class="label">
<span class="label-text-alt">Diretório de configurações do Jitsi</span>
</div>
</div>
</div>
<!-- Status Configuração Servidor -->
{#if configuradoNoServidor}
<div class="alert alert-success mt-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>
Configuração aplicada no servidor
{#if configCompleta?.data?.configuradoNoServidorEm}
em {new Date(configCompleta.data.configuradoNoServidorEm).toLocaleString('pt-BR')}
{/if}
</span>
</div>
{/if}
<!-- Botões SSH/Docker -->
<div class="mt-4 flex gap-3">
<button
class="btn btn-outline btn-info"
onclick={testarConexaoSSH}
disabled={testandoSSH || processando || aplicandoServidor}
>
{#if testandoSSH}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{/if}
Testar SSH
</button>
<button
class="btn btn-success"
onclick={aplicarConfiguracaoServidor}
disabled={aplicandoServidor ||
processando ||
testando ||
testandoSSH ||
!configAtual?.data?._id}
>
{#if aplicandoServidor}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
{/if}
Aplicar no Servidor Docker
</button>
</div>
{/if}
<!-- Ações -->
<div class="card-actions justify-end mt-6 gap-3">
<div class="card-actions mt-6 justify-end gap-3">
<button
class="btn btn-outline btn-info"
onclick={testarConexao}
@@ -412,12 +750,12 @@
{/if}
<!-- Exemplos Comuns -->
<div class="card bg-base-100 shadow-xl mt-6">
<div class="card bg-base-100 mt-6 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Exemplos de Configuração</h2>
<div class="overflow-x-auto">
<table class="table table-sm">
<table class="table-sm table">
<thead>
<tr>
<th>Ambiente</th>
@@ -461,7 +799,7 @@
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
class="h-6 w-6 shrink-0 stroke-current"
>
<path
stroke-linecap="round"
@@ -473,12 +811,12 @@
<div>
<p>
<strong>Dica:</strong> Para servidor Jitsi Docker local, use
<code>localhost:8443</code> com HTTPS habilitado. Para servidor em
produção, use o domínio completo do seu servidor Jitsi.
<code>localhost:8443</code> com HTTPS habilitado. Para servidor em produção, use o domínio completo
do seu servidor Jitsi.
</p>
<p class="text-sm mt-1">
A configuração será aplicada imediatamente após salvar. Usuários precisarão
recarregar a página para usar a nova configuração.
<p class="mt-1 text-sm">
A configuração será aplicada imediatamente após salvar. Usuários precisarão recarregar a
página para usar a nova configuração.
</p>
</div>
</div>
@@ -490,7 +828,7 @@
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
class="h-6 w-6 shrink-0 stroke-current"
>
<path
stroke-linecap="round"
@@ -501,10 +839,10 @@
</svg>
<div>
<p class="font-bold">Certificados Autoassinados Ativados</p>
<p class="text-sm mt-1">
Para certificados autoassinados (desenvolvimento local), os usuários precisarão
aceitar o certificado no navegador na primeira conexão. Em produção, use
certificados válidos (Let's Encrypt, etc.).
<p class="mt-1 text-sm">
Para certificados autoassinados (desenvolvimento local), os usuários precisarão aceitar o
certificado no navegador na primeira conexão. Em produção, use certificados válidos (Let's
Encrypt, etc.).
</p>
</div>
</div>
@@ -517,7 +855,7 @@
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
class="h-6 w-6 shrink-0 stroke-current"
>
<path
stroke-linecap="round"
@@ -528,12 +866,11 @@
</svg>
<div>
<p class="font-bold">HTTP Ativado (Não Seguro)</p>
<p class="text-sm mt-1">
O uso de HTTP não é recomendado para produção. Use HTTPS com certificado válido
para garantir segurança nas chamadas.
<p class="mt-1 text-sm">
O uso de HTTP não é recomendado para produção. Use HTTPS com certificado válido para
garantir segurança nas chamadas.
</p>
</div>
</div>
{/if}
</div>