diff options
Diffstat (limited to 'frontend/src/lib/components/AddServiceModal.svelte')
| -rw-r--r-- | frontend/src/lib/components/AddServiceModal.svelte | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/frontend/src/lib/components/AddServiceModal.svelte b/frontend/src/lib/components/AddServiceModal.svelte new file mode 100644 index 0000000..de1ba13 --- /dev/null +++ b/frontend/src/lib/components/AddServiceModal.svelte @@ -0,0 +1,224 @@ +<script lang="ts"> + import type { Service } from '$lib/types'; + + let { + show = false, + onclose, + oncreated + }: { + show: boolean; + onclose: () => void; + oncreated: (svc: Service) => void; + } = $props(); + + let name = $state(''); + let url = $state(''); + let discordWebhookUrl = $state(''); + let alertEmail = $state(''); + let keywordToFind = $state(''); + let error = $state(''); + let saving = $state(false); + + async function handleSubmit(e: Event) { + e.preventDefault(); + error = ''; + + if (!name.trim() || !url.trim()) { + error = 'Preencha nome e URL'; + return; + } + try { + new URL(url); + } catch { + error = 'URL inválida — deve começar com http:// ou https://'; + return; + } + + saving = true; + try { + const res = await fetch('/api/services', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: name.trim(), + url: url.trim(), + discord_webhook_url: discordWebhookUrl.trim() || undefined, + alert_email: alertEmail.trim() || undefined, + keyword_to_find: keywordToFind.trim() || undefined + }) + }); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.error || 'Erro ao criar serviço'); + } + const svc: Service = await res.json(); + oncreated(svc); + name = ''; + url = ''; + discordWebhookUrl = ''; + alertEmail = ''; + keywordToFind = ''; + onclose(); + } catch (e: any) { + error = e.message; + } finally { + saving = false; + } + } + + function handleBackdrop(e: MouseEvent) { + if (e.target === e.currentTarget) onclose(); + } + + function handleKeydown(e: KeyboardEvent) { + if (e.key === 'Escape') onclose(); + } +</script> + +<svelte:window onkeydown={handleKeydown} /> + +{#if show} + <!-- svelte-ignore a11y_click_events_have_key_events --> + <div + role="button" + tabindex="-1" + aria-label="Fechar modal" + class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" + onclick={handleBackdrop} + > + <div + class="w-full max-w-md animate-[modalIn_0.2s_ease-out] rounded-2xl border border-[var(--border-color)] bg-[var(--bg-secondary)] p-6 shadow-2xl" + > + <div class="mb-5 flex items-center justify-between"> + <h2 class="text-sm font-semibold uppercase tracking-wider text-[var(--text-secondary)]"> + Novo Serviço + </h2> + <button + onclick={onclose} + class="rounded-lg p-1.5 text-[var(--text-muted)] transition-colors hover:bg-[var(--border-color)] hover:text-white" + aria-label="Fechar" + > + <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> + <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /> + </svg> + </button> + </div> + + <form onsubmit={handleSubmit} class="space-y-4"> + <div> + <label for="modal-name" class="mb-1.5 block text-xs font-medium text-[var(--text-secondary)]"> + Nome do Serviço + </label> + <input + id="modal-name" + bind:value={name} + type="text" + placeholder="Meu Site" + class="w-full rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] px-3.5 py-2.5 text-sm text-white outline-none transition-all placeholder:text-[var(--text-muted)] focus:border-[var(--green)]/50 focus:ring-1 focus:ring-[var(--green)]/20" + /> + </div> + + <div> + <label for="modal-url" class="mb-1.5 block text-xs font-medium text-[var(--text-secondary)]"> + URL + </label> + <input + id="modal-url" + bind:value={url} + type="url" + placeholder="https://exemplo.com" + class="w-full rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] px-3.5 py-2.5 text-sm text-white outline-none transition-all placeholder:text-[var(--text-muted)] focus:border-[var(--green)]/50 focus:ring-1 focus:ring-[var(--green)]/20" + /> + </div> + + <div class="border-t border-[var(--border-color)] pt-4"> + <p class="mb-3 text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)]"> + Alertas (opcional) + </p> + <div> + <label + for="modal-discord" + class="mb-1.5 block text-xs font-medium text-[var(--text-secondary)]" + > + Webhook do Discord + </label> + <input + id="modal-discord" + bind:value={discordWebhookUrl} + type="url" + placeholder="https://discord.com/api/webhooks/..." + class="w-full rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] px-3.5 py-2.5 text-sm text-white outline-none transition-all placeholder:text-[var(--text-muted)] focus:border-[var(--green)]/50 focus:ring-1 focus:ring-[var(--green)]/20" + /> + </div> + <div class="mt-3"> + <label + for="modal-email" + class="mb-1.5 block text-xs font-medium text-[var(--text-secondary)]" + > + E-mail de Alerta + </label> + <input + id="modal-email" + bind:value={alertEmail} + type="email" + placeholder="admin@exemplo.com" + class="w-full rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] px-3.5 py-2.5 text-sm text-white outline-none transition-all placeholder:text-[var(--text-muted)] focus:border-[var(--green)]/50 focus:ring-1 focus:ring-[var(--green)]/20" + /> + </div> + <div class="mt-3"> + <label + for="modal-keyword" + class="mb-1.5 block text-xs font-medium text-[var(--text-secondary)]" + > + Verificar Palavra-chave na Página (opcional) + </label> + <input + id="modal-keyword" + bind:value={keywordToFind} + type="text" + placeholder="Bem-vindo, Login, OK..." + class="w-full rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] px-3.5 py-2.5 text-sm text-white outline-none transition-all placeholder:text-[var(--text-muted)] focus:border-[var(--green)]/50 focus:ring-1 focus:ring-[var(--green)]/20" + /> + <p class="mt-1 text-[9px] text-[var(--text-muted)]"> + Se informado, o monitor vai ler o HTML e marcar como DOWN se não encontrar essa palavra. + </p> + </div> + </div> + + {#if error} + <p class="rounded-lg bg-[var(--red)]/5 px-3 py-2 text-xs text-[var(--red)]">{error}</p> + {/if} + + <div class="flex gap-3 pt-1"> + <button + type="button" + onclick={onclose} + class="flex-1 rounded-xl border border-[var(--border-color)] px-4 py-2.5 text-sm font-medium text-[var(--text-muted)] transition-colors hover:border-[var(--text-muted)]/30 hover:text-white" + > + Cancelar + </button> + <button + type="submit" + disabled={saving} + class="flex-1 rounded-xl border border-[var(--green)] bg-[var(--green)]/10 px-4 py-2.5 text-sm font-medium text-[var(--green)] transition-all hover:bg-[var(--green)]/20 disabled:cursor-not-allowed disabled:opacity-40" + > + {saving ? 'Salvando…' : 'Adicionar'} + </button> + </div> + </form> + </div> + </div> +{/if} + +<style> + @keyframes modalIn { + from { + opacity: 0; + transform: scale(0.95) translateY(-8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } + } +</style> |