Skip to content

Add a button editing action secret #34348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 8, 2025
11 changes: 8 additions & 3 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3722,13 +3722,18 @@ owner.settings.chef.keypair.description = A key pair is necessary to authenticat
secrets = Secrets
description = Secrets will be passed to certain actions and cannot be read otherwise.
none = There are no secrets yet.
creation = Add Secret

; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description = Description
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
creation.description_placeholder = Enter short description (optional).
creation.success = The secret "%s" has been added.
creation.failed = Failed to add secret.

save_success = The secret "%s" has been saved.
save_failed = Failed to save secret.

add_secret = Add secret
edit_secret = Edit secret
deletion = Remove secret
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
deletion.success = The secret has been removed.
Expand Down
4 changes: 2 additions & 2 deletions routers/web/shared/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL
s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data), form.Description)
if err != nil {
log.Error("CreateOrUpdateSecret failed: %v", err)
ctx.JSONError(ctx.Tr("secrets.creation.failed"))
ctx.JSONError(ctx.Tr("secrets.save_failed"))
return
}

ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name))
ctx.Flash.Success(ctx.Tr("secrets.save_success", s.Name))
ctx.JSONRedirect(redirectURL)
}

Expand Down
24 changes: 19 additions & 5 deletions templates/shared/secrets/add_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
<button class="ui primary tiny button show-modal"
data-modal="#add-secret-modal"
data-modal-form.action="{{.Link}}"
data-modal-header="{{ctx.Locale.Tr "secrets.creation"}}"
data-modal-header="{{ctx.Locale.Tr "secrets.add_secret"}}"
data-modal-secret-name.value=""
data-modal-secret-name.read-only="false"
data-modal-secret-data=""
data-modal-secret-description=""
>
{{ctx.Locale.Tr "secrets.creation"}}
{{ctx.Locale.Tr "secrets.add_secret"}}
</button>
</div>
</h4>
Expand All @@ -33,6 +37,18 @@
<span class="color-text-light-2">
{{ctx.Locale.Tr "settings.added_on" (DateUtils.AbsoluteShort .CreatedUnix)}}
</span>
<button class="ui btn interact-bg show-modal tw-p-2"
data-modal="#add-secret-modal"
data-modal-form.action="{{$.Link}}"
data-modal-header="{{ctx.Locale.Tr "secrets.edit_secret"}}"
data-tooltip-content="{{ctx.Locale.Tr "secrets.edit_secret"}}"
data-modal-secret-name.value="{{.Name}}"
data-modal-secret-name.read-only="true"
data-modal-secret-data=""
data-modal-secret-description="{{if .Description}}{{.Description}}{{end}}"
>
{{svg "octicon-pencil"}}
</button>
<button class="ui btn interact-bg link-action tw-p-2"
data-url="{{$.Link}}/delete?id={{.ID}}"
data-modal-confirm="{{ctx.Locale.Tr "secrets.deletion.description"}}"
Expand All @@ -51,9 +67,7 @@

{{/* Add secret dialog */}}
<div class="ui small modal" id="add-secret-modal">
<div class="header">
<span id="actions-modal-header"></span>
</div>
<div class="header"></div>
<form class="ui form form-fetch-action" method="post">
<div class="content">
{{.CsrfTokenHtml}}
Expand Down
14 changes: 14 additions & 0 deletions web_src/js/features/common-button.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {assignElementProperty} from './common-button.ts';

test('assignElementProperty', () => {
const elForm = document.createElement('form');
assignElementProperty(elForm, 'action', '/test-link');
expect(elForm.action).contains('/test-link'); // the DOM always returns absolute URL
assignElementProperty(elForm, 'text-content', 'dummy');
expect(elForm.textContent).toBe('dummy');

const elInput = document.createElement('input');
expect(elInput.readOnly).toBe(false);
assignElementProperty(elInput, 'read-only', 'true');
expect(elInput.readOnly).toBe(true);
});
23 changes: 19 additions & 4 deletions web_src/js/features/common-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,29 @@ function onHidePanelClick(el: HTMLElement, e: MouseEvent) {
throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code
}

export function assignElementProperty(el: any, name: string, val: string) {
name = camelize(name);
const old = el[name];
if (typeof old === 'boolean') {
el[name] = val === 'true';
} else if (typeof old === 'number') {
el[name] = parseFloat(val);
} else if (typeof old === 'string') {
el[name] = val;
} else {
// in the future, we could introduce a better typing system like `data-modal-form.action:string="..."`
throw new Error(`cannot assign element property ${name} by value ${val}`);
}
}

function onShowModalClick(el: HTMLElement, e: MouseEvent) {
// A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute.
// Each "data-modal-{target}" attribute will be filled to target element's value or text-content.
// * First, try to query '#target'
// * Then, try to query '[name=target]'
// * Then, try to query '.target'
// * Then, try to query 'target' as HTML tag
// If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set.
// If there is a ".{prop-name}" part like "data-modal-form.action", the "form" element's "action" property will be set, the "prop-name" will be camel-cased to "propName".
e.preventDefault();
const modalSelector = el.getAttribute('data-modal');
const elModal = document.querySelector(modalSelector);
Expand All @@ -122,7 +137,7 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
}

const attrTargetCombo = attrib.name.substring(modalAttrPrefix.length);
const [attrTargetName, attrTargetAttr] = attrTargetCombo.split('.');
const [attrTargetName, attrTargetProp] = attrTargetCombo.split('.');
// try to find target by: "#target" -> "[name=target]" -> ".target" -> "<target> tag"
const attrTarget = elModal.querySelector(`#${attrTargetName}`) ||
elModal.querySelector(`[name=${attrTargetName}]`) ||
Expand All @@ -133,8 +148,8 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
continue;
}

if (attrTargetAttr) {
(attrTarget as any)[camelize(attrTargetAttr)] = attrib.value;
if (attrTargetProp) {
assignElementProperty(attrTarget, attrTargetProp, attrib.value);
} else if (attrTarget.matches('input, textarea')) {
(attrTarget as HTMLInputElement | HTMLTextAreaElement).value = attrib.value; // FIXME: add more supports like checkbox
} else {
Expand Down