mirror of
https://github.com/thomiceli/opengist.git
synced 2025-06-13 05:47:12 +02:00
Style preference tab for user (#467)
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/thomiceli/opengist/internal/git"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@ -18,6 +19,7 @@ type User struct {
|
||||
GitlabID string
|
||||
GiteaID string
|
||||
OIDCID string `gorm:"column:oidc_id"`
|
||||
StylePreferences string
|
||||
|
||||
Gists []Gist `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:UserID"`
|
||||
SSHKeys []SSHKey `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:UserID"`
|
||||
@ -234,6 +236,15 @@ func (user *User) HasMFA() (bool, bool, error) {
|
||||
return webauthn, totp, err
|
||||
}
|
||||
|
||||
func (user *User) GetStyle() *UserStyleDTO {
|
||||
style := new(UserStyleDTO)
|
||||
err := json.Unmarshal([]byte(user.StylePreferences), style)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
// -- DTO -- //
|
||||
|
||||
type UserDTO struct {
|
||||
@ -251,3 +262,18 @@ func (dto *UserDTO) ToUser() *User {
|
||||
type UserUsernameDTO struct {
|
||||
Username string `form:"username" validate:"required,max=24,alphanumdash,notreserved"`
|
||||
}
|
||||
|
||||
type UserStyleDTO struct {
|
||||
SoftWrap bool `form:"softwrap" json:"soft_wrap"`
|
||||
RemovedLineColor string `form:"removedlinecolor" json:"removed_line_color" validate:"min=0,max=7"`
|
||||
AddedLineColor string `form:"addedlinecolor" json:"added_line_color" validate:"min=0,max=7"`
|
||||
GitLineColor string `form:"gitlinecolor" json:"git_line_color" validate:"min=0,max=7"`
|
||||
}
|
||||
|
||||
func (dto *UserStyleDTO) ToJson() string {
|
||||
data, err := json.Marshal(dto)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
@ -148,6 +148,17 @@ settings.create-password-help: Create your password to login to Opengist via HTT
|
||||
settings.change-password: Change password
|
||||
settings.change-password-help: Change your password to login to Opengist via HTTP
|
||||
settings.password-label-title: Password
|
||||
settings.header.account: Account
|
||||
settings.header.mfa: MFA
|
||||
settings.header.ssh: SSH
|
||||
settings.header.style: Style
|
||||
settings.style.gist-code: Gist code
|
||||
settings.style.no-soft-wrap: No Soft Wrap
|
||||
settings.style.soft-wrap: Soft Wrap
|
||||
settings.style.removed-lines-color: Removed lines color
|
||||
settings.style.added-lines-color: Added lines color
|
||||
settings.style.git-lines-color: Git lines color
|
||||
settings.style.save-style: Save style
|
||||
|
||||
auth.signup-disabled: Administrator has disabled signing up
|
||||
auth.login: Login
|
||||
|
@ -14,7 +14,7 @@ func BeginTotp(ctx *context.Context) error {
|
||||
return ctx.ErrorRes(500, "Cannot check for user MFA", err)
|
||||
} else if hasTotp {
|
||||
ctx.AddFlash(ctx.Tr("auth.totp.already-enabled"), "error")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/mfa")
|
||||
}
|
||||
|
||||
ogUrl, err := url.Parse(ctx.GetData("baseHttpUrl").(string))
|
||||
@ -47,7 +47,7 @@ func FinishTotp(ctx *context.Context) error {
|
||||
return ctx.ErrorRes(500, "Cannot check for user MFA", err)
|
||||
} else if hasTotp {
|
||||
ctx.AddFlash(ctx.Tr("auth.totp.already-enabled"), "error")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/mfa")
|
||||
}
|
||||
|
||||
dto := &db.TOTPDTO{}
|
||||
@ -134,7 +134,7 @@ func AssertTotp(ctx *context.Context) error {
|
||||
}
|
||||
|
||||
ctx.AddFlash(ctx.Tr("auth.totp.code-used", dto.Code), "warning")
|
||||
redirectUrl = "/settings"
|
||||
redirectUrl = "/settings/mfa"
|
||||
}
|
||||
|
||||
sess.Values["user"] = userId
|
||||
@ -157,7 +157,7 @@ func DisableTotp(ctx *context.Context) error {
|
||||
}
|
||||
|
||||
ctx.AddFlash(ctx.Tr("auth.totp.disabled"), "success")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/mfa")
|
||||
}
|
||||
|
||||
func RegenerateTotpRecoveryCodes(ctx *context.Context) error {
|
||||
|
@ -5,13 +5,19 @@ import (
|
||||
"github.com/thomiceli/opengist/internal/web/context"
|
||||
)
|
||||
|
||||
func UserSettings(ctx *context.Context) error {
|
||||
func UserAccount(ctx *context.Context) error {
|
||||
user := ctx.User
|
||||
|
||||
keys, err := db.GetSSHKeysByUserID(user.ID)
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot get SSH keys", err)
|
||||
}
|
||||
ctx.SetData("email", user.Email)
|
||||
ctx.SetData("hasPassword", user.Password != "")
|
||||
ctx.SetData("disableForm", ctx.GetData("DisableLoginForm"))
|
||||
ctx.SetData("settingsHeaderPage", "account")
|
||||
ctx.SetData("htmlTitle", ctx.TrH("settings"))
|
||||
return ctx.Html("settings_account.html")
|
||||
}
|
||||
|
||||
func UserMFA(ctx *context.Context) error {
|
||||
user := ctx.User
|
||||
|
||||
passkeys, err := db.GetAllCredentialsForUser(user.ID)
|
||||
if err != nil {
|
||||
@ -23,12 +29,48 @@ func UserSettings(ctx *context.Context) error {
|
||||
return ctx.ErrorRes(500, "Cannot get MFA status", err)
|
||||
}
|
||||
|
||||
ctx.SetData("email", user.Email)
|
||||
ctx.SetData("sshKeys", keys)
|
||||
ctx.SetData("passkeys", passkeys)
|
||||
ctx.SetData("hasTotp", hasTotp)
|
||||
ctx.SetData("hasPassword", user.Password != "")
|
||||
ctx.SetData("disableForm", ctx.GetData("DisableLoginForm"))
|
||||
ctx.SetData("settingsHeaderPage", "mfa")
|
||||
ctx.SetData("htmlTitle", ctx.TrH("settings"))
|
||||
return ctx.Html("settings.html")
|
||||
return ctx.Html("settings_mfa.html")
|
||||
}
|
||||
|
||||
func UserSSHKeys(ctx *context.Context) error {
|
||||
user := ctx.User
|
||||
|
||||
keys, err := db.GetSSHKeysByUserID(user.ID)
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot get SSH keys", err)
|
||||
}
|
||||
|
||||
ctx.SetData("sshKeys", keys)
|
||||
ctx.SetData("settingsHeaderPage", "ssh")
|
||||
ctx.SetData("htmlTitle", ctx.TrH("settings"))
|
||||
return ctx.Html("settings_ssh.html")
|
||||
}
|
||||
|
||||
func UserStyle(ctx *context.Context) error {
|
||||
ctx.SetData("settingsHeaderPage", "style")
|
||||
ctx.SetData("htmlTitle", ctx.TrH("settings"))
|
||||
return ctx.Html("settings_style.html")
|
||||
}
|
||||
|
||||
func ProcessUserStyle(ctx *context.Context) error {
|
||||
styleDto := new(db.UserStyleDTO)
|
||||
if err := ctx.Bind(styleDto); err != nil {
|
||||
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
|
||||
}
|
||||
|
||||
if err := ctx.Validate(styleDto); err != nil {
|
||||
return ctx.ErrorRes(400, "Invalid data", err)
|
||||
}
|
||||
user := ctx.User
|
||||
user.StylePreferences = styleDto.ToJson()
|
||||
if err := user.Update(); err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot update user styles", err)
|
||||
}
|
||||
|
||||
ctx.AddFlash("Updated style", "success")
|
||||
return ctx.RedirectTo("/settings/style")
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func SshKeysProcess(ctx *context.Context) error {
|
||||
|
||||
if err := ctx.Validate(dto); err != nil {
|
||||
ctx.AddFlash(validator.ValidationMessages(&err, ctx.GetData("locale").(*i18n.Locale)), "error")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/ssh")
|
||||
}
|
||||
key := dto.ToSSHKey()
|
||||
|
||||
@ -29,7 +29,7 @@ func SshKeysProcess(ctx *context.Context) error {
|
||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Content))
|
||||
if err != nil {
|
||||
ctx.AddFlash(ctx.Tr("flash.user.invalid-ssh-key"), "error")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/ssh")
|
||||
}
|
||||
key.Content = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pubKey)))
|
||||
|
||||
@ -38,7 +38,7 @@ func SshKeysProcess(ctx *context.Context) error {
|
||||
return ctx.ErrorRes(500, "Cannot check if SSH key exists", err)
|
||||
}
|
||||
ctx.AddFlash(ctx.Tr("settings.ssh-key-exists"), "error")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/ssh")
|
||||
}
|
||||
|
||||
if err := key.Create(); err != nil {
|
||||
@ -46,20 +46,20 @@ func SshKeysProcess(ctx *context.Context) error {
|
||||
}
|
||||
|
||||
ctx.AddFlash(ctx.Tr("flash.user.ssh-key-added"), "success")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/ssh")
|
||||
}
|
||||
|
||||
func SshKeysDelete(ctx *context.Context) error {
|
||||
user := ctx.User
|
||||
keyId, err := strconv.Atoi(ctx.Param("id"))
|
||||
if err != nil {
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/ssh")
|
||||
}
|
||||
|
||||
key, err := db.GetSSHKeyByID(uint(keyId))
|
||||
|
||||
if err != nil || key.UserID != user.ID {
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/ssh")
|
||||
}
|
||||
|
||||
if err := key.Delete(); err != nil {
|
||||
@ -67,5 +67,5 @@ func SshKeysDelete(ctx *context.Context) error {
|
||||
}
|
||||
|
||||
ctx.AddFlash(ctx.Tr("flash.user.ssh-key-deleted"), "success")
|
||||
return ctx.RedirectTo("/settings")
|
||||
return ctx.RedirectTo("/settings/ssh")
|
||||
}
|
||||
|
@ -275,6 +275,7 @@ func sessionInit(next Handler) Handler {
|
||||
if user != nil {
|
||||
ctx.User = user
|
||||
ctx.SetData("userLogged", user)
|
||||
ctx.SetData("currentStyle", user.GetStyle())
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
|
@ -186,6 +186,10 @@ func (s *Server) setFuncMap() {
|
||||
}
|
||||
return str
|
||||
},
|
||||
"hexToRgb": func(hex string) string {
|
||||
h, _ := strconv.ParseUint(strings.TrimPrefix(hex, "#"), 16, 32)
|
||||
return fmt.Sprintf("%d, %d, %d,", (h>>16)&0xFF, (h>>8)&0xFF, h&0xFF)
|
||||
},
|
||||
}
|
||||
|
||||
t := template.Must(template.New("t").Funcs(fm).ParseFS(templates.Files, "*/*.html"))
|
||||
|
@ -56,7 +56,11 @@ func (s *Server) registerRoutes() {
|
||||
sA := r.SubGroup("/settings")
|
||||
{
|
||||
sA.Use(logged)
|
||||
sA.GET("", settings.UserSettings)
|
||||
sA.GET("", settings.UserAccount)
|
||||
sA.GET("/mfa", settings.UserMFA)
|
||||
sA.GET("/ssh", settings.UserSSHKeys)
|
||||
sA.GET("/style", settings.UserStyle)
|
||||
sA.POST("/style", settings.ProcessUserStyle)
|
||||
sA.POST("/email", settings.EmailProcess)
|
||||
sA.DELETE("/account", settings.AccountDeleteProcess)
|
||||
sA.POST("/ssh-keys", settings.SshKeysProcess)
|
||||
|
13
public/style.css
vendored
13
public/style.css
vendored
@ -10,6 +10,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--red-diff: rgba(255, 0, 0, .1);
|
||||
--green-diff: rgba(0, 255, 128, .1);
|
||||
--git-diff: rgba(143, 143, 143, 0.38);
|
||||
}
|
||||
|
||||
html {
|
||||
@apply bg-gray-50 dark:bg-gray-800;
|
||||
}
|
||||
@ -41,18 +47,19 @@ pre {
|
||||
.code .line-num {
|
||||
width: 4%;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.red-diff {
|
||||
background-color: rgba(255, 0, 0, .1);
|
||||
background-color: var(--red-diff);
|
||||
}
|
||||
|
||||
.green-diff {
|
||||
background-color: rgba(0, 255, 128, .1);
|
||||
background-color: var(--green-diff);
|
||||
}
|
||||
|
||||
.gray-diff {
|
||||
background-color: rgba(143, 143, 143, 0.38);
|
||||
background-color: var(--git-diff);
|
||||
@apply py-4 !important
|
||||
}
|
||||
|
||||
|
45
public/style_preferences.ts
Normal file
45
public/style_preferences.ts
Normal file
@ -0,0 +1,45 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const noSoftWrapRadio = document.getElementById('no-soft-wrap');
|
||||
const softWrapRadio = document.getElementById('soft-wrap');
|
||||
|
||||
function updateRootClass() {
|
||||
const table = document.querySelector("table");
|
||||
|
||||
if (softWrapRadio.checked) {
|
||||
table.classList.remove('whitespace-pre');
|
||||
table.classList.add('whitespace-pre-wrap');
|
||||
} else {
|
||||
table.classList.remove('whitespace-pre-wrap');
|
||||
table.classList.add('whitespace-pre');
|
||||
}
|
||||
}
|
||||
|
||||
noSoftWrapRadio.addEventListener('change', updateRootClass);
|
||||
softWrapRadio.addEventListener('change', updateRootClass);
|
||||
|
||||
|
||||
document.getElementById('removedlinecolor').addEventListener('change', function(event) {
|
||||
const color = hexToRgba(event.target.value, 0.1);
|
||||
document.documentElement.style.setProperty('--red-diff', color);
|
||||
});
|
||||
|
||||
document.getElementById('addedlinecolor').addEventListener('change', function(event) {
|
||||
const color = hexToRgba(event.target.value, 0.1);
|
||||
document.documentElement.style.setProperty('--green-diff', color);
|
||||
});
|
||||
|
||||
document.getElementById('gitlinecolor').addEventListener('change', function(event) {
|
||||
const color = hexToRgba(event.target.value, 0.38);
|
||||
document.documentElement.style.setProperty('--git-diff', color);
|
||||
});
|
||||
});
|
||||
|
||||
function hexToRgba(hex, opacity) {
|
||||
hex = hex.replace('#', '');
|
||||
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
}
|
3
public/vite.config.js
vendored
3
public/vite.config.js
vendored
@ -15,7 +15,8 @@ export default defineConfig({
|
||||
'./public/admin.ts',
|
||||
'./public/gist.ts',
|
||||
'./public/embed.ts',
|
||||
'./public/webauthn.ts'
|
||||
'./public/webauthn.ts',
|
||||
'./public/style_preferences.ts'
|
||||
]
|
||||
},
|
||||
assetsInlineLimit: 0,
|
||||
|
10
templates/base/base_header.html
vendored
10
templates/base/base_header.html
vendored
@ -50,6 +50,16 @@
|
||||
{{ else }}
|
||||
<title>{{ if $.c.CustomName }}{{ $.c.CustomName }}{{ else }}Opengist{{ end }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ if .currentStyle }}
|
||||
<style>
|
||||
:root {
|
||||
--red-diff: rgba({{ hexToRgb .currentStyle.RemovedLineColor }} 0.1);
|
||||
--green-diff: rgba({{ hexToRgb .currentStyle.AddedLineColor }} 0.1);
|
||||
--git-diff: rgba({{ hexToRgb .currentStyle.GitLineColor }} 0.38);
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<div id="app" class="text-gray-700 dark:text-white min-h-full bg-white dark:bg-gray-900">
|
||||
|
8
templates/base/settings_footer.html
vendored
Normal file
8
templates/base/settings_footer.html
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{{ if false }}{{/* prevent IDE errors */}}
|
||||
<div><main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "settings_footer" }}
|
||||
</main>
|
||||
</div>
|
||||
{{ end }}
|
28
templates/base/settings_header.html
vendored
Normal file
28
templates/base/settings_header.html
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
{{ define "settings_header" }}
|
||||
<div class="py-10">
|
||||
<header class="pb-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold leading-tight">{{ .locale.Tr "settings" }}</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="mb-4">
|
||||
<div class="">
|
||||
<nav class="flex space-x-4" aria-label="Tabs">
|
||||
<a href="{{ $.c.ExternalUrl }}/settings" class="{{ if eq .settingsHeaderPage "account" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}">{{ .locale.Tr "settings.header.account" }} </a>
|
||||
<a href="{{ $.c.ExternalUrl }}/settings/mfa" class="{{ if eq .settingsHeaderPage "mfa" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">{{ .locale.Tr "settings.header.mfa" }}</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/settings/ssh" class="{{ if eq .settingsHeaderPage "ssh" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">{{ .locale.Tr "settings.header.ssh" }}</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/settings/style" class="{{ if eq .settingsHeaderPage "style" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">{{ .locale.Tr "settings.header.style" }}</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if false }}
|
||||
{{/* prevent IDE errors */}}
|
||||
</main></div>
|
||||
{{ end }}
|
4
templates/pages/gist.html
vendored
4
templates/pages/gist.html
vendored
@ -74,11 +74,11 @@
|
||||
<div class="code">
|
||||
{{ $fileslug := slug $file.Filename }}
|
||||
{{ if ne $file.Content "" }}
|
||||
<table class="chroma table-code w-full whitespace-pre" data-filename-slug="{{ $fileslug }}" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<table class="chroma table-code w-full {{ if $.currentStyle }}{{ if $.currentStyle.SoftWrap }}whitespace-pre-wrap{{ else }}whitespace-pre{{ end }}{{ else }}whitespace-pre{{ end }}" data-filename-slug="{{ $fileslug }}" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<tbody>
|
||||
{{ $ii := "1" }}
|
||||
{{ $i := toInt $ii }}
|
||||
{{ range $line := $file.Lines }}<tr><td id="file-{{ $fileslug }}-{{$i}}" class="select-none line-num px-4">{{$i}}</td><td class="line-code">{{ $line | safe }}</td></tr>{{ $i = inc $i }}{{ end }}
|
||||
{{ range $line := $file.Lines }}<tr><td id="file-{{ $fileslug }}-{{$i}}" class="select-none line-num px-4">{{$i}}</td><td class="line-code break-all">{{ $line | safe }}</td></tr>{{ $i = inc $i }}{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
|
4
templates/pages/revisions.html
vendored
4
templates/pages/revisions.html
vendored
@ -54,7 +54,7 @@
|
||||
{{ else if eq $file.Content "" }}
|
||||
<p class="m-2 ml-4 text-sm">{{ $.locale.Tr "gist.revision.empty-file" }}</p>
|
||||
{{ else }}
|
||||
<table class="code chroma table-code w-full whitespace-pre" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0">
|
||||
<table class="code chroma table-code w-full {{ if $.currentStyle }}{{ if $.currentStyle.SoftWrap }}whitespace-pre-wrap{{ else }}whitespace-pre{{ end }}{{ else }}whitespace-pre{{ end }}" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0">
|
||||
<tbody>
|
||||
{{ $left := 0 }}
|
||||
{{ $right := 0 }}
|
||||
@ -83,7 +83,7 @@
|
||||
{{ $right = inc $right }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<td class="select-none" style="width: 2%;">{{ if ne (index $line 0) 64 }}{{ slice $line 0 1 }}{{ end }}</td>
|
||||
<td class="select-none" style="width: 2%; vertical-align: top;">{{ if ne (index $line 0) 64 }}{{ slice $line 0 1 }}{{ end }}</td>
|
||||
<td>{{ if ne (index $line 0) 64 }}{{ slice $line 1 }}{{ else }}{{ $line }}{{ end }}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
316
templates/pages/settings.html
vendored
316
templates/pages/settings.html
vendored
@ -1,316 +0,0 @@
|
||||
{{ template "header" .}}
|
||||
<div class="py-10">
|
||||
<header class="pb-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold leading-tight">{{ .locale.Tr "settings" }}</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="relative mx-auto max-w-[40rem] space-y-8">
|
||||
<div class="sm:grid {{ if not .disableForm }}grid-cols-2{{ else }}grid-cols-1{{ end }} gap-x-4 md:gap-x-8 space-y-8 md:space-y-0">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10 h-full">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.change-username" }}
|
||||
</h2>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/username" method="post">
|
||||
<div>
|
||||
<div class="mt-1">
|
||||
<input id="username-change" name="username" type="text" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
{{ .locale.Tr "settings.change-username" }}
|
||||
</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ if not .disableForm }}
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{if .hasPassword}}
|
||||
{{ .locale.Tr "settings.change-password" }}
|
||||
{{else}}
|
||||
{{ .locale.Tr "settings.create-password" }}
|
||||
{{end}}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{if .hasPassword}}
|
||||
{{ .locale.Tr "settings.change-password-help" }}
|
||||
{{else}}
|
||||
{{ .locale.Tr "settings.create-password-help" }}
|
||||
{{end}}
|
||||
</h3>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/password" method="post">
|
||||
<div>
|
||||
<label for="password-change" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> {{ .locale.Tr "settings.password-label-title" }} </label>
|
||||
<div class="mt-1">
|
||||
<input id="password-change" name="password" type="password" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
{{if .hasPassword}}
|
||||
{{ .locale.Tr "settings.change-password" }}
|
||||
{{else}}
|
||||
{{ .locale.Tr "settings.create-password" }}
|
||||
{{end}}
|
||||
</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.email" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "settings.email-help" }}
|
||||
</h3>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/email" method="post">
|
||||
<div>
|
||||
<div class="mt-1">
|
||||
<input id="email" name="email" value="{{ .userLogged.Email }}" type="email" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "settings.email-set" }}</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ if or .githubOauth .gitlabOauth .giteaOauth .oidcOauth }}
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300 mb-2">
|
||||
{{ .locale.Tr "settings.link-accounts" }}
|
||||
</h2>
|
||||
<div class="gap-y-2">
|
||||
|
||||
{{ if .githubOauth }}
|
||||
{{ if .userLogged.GithubID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/github/unlink" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your GitHub account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
{{ .locale.Tr "settings.unlink-github-account" }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "settings.link-github-account" }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .gitlabOauth }}
|
||||
{{ if .userLogged.GitlabID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitlab/unlink" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your GitLab account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
{{ .locale.Tr "settings.unlink-gitlab-account" }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitlab" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "settings.link-gitlab-account" }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .giteaOauth }}
|
||||
{{ if .userLogged.GiteaID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitea/unlink" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your Gitea account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
{{ .locale.Tr "settings.unlink-gitea-account" }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitea" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "settings.link-gitea-account" }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if .oidcOauth }}
|
||||
{{ if .userLogged.OIDCID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/openid-connect/unlink" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your OpenID account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
Unlink OpenID account
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/openid-connect" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
Link OpenID account
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "auth.totp" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "auth.totp.help" }}
|
||||
</h3>
|
||||
{{ if .hasTotp }}
|
||||
<div class="flex">
|
||||
<form method="post" action="{{ $.c.ExternalUrl }}/settings/totp" onconfirm="" class="mr-2">
|
||||
<input type="hidden" name="_method" value="DELETE" />
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500">{{ .locale.Tr "auth.totp.disable" }}</button>
|
||||
</form>
|
||||
<form method="post" action="{{ $.c.ExternalUrl }}/settings/totp/regenerate" onconfirm="">
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.totp.regenerate-recovery-codes" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/settings/totp/generate" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.totp.use" }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:grid grid-cols-2 gap-x-4 md:gap-x-8">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "auth.mfa.passkeys" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "auth.mfa.passkeys-help" }}
|
||||
</h3>
|
||||
<form class="space-y-6" id="webauthn">
|
||||
<div>
|
||||
<label for="passkeyname" class="block text-sm font-medium text-slate-700 dark:text-slate-300">{{ .locale.Tr "auth.mfa.passkey-name" }}</label>
|
||||
<div class="mt-1">
|
||||
<input id="passkeyname" name="passkeyname" type="text" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
{{ .csrfHtml }}
|
||||
<button id="bind-passkey-button" type="button" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.mfa.bind-passkey" }}</button>
|
||||
</form>
|
||||
<div class="flex items-center justify-center mt-4">
|
||||
<p id="login-passkey-wait" class="hidden text-sm font-medium items-center text-slate-700 dark:text-slate-300">{{ .locale.Tr "auth.mfa.waiting-for-passkey-input" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-6 flow-root">
|
||||
<ul role="list" class="-my-5 divide-y divide-gray-300 dark:divide-gray-700 list-none">
|
||||
{{ if .passkeys }}
|
||||
{{ range $passkey := .passkeys }}
|
||||
<li class="py-5">
|
||||
<div class="inline-flex">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-12 h-12 mr-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300">{{ .Name }}</h3>
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "auth.mfa.passkey-added-at" }} <span class="moment-timestamp-date">{{ .CreatedAt }}</span></p>
|
||||
{{ if eq .LastUsedAt 0 }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "auth.mfa.passkey-never-used" }}</p>
|
||||
{{ else }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "auth.mfa.passkey-last-used" }} <span class="moment-timestamp">{{ .LastUsedAt }}</span></p>
|
||||
{{ end }}
|
||||
</div>
|
||||
<form action="{{ $.c.ExternalUrl }}/settings/passkeys/{{.ID}}" method="post" class="inline-block">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
{{ $.csrfHtml }}
|
||||
<button type="submit" onclick="return confirm('{{ $.locale.Tr "auth.mfa.delete-passkey-confirm" }}');" class="align-middle items-center leading-2 ml-2 px-3 py-1 border border-transparent border-gray-200 dark:border-gray-700 text-xs font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500">{{ $.locale.Tr "auth.mfa.delete-passkey" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:grid grid-cols-2 gap-x-4 md:gap-x-8">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.add-ssh-key" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "settings.add-ssh-key-help" }}
|
||||
</h3>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/ssh-keys" method="post">
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> {{ .locale.Tr "settings.add-ssh-key-title" }} </label>
|
||||
<div class="mt-1">
|
||||
<input id="title" name="title" type="text" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<label for="sshkey" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> {{ .locale.Tr "settings.add-ssh-key-content" }} </label>
|
||||
<div class="mt-1">
|
||||
<textarea id="sshkey" required autocomplete="off" name="content" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "settings.add-ssh-key" }}</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-6 flow-root">
|
||||
<ul role="list" class="-my-5 divide-y divide-gray-300 dark:divide-gray-700 list-none">
|
||||
{{ if .sshKeys }}
|
||||
{{ range $key := .sshKeys }}
|
||||
<li class="py-5">
|
||||
<div class="inline-flex">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-12 h-12 mr-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300">{{ .Title }}</h3>
|
||||
<p class="mt-1 text-xs text-slate-600 dark:text-slate-400 line-clamp-2 code" style="overflow-wrap: anywhere">SHA256:{{.SHA}}</p>
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "settings.ssh-key-added-at" }} <span class="moment-timestamp-date">{{ .CreatedAt }}</span></p>
|
||||
{{ if eq .LastUsedAt 0 }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "settings.ssh-key-never-used" }}</p>
|
||||
{{ else }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "settings.ssh-key-last-used" }} <span class="moment-timestamp">{{ .LastUsedAt }}</span></p>
|
||||
{{ end }}
|
||||
</div>
|
||||
<form action="{{ $.c.ExternalUrl }}/settings/ssh-keys/{{.ID}}" method="post" class="inline-block">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
{{ $.csrfHtml }}
|
||||
|
||||
<button type="submit" onclick="return confirm('{{ $.locale.Tr "settings.delete-ssh-key-confirm" }}')" class="align-middle items-center leading-2 ml-2 px-3 py-1 border border-transparent border-gray-200 dark:border-gray-700 text-xs font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500">{{ $.locale.Tr "settings.delete-ssh-key" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.delete-account" }}
|
||||
</h2>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/account" method="post">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
<button type="submit" onclick="return confirm('{{ .locale.Tr "settings.delete-account-confirm" }}')" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 mt-2">{{ .locale.Tr "settings.delete-account" }}</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module" src="{{ asset "webauthn.ts" }}"></script>
|
||||
|
||||
|
||||
{{ template "footer" .}}
|
162
templates/pages/settings_account.html
vendored
Normal file
162
templates/pages/settings_account.html
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
{{ template "header" .}}
|
||||
{{ template "settings_header" .}}
|
||||
<div class="relative mx-auto max-w-[40rem] space-y-8">
|
||||
<div class="sm:grid {{ if not .disableForm }}grid-cols-2{{ else }}grid-cols-1{{ end }} gap-x-4 md:gap-x-8 space-y-8 md:space-y-0">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10 h-full">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.change-username" }}
|
||||
</h2>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/username" method="post">
|
||||
<div>
|
||||
<div class="mt-1">
|
||||
<input id="username-change" name="username" type="text" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
{{ .locale.Tr "settings.change-username" }}
|
||||
</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ if not .disableForm }}
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{if .hasPassword}}
|
||||
{{ .locale.Tr "settings.change-password" }}
|
||||
{{else}}
|
||||
{{ .locale.Tr "settings.create-password" }}
|
||||
{{end}}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{if .hasPassword}}
|
||||
{{ .locale.Tr "settings.change-password-help" }}
|
||||
{{else}}
|
||||
{{ .locale.Tr "settings.create-password-help" }}
|
||||
{{end}}
|
||||
</h3>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/password" method="post">
|
||||
<div>
|
||||
<label for="password-change" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> {{ .locale.Tr "settings.password-label-title" }} </label>
|
||||
<div class="mt-1">
|
||||
<input id="password-change" name="password" type="password" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
{{if .hasPassword}}
|
||||
{{ .locale.Tr "settings.change-password" }}
|
||||
{{else}}
|
||||
{{ .locale.Tr "settings.create-password" }}
|
||||
{{end}}
|
||||
</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.email" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "settings.email-help" }}
|
||||
</h3>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/email" method="post">
|
||||
<div>
|
||||
<div class="mt-1">
|
||||
<input id="email" name="email" value="{{ .userLogged.Email }}" type="email" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "settings.email-set" }}</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ if or .githubOauth .gitlabOauth .giteaOauth .oidcOauth }}
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300 mb-2">
|
||||
{{ .locale.Tr "settings.link-accounts" }}
|
||||
</h2>
|
||||
<div class="gap-y-2">
|
||||
|
||||
{{ if .githubOauth }}
|
||||
{{ if .userLogged.GithubID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/github/unlink" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your GitHub account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
{{ .locale.Tr "settings.unlink-github-account" }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "settings.link-github-account" }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .gitlabOauth }}
|
||||
{{ if .userLogged.GitlabID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitlab/unlink" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your GitLab account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
{{ .locale.Tr "settings.unlink-gitlab-account" }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitlab" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "settings.link-gitlab-account" }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .giteaOauth }}
|
||||
{{ if .userLogged.GiteaID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitea/unlink" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your Gitea account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
{{ .locale.Tr "settings.unlink-gitea-account" }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitea" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "settings.link-gitea-account" }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if .oidcOauth }}
|
||||
{{ if .userLogged.OIDCID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/openid-connect/unlink" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your OpenID account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
Unlink OpenID account
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/openid-connect" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
Link OpenID account
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.delete-account" }}
|
||||
</h2>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/account" method="post">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
<button type="submit" onclick="return confirm('{{ .locale.Tr "settings.delete-account-confirm" }}')" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 mt-2">{{ .locale.Tr "settings.delete-account" }}</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "settings_footer" .}}
|
||||
{{ template "footer" .}}
|
91
templates/pages/settings_mfa.html
vendored
Normal file
91
templates/pages/settings_mfa.html
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
{{ template "header" .}}
|
||||
{{ template "settings_header" .}}
|
||||
<div class="relative mx-auto max-w-[40rem] space-y-8">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "auth.totp" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "auth.totp.help" }}
|
||||
</h3>
|
||||
{{ if .hasTotp }}
|
||||
<div class="flex">
|
||||
<form method="post" action="{{ $.c.ExternalUrl }}/settings/totp" onconfirm="" class="mr-2">
|
||||
<input type="hidden" name="_method" value="DELETE" />
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500">{{ .locale.Tr "auth.totp.disable" }}</button>
|
||||
</form>
|
||||
<form method="post" action="{{ $.c.ExternalUrl }}/settings/totp/regenerate" onconfirm="">
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.totp.regenerate-recovery-codes" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/settings/totp/generate" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.totp.use" }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:grid grid-cols-2 gap-x-4 md:gap-x-8">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "auth.mfa.passkeys" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "auth.mfa.passkeys-help" }}
|
||||
</h3>
|
||||
<form class="space-y-6" id="webauthn">
|
||||
<div>
|
||||
<label for="passkeyname" class="block text-sm font-medium text-slate-700 dark:text-slate-300">{{ .locale.Tr "auth.mfa.passkey-name" }}</label>
|
||||
<div class="mt-1">
|
||||
<input id="passkeyname" name="passkeyname" type="text" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
{{ .csrfHtml }}
|
||||
<button id="bind-passkey-button" type="button" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.mfa.bind-passkey" }}</button>
|
||||
</form>
|
||||
<div class="flex items-center justify-center mt-4">
|
||||
<p id="login-passkey-wait" class="hidden text-sm font-medium items-center text-slate-700 dark:text-slate-300">{{ .locale.Tr "auth.mfa.waiting-for-passkey-input" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-6 flow-root">
|
||||
<ul role="list" class="-my-5 divide-y divide-gray-300 dark:divide-gray-700 list-none">
|
||||
{{ if .passkeys }}
|
||||
{{ range $passkey := .passkeys }}
|
||||
<li class="py-5">
|
||||
<div class="inline-flex">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-12 h-12 mr-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300">{{ .Name }}</h3>
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "auth.mfa.passkey-added-at" }} <span class="moment-timestamp-date">{{ .CreatedAt }}</span></p>
|
||||
{{ if eq .LastUsedAt 0 }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "auth.mfa.passkey-never-used" }}</p>
|
||||
{{ else }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "auth.mfa.passkey-last-used" }} <span class="moment-timestamp">{{ .LastUsedAt }}</span></p>
|
||||
{{ end }}
|
||||
</div>
|
||||
<form action="{{ $.c.ExternalUrl }}/settings/passkeys/{{.ID}}" method="post" class="inline-block">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
{{ $.csrfHtml }}
|
||||
<button type="submit" onclick="return confirm('{{ $.locale.Tr "auth.mfa.delete-passkey-confirm" }}');" class="align-middle items-center leading-2 ml-2 px-3 py-1 border border-transparent border-gray-200 dark:border-gray-700 text-xs font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500">{{ $.locale.Tr "auth.mfa.delete-passkey" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="{{ asset "webauthn.ts" }}"></script>
|
||||
</div>
|
||||
|
||||
{{ template "settings_footer" .}}
|
||||
{{ template "footer" .}}
|
69
templates/pages/settings_ssh.html
vendored
Normal file
69
templates/pages/settings_ssh.html
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
{{ template "header" .}}
|
||||
{{ template "settings_header" .}}
|
||||
<div class="relative mx-auto max-w-[40rem] space-y-8">
|
||||
<div class="sm:grid grid-cols-2 gap-x-4 md:gap-x-8">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.add-ssh-key" }}
|
||||
</h2>
|
||||
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
|
||||
{{ .locale.Tr "settings.add-ssh-key-help" }}
|
||||
</h3>
|
||||
<form class="space-y-6" action="{{ $.c.ExternalUrl }}/settings/ssh-keys" method="post">
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> {{ .locale.Tr "settings.add-ssh-key-title" }} </label>
|
||||
<div class="mt-1">
|
||||
<input id="title" name="title" type="text" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<label for="sshkey" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> {{ .locale.Tr "settings.add-ssh-key-content" }} </label>
|
||||
<div class="mt-1">
|
||||
<textarea id="sshkey" required autocomplete="off" name="content" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "settings.add-ssh-key" }}</button>
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-6 flow-root">
|
||||
<ul role="list" class="-my-5 divide-y divide-gray-300 dark:divide-gray-700 list-none">
|
||||
{{ if .sshKeys }}
|
||||
{{ range $key := .sshKeys }}
|
||||
<li class="py-5">
|
||||
<div class="inline-flex">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-12 h-12 mr-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300">{{ .Title }}</h3>
|
||||
<p class="mt-1 text-xs text-slate-600 dark:text-slate-400 line-clamp-2 code" style="overflow-wrap: anywhere">SHA256:{{.SHA}}</p>
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "settings.ssh-key-added-at" }} <span class="moment-timestamp-date">{{ .CreatedAt }}</span></p>
|
||||
{{ if eq .LastUsedAt 0 }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "settings.ssh-key-never-used" }}</p>
|
||||
{{ else }}
|
||||
<p class="text-xs text-gray-500 line-clamp-2">{{ $.locale.Tr "settings.ssh-key-last-used" }} <span class="moment-timestamp">{{ .LastUsedAt }}</span></p>
|
||||
{{ end }}
|
||||
</div>
|
||||
<form action="{{ $.c.ExternalUrl }}/settings/ssh-keys/{{.ID}}" method="post" class="inline-block">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
{{ $.csrfHtml }}
|
||||
|
||||
<button type="submit" onclick="return confirm('{{ $.locale.Tr "settings.delete-ssh-key-confirm" }}')" class="align-middle items-center leading-2 ml-2 px-3 py-1 border border-transparent border-gray-200 dark:border-gray-700 text-xs font-medium rounded-md shadow-sm text-white dark:text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500">{{ $.locale.Tr "settings.delete-ssh-key" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "settings_footer" .}}
|
||||
{{ template "footer" .}}
|
110
templates/pages/settings_style.html
vendored
Normal file
110
templates/pages/settings_style.html
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
{{ template "header" .}}
|
||||
{{ template "settings_header" .}}
|
||||
<div class="relative mx-auto max-w-[40rem] space-y-8">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
{{ .locale.Tr "settings.style.gist-code" }}
|
||||
</h2>
|
||||
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 overflow-auto mt-4">
|
||||
<div class="border-b-1 border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 my-auto block">
|
||||
<div class="ml-4 py-1.5 flex">
|
||||
|
||||
<span class="flex-auto inline-flex items-center text-sm text-slate-700 dark:text-slate-300 filename">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-700 dark:text-slate-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
<a href="#" class="hover:text-primary-600 ml-2 mr-1">file.txt</a>
|
||||
<span class="hidden sm:block">
|
||||
<span class="text-gray-400"> · 95 B · Text</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="isolate inline-flex rounded-md shadow-sm mr-2">
|
||||
<button class="relative inline-flex items-center rounded-l-md bg-white text-gray-500 dark:text-slate-300 float-right px-2.5 py-1 leading-4 text-xs font-medium dark:bg-gray-600 border border-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-slate-700 dark:hover:text-slate-300 select-none">
|
||||
{{ $.locale.Tr "gist.raw" }}
|
||||
</button>
|
||||
<button type="button" class="relative -ml-px inline-flex items-center bg-white text-gray-500 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10 px-1 py-1 dark:text-slate-300 dark:bg-gray-600 dark:hover:bg-gray-700 copy-gist-btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="relative -ml-px inline-flex items-center rounded-r-md bg-white text-gray-500 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10 px-1 py-1 dark:text-slate-300 dark:bg-gray-600 dark:hover:bg-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto">
|
||||
<div class="code">
|
||||
<table class="chroma table-code w-full {{ if .currentStyle }}{{ if .currentStyle.SoftWrap }}whitespace-pre-wrap{{ else }}whitespace-pre{{ end }}{{ else }}whitespace-pre{{ end }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="select-none line-num px-4">1</td><td class="line-code break-all">This is a string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="select-none line-num px-4">2</td><td class="line-code break-all">This is a really really really really really really really really long string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="select-none line-num px-4">3</td><td class="line-code break-all"></td>
|
||||
</tr>
|
||||
<tr class="red-diff">
|
||||
<td class="select-none line-num px-4">4</td><td class="line-code break-all">- code removed</td>
|
||||
</tr>
|
||||
<tr class="red-diff">
|
||||
<td class="select-none line-num px-4">5</td><td class="line-code break-all">- another pretty pretty pretty pretty pretty pretty long code removed</td>
|
||||
</tr>
|
||||
<tr class="green-diff">
|
||||
<td class="select-none line-num px-4">6</td><td class="line-code break-all">+ code added</td>
|
||||
</tr>
|
||||
<tr class="green-diff">
|
||||
<td class="select-none line-num px-4">7</td><td class="line-code break-all">+ added a line which help to demonstrate the difference between enabling and disabling soft wrap</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="select-none line-num px-4">8</td><td class="line-code break-all"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<form method="post" action="{{ $.c.ExternalUrl }}/settings/style" class="mr-2">
|
||||
<div class="mt-6 space-y-6 sm:flex sm:items-center sm:space-x-10 sm:space-y-0">
|
||||
<div class="flex items-center">
|
||||
<input id="no-soft-wrap" value="false" name="softwrap" type="radio" {{ if .currentStyle }}{{ if not .currentStyle.SoftWrap }}checked{{ end }}{{ else }}checked{{ end }} class="relative size-4 appearance-none rounded-full border border-gray-300 bg-white before:absolute before:inset-1 before:rounded-full before:bg-white checked:border-indigo-600 checked:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:before:bg-gray-400 forced-colors:appearance-auto forced-colors:before:hidden [&:not(:checked)]:before:hidden">
|
||||
<label for="no-soft-wrap" class="ml-3 block text-sm font-medium text-slate-700 dark:text-slate-300">{{ .locale.Tr "settings.style.no-soft-wrap" }}</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="soft-wrap" value="true" name="softwrap" type="radio" {{ if .currentStyle }}{{ if .currentStyle.SoftWrap }}checked{{ end }}{{ end }} class="relative size-4 appearance-none rounded-full border border-gray-300 bg-white before:absolute before:inset-1 before:rounded-full before:bg-white checked:border-indigo-600 checked:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:before:bg-gray-400 forced-colors:appearance-auto forced-colors:before:hidden [&:not(:checked)]:before:hidden">
|
||||
<label for="soft-wrap" class="ml-3 block text-sm font-medium text-slate-700 dark:text-slate-300">{{ .locale.Tr "settings.style.soft-wrap" }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 space-y-6 sm:flex sm:items-center sm:space-x-10 sm:space-y-0">
|
||||
<div class="flex-2">
|
||||
<label for="removedlinecolor" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">{{ .locale.Tr "settings.style.removed-lines-color" }}</label>
|
||||
<input type="color" value="{{ if .currentStyle }}{{ .currentStyle.RemovedLineColor }}{{ else }}#ff0000{{ end }}" id="removedlinecolor" name="removedlinecolor">
|
||||
</div>
|
||||
<div class="flex-2">
|
||||
<label for="addedlinecolor" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">{{ .locale.Tr "settings.style.added-lines-color" }}</label>
|
||||
<input type="color" value="{{ if .currentStyle }}{{ .currentStyle.AddedLineColor }}{{ else }}#00ff80{{ end }}" id="addedlinecolor" name="addedlinecolor">
|
||||
</div>
|
||||
<div class="flex-2">
|
||||
<label for="gitlinecolor" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">{{ .locale.Tr "settings.style.git-lines-color" }}</label>
|
||||
<input type="color" value="{{ if .currentStyle }}{{ .currentStyle.GitLineColor }}{{ else }}#8f8f8f{{ end }}" id="gitlinecolor" name="gitlinecolor">
|
||||
</div>
|
||||
</div>
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" class="mt-4 inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "settings.style.save-style" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="{{ asset "style_preferences.ts" }}"></script>
|
||||
|
||||
{{ template "settings_footer" .}}
|
||||
{{ template "footer" .}}
|
2
templates/pages/totp.html
vendored
2
templates/pages/totp.html
vendored
@ -23,7 +23,7 @@
|
||||
|
||||
</div>
|
||||
<div class="mt-8 sm:w-full sm:max-w-md mx-auto flex flex-col items-center">
|
||||
<a href="{{ $.c.ExternalUrl }}/settings" class="px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.totp.proceed" }}</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/settings/mfa" class="px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">{{ .locale.Tr "auth.totp.proceed" }}</a>
|
||||
</div>
|
||||
|
||||
{{ else }}
|
||||
|
4
templates/partials/_gist_preview.html
vendored
4
templates/partials/_gist_preview.html
vendored
@ -63,7 +63,7 @@
|
||||
{{ if isMarkdown .gist.PreviewFilename }}
|
||||
<div class="chroma preview markdown markdown-body p-8">{{ .gist.HTML | safe }}</div>
|
||||
{{ else }}
|
||||
<table class="chroma table-code w-full whitespace-pre" data-filename="{{ .gist.PreviewFilename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<table class="chroma table-code w-full {{ if .currentStyle }}{{ if .currentStyle.SoftWrap }}whitespace-pre-wrap{{ else }}whitespace-pre{{ end }}{{ else }}whitespace-pre{{ end }}" data-filename="{{ .gist.PreviewFilename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<tbody>
|
||||
{{ $ii := "1" }}
|
||||
{{ $i := toInt $ii }}
|
||||
@ -71,7 +71,7 @@
|
||||
|
||||
<tr>
|
||||
<td class="select-none line-num px-4">{{$i}}</td>
|
||||
<td class="line-code">{{ $line | safe }}</td>
|
||||
<td class="line-code break-all">{{ $line | safe }}</td>
|
||||
</tr>
|
||||
{{ $i = inc $i }}
|
||||
{{ end }}
|
||||
|
Reference in New Issue
Block a user