<template>
<div class="synapse-panel">
<div class="synapse-header">
<h2 class="synapse-title">Synapse deliveries</h2>
<GnButton variant="primary" icon="ph ph-plus" @click="createModalOpen = true">
Add Target
</GnButton>
</div>
<p class="synapse-description">
Deliveries from the GNEXUS Synapse notification hub arrive at this Navi's
webhook and are verified against a per-user target secret. Create the
target in the Synapse admin panel, then register it here to let Navi
accept its deliveries.
</p>
<div v-if="targetsStore.loading" class="synapse-loading">
<GnLoader circle label="Loading targets" />
</div>
<GnTable
v-else
:columns="columns"
:rows="targetsStore.targets"
empty-text="No Synapse targets registered. Add one to start receiving deliveries."
>
<template #cell-created_at="{ row }">
{{ formatDate(row.created_at) }}
</template>
<template #cell-actions="{ row }">
<GnIconButton
icon="ph ph-trash"
label="Revoke"
@click="onRevoke(row.id, row.token_ref)"
/>
</template>
</GnTable>
<CreateTargetModal
:open="createModalOpen"
:creating="targetsStore.creating"
@close="createModalOpen = false"
@submit="onCreate"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useSynapseTargetsStore } from '@/stores/synapseTargets.js'
import { useConfirm } from '@/composables/useConfirm.js'
import { useToast } from 'gnexus-ui-kit/vue'
import { GnButton, GnIconButton, GnLoader, GnTable } from 'gnexus-ui-kit/vue'
import CreateTargetModal from './CreateTargetModal.vue'
const targetsStore = useSynapseTargetsStore()
const confirm = useConfirm()
const toast = useToast()
const createModalOpen = ref(false)
const columns = [
{ key: 'token_ref', label: 'Token ref' },
{ key: 'created_at', label: 'Created' },
{ key: 'actions', label: '' },
]
onMounted(() => {
targetsStore.fetchTargets()
})
async function onCreate(tokenRef, secret) {
try {
await targetsStore.createTarget(tokenRef, secret)
createModalOpen.value = false
toast.success({ title: 'Target added' })
} catch (err) {
toast.error({ title: 'Failed to add target', text: err.message })
}
}
async function onRevoke(id, tokenRef) {
const ok = await confirm(`Revoke Synapse target "${tokenRef}"? This cannot be undone.`)
if (!ok) return
try {
await targetsStore.revokeTarget(id)
toast.success({ title: 'Target revoked' })
} catch (err) {
toast.error({ title: 'Failed to revoke target', text: err.message })
}
}
function formatDate(iso) {
if (!iso) return null
const d = new Date(iso)
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
}
</script>
<style scoped lang="scss">
@use 'kit-deps' as *;
.synapse-panel {
margin-bottom: 24px;
}
.synapse-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.synapse-title {
font-size: 1.1rem;
font-weight: 600;
margin: 0;
color: var(--gn-color-text-medium);
}
.synapse-description {
margin: 0 0 16px;
font-size: 0.875rem;
color: var(--gn-color-text-medium);
}
.synapse-loading {
padding: 24px;
text-align: center;
}
</style>