aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/lib/components/AddServiceModal.svelte
diff options
context:
space:
mode:
authorzwlucas <lucas.fariamo08@gmail.com>2026-05-29 18:57:37 +0000
committerzwlucas <lucas.fariamo08@gmail.com>2026-05-29 18:57:37 +0000
commit6cc0bfd1d79074df790272b8091b1f0226d14283 (patch)
treecab18b0f708cf3b0eaeeeb98e2cf81459365b33e /frontend/src/lib/components/AddServiceModal.svelte
downloadyaum-6cc0bfd1d79074df790272b8091b1f0226d14283.tar.gz
yaum-6cc0bfd1d79074df790272b8091b1f0226d14283.zip
feat: upload project
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'frontend/src/lib/components/AddServiceModal.svelte')
-rw-r--r--frontend/src/lib/components/AddServiceModal.svelte224
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>