533 lines
15 KiB
Svelte
533 lines
15 KiB
Svelte
<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';
|
|
|
|
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');
|
|
let useHttps = $state(false);
|
|
let acceptSelfSignedCert = $state(false);
|
|
|
|
let processando = $state(false);
|
|
let testando = $state(false);
|
|
let mensagem = $state<{ tipo: 'success' | 'error'; texto: string; detalhes?: string } | null>(
|
|
null
|
|
);
|
|
|
|
function mostrarMensagem(tipo: 'success' | 'error', texto: string, detalhes?: string) {
|
|
mensagem = { tipo, texto, detalhes };
|
|
setTimeout(() => {
|
|
mensagem = null;
|
|
}, 8000);
|
|
}
|
|
|
|
// Carregar config existente
|
|
$effect(() => {
|
|
if (configAtual?.data) {
|
|
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';
|
|
useHttps = false;
|
|
acceptSelfSignedCert = false;
|
|
}
|
|
});
|
|
|
|
// Ativar HTTPS automaticamente se domínio contém porta 8443
|
|
$effect(() => {
|
|
if (domain.includes(':8443')) {
|
|
useHttps = true;
|
|
// Para localhost com porta 8443, geralmente é certificado autoassinado
|
|
if (domain.includes('localhost')) {
|
|
acceptSelfSignedCert = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
async function salvarConfiguracao() {
|
|
// Validação de campos obrigatórios
|
|
if (!domain?.trim() || !appId?.trim() || !roomPrefix?.trim()) {
|
|
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');
|
|
return;
|
|
}
|
|
|
|
if (!currentUser?.data) {
|
|
mostrarMensagem('error', 'Usuário não autenticado');
|
|
return;
|
|
}
|
|
|
|
processando = true;
|
|
try {
|
|
const resultado = await client.mutation(api.configuracaoJitsi.salvarConfigJitsi, {
|
|
domain: domain.trim(),
|
|
appId: appId.trim(),
|
|
roomPrefix: roomPrefix.trim(),
|
|
useHttps,
|
|
acceptSelfSignedCert,
|
|
configuradoPorId: currentUser.data._id as Id<'usuarios'>
|
|
});
|
|
|
|
if (resultado.sucesso) {
|
|
mostrarMensagem('success', 'Configuração salva com sucesso!');
|
|
} else {
|
|
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');
|
|
} finally {
|
|
processando = false;
|
|
}
|
|
}
|
|
|
|
async function testarConexao() {
|
|
if (!domain?.trim()) {
|
|
mostrarMensagem('error', 'Preencha o domínio antes de testar');
|
|
return;
|
|
}
|
|
|
|
testando = true;
|
|
try {
|
|
const resultado = await client.action(api.configuracaoJitsi.testarConexaoJitsi, {
|
|
domain: domain.trim(),
|
|
useHttps,
|
|
acceptSelfSignedCert
|
|
});
|
|
|
|
if (resultado.sucesso) {
|
|
const mensagemSucesso = resultado.aviso
|
|
? `Conexão testada com sucesso! ${resultado.aviso}`
|
|
: 'Conexão testada com sucesso! Servidor Jitsi está acessível.';
|
|
mostrarMensagem('success', mensagemSucesso);
|
|
} else {
|
|
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');
|
|
} finally {
|
|
testando = false;
|
|
}
|
|
}
|
|
|
|
const statusConfig = $derived(configAtual?.data?.ativo ? 'Configurado' : 'Não configurado');
|
|
|
|
const isLoading = $derived(configAtual === undefined);
|
|
const hasError = $derived(configAtual === null && !isLoading);
|
|
</script>
|
|
|
|
<div class="container mx-auto max-w-4xl px-4 py-6">
|
|
<!-- Header -->
|
|
<div class="mb-6 flex items-center justify-between">
|
|
<div class="flex items-center gap-4">
|
|
<div class="bg-primary/10 rounded-xl p-3">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="text-primary h-8 w-8"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mensagens -->
|
|
{#if mensagem}
|
|
<div
|
|
class="alert mb-6"
|
|
class:alert-success={mensagem.tipo === 'success'}
|
|
class:alert-error={mensagem.tipo === 'error'}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
{#if mensagem.tipo === 'success'}
|
|
<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"
|
|
/>
|
|
{:else}
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
{/if}
|
|
</svg>
|
|
<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}
|
|
|
|
<!-- Loading State -->
|
|
{#if isLoading}
|
|
<div class="alert alert-info mb-6">
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
<span>Carregando configurações...</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Status -->
|
|
{#if !isLoading}
|
|
<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="h-6 w-6 shrink-0 stroke-current"
|
|
>
|
|
{#if configAtual?.data?.ativo}
|
|
<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"
|
|
/>
|
|
{:else}
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
/>
|
|
{/if}
|
|
</svg>
|
|
<span>
|
|
<strong>Status:</strong>
|
|
{statusConfig}
|
|
{#if configAtual?.data?.testadoEm}
|
|
- Última conexão testada em {new Date(configAtual.data.testadoEm).toLocaleString('pt-BR')}
|
|
{/if}
|
|
</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Formulário -->
|
|
{#if !isLoading}
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title mb-4">Dados do Servidor Jitsi</h2>
|
|
|
|
<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">
|
|
<span class="label-text font-medium">Domínio do Servidor *</span>
|
|
</label>
|
|
<input
|
|
id="jitsi-domain"
|
|
type="text"
|
|
bind:value={domain}
|
|
placeholder="localhost:8443 ou meet.example.com"
|
|
class="input input-bordered"
|
|
/>
|
|
<div class="label">
|
|
<span class="label-text-alt"
|
|
>Ex: localhost:8443 (local), meet.example.com (produção)</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- App ID -->
|
|
<div class="form-control">
|
|
<label class="label" for="jitsi-app-id">
|
|
<span class="label-text font-medium">App ID *</span>
|
|
</label>
|
|
<input
|
|
id="jitsi-app-id"
|
|
type="text"
|
|
bind:value={appId}
|
|
placeholder="sgse-app"
|
|
class="input input-bordered"
|
|
/>
|
|
<div class="label">
|
|
<span class="label-text-alt">Identificador da aplicação Jitsi</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Room Prefix -->
|
|
<div class="form-control">
|
|
<label class="label" for="jitsi-room-prefix">
|
|
<span class="label-text font-medium">Prefixo de Sala *</span>
|
|
</label>
|
|
<input
|
|
id="jitsi-room-prefix"
|
|
type="text"
|
|
bind:value={roomPrefix}
|
|
placeholder="sgse"
|
|
class="input input-bordered"
|
|
/>
|
|
<div class="label">
|
|
<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="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" />
|
|
<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
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer gap-3">
|
|
<input
|
|
type="checkbox"
|
|
bind:checked={acceptSelfSignedCert}
|
|
class="checkbox checkbox-warning"
|
|
/>
|
|
<span class="label-text font-medium">Aceitar Certificados Autoassinados</span>
|
|
</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
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ações -->
|
|
<div class="card-actions mt-6 justify-end gap-3">
|
|
<button
|
|
class="btn btn-outline btn-info"
|
|
onclick={testarConexao}
|
|
disabled={testando || processando}
|
|
>
|
|
{#if testando}
|
|
<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 Conexão
|
|
</button>
|
|
|
|
<button
|
|
class="btn btn-primary"
|
|
onclick={salvarConfiguracao}
|
|
disabled={processando || testando}
|
|
>
|
|
{#if processando}
|
|
<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="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"
|
|
/>
|
|
</svg>
|
|
{/if}
|
|
Salvar Configuração
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Exemplos Comuns -->
|
|
<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-sm table">
|
|
<thead>
|
|
<tr>
|
|
<th>Ambiente</th>
|
|
<th>Domínio</th>
|
|
<th>App ID</th>
|
|
<th>Prefixo Sala</th>
|
|
<th>HTTPS</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Docker Local</strong></td>
|
|
<td>localhost:8443</td>
|
|
<td>sgse-app</td>
|
|
<td>sgse</td>
|
|
<td>Sim</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Produção</strong></td>
|
|
<td>meet.example.com</td>
|
|
<td>sgse-app</td>
|
|
<td>sgse</td>
|
|
<td>Sim</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Desenvolvimento</strong></td>
|
|
<td>localhost:8000</td>
|
|
<td>sgse-app</td>
|
|
<td>sgse-dev</td>
|
|
<td>Não</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Avisos -->
|
|
<div class="alert alert-info mt-6">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
<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.
|
|
</p>
|
|
<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>
|
|
|
|
<!-- Aviso sobre Certificados Autoassinados -->
|
|
{#if acceptSelfSignedCert}
|
|
<div class="alert alert-warning mt-4">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
></path>
|
|
</svg>
|
|
<div>
|
|
<p class="font-bold">Certificados Autoassinados Ativados</p>
|
|
<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>
|
|
{/if}
|
|
|
|
<!-- Aviso sobre HTTP -->
|
|
{#if !useHttps}
|
|
<div class="alert alert-error mt-4">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
></path>
|
|
</svg>
|
|
<div>
|
|
<p class="font-bold">HTTP Ativado (Não Seguro)</p>
|
|
<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>
|