Newer
Older
gnexus-creds / frontend / src / components / SecretDetailPanel.vue
<script setup>
import { computed } from "vue";
import {
  GnActionList,
  GnBadge,
  GnDescriptionList,
  GnDropdown,
  GnIconButton,
  GnToolbar
} 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 },
  copiedFieldKey: { type: String, default: "" },
  visibleFieldKeys: { type: Set, default: () => new Set() }
});

const emit = defineEmits([
  "back",
  "reveal",
  "hide",
  "edit-metadata",
  "toggle-field-visibility",
  "copy-field",
  "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>
  <div v-if="showHeader" class="detail-header">
    <GnIconButton
      v-if="showBack"
      icon="ph ph-arrow-left"
      label="Back to list"
      @click="emit('back')"
    />
    <strong class="detail-title">{{ secret.title }}</strong>
    <div v-if="showActions" class="detail-actions">
      <GnBadge :variant="secret.status === 'actual' ? 'success' : 'warning'">
        {{ secret.status }}
      </GnBadge>
      <GnIconButton
        v-if="!fields"
        icon="ph ph-eye"
        label="Reveal secret"
        @click="emit('reveal')"
      />
      <GnIconButton
        v-else
        icon="ph ph-eye-slash"
        label="Hide secret"
        @click="emit('hide')"
      />
      <GnIconButton
        icon="ph ph-pencil-simple"
        label="Edit metadata"
        @click="emit('edit-metadata')"
      />
      <GnDropdown :items="actionItems" class="secret-dropdown">
        <template #trigger="{ toggle }">
          <GnIconButton
            icon="ph ph-dots-three-outline"
            label="Secret actions"
            @click="toggle"
          />
        </template>
      </GnDropdown>
    </div>
  </div>

  <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>
        <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)"
        />
        <GnIconButton
          :icon="copiedFieldKey === item.key ? 'ph ph-check' : 'ph ph-copy'"
          :label="copiedFieldKey === item.key ? 'Copied' : 'Copy field value'"
          @click="emit('copy-field', { value: item.raw.value, key: item.key })"
        />
      </template>
    </GnActionList>
  </section>
</template>