Newer
Older
navi-1 / webclient / src / components / settings / SynapseTargetsPanel.vue
<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 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>