- Updated various Svelte components to improve code readability and maintainability. - Standardized button classes across components for a consistent user interface. - Enhanced error handling and user feedback in modals and forms. - Cleaned up unnecessary imports and optimized component structure for better performance.
480 lines
13 KiB
Svelte
480 lines
13 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';
|
|
|
|
let { onClose }: { onClose: () => void } = $props();
|
|
|
|
const client = useConvexClient();
|
|
const alertasQuery = useQuery(api.monitoramento.listarAlertas, {});
|
|
const alertas = $derived.by(() => {
|
|
if (!alertasQuery) return [];
|
|
// O useQuery pode retornar o array diretamente ou em .data
|
|
if (Array.isArray(alertasQuery)) return alertasQuery;
|
|
return alertasQuery.data ?? [];
|
|
});
|
|
|
|
// Estado para novo alerta
|
|
let editingAlertId = $state<Id<'alertConfigurations'> | null>(null);
|
|
let metricName = $state('cpuUsage');
|
|
let threshold = $state(80);
|
|
let operator = $state<'>' | '<' | '>=' | '<=' | '=='>('>');
|
|
let enabled = $state(true);
|
|
let notifyByEmail = $state(false);
|
|
let notifyByChat = $state(true);
|
|
let saving = $state(false);
|
|
let showForm = $state(false);
|
|
|
|
const metricOptions = [
|
|
{ value: 'cpuUsage', label: 'Uso de CPU (%)' },
|
|
{ value: 'memoryUsage', label: 'Uso de Memória (%)' },
|
|
{ value: 'networkLatency', label: 'Latência de Rede (ms)' },
|
|
{ value: 'storageUsed', label: 'Armazenamento Usado (%)' },
|
|
{ value: 'usuariosOnline', label: 'Usuários Online' },
|
|
{ value: 'mensagensPorMinuto', label: 'Mensagens por Minuto' },
|
|
{ value: 'tempoRespostaMedio', label: 'Tempo de Resposta (ms)' },
|
|
{ value: 'errosCount', label: 'Contagem de Erros' }
|
|
];
|
|
|
|
const operatorOptions = [
|
|
{ value: '>', label: 'Maior que (>)' },
|
|
{ value: '>=', label: 'Maior ou igual (≥)' },
|
|
{ value: '<', label: 'Menor que (<)' },
|
|
{ value: '<=', label: 'Menor ou igual (≤)' },
|
|
{ value: '==', label: 'Igual a (=)' }
|
|
];
|
|
|
|
function resetForm() {
|
|
editingAlertId = null;
|
|
metricName = 'cpuUsage';
|
|
threshold = 80;
|
|
operator = '>';
|
|
enabled = true;
|
|
notifyByEmail = false;
|
|
notifyByChat = true;
|
|
showForm = false;
|
|
}
|
|
|
|
function editAlert(alert: any) {
|
|
editingAlertId = alert._id;
|
|
metricName = alert.metricName;
|
|
threshold = alert.threshold;
|
|
operator = alert.operator;
|
|
enabled = alert.enabled;
|
|
notifyByEmail = alert.notifyByEmail;
|
|
notifyByChat = alert.notifyByChat;
|
|
showForm = true;
|
|
}
|
|
|
|
async function saveAlert() {
|
|
saving = true;
|
|
try {
|
|
await client.mutation(api.monitoramento.configurarAlerta, {
|
|
alertId: editingAlertId || undefined,
|
|
metricName,
|
|
threshold,
|
|
operator,
|
|
enabled,
|
|
notifyByEmail,
|
|
notifyByChat
|
|
});
|
|
|
|
resetForm();
|
|
} catch (error) {
|
|
console.error('Erro ao salvar alerta:', error);
|
|
alert('Erro ao salvar alerta. Tente novamente.');
|
|
} finally {
|
|
saving = false;
|
|
}
|
|
}
|
|
|
|
async function deleteAlert(alertId: Id<'alertConfigurations'>) {
|
|
if (!confirm('Tem certeza que deseja deletar este alerta?')) return;
|
|
|
|
try {
|
|
await client.mutation(api.monitoramento.deletarAlerta, { alertId });
|
|
} catch (error) {
|
|
console.error('Erro ao deletar alerta:', error);
|
|
alert('Erro ao deletar alerta. Tente novamente.');
|
|
}
|
|
}
|
|
|
|
function getMetricLabel(metricName: string): string {
|
|
return metricOptions.find((m) => m.value === metricName)?.label || metricName;
|
|
}
|
|
|
|
function getOperatorLabel(op: string): string {
|
|
return operatorOptions.find((o) => o.value === op)?.label || op;
|
|
}
|
|
</script>
|
|
|
|
<dialog class="modal modal-open">
|
|
<div class="modal-box from-base-100 to-base-200 max-w-4xl bg-linear-to-br">
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
|
onclick={onClose}
|
|
>
|
|
✕
|
|
</button>
|
|
|
|
<h3 class="text-primary mb-2 text-3xl font-bold">⚙️ Configuração de Alertas</h3>
|
|
<p class="text-base-content/60 mb-6">
|
|
Configure alertas personalizados para monitoramento do sistema
|
|
</p>
|
|
|
|
<!-- Botão Novo Alerta -->
|
|
{#if !showForm}
|
|
<button type="button" class="btn btn-primary mb-6" onclick={() => (showForm = true)}>
|
|
<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="M12 4v16m8-8H4"
|
|
/>
|
|
</svg>
|
|
Novo Alerta
|
|
</button>
|
|
{/if}
|
|
|
|
<!-- Formulário de Alerta -->
|
|
{#if showForm}
|
|
<div class="card bg-base-100 border-primary/20 mb-6 border-2 shadow-xl">
|
|
<div class="card-body">
|
|
<h4 class="card-title text-xl">
|
|
{editingAlertId ? 'Editar Alerta' : 'Novo Alerta'}
|
|
</h4>
|
|
|
|
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<!-- Métrica -->
|
|
<div class="form-control">
|
|
<label class="label" for="metric">
|
|
<span class="label-text font-semibold">Métrica</span>
|
|
</label>
|
|
<select
|
|
id="metric"
|
|
class="select select-bordered select-primary"
|
|
bind:value={metricName}
|
|
>
|
|
{#each metricOptions as option}
|
|
<option value={option.value}>{option.label}</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Operador -->
|
|
<div class="form-control">
|
|
<label class="label" for="operator">
|
|
<span class="label-text font-semibold">Condição</span>
|
|
</label>
|
|
<select
|
|
id="operator"
|
|
class="select select-bordered select-primary"
|
|
bind:value={operator}
|
|
>
|
|
{#each operatorOptions as option}
|
|
<option value={option.value}>{option.label}</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Threshold -->
|
|
<div class="form-control">
|
|
<label class="label" for="threshold">
|
|
<span class="label-text font-semibold">Valor Limite</span>
|
|
</label>
|
|
<input
|
|
id="threshold"
|
|
type="number"
|
|
class="input input-bordered input-primary"
|
|
bind:value={threshold}
|
|
min="0"
|
|
step="1"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Ativo -->
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-4">
|
|
<span class="label-text font-semibold">Alerta Ativo</span>
|
|
<input type="checkbox" class="toggle toggle-primary" bind:checked={enabled} />
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notificações -->
|
|
<div class="divider">Método de Notificação</div>
|
|
<div class="flex gap-6">
|
|
<label class="label cursor-pointer gap-3">
|
|
<input
|
|
type="checkbox"
|
|
class="checkbox checkbox-primary"
|
|
bind:checked={notifyByChat}
|
|
/>
|
|
<span class="label-text">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="mr-2 inline 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 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
|
|
/>
|
|
</svg>
|
|
Notificar por Chat
|
|
</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer gap-3">
|
|
<input
|
|
type="checkbox"
|
|
class="checkbox checkbox-secondary"
|
|
bind:checked={notifyByEmail}
|
|
/>
|
|
<span class="label-text">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="mr-2 inline h-5 w-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
Notificar por E-mail
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Preview -->
|
|
<div class="alert alert-info 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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
<div>
|
|
<h4 class="font-bold">Preview do Alerta:</h4>
|
|
<p class="text-sm">
|
|
Alertar quando <strong>{getMetricLabel(metricName)}</strong> for
|
|
<strong>{getOperatorLabel(operator)}</strong> a
|
|
<strong>{threshold}</strong>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Botões -->
|
|
<div class="card-actions mt-4 justify-end">
|
|
<button type="button" class="btn" onclick={resetForm} disabled={saving}>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary"
|
|
onclick={saveAlert}
|
|
disabled={saving || (!notifyByChat && !notifyByEmail)}
|
|
>
|
|
{#if saving}
|
|
<span class="loading loading-spinner"></span>
|
|
Salvando...
|
|
{: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>
|
|
Salvar Alerta
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Lista de Alertas -->
|
|
<div class="divider">Alertas Configurados</div>
|
|
|
|
{#if alertas.length > 0}
|
|
<div class="overflow-x-auto">
|
|
<table class="table-zebra table">
|
|
<thead>
|
|
<tr>
|
|
<th>Métrica</th>
|
|
<th>Condição</th>
|
|
<th>Status</th>
|
|
<th>Notificações</th>
|
|
<th>Ações</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each alertas as alerta}
|
|
<tr class={!alerta.enabled ? 'opacity-50' : ''}>
|
|
<td>
|
|
<div class="font-semibold">
|
|
{getMetricLabel(alerta.metricName)}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="badge badge-outline">
|
|
{getOperatorLabel(alerta.operator)}
|
|
{alerta.threshold}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{#if alerta.enabled}
|
|
<div class="badge badge-success gap-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-3 w-3"
|
|
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>
|
|
Ativo
|
|
</div>
|
|
{:else}
|
|
<div class="badge badge-ghost gap-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-3 w-3"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<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"
|
|
/>
|
|
</svg>
|
|
Inativo
|
|
</div>
|
|
{/if}
|
|
</td>
|
|
<td>
|
|
<div class="flex gap-1">
|
|
{#if alerta.notifyByChat}
|
|
<div class="badge badge-primary badge-sm">Chat</div>
|
|
{/if}
|
|
{#if alerta.notifyByEmail}
|
|
<div class="badge badge-secondary badge-sm">Email</div>
|
|
{/if}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="flex gap-2">
|
|
<button type="button" class="btn btn-xs" onclick={() => editAlert(alerta)}>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-xs text-error"
|
|
onclick={() => deleteAlert(alerta._id)}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{:else}
|
|
<div class="alert">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="stroke-info h-6 w-6 shrink-0"
|
|
>
|
|
<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>
|
|
<span>Nenhum alerta configurado. Clique em "Novo Alerta" para criar um.</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="modal-action">
|
|
<button type="button" class="btn btn-lg" onclick={onClose}>Fechar</button>
|
|
</div>
|
|
</div>
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
<form method="dialog" class="modal-backdrop" onclick={onClose}>
|
|
<button type="button">close</button>
|
|
</form>
|
|
</dialog>
|