Newer
Older
gn-synapse / frontend / src / views / TypesView.vue
<script setup>
// Реестр типов: (source, subject, action) — что источники могут присылать.
import { onMounted, ref } from "vue";
import {
  GnButton,
  GnConfirmDialog,
  GnInput,
  GnLoader,
  GnModal,
  GnPageHeader,
  GnSelect,
  GnStatusCard,
  GnTable,
} from "gnexus-ui-kit/vue";
import { useToast } from "gnexus-ui-kit/vue";
import { api } from "../api.js";
import { fmtTime } from "../ui.js";

const toast = useToast();

const rows = ref([]);
const sources = ref([]);
const loading = ref(true);
const error = ref("");

async function load() {
  loading.value = true;
  error.value = "";
  try {
    const [types, src] = await Promise.all([api.listTypes(), api.listSources()]);
    rows.value = types;
    sources.value = src;
  } catch (ex) {
    error.value = ex.message;
  } finally {
    loading.value = false;
  }
}

const showCreate = ref(false);
const form = ref({ source_id: "", subject: "", action: "" });
const busy = ref(false);

const sourceOptions = [{ value: "", label: "— выберите источник —" }];

async function createType() {
  if (!form.value.source_id || !form.value.subject || !form.value.action) return;
  busy.value = true;
  try {
    await api.createType({
      source_id: Number(form.value.source_id),
      subject: form.value.subject.trim(),
      action: form.value.action.trim(),
    });
    showCreate.value = false;
    form.value = { source_id: "", subject: "", action: "" };
    toast.success("Тип создан");
    await load();
  } catch (ex) {
    toast.error(ex.message);
  } finally {
    busy.value = false;
  }
}

const confirmDelete = ref(null);
async function doDelete() {
  try {
    await api.deleteType(confirmDelete.value.id);
    toast.success("Тип удалён");
    await load();
  } catch (ex) {
    toast.error(ex.message);
  } finally {
    confirmDelete.value = null;
  }
}

onMounted(load);
</script>

<template>
  <main class="container page">
    <GnPageHeader
      kicker="Synapse"
      title="Типы"
      subtitle="Реестр (source, subject, action). Событие с незарегистрированной тройкой отклоняется с 422"
    >
      <template #actions>
        <GnButton variant="primary" icon="ph-plus" @click="showCreate = true">Тип</GnButton>
      </template>
    </GnPageHeader>

    <GnLoader v-if="loading" />
    <GnStatusCard v-else-if="error" variant="error" icon="ph-warning-circle" :text="error" />
    <template v-else>
      <GnTable
        :rows="rows"
        empty-text="Типов нет — создайте первый"
        :columns="[
          { key: 'source_name', label: 'Source' },
          { key: 'subject', label: 'Subject' },
          { key: 'action', label: 'Action' },
          { key: 'created_at', label: 'Создан' },
          { key: 'actions', label: '' },
        ]"
      >
        <template #cell-created_at="{ value }">{{ fmtTime(value) }}</template>
        <template #cell-actions="{ row }">
          <GnButton variant="danger" size="sm" icon="ph-trash" @click="confirmDelete = row" />
        </template>
      </GnTable>
    </template>

    <GnModal v-model:open="showCreate" title="Новый тип">
      <div class="form">
        <GnSelect
          v-model="form.source_id"
          label="Источник"
          :options="sourceOptions.concat(sources.map((s) => ({ value: s.id, label: s.name })))"
        />
        <GnInput v-model="form.subject" label="Subject" icon="ph-shapes" help="Сущность: container, issue, build…" />
        <GnInput v-model="form.action" label="Action" icon="ph-lightning" help="Что случилось: down, created, resolved…" />
      </div>
      <template #actions>
        <GnButton variant="secondary" @click="showCreate = false">Отмена</GnButton>
        <GnButton
          variant="primary"
          :loading="busy"
          :disabled="!form.source_id || !form.subject.trim() || !form.action.trim()"
          @click="createType"
        >
          Создать
        </GnButton>
      </template>
    </GnModal>

    <GnConfirmDialog
      :open="confirmDelete !== null" @update:open="(v) => (v ? null : (confirmDelete = null))"
      :key="confirmDelete?.id"
      title="Удалить тип?"
      :message="confirmDelete ? `Тип (${confirmDelete.source_name}, ${confirmDelete.subject}, ${confirmDelete.action}) будет удалён — события такой тройки отклонятся с 422.` : ''"
      confirm-variant="danger"
      confirm-text="Удалить"
      cancel-text="Отмена"
      @confirm="doDelete"
    />
  </main>
</template>

<style scoped>
.page {
  max-width: 1440px;
  margin-inline: auto;
  padding-block: var(--gn-space-6);
  display: flex;
  flex-direction: column;
  gap: var(--gn-space-4);
}
.form {
  display: flex;
  flex-direction: column;
  gap: var(--gn-space-4);
}
</style>