2
0
Files
gitcaddy-vault/templates/repo/vault/view.tmpl
logikonline 3a920ce90c
All checks were successful
Build and Release / Tests (push) Successful in 1m5s
Build and Release / Lint (push) Successful in 1m38s
Build and Release / Create Release (push) Successful in 0s
feat(vault): add confirmation modals for destructive operations
Replaced inline confirm() dialogs with proper modal dialogs for key migration, DEK rotation, token revocation, and version rollback operations. Improves UX and provides better context for destructive actions.
2026-02-06 22:22:08 -05:00

255 lines
10 KiB
Cheetah

{{template "repo/vault/layout_head" (dict "ctxData" . "pageClass" "repository vault secret-view")}}
<div class="ui segment">
<div class="tw-flex tw-items-start tw-justify-between tw-gap-4">
<div>
<h4 class="ui header tw-mb-0">
{{svg "octicon-key" 20}} {{.Secret.Name}}
{{if .Secret.IsDeleted}}
<span class="ui red label">{{ctx.Locale.Tr "vault.deleted"}}</span>
{{end}}
</h4>
{{if .Secret.Description}}
<p class="text grey tw-mt-2 tw-mb-0">{{.Secret.Description}}</p>
{{end}}
</div>
<div class="tw-flex tw-gap-2 tw-flex-shrink-0">
<a class="ui button" href="{{.RepoLink}}/vault/secrets/{{.Secret.Name}}/versions">
{{svg "octicon-history" 16}} {{ctx.Locale.Tr "vault.view_all_versions"}}
</a>
<a class="ui button" href="{{.RepoLink}}/vault">
{{svg "octicon-arrow-left" 16}} {{ctx.Locale.Tr "vault.back_to_list"}}
</a>
</div>
</div>
<div class="ui four column stackable grid tw-mt-4">
<div class="column">
<div class="ui segment tw-text-center">
<div class="tw-font-semibold tw-text-lg">v{{.Secret.CurrentVersion}}</div>
<div class="tw-text-secondary">{{ctx.Locale.Tr "vault.version"}}</div>
</div>
</div>
<div class="column">
<div class="ui segment tw-text-center">
<div class="tw-font-semibold tw-text-lg">{{.Secret.Type}}</div>
<div class="tw-text-secondary">{{ctx.Locale.Tr "vault.secret_type"}}</div>
</div>
</div>
<div class="column">
<div class="ui segment tw-text-center">
<div class="tw-font-semibold tw-text-lg">{{DateUtils.TimeSince .Secret.CreatedUnix}}</div>
<div class="tw-text-secondary">{{ctx.Locale.Tr "vault.created"}}</div>
</div>
</div>
<div class="column">
<div class="ui segment tw-text-center">
<div class="tw-font-semibold tw-text-lg">{{DateUtils.TimeSince .Secret.UpdatedUnix}}</div>
<div class="tw-text-secondary">{{ctx.Locale.Tr "vault.updated"}}</div>
</div>
</div>
</div>
</div>
{{if .CanWrite}}
<!-- Editor view for users who can write -->
<div class="ui segment">
<h5 class="ui header">{{ctx.Locale.Tr "vault.edit_secret"}}</h5>
<form class="ui form" id="edit-secret-form" action="{{.RepoLink}}/vault/secrets/{{.Secret.Name}}" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<label>{{ctx.Locale.Tr "vault.value"}}</label>
<textarea name="value" id="edit-value" required style="min-height: 100px; resize: vertical; font-family: monospace;">{{.SecretValue}}</textarea>
<small class="tw-text-secondary">{{ctx.Locale.Tr "vault.edit_hint"}}</small>
</div>
<div class="tw-flex tw-gap-4">
<div class="field tw-flex-1">
<label>{{ctx.Locale.Tr "vault.type"}}</label>
<select name="type" class="ui dropdown">
<option value="generic" {{if eq .Secret.Type "generic"}}selected{{end}}>{{ctx.Locale.Tr "vault.type_generic"}}</option>
<option value="password" {{if eq .Secret.Type "password"}}selected{{end}}>{{ctx.Locale.Tr "vault.type_password"}}</option>
<option value="api_key" {{if eq .Secret.Type "api_key"}}selected{{end}}>{{ctx.Locale.Tr "vault.type_api_key"}}</option>
<option value="certificate" {{if eq .Secret.Type "certificate"}}selected{{end}}>{{ctx.Locale.Tr "vault.type_certificate"}}</option>
<option value="ssh_key" {{if eq .Secret.Type "ssh_key"}}selected{{end}}>{{ctx.Locale.Tr "vault.type_ssh_key"}}</option>
<option value="file" {{if eq .Secret.Type "file"}}selected{{end}}>{{ctx.Locale.Tr "vault.type_file"}}</option>
</select>
</div>
<div class="field tw-flex-1">
<label>{{ctx.Locale.Tr "vault.version_comment"}}</label>
<input type="text" name="comment" placeholder="{{ctx.Locale.Tr "vault.version_comment_placeholder"}}">
</div>
</div>
<div class="tw-flex tw-justify-between tw-items-center tw-mt-4">
<div class="tw-flex tw-gap-2">
<button class="ui primary button" type="submit">
{{svg "octicon-check" 16}} {{ctx.Locale.Tr "vault.save"}}
</button>
<button class="ui button" type="button" id="copy-secret" data-tooltip="{{ctx.Locale.Tr "vault.copy_value"}}">
{{svg "octicon-copy" 16}} {{ctx.Locale.Tr "vault.copy"}}
</button>
</div>
{{if .Secret.IsDeleted}}
<button class="ui green button" type="button" onclick="document.getElementById('restore-form').submit();">
{{svg "octicon-history" 16}} {{ctx.Locale.Tr "vault.restore"}}
</button>
{{else}}
<button class="ui red button show-modal" type="button" data-modal="#delete-secret-modal">
{{svg "octicon-trash" 16}} {{ctx.Locale.Tr "vault.delete"}}
</button>
{{end}}
</div>
</form>
{{if .Secret.IsDeleted}}
<form id="restore-form" class="tw-hidden" action="{{.RepoLink}}/vault/secrets/{{.Secret.Name}}/restore" method="post">
{{.CsrfTokenHtml}}
</form>
{{else}}
<form id="delete-form" class="tw-hidden" action="{{.RepoLink}}/vault/secrets/{{.Secret.Name}}/delete" method="post">
{{.CsrfTokenHtml}}
</form>
{{end}}
</div>
<script>
(function() {
const editValue = document.getElementById('edit-value');
const btnCopy = document.getElementById('copy-secret');
btnCopy?.addEventListener('click', function() {
navigator.clipboard.writeText(editValue.value).then(function() {
const originalText = btnCopy.innerHTML;
btnCopy.innerHTML = '{{svg "octicon-check" 16}} {{ctx.Locale.Tr "vault.copied"}}';
setTimeout(function() {
btnCopy.innerHTML = originalText;
}, 2000);
});
});
})();
</script>
{{else}}
<!-- Read-only view for users who cannot write -->
<div class="ui segment">
<div class="tw-flex tw-items-center tw-justify-between tw-mb-4">
<h5 class="ui header tw-mb-0">{{ctx.Locale.Tr "vault.secret_value"}}</h5>
<div class="ui tiny buttons">
<button class="ui button active" id="view-hidden" data-tooltip="{{ctx.Locale.Tr "vault.view_hidden"}}">
{{svg "octicon-eye-closed" 14}} {{ctx.Locale.Tr "vault.hidden"}}
</button>
<button class="ui button" id="view-raw" data-tooltip="{{ctx.Locale.Tr "vault.view_raw"}}">
{{svg "octicon-eye" 14}} {{ctx.Locale.Tr "vault.raw"}}
</button>
<button class="ui button" id="copy-secret" data-tooltip="{{ctx.Locale.Tr "vault.copy_value"}}">
{{svg "octicon-copy" 14}} {{ctx.Locale.Tr "vault.copy"}}
</button>
</div>
</div>
<div class="ui form">
<div class="field">
<!-- Hidden view (default) -->
<div id="secret-hidden" class="ui segment secondary tw-font-mono" style="min-height: 60px; max-height: 300px; overflow: auto;">
<span class="tw-text-secondary">••••••••••••••••••••••••••••••••</span>
</div>
<!-- Raw view (hidden by default) -->
<textarea id="secret-value" class="tw-hidden tw-font-mono" readonly style="min-height: 100px; max-height: 400px; resize: vertical; width: 100%;">{{.SecretValue}}</textarea>
</div>
</div>
</div>
<script>
(function() {
const hiddenView = document.getElementById('secret-hidden');
const rawView = document.getElementById('secret-value');
const btnHidden = document.getElementById('view-hidden');
const btnRaw = document.getElementById('view-raw');
const btnCopy = document.getElementById('copy-secret');
function showHidden() {
hiddenView.classList.remove('tw-hidden');
rawView.classList.add('tw-hidden');
btnHidden.classList.add('active');
btnRaw.classList.remove('active');
}
function showRaw() {
hiddenView.classList.add('tw-hidden');
rawView.classList.remove('tw-hidden');
btnHidden.classList.remove('active');
btnRaw.classList.add('active');
rawView.style.height = 'auto';
rawView.style.height = Math.min(Math.max(rawView.scrollHeight, 100), 400) + 'px';
}
btnHidden?.addEventListener('click', showHidden);
btnRaw?.addEventListener('click', showRaw);
btnCopy?.addEventListener('click', function() {
navigator.clipboard.writeText(rawView.value).then(function() {
const originalText = btnCopy.innerHTML;
btnCopy.innerHTML = '{{svg "octicon-check" 14}} {{ctx.Locale.Tr "vault.copied"}}';
setTimeout(function() {
btnCopy.innerHTML = originalText;
}, 2000);
});
});
})();
</script>
{{end}}
{{if .Versions}}
<div class="ui segment">
<h5 class="ui header">{{ctx.Locale.Tr "vault.version_history"}}</h5>
<table class="ui very basic striped table">
<thead>
<tr>
<th>{{ctx.Locale.Tr "vault.version"}}</th>
<th>{{ctx.Locale.Tr "vault.comment"}}</th>
<th>{{ctx.Locale.Tr "vault.created"}}</th>
{{if .CanWrite}}
<th class="right aligned">{{ctx.Locale.Tr "actions"}}</th>
{{end}}
</tr>
</thead>
<tbody>
{{range .Versions}}
<tr{{if eq .Version $.Secret.CurrentVersion}} class="positive"{{end}}>
<td>
v{{.Version}}
{{if eq .Version $.Secret.CurrentVersion}}
<span class="ui green label">{{ctx.Locale.Tr "vault.current"}}</span>
{{end}}
</td>
<td>{{if .Comment}}{{.Comment}}{{else}}<span class="text grey">-</span>{{end}}</td>
<td>{{DateUtils.TimeSince .CreatedUnix}}</td>
{{if $.CanWrite}}
<td class="right aligned">
{{if ne .Version $.Secret.CurrentVersion}}
<form class="ui inline" action="{{$.RepoLink}}/vault/secrets/{{$.Secret.Name}}/rollback" method="post">
{{$.CsrfTokenHtml}}
<input type="hidden" name="version" value="{{.Version}}">
<button class="ui tiny button" type="submit">
{{svg "octicon-history" 14}} {{ctx.Locale.Tr "vault.rollback"}}
</button>
</form>
{{end}}
</td>
{{end}}
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
{{if not .Secret.IsDeleted}}
<div class="ui small modal" id="delete-secret-modal">
<div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "vault.delete_secret"}}</div>
<div class="content">
<p>{{ctx.Locale.Tr "vault.confirm_delete"}}</p>
</div>
<div class="actions">
<button class="ui cancel button">{{ctx.Locale.Tr "cancel"}}</button>
<button class="ui red button" onclick="document.getElementById('delete-form').submit();">{{ctx.Locale.Tr "vault.delete"}}</button>
</div>
</div>
{{end}}
{{template "repo/vault/layout_footer" .}}