<template> <section class="profile-section"> <div class="section-header"> <h3 class="heading">Alerts</h3> <button class="button" @click="showAlertDialog = true" > <Icon icon="alarm-add" /> <span class="button-label">New alert</span> </button> </div> <ul class="alerts nolist"> <li v-for="alert in alerts" :key="`alert-${alert.id}`" class="alert" > <div class="alert-details" :class="{ and: alert.and.fields, or: !alert.and.fields }" > <span v-if="alert.tags.length > 0" class="alert-detail alert-tags" :class="{ and: alert.and.tags, or: !alert.and.tags }" > <span class="alert-values"> <span v-for="tag in alert.tags" :key="`tag-${alert.id}-${tag.id}`" class="alert-key" > <a :href="`/tag/${tag.slug}`" class="alert-value link" >{{ tag.name }}</a> </span> </span> </span> <span v-if="alert.actors.length > 0" class="alert-detail alert-actors" :class="{ and: alert.and.actors, or: !alert.and.actors }" > <span class="alert-values">with <span v-for="actor in alert.actors" :key="`actor-${alert.id}-${actor.id}`" class="alert-key" > <a :href="`/actor/${actor.id}/${actor.slug}`" class="alert-value link" >{{ actor.name }}</a> </span> </span> </span> <span v-if="alert.entities.length > 0" class="alert-detail alert-entities or" > <span class="alert-values">for <span v-for="entity in alert.entities" :key="`entity-${alert.id}-${entity.id}`" class="alert-key" > <a :href="`/${entity.type}/${entity.slug}`" class="alert-value link" > <Icon v-if="entity.type === 'network'" icon="device_hub" />{{ entity.name }} </a> </span> </span> </span> <span v-if="alert.matches.length > 0" class="alert-detail alert-matches" :class="{ and: alert.and.matches, or: !alert.and.matches }" > <span class="alert-values">matching <span v-for="match in alert.matches" :key="`match-${alert.id}-${match.id}`" class="alert-key" > <span class="alert-value">{{ match.property }}: <span class="alert-regex" title="If your original expression was not a /regular expression/, it was converted, and new characters may have been added for syntactical purposes. These characters do not alter the function of the expression; they ensure it." >{{ match.expression }}</span> </span> </span> </span> </span> </div> <div class="alert-meta"> <div class="alert-triggers"> <Icon v-if="alert.notify" v-tooltip="alert.notify ? 'Notify in traxxx' : undefined" icon="bell2" class="trigger" /> <Icon v-if="alert.stashes.some((stash) => !stash.isPrimary)" v-tooltip="`Add to ${alert.stashes.map((stash) => stash.name).join(', ')}`" icon="folder-heart" class="trigger" /> <Icon v-else-if="alert.stashes.length > 0" v-tooltip="alert.stashes.length > 0 ? 'Add to Favorites' : undefined" icon="heart7" class="trigger" /> <!-- <Icon icon="envelop5" title="E-mail me" :class="{ trigger: alert.email }" /> --> </div> <div class="alert-actions"> <span v-tooltip="format(alert.createdAt, 'yyyy-MM-dd hh:mm')" class="alert-id" title="Alert ID" >#{{ alert.id }}</span> <Icon icon="bin" @click="removeAlert(alert)" /> </div> </div> </li> </ul> <AlertDialog v-if="showAlertDialog" @close="showAlertDialog = false; reloadAlerts();" /> </section> </template> <script setup> import { ref, inject } from 'vue'; import { format } from 'date-fns'; import AlertDialog from '#/components/alerts/create.vue'; import { get, del } from '#/src/api.js'; const pageContext = inject('pageContext'); const alerts = ref(pageContext.pageProps.alerts); const showAlertDialog = ref(false); const done = ref(true); async function reloadAlerts() { alerts.value = await get('/alerts'); } async function removeAlert(alert) { if (done.value === false) { return; } if (!confirm(`Are you sure you want to remove alert #${alert.id}?`)) { // eslint-disable-line no-restricted-globals, no-alert return; } done.value = false; const alertLabel = [ ...alert.actors.map((actor) => actor.name), ...alert.tags.map((tag) => tag.name), ...alert.entities.map((entity) => entity.name), ...alert.matches.map((match) => match.expression), ].filter(Boolean).join(', '); try { await del(`/alerts/${alert.id}`, { undoFeedback: `Removed alert for '${alertLabel}'`, errorFeedback: `Failed to remove alert for '${alertLabel}'`, appendErrorMessage: true, }); await reloadAlerts(); } finally { done.value = true; } } </script> <style scoped> .alerts { width: 100%; margin-bottom: 1rem; } .alert { padding: 0 0 0 .5rem; display: flex; align-items: stretch; border-bottom: solid 1px var(--glass-weak-40); &:hover { border-color: var(--glass-weak-30); } } .alert-triggers { display: flex; align-items: center; gap: .5rem; margin-right: .75rem; .icon { fill: var(--glass-weak-40); } .icon.trigger { fill: var(--glass-weak-10); } } .alert-details { padding: .25rem 0; flex-grow: 1; line-height: 2.5; color: var(--glass); &.and .alert-detail:not(:last-child):after { content: ' and '; } &.or .alert-detail:not(:last-child):after { content: ' or '; } } .alert-value { color: var(--text); .icon { margin-right: .25rem; fill: var(--glass); transform: translateY(2px); } } .alert-values { padding: .5rem .5rem; border-bottom: solid 1px var(--primary-light-20); border-radius: .3rem; } .alert-detail { &.and .alert-key:not(:last-child):after { content: ' and '; } &.or .alert-key:not(:last-child):after { content: ' or '; } } .alert-regex { &:before, &:after { content: '╱'; padding: 0 .1rem; color: var(--primary-light-20); } } .alert-meta { display: flex; } .alert-actions { display: flex; align-items: center; font-size: .9rem; color: var(--glass-weak-10); .icon { height: 100%; padding: 0 .75rem; fill: var(--glass); &:hover { cursor: pointer; fill: var(--primary); } } } @media(--compact) { .profile-header { border-radius: 0; } .section-header { padding: .5rem 1rem .5rem 1rem; } .stashes { padding: 0 1rem 1rem 1rem; } .alert { padding: 0 .5rem; } } @media(--small-20) { .alert { flex-direction: column; padding-right: 0; } .alert-meta { padding: .5rem 0 .5rem 0; justify-content: space-between; } } </style>