Browse Source

Add user secrets (#22191)

Fixes #22183
Replaces #22187

This PR adds secrets for users. I refactored the files for organizations
and repos to use the same logic and templates. I splitted the secrets
from deploy keys again and reverted the fix from #22187.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
tags/v1.19.0-rc0
KN4CK3R 1 year ago
parent
commit
5882e179a9
No account linked to committer's email address

+ 8
- 8
docs/content/doc/secrets/overview.en-us.md View File

@@ -1,6 +1,6 @@
---
date: "2022-12-19T21:26:00+08:00"
title: "Encrypted secrets"
title: "Secrets"
slug: "secrets/overview"
draft: false
toc: false
@@ -12,24 +12,24 @@ menu:
identifier: "overview"
---

# Encrypted secrets
# Secrets

Encrypted secrets allow you to store sensitive information in your organization or repository.
Secrets allow you to store sensitive information in your user, organization or repository.
Secrets are available on Gitea 1.19+.

# Naming your secrets

The following rules apply to secret names:

Secret names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.
- Secret names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.

Secret names must not start with the `GITHUB_` and `GITEA_` prefix.
- Secret names must not start with the `GITHUB_` and `GITEA_` prefix.

Secret names must not start with a number.
- Secret names must not start with a number.

Secret names are not case-sensitive.
- Secret names are not case-sensitive.

Secret names must be unique at the level they are created at.
- Secret names must be unique at the level they are created at.

For example, a secret created at the repository level must have a unique name in that repository, and a secret created at the organization level must have a unique name at that level.


+ 1
- 1
options/locale/locale_en-US.ini View File

@@ -3254,7 +3254,7 @@ creation.value_placeholder = Input any content. Whitespace at the start and end
creation.success = The secret '%s' has been added.
creation.failed = Failed to add secret.
deletion = Remove secret
deletion.description = Removing a secret will revoke its access to repositories. Continue?
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
deletion.success = The secret has been removed.
deletion.failed = Failed to remove secret.


+ 0
- 51
routers/web/org/setting.go View File

@@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
secret_model "code.gitea.io/gitea/models/secret"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
@@ -38,8 +37,6 @@ const (
tplSettingsHooks base.TplName = "org/settings/hooks"
// tplSettingsLabels template path for render labels settings
tplSettingsLabels base.TplName = "org/settings/labels"
// tplSettingsSecrets template path for render secrets settings
tplSettingsSecrets base.TplName = "org/settings/secrets"
// tplSettingsRunners template path for render runners settings
tplSettingsRunners base.TplName = "org/settings/runners"
// tplSettingsRunnersEdit template path for render runners edit settings
@@ -253,51 +250,3 @@ func Labels(ctx *context.Context) {
ctx.Data["LabelTemplates"] = repo_module.LabelTemplates
ctx.HTML(http.StatusOK, tplSettingsLabels)
}

// Secrets render organization secrets page
func Secrets(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.secrets")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsOrgSettingsSecrets"] = true

secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{OwnerID: ctx.Org.Organization.ID})
if err != nil {
ctx.ServerError("FindSecrets", err)
return
}
ctx.Data["Secrets"] = secrets

ctx.HTML(http.StatusOK, tplSettingsSecrets)
}

// SecretsPost add secrets
func SecretsPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.AddSecretForm)

_, err := secret_model.InsertEncryptedSecret(ctx, ctx.Org.Organization.ID, 0, form.Title, form.Content)
if err != nil {
ctx.Flash.Error(ctx.Tr("secrets.creation.failed"))
log.Error("validate secret: %v", err)
ctx.Redirect(ctx.Org.OrgLink + "/settings/secrets")
return
}

log.Trace("Org %d: secret added", ctx.Org.Organization.ID)
ctx.Flash.Success(ctx.Tr("secrets.creation.success", form.Title))
ctx.Redirect(ctx.Org.OrgLink + "/settings/secrets")
}

// SecretsDelete delete secrets
func SecretsDelete(ctx *context.Context) {
id := ctx.FormInt64("id")
if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil {
ctx.Flash.Error(ctx.Tr("secrets.deletion.failed"))
log.Error("delete secret %d: %v", id, err)
} else {
ctx.Flash.Success(ctx.Tr("secrets.deletion.success"))
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": ctx.Org.OrgLink + "/settings/secrets",
})
}

+ 48
- 0
routers/web/org/setting_secrets.go View File

@@ -0,0 +1,48 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package org

import (
"net/http"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
shared "code.gitea.io/gitea/routers/web/shared/secrets"
)

const (
tplSettingsSecrets base.TplName = "org/settings/secrets"
)

// Secrets render organization secrets page
func Secrets(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("secrets.secrets")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsOrgSettingsSecrets"] = true

shared.SetSecretsContext(ctx, ctx.ContextUser.ID, 0)
if ctx.Written() {
return
}

ctx.HTML(http.StatusOK, tplSettingsSecrets)
}

// SecretsPost add secrets
func SecretsPost(ctx *context.Context) {
shared.PerformSecretsPost(
ctx,
ctx.ContextUser.ID,
0,
ctx.Org.OrgLink+"/settings/secrets",
)
}

// SecretsDelete delete secrets
func SecretsDelete(ctx *context.Context) {
shared.PerformSecretsDelete(
ctx,
ctx.Org.OrgLink+"/settings/secrets",
)
}

+ 0
- 39
routers/web/repo/setting.go View File

@@ -19,7 +19,6 @@ import (
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
secret_model "code.gitea.io/gitea/models/secret"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
@@ -1131,33 +1130,9 @@ func DeployKeys(ctx *context.Context) {
}
ctx.Data["Deploykeys"] = keys

secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{RepoID: ctx.Repo.Repository.ID})
if err != nil {
ctx.ServerError("FindSecrets", err)
return
}
ctx.Data["Secrets"] = secrets

ctx.HTML(http.StatusOK, tplDeployKeys)
}

// SecretsPost response for creating a new secret
func SecretsPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.AddSecretForm)

_, err := secret_model.InsertEncryptedSecret(ctx, 0, ctx.Repo.Repository.ID, form.Title, form.Content)
if err != nil {
ctx.Flash.Error(ctx.Tr("secrets.creation.failed"))
log.Error("validate secret: %v", err)
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
return
}

log.Trace("Secret added: %d", ctx.Repo.Repository.ID)
ctx.Flash.Success(ctx.Tr("secrets.creation.success", form.Title))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
}

// DeployKeysPost response for adding a deploy key of a repository
func DeployKeysPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.AddKeyForm)
@@ -1219,20 +1194,6 @@ func DeployKeysPost(ctx *context.Context) {
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
}

func DeleteSecret(ctx *context.Context) {
id := ctx.FormInt64("id")
if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil {
ctx.Flash.Error(ctx.Tr("secrets.deletion.failed"))
log.Error("delete secret %d: %v", id, err)
} else {
ctx.Flash.Success(ctx.Tr("secrets.deletion.success"))
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": ctx.Repo.RepoLink + "/settings/keys",
})
}

// DeleteDeployKey response for deleting a deploy key
func DeleteDeployKey(ctx *context.Context) {
if err := asymkey_service.DeleteDeployKey(ctx.Doer, ctx.FormInt64("id")); err != nil {

+ 46
- 0
routers/web/repo/setting_secrets.go View File

@@ -0,0 +1,46 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package repo

import (
"net/http"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/secrets"
)

const (
tplSecrets base.TplName = "repo/settings/secrets"
)

func Secrets(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("secrets.secrets")
ctx.Data["PageIsSettingsSecrets"] = true
ctx.Data["DisableSSH"] = setting.SSH.Disabled

shared.SetSecretsContext(ctx, 0, ctx.Repo.Repository.ID)
if ctx.Written() {
return
}

ctx.HTML(http.StatusOK, tplSecrets)
}

func SecretsPost(ctx *context.Context) {
shared.PerformSecretsPost(
ctx,
0,
ctx.Repo.Repository.ID,
ctx.Repo.RepoLink+"/settings/secrets",
)
}

func DeleteSecret(ctx *context.Context) {
shared.PerformSecretsDelete(
ctx,
ctx.Repo.RepoLink+"/settings/secrets",
)
}

+ 54
- 0
routers/web/shared/secrets/secrets.go View File

@@ -0,0 +1,54 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package secrets

import (
"net/http"

"code.gitea.io/gitea/models/db"
secret_model "code.gitea.io/gitea/models/secret"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
)

func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{OwnerID: ownerID, RepoID: repoID})
if err != nil {
ctx.ServerError("FindSecrets", err)
return
}

ctx.Data["Secrets"] = secrets
}

func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
form := web.GetForm(ctx).(*forms.AddSecretForm)

s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, form.Title, form.Content)
if err != nil {
log.Error("InsertEncryptedSecret: %v", err)
ctx.Flash.Error(ctx.Tr("secrets.creation.failed"))
} else {
ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name))
}

ctx.Redirect(redirectURL)
}

func PerformSecretsDelete(ctx *context.Context, redirectURL string) {
id := ctx.FormInt64("id")

if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil {
log.Error("Delete secret %d failed: %v", id, err)
ctx.Flash.Error(ctx.Tr("secrets.deletion.failed"))
} else {
ctx.Flash.Success(ctx.Tr("secrets.deletion.success"))
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": redirectURL,
})
}

+ 45
- 0
routers/web/user/setting/secrets.go View File

@@ -0,0 +1,45 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package setting

import (
"net/http"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/secrets"
)

const (
tplSettingsSecrets base.TplName = "user/settings/secrets"
)

func Secrets(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("secrets.secrets")
ctx.Data["PageIsSettingsSecrets"] = true

shared.SetSecretsContext(ctx, ctx.Doer.ID, 0)
if ctx.Written() {
return
}

ctx.HTML(http.StatusOK, tplSettingsSecrets)
}

func SecretsPost(ctx *context.Context) {
shared.PerformSecretsPost(
ctx,
ctx.Doer.ID,
0,
setting.AppSubURL+"/user/settings/secrets",
)
}

func SecretsDelete(ctx *context.Context) {
shared.PerformSecretsDelete(
ctx,
setting.AppSubURL+"/user/settings/secrets",
)
}

+ 11
- 4
routers/web/web.go View File

@@ -469,6 +469,11 @@ func RegisterRoutes(m *web.Route) {
})
})
}, packagesEnabled)
m.Group("/secrets", func() {
m.Get("", user_setting.Secrets)
m.Post("", web.Bind(forms.AddSecretForm{}), user_setting.SecretsPost)
m.Post("/delete", user_setting.SecretsDelete)
})
m.Get("/organization", user_setting.Organization)
m.Get("/repos", user_setting.Repos)
m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository)
@@ -982,10 +987,12 @@ func RegisterRoutes(m *web.Route) {
m.Combo("").Get(repo.DeployKeys).
Post(web.Bind(forms.AddKeyForm{}), repo.DeployKeysPost)
m.Post("/delete", repo.DeleteDeployKey)
m.Group("/secrets", func() {
m.Post("", web.Bind(forms.AddSecretForm{}), repo.SecretsPost)
m.Post("/delete", repo.DeleteSecret)
})
})

m.Group("/secrets", func() {
m.Get("", repo.Secrets)
m.Post("", web.Bind(forms.AddSecretForm{}), repo.SecretsPost)
m.Post("/delete", repo.DeleteSecret)
})

m.Group("/lfs", func() {

+ 1
- 69
templates/org/settings/secrets.tmpl View File

@@ -6,78 +6,10 @@
{{template "org/settings/navbar" .}}
<div class="ui twelve wide column content">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{.locale.Tr "secrets.secrets"}}
<div class="ui right">
<div class="ui primary tiny show-panel button" data-panel="#add-secret-panel">{{.locale.Tr "secrets.creation"}}</div>
</div>
</h4>
<div class="ui attached segment">
<div class="{{if not .HasError}}hide {{end}}mb-4" id="add-secret-panel">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="field">
{{.locale.Tr "secrets.description"}}
</div>
<div class="field{{if .Err_Title}} error{{end}}">
<label for="secret-title">{{.locale.Tr "secrets.name"}}</label>
<input id="secret-title" name="title" value="{{.title}}" autofocus required pattern="^[a-zA-Z_][a-zA-Z0-9_]*$" placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}">
</div>
<div class="field{{if .Err_Content}} error{{end}}">
<label for="secret-content">{{.locale.Tr "secrets.value"}}</label>
<textarea id="secret-content" name="content" required placeholder="{{.locale.Tr "secrets.creation.value_placeholder"}}">{{.content}}</textarea>
</div>
<button class="ui green button">
{{.locale.Tr "secrets.creation"}}
</button>
<button class="ui hide-panel button" data-panel="#add-secret-panel">
{{.locale.Tr "cancel"}}
</button>
</form>
</div>
{{if .Secrets}}
<div class="ui key list">
{{range .Secrets}}
<div class="item">
<div class="right floated content">
<button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
{{$.locale.Tr "settings.delete_key"}}
</button>
</div>
<div class="left floated content">
<i>{{svg "octicon-key" 32}}</i>
</div>
<div class="content">
<strong>{{.Name}}</strong>
<div class="print meta">******</div>
<div class="activity meta">
<i>
{{$.locale.Tr "settings.add_on"}}
<span>{{.CreatedUnix.FormatShort}}</span>
</i>
</div>
</div>
</div>
{{end}}
</div>
{{else}}
{{.locale.Tr "secrets.none"}}
{{end}}
</div>
{{template "shared/secrets/add_list" .}}
</div>
</div>
</div>
</div>

<div class="ui small basic delete modal">
<div class="ui header">
{{svg "octicon-trash" 16 "mr-2"}}
{{.locale.Tr "secrets.deletion"}}
</div>
<div class="content">
<p>{{.locale.Tr "secrets.deletion.description"}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

{{template "base/footer" .}}

+ 2
- 4
templates/repo/settings/deploy_keys.tmpl View File

@@ -51,7 +51,7 @@
{{range .Deploykeys}}
<div class="item">
<div class="right floated content">
<button class="ui red tiny button delete-button" data-modal-id="delete-deploy_keys-modal" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
<button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
{{$.locale.Tr "settings.delete_key"}}
</button>
</div>
@@ -75,11 +75,9 @@
{{end}}
</div>
</div>
<br/>
{{template "repo/settings/secrets" .}}
</div>

<div class="ui small basic delete modal" id="delete-deploy_keys-modal">
<div class="ui small basic delete modal">
<div class="ui icon header">
{{svg "octicon-trash"}}
{{.locale.Tr "repo.settings.deploy_key_deletion"}}

+ 2
- 1
templates/repo/settings/nav.tmpl View File

@@ -12,7 +12,8 @@
{{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}}
<li {{if .PageIsSettingsGitHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks/git">{{.locale.Tr "repo.settings.githooks"}}</a></li>
{{end}}
<li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.locale.Tr "secrets.secrets"}}</a></li>
<li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.locale.Tr "repo.settings.deploy_keys"}}</a></li>
<li {{if .PageIsSettingsSecrets}}class="current"{{end}}><a href="{{.RepoLink}}/settings/secrets">{{.locale.Tr "secrets.secrets"}}</a></li>
</ul>
</div>
</div>

+ 3
- 0
templates/repo/settings/navbar.tmpl View File

@@ -25,6 +25,9 @@
</a>
{{end}}
<a class="{{if .PageIsSettingsKeys}}active {{end}}item" href="{{.RepoLink}}/settings/keys">
{{.locale.Tr "repo.settings.deploy_keys"}}
</a>
<a class="{{if .PageIsSettingsSecrets}}active {{end}}item" href="{{.RepoLink}}/settings/secrets">
{{.locale.Tr "secrets.secrets"}}
</a>
{{if .LFSStartServer}}

+ 8
- 78
templates/repo/settings/secrets.tmpl View File

@@ -1,80 +1,10 @@
<div class="ui container">
<h4 class="ui top attached header">
{{.locale.Tr "secrets.secrets"}}
<div class="ui right">
<div class="ui primary tiny show-panel button" data-panel="#add-secret-panel">{{.locale.Tr "secrets.creation"}}</div>
</div>
</h4>
<div class="ui attached segment">
<div class="{{if not .HasError}}hide {{end}}mb-4" id="add-secret-panel">
<form class="ui form" action="{{.Link}}/secrets" method="post">
{{.CsrfTokenHtml}}
<div class="field">
{{.locale.Tr "secrets.description"}}
</div>
<div class="field{{if .Err_Title}} error{{end}}">
<label for="secret-title">{{.locale.Tr "secrets.name"}}</label>
<input id="secret-title" name="title" value="{{.title}}" autofocus required pattern="^[a-zA-Z_][a-zA-Z0-9_]*$" placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}">
</div>
<div class="field{{if .Err_Content}} error{{end}}">
<label for="secret-content">{{.locale.Tr "secrets.value"}}</label>
<textarea id="secret-content" name="content" required placeholder="{{.locale.Tr "secrets.creation.value_placeholder"}}">{{.content}}</textarea>
</div>
<button class="ui green button">
{{.locale.Tr "secrets.creation"}}
</button>
<button class="ui hide-panel button" data-panel="#add-secret-panel">
{{.locale.Tr "cancel"}}
</button>
</form>
</div>
{{if .Secrets}}
<div class="ui key list">
{{range .Secrets}}
<div class="item">
<div class="right floated content">
<button class="ui red tiny button delete-button" data-modal-id="delete-secret-modal" data-url="{{$.Link}}/secrets/delete" data-id="{{.ID}}">
{{$.locale.Tr "settings.delete_key"}}
</button>
</div>
<div class="left floated content">
<i>{{svg "octicon-key" 32}}</i>
</div>
<div class="content">
<strong>{{.Name}}</strong>
<div class="print meta">******</div>
<div class="activity meta">
<i>
{{$.locale.Tr "settings.add_on"}}
<span>{{.CreatedUnix.FormatShort}}</span>
</i>
</div>
</div>
</div>
{{end}}
</div>
{{else}}
{{.locale.Tr "secrets.none"}}
{{end}}
</div>
</div>

<div class="ui small basic delete modal" id="delete-secret-modal">
<div class="ui icon header">
{{svg "octicon-trash"}}
{{.locale.Tr "secrets.deletion"}}
</div>
<div class="content">
<p>{{.locale.Tr "secrets.deletion.description"}}</p>
</div>
<div class="actions">
<div class="ui red basic inverted cancel button">
<i class="remove icon"></i>
{{.locale.Tr "modal.no"}}
</div>
<div class="ui green basic inverted ok button">
<i class="checkmark icon"></i>
{{.locale.Tr "modal.yes"}}
</div>
{{template "base/head" .}}
<div class="page-content repository settings">
{{template "repo/header" .}}
{{template "repo/settings/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
{{template "shared/secrets/add_list" .}}
</div>
</div>
{{template "base/footer" .}}

+ 68
- 0
templates/shared/secrets/add_list.tmpl View File

@@ -0,0 +1,68 @@
<h4 class="ui top attached header">
{{.locale.Tr "secrets.secrets"}}
<div class="ui right">
<div class="ui primary tiny show-panel button" data-panel="#add-secret-panel">{{.locale.Tr "secrets.creation"}}</div>
</div>
</h4>
<div class="ui attached segment">
<div class="{{if not .HasError}}hide {{end}}mb-4" id="add-secret-panel">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="field">
{{.locale.Tr "secrets.description"}}
</div>
<div class="field{{if .Err_Title}} error{{end}}">
<label for="secret-title">{{.locale.Tr "secrets.name"}}</label>
<input id="secret-title" name="title" value="{{.title}}" autofocus required pattern="^[a-zA-Z_][a-zA-Z0-9_]*$" placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}">
</div>
<div class="field{{if .Err_Content}} error{{end}}">
<label for="secret-content">{{.locale.Tr "secrets.value"}}</label>
<textarea id="secret-content" name="content" required placeholder="{{.locale.Tr "secrets.creation.value_placeholder"}}">{{.content}}</textarea>
</div>
<button class="ui green button">
{{.locale.Tr "secrets.creation"}}
</button>
<button class="ui hide-panel button" data-panel="#add-secret-panel">
{{.locale.Tr "cancel"}}
</button>
</form>
</div>
{{if .Secrets}}
<div class="ui key list">
{{range .Secrets}}
<div class="item">
<div class="right floated content">
<button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
{{$.locale.Tr "settings.delete_key"}}
</button>
</div>
<div class="left floated content">
<i>{{svg "octicon-key" 32}}</i>
</div>
<div class="content">
<strong>{{.Name}}</strong>
<div class="print meta">******</div>
<div class="activity meta">
<i>
{{$.locale.Tr "settings.add_on"}}
<span>{{.CreatedUnix.FormatShort}}</span>
</i>
</div>
</div>
</div>
{{end}}
</div>
{{else}}
{{.locale.Tr "secrets.none"}}
{{end}}
</div>
<div class="ui small basic delete modal">
<div class="ui header">
{{svg "octicon-trash" 16 "mr-2"}}
{{.locale.Tr "secrets.deletion"}}
</div>
<div class="content">
<p>{{.locale.Tr "secrets.deletion.description"}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

+ 3
- 0
templates/user/settings/navbar.tmpl View File

@@ -18,6 +18,9 @@
<a class="{{if .PageIsSettingsKeys}}active {{end}}item" href="{{AppSubUrl}}/user/settings/keys">
{{.locale.Tr "settings.ssh_gpg_keys"}}
</a>
<a class="{{if .PageIsSettingsSecrets}}active {{end}}item" href="{{AppSubUrl}}/user/settings/secrets">
{{.locale.Tr "secrets.secrets"}}
</a>
{{if .EnablePackages}}
<a class="{{if .PageIsSettingsPackages}}active {{end}}item" href="{{AppSubUrl}}/user/settings/packages">
{{.locale.Tr "packages.title"}}

+ 10
- 0
templates/user/settings/secrets.tmpl View File

@@ -0,0 +1,10 @@
{{template "base/head" .}}
<div class="page-content user settings secrets">
{{template "user/settings/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
{{template "shared/secrets/add_list" .}}
</div>
</div>

{{template "base/footer" .}}

Loading…
Cancel
Save