Newer
Older
gnexus-creds / frontend / src / components / SecretDetailPanel.vue
<script setup>
import { computed } from "vue";
import {
  GnActionList,
  GnBadge,
  GnCopyButton,
  GnDescriptionList,
  GnDropdown,
  GnIconButton,
  GnPageHeader,
  GnToolbar,
  GnTooltip
} from "gnexus-ui-kit/vue";

const props = defineProps({
  secret: { type: Object, required: true },
  fields: { type: Array, default: null },
  showHeader: { type: Boolean, default: true },
  showBack: { type: Boolean, default: false },
  showActions: { type: Boolean, default: true },
  visibleFieldKeys: { type: Set, default: () => new Set() }
});

const emit = defineEmits([
  "back",
  "reveal",
  "hide",
  "edit-metadata",
  "toggle-field-visibility",
  "status-change",
  "archive",
  "delete",
  "navigate"
]);

const details = computed(() => [
  { label: "Purpose", value: props.secret.purpose || "Not set" },
  { label: "Category", value: props.secret.category || "Not set" },
  { label: "Tags", value: props.secret.tags?.join(", ") || "No tags" },
  {
    label: "Access",
    value:
      `UI ${props.secret.allow_ui ? "on" : "off"} · REST ` +
      `${props.secret.allow_rest_api ? "on" : "off"} · MCP ` +
      `${props.secret.allow_mcp ? "on" : "off"}`
  }
]);

const fieldItems = computed(() =>
  (props.fields || []).map((field, index) => ({
    key: `${field.name}:${index}`,
    title: field.name,
    subtitle: field.encrypted ? "encrypted" : "plain",
    raw: field
  }))
);

const actionItems = computed(() => [
  {
    label: "Versions",
    icon: "ph ph-clock-counter-clockwise",
    onSelect: () => emit("navigate", "history")
  },
  {
    label: "Audit",
    icon: "ph ph-list-checks",
    onSelect: () => emit("navigate", "audit")
  },
  {
    label: "Edit fields",
    icon: "ph ph-list-plus",
    onSelect: () => emit("navigate", "edit-fields")
  },
  {
    label: `Mark ${props.secret.status === "actual" ? "outdated" : "actual"}`,
    icon: "ph ph-arrows-clockwise",
    onSelect: () =>
      emit("status-change", props.secret.status === "actual" ? "outdated" : "actual")
  },
  { label: "Archive", icon: "ph ph-archive", onSelect: () => emit("archive") },
  {
    label: "Delete",
    icon: "ph ph-trash",
    danger: true,
    onSelect: () => emit("delete")
  }
]);

function maskValue(value) {
  return "*".repeat(Math.max(8, String(value || "").length));
}

function isVisible(key) {
  return props.visibleFieldKeys.has(key);
}
</script>

<template>
  <GnPageHeader
    v-if="showHeader"
    :title="secret.title"
    compact
  >
    <template #kicker>
      <GnIconButton
        v-if="showBack"
        icon="ph ph-arrow-left"
        label="Back to list"
        @click="emit('back')"
      />
    </template>
    <template #actions>
      <div v-if="showActions" class="detail-actions">
        <GnBadge :variant="secret.status === 'actual' ? 'success' : 'warning'">
          {{ secret.status }}
        </GnBadge>
        <GnTooltip :text="fields ? 'Mask values' : 'Show decrypted values'">
          <GnIconButton
            :icon="fields ? 'ph ph-eye-slash' : 'ph ph-eye'"
            :label="fields ? 'Hide secret' : 'Reveal secret'"
            @click="fields ? emit('hide') : emit('reveal')"
          />
        </GnTooltip>
        <GnTooltip text="Edit metadata">
          <GnIconButton
            icon="ph ph-pencil-simple"
            label="Edit metadata"
            @click="emit('edit-metadata')"
          />
        </GnTooltip>
        <GnDropdown :items="actionItems" class="secret-dropdown">
          <template #trigger="{ toggle }">
            <GnIconButton
              icon="ph ph-dots-three-outline"
              label="Secret actions"
              @click="toggle"
            />
          </template>
        </GnDropdown>
      </div>
    </template>
  </GnPageHeader>

  <GnDescriptionList :items="details" />

  <section v-if="fields?.length" class="workspace-surface inset-surface">
    <GnToolbar title="Revealed fields" />
    <GnActionList :items="fieldItems" class="revealed-fields-list">
      <template #controls="{ item }">
        <code class="secret-value">
          {{ isVisible(item.key) ? item.raw.value : maskValue(item.raw.value) }}
        </code>
        <GnTooltip :text="isVisible(item.key) ? 'Hide value' : 'Show value'">
          <GnIconButton
            :icon="isVisible(item.key) ? 'ph ph-eye-slash' : 'ph ph-eye'"
            :label="isVisible(item.key) ? 'Hide field value' : 'Show field value'"
            @click="emit('toggle-field-visibility', item.key)"
          />
        </GnTooltip>
        <GnCopyButton :text="item.raw.value" size="sm" />
      </template>
    </GnActionList>
  </section>
</template>