add confirmation to delete ssh key
This commit is contained in:
parent
9b01a3501b
commit
062adbed8a
9 changed files with 169 additions and 121 deletions
|
@ -272,8 +272,9 @@ func runWeb(ctx *cli.Context) {
|
||||||
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
||||||
m.Get("/password", user.SettingsPassword)
|
m.Get("/password", user.SettingsPassword)
|
||||||
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||||
m.Get("/ssh", user.SettingsSSHKeys)
|
m.Combo("/ssh").Get(user.SettingsSSHKeys).
|
||||||
m.Post("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
||||||
|
m.Post("/ssh/delete", user.DeleteSSHKey)
|
||||||
m.Get("/social", user.SettingsSocial)
|
m.Get("/social", user.SettingsSocial)
|
||||||
m.Combo("/applications").Get(user.SettingsApplications).
|
m.Combo("/applications").Get(user.SettingsApplications).
|
||||||
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
|
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
|
||||||
|
|
|
@ -276,6 +276,9 @@ key_name = Key Name
|
||||||
key_content = Content
|
key_content = Content
|
||||||
add_key_success = New SSH key '%s' has been added successfully!
|
add_key_success = New SSH key '%s' has been added successfully!
|
||||||
delete_key = Delete
|
delete_key = Delete
|
||||||
|
ssh_key_deletion = SSH Key Deletion
|
||||||
|
ssh_key_deletion_desc = Delete this SSH key will remove all related accesses for your account. Do you want to continue?
|
||||||
|
ssh_key_deletion_success = SSH key has been deleted successfully!
|
||||||
add_on = Added on
|
add_on = Added on
|
||||||
last_used = Last used on
|
last_used = Last used on
|
||||||
no_activity = No recent activity
|
no_activity = No recent activity
|
||||||
|
|
|
@ -466,7 +466,15 @@ func deletePublicKey(e *xorm.Session, key *PublicKey) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
|
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
|
||||||
func DeletePublicKey(key *PublicKey) (err error) {
|
func DeletePublicKey(id int64) (err error) {
|
||||||
|
key := &PublicKey{ID: id}
|
||||||
|
has, err := x.Id(key.ID).Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
defer sessionRelease(sess)
|
defer sessionRelease(sess)
|
||||||
if err = sess.Begin(); err != nil {
|
if err = sess.Begin(); err != nil {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -677,6 +677,13 @@ func SettingsDeployKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||||
ctx.Data["PageIsSettingsKeys"] = true
|
ctx.Data["PageIsSettingsKeys"] = true
|
||||||
|
|
||||||
|
keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "ListDeployKeys", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Deploykeys"] = keys
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
ctx.HTML(200, DEPLOY_KEYS)
|
ctx.HTML(200, DEPLOY_KEYS)
|
||||||
return
|
return
|
||||||
|
|
|
@ -269,12 +269,12 @@ func SettingsSSHKeys(ctx *middleware.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("settings")
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
ctx.Data["PageIsSettingsSSHKeys"] = true
|
ctx.Data["PageIsSettingsSSHKeys"] = true
|
||||||
|
|
||||||
var err error
|
keys, err := models.ListPublicKeys(ctx.User.Id)
|
||||||
ctx.Data["Keys"], err = models.ListPublicKeys(ctx.User.Id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Handle(500, "ssh.ListPublicKey", err)
|
ctx.Handle(500, "ListPublicKeys", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.Data["Keys"] = keys
|
||||||
|
|
||||||
ctx.HTML(200, SETTINGS_SSH_KEYS)
|
ctx.HTML(200, SETTINGS_SSH_KEYS)
|
||||||
}
|
}
|
||||||
|
@ -283,31 +283,13 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
ctx.Data["Title"] = ctx.Tr("settings")
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
ctx.Data["PageIsSettingsSSHKeys"] = true
|
ctx.Data["PageIsSettingsSSHKeys"] = true
|
||||||
|
|
||||||
var err error
|
keys, err := models.ListPublicKeys(ctx.User.Id)
|
||||||
ctx.Data["Keys"], err = models.ListPublicKeys(ctx.User.Id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Handle(500, "ssh.ListPublicKey", err)
|
ctx.Handle(500, "ListPublicKeys", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.Data["Keys"] = keys
|
||||||
|
|
||||||
// Delete SSH key.
|
|
||||||
if ctx.Query("_method") == "DELETE" {
|
|
||||||
id := com.StrTo(ctx.Query("id")).MustInt64()
|
|
||||||
if id <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = models.DeletePublicKey(&models.PublicKey{ID: id}); err != nil {
|
|
||||||
ctx.Handle(500, "DeletePublicKey", err)
|
|
||||||
} else {
|
|
||||||
log.Trace("SSH key deleted: %s", ctx.User.Name)
|
|
||||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new SSH key.
|
|
||||||
if ctx.Req.Method == "POST" {
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
ctx.HTML(200, SETTINGS_SSH_KEYS)
|
ctx.HTML(200, SETTINGS_SSH_KEYS)
|
||||||
return
|
return
|
||||||
|
@ -325,24 +307,34 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = models.AddPublicKey(ctx.User.Id, form.Title, content); err != nil {
|
if err = models.AddPublicKey(ctx.User.Id, form.Title, content); err != nil {
|
||||||
|
ctx.Data["HasError"] = true
|
||||||
switch {
|
switch {
|
||||||
case models.IsErrKeyAlreadyExist(err):
|
case models.IsErrKeyAlreadyExist(err):
|
||||||
|
ctx.Data["Err_Content"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), SETTINGS_SSH_KEYS, &form)
|
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), SETTINGS_SSH_KEYS, &form)
|
||||||
case models.IsErrKeyNameAlreadyUsed(err):
|
case models.IsErrKeyNameAlreadyUsed(err):
|
||||||
|
ctx.Data["Err_Title"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_name_used"), SETTINGS_SSH_KEYS, &form)
|
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_name_used"), SETTINGS_SSH_KEYS, &form)
|
||||||
default:
|
default:
|
||||||
ctx.Handle(500, "AddPublicKey", err)
|
ctx.Handle(500, "AddPublicKey", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
log.Trace("SSH key added: %s", ctx.User.Name)
|
|
||||||
ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
|
|
||||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.HTML(200, SETTINGS_SSH_KEYS)
|
ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
|
||||||
|
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSSHKey(ctx *middleware.Context) {
|
||||||
|
if err := models.DeletePublicKey(ctx.QueryInt64("id")); err != nil {
|
||||||
|
ctx.Flash.Error("DeletePublicKey: " + err.Error())
|
||||||
|
} else {
|
||||||
|
ctx.Flash.Success(ctx.Tr("settings.ssh_key_deletion_success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(200, map[string]interface{}{
|
||||||
|
"redirect": setting.AppSubUrl + "/user/settings/ssh",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func SettingsSocial(ctx *middleware.Context) {
|
func SettingsSocial(ctx *middleware.Context) {
|
||||||
|
@ -389,6 +381,12 @@ func SettingsApplicationsPost(ctx *middleware.Context, form auth.NewAccessTokenF
|
||||||
ctx.Data["PageIsSettingsApplications"] = true
|
ctx.Data["PageIsSettingsApplications"] = true
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
|
tokens, err := models.ListAccessTokens(ctx.User.Id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "ListAccessTokens", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Tokens"] = tokens
|
||||||
ctx.HTML(200, SETTINGS_APPLICATIONS)
|
ctx.HTML(200, SETTINGS_APPLICATIONS)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,12 +53,12 @@
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="field {{if .Err_Title}}error{{end}}">
|
<div class="field {{if .Err_Title}}error{{end}}">
|
||||||
<label>{{.i18n.Tr "repo.settings.title"}}</label>
|
<label for="title">{{.i18n.Tr "repo.settings.title"}}</label>
|
||||||
<input name="title" value="{{.title}}" autofocus required>
|
<input id="title" name="title" value="{{.title}}" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
<div class="field {{if .Err_Content}}error{{end}}">
|
<div class="field {{if .Err_Content}}error{{end}}">
|
||||||
<label>{{.i18n.Tr "repo.settings.deploy_key_content"}}</label>
|
<label for="content">{{.i18n.Tr "repo.settings.deploy_key_content"}}</label>
|
||||||
<textarea name="content" required>{{.content}}</textarea>
|
<textarea id="content" name="content" required>{{.content}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<button class="ui green button">
|
<button class="ui green button">
|
||||||
{{.i18n.Tr "repo.settings.add_deploy_key"}}
|
{{.i18n.Tr "repo.settings.add_deploy_key"}}
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
<li {{if .PageIsSettingsPassword}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/password">{{.i18n.Tr "settings.password"}}</a></li>
|
<li {{if .PageIsSettingsPassword}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/password">{{.i18n.Tr "settings.password"}}</a></li>
|
||||||
<li {{if .PageIsSettingsEmails}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/email">{{.i18n.Tr "settings.emails"}}</a></li>
|
<li {{if .PageIsSettingsEmails}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/email">{{.i18n.Tr "settings.emails"}}</a></li>
|
||||||
<li {{if .PageIsSettingsSSHKeys}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/ssh">{{.i18n.Tr "settings.ssh_keys"}}</a></li>
|
<li {{if .PageIsSettingsSSHKeys}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/ssh">{{.i18n.Tr "settings.ssh_keys"}}</a></li>
|
||||||
|
{{if .HasOAuthService}}
|
||||||
<li {{if .PageIsSettingsSocial}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/social">{{.i18n.Tr "settings.social"}}</a></li>
|
<li {{if .PageIsSettingsSocial}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/social">{{.i18n.Tr "settings.social"}}</a></li>
|
||||||
|
{{end}}
|
||||||
<li {{if .PageIsSettingsApplications}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/applications">{{.i18n.Tr "settings.applications"}}</a></li>
|
<li {{if .PageIsSettingsApplications}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/applications">{{.i18n.Tr "settings.applications"}}</a></li>
|
||||||
<li {{if .PageIsSettingsDelete}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/delete">{{.i18n.Tr "settings.delete"}}</a></li>
|
<li {{if .PageIsSettingsDelete}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/delete">{{.i18n.Tr "settings.delete"}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,63 +1,92 @@
|
||||||
{{template "ng/base/head" .}}
|
{{template "base/head" .}}
|
||||||
{{template "ng/base/header" .}}
|
<div class="user settings">
|
||||||
<div id="setting-wrapper" class="main-wrapper">
|
<div class="ui container">
|
||||||
<div id="user-profile-setting" class="container clear">
|
<div class="ui grid">
|
||||||
{{template "user/settings/nav" .}}
|
{{template "user/settings/navbar" .}}
|
||||||
<div class="grid-4-5 left">
|
<div class="twelve wide column content">
|
||||||
<div class="setting-content">
|
{{template "base/alert" .}}
|
||||||
{{template "ng/base/alert" .}}
|
<h4 class="ui top attached header">
|
||||||
<div id="user-ssh-setting-content">
|
{{.i18n.Tr "settings.manage_ssh_keys"}}
|
||||||
<div id="user-ssh-panel" class="panel panel-radius">
|
<div class="ui right">
|
||||||
<div class="panel-header">
|
<div class="ui blue tiny show-panel button" data-panel="#add-ssh-key-panel">{{.i18n.Tr "settings.add_key"}}</div>
|
||||||
<a class="show-form-btn" data-target-form="#user-ssh-add-form">
|
</div>
|
||||||
<button class="btn btn-medium btn-black btn-radius right">{{.i18n.Tr "settings.add_key"}}</button>
|
</h4>
|
||||||
</a>
|
<div class="ui attached segment">
|
||||||
<strong>{{.i18n.Tr "settings.manage_ssh_keys"}}</strong>
|
<div class="ui key list">
|
||||||
|
<div class="item">
|
||||||
|
{{.i18n.Tr "settings.ssh_desc"}}
|
||||||
</div>
|
</div>
|
||||||
<ul class="panel-body setting-list">
|
|
||||||
<li>{{.i18n.Tr "settings.ssh_desc"}}</li>
|
|
||||||
{{range .Keys}}
|
{{range .Keys}}
|
||||||
<li class="ssh clear">
|
<div class="item ui grid">
|
||||||
<span class="active-icon left label label-{{if .HasRecentActivity}}green{{else}}gray{{end}} label-radius"></span>
|
<div class="one wide column">
|
||||||
|
<i class="ssh-key-state-indicator fa fa-circle{{if .HasRecentActivity}} active invert poping up{{else}}-o{{end}}" {{if .HasRecentActivity}}data-content="{{$.i18n.Tr "settings.key_state_desc"}}" data-variation="inverted"{{end}}></i>
|
||||||
|
</div>
|
||||||
|
<div class="one wide column">
|
||||||
<i class="mega-octicon octicon-key left"></i>
|
<i class="mega-octicon octicon-key left"></i>
|
||||||
<div class="ssh-content left">
|
|
||||||
<p><strong>{{.Name}}</strong></p>
|
|
||||||
<p class="print">{{.Fingerprint}}</p>
|
|
||||||
<p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created}}</span> — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span title="{{DateFmtLong .Updated}}">{{DateFmtShort .Updated}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p>
|
|
||||||
</div>
|
</div>
|
||||||
<form action="{{AppSubUrl}}/user/settings/ssh" method="post">
|
<div class="eleven wide column">
|
||||||
{{$.CsrfTokenHtml}}
|
<strong>{{.Name}}</strong>
|
||||||
<input name="_method" type="hidden" value="DELETE">
|
<div class="print meta">
|
||||||
<input name="id" type="hidden" value="{{.ID}}">
|
{{.Fingerprint}}
|
||||||
<button class="right ssh-btn btn btn-red btn-radius btn-small">{{$.i18n.Tr "settings.delete_key"}}</button>
|
</div>
|
||||||
</form>
|
<div class="activity meta">
|
||||||
</li>
|
<i>{{$.i18n.Tr "settings.add_on"}} <span>{{DateFmtShort .Created}}</span> — <i class="octicon octicon-info"></i> {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span>{{DateFmtShort .Updated}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="two wide column">
|
||||||
|
<button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
|
||||||
|
{{$.i18n.Tr "settings.delete_key"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<p>{{.i18n.Tr "settings.ssh_helper" "https://help.github.com/articles/generating-ssh-keys" "https://help.github.com/ssh-issues/" | Str2html}}</p>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<form class="panel panel-radius form form-align form-settings-add hide" id="user-ssh-add-form" action="{{AppSubUrl}}/user/settings/ssh" method="post">
|
<p>{{.i18n.Tr "settings.ssh_helper" "https://help.github.com/articles/generating-ssh-keys" "https://help.github.com/ssh-issues/" | Str2html}}</p>
|
||||||
|
<div {{if not .HasError}}class="hide"{{end}} id="add-ssh-key-panel">
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{.i18n.Tr "settings.add_new_key"}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<p class="panel-header"><strong>{{.i18n.Tr "settings.add_new_key"}}</strong></p>
|
<div class="field {{if .Err_Title}}error{{end}}">
|
||||||
<div class="panel-body">
|
<label for="title">{{.i18n.Tr "settings.key_name"}}</label>
|
||||||
<p class="field">
|
<input id="title" name="title" value="{{.title}}" autofocus required>
|
||||||
<label class="req" for="ssh-title">{{.i18n.Tr "settings.key_name"}}</label>
|
|
||||||
<input class="ipt ipt-radius" id="ssh-title" name="title" type="text" required />
|
|
||||||
</p>
|
|
||||||
<p class="field clear">
|
|
||||||
<label class="left req" for="ssh-key">{{.i18n.Tr "settings.key_content"}}</label>
|
|
||||||
<textarea class="ipt ipt-radius left" name="content" id="ssh-key" required></textarea>
|
|
||||||
</p>
|
|
||||||
<p class="field">
|
|
||||||
<label></label>
|
|
||||||
<button class="btn btn-green btn-radius" id="ssh-add-btn">{{.i18n.Tr "settings.add_key"}}</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field {{if .Err_Content}}error{{end}}">
|
||||||
|
<label for="content">{{.i18n.Tr "settings.key_content"}}</label>
|
||||||
|
<textarea id="content" name="content" required>{{.content}}</textarea>
|
||||||
|
</div>
|
||||||
|
<button class="ui green button">
|
||||||
|
{{.i18n.Tr "settings.add_key"}}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "ng/base/footer" .}}
|
|
||||||
|
<div class="ui small basic delete modal">
|
||||||
|
<div class="ui icon header">
|
||||||
|
<i class="trash icon"></i>
|
||||||
|
{{.i18n.Tr "settings.ssh_key_deletion"}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>{{.i18n.Tr "settings.ssh_key_deletion_desc"}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="ui red basic inverted cancel button">
|
||||||
|
<i class="remove icon"></i>
|
||||||
|
{{.i18n.Tr "modal.no"}}
|
||||||
|
</div>
|
||||||
|
<div class="ui green basic inverted ok button">
|
||||||
|
<i class="checkmark icon"></i>
|
||||||
|
{{.i18n.Tr "modal.yes"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
Reference in a new issue