<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>