diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go
index 5a3ad9b1..582ee0bb 100644
--- a/appview/pages/funcmap.go
+++ b/appview/pages/funcmap.go
@@ -21,6 +21,7 @@ import (
"github.com/go-enry/go-enry/v2"
"tangled.sh/tangled.sh/core/appview/filetree"
"tangled.sh/tangled.sh/core/appview/pages/markup"
+ "tangled.sh/tangled.sh/core/crypto"
)
func (p *Pages) funcMap() template.FuncMap {
@@ -282,6 +283,13 @@ func (p *Pages) funcMap() template.FuncMap {
// TODO: extend this to handle other cases?
return strings.ReplaceAll(s, ":", "_")
},
+ "sshFingerprint": func(pubKey string) string {
+ fp, err := crypto.SSHFingerprint(pubKey)
+ if err != nil {
+ return "error"
+ }
+ return fp
+ },
}
}
diff --git a/appview/pages/pages.go b/appview/pages/pages.go
index 7e0d334b..25b5c355 100644
--- a/appview/pages/pages.go
+++ b/appview/pages/pages.go
@@ -306,14 +306,36 @@ func (p *Pages) Timeline(w io.Writer, params TimelineParams) error {
return p.execute("timeline/timeline", w, params)
}
-type SettingsParams struct {
+type UserProfileSettingsParams struct {
+ LoggedInUser *oauth.User
+ Tabs []map[string]any
+ Tab string
+}
+
+func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error {
+ return p.execute("user/settings/profile", w, params)
+}
+
+type UserKeysSettingsParams struct {
LoggedInUser *oauth.User
PubKeys []db.PublicKey
+ Tabs []map[string]any
+ Tab string
+}
+
+func (p *Pages) UserKeysSettings(w io.Writer, params UserKeysSettingsParams) error {
+ return p.execute("user/settings/keys", w, params)
+}
+
+type UserEmailsSettingsParams struct {
+ LoggedInUser *oauth.User
Emails []db.Email
+ Tabs []map[string]any
+ Tab string
}
-func (p *Pages) Settings(w io.Writer, params SettingsParams) error {
- return p.execute("settings", w, params)
+func (p *Pages) UserEmailsSettings(w io.Writer, params UserEmailsSettingsParams) error {
+ return p.execute("user/settings/emails", w, params)
}
type KnotsParams struct {
diff --git a/appview/pages/templates/settings.html b/appview/pages/templates/settings.html
deleted file mode 100644
index 8a1979ba..00000000
--- a/appview/pages/templates/settings.html
+++ /dev/null
@@ -1,192 +0,0 @@
-{{ define "title" }}settings{{ end }}
-
-{{ define "content" }}
-
-
- {{ block "profile" . }} {{ end }}
- {{ block "keys" . }} {{ end }}
- {{ block "emails" . }} {{ end }}
-
-{{ end }}
-
-{{ define "profile" }}
-profile
-
-
- {{ if .LoggedInUser.Handle }}
- handle
- @{{ .LoggedInUser.Handle }}
- {{ end }}
- did
- {{ .LoggedInUser.Did }}
- pds
- {{ .LoggedInUser.Pds }}
-
-
-{{ end }}
-
-{{ define "keys" }}
-ssh keys
-
- SSH public keys added here will be broadcasted to knots that you are a member of, allowing you to push to repositories there.
-
- {{ range $index, $key := .PubKeys }}
-
-
-
- {{ i "key" "w-3 h-3 dark:text-gray-300" }}
-
{{ .Name }}
-
-
added {{ template "repo/fragments/time" .Created }}
-
- {{ .Key }}
-
-
-
- {{ i "trash-2" "w-5 h-5" }}
- delete
- {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
-
-
- {{ end }}
-
-
-
-{{ end }}
-
-{{ define "emails" }}
-email addresses
-
-Commits authored using emails listed here will be associated with your Tangled profile.
-
- {{ range $index, $email := .Emails }}
-
-
-
- {{ i "mail" "w-3 h-3 dark:text-gray-300" }}
-
{{ .Address }}
-
- {{ if .Verified }}
- verified
- {{ else }}
- unverified
- {{ end }}
- {{ if .Primary }}
- primary
- {{ end }}
-
-
-
added {{ template "repo/fragments/time" .CreatedAt }}
-
-
- {{ if not .Verified }}
-
- {{ i "rotate-cw" "w-5 h-5" }}
- resend
-
- {{ end }}
- {{ if and (not .Primary) .Verified }}
-
- set as primary
-
- {{ end }}
- {{ if not .Primary }}
-
- {{ end }}
-
-
- {{ end }}
-
-
-
-{{ end }}
diff --git a/appview/pages/templates/user/settings/emails.html b/appview/pages/templates/user/settings/emails.html
new file mode 100644
index 00000000..6bee6050
--- /dev/null
+++ b/appview/pages/templates/user/settings/emails.html
@@ -0,0 +1,94 @@
+{{ define "title" }}{{ .Tab }} settings{{ end }}
+
+{{ define "content" }}
+
+
+
+
+ {{ template "user/settings/fragments/sidebar" . }}
+
+
+ {{ template "emailSettings" . }}
+
+
+
+{{ end }}
+
+{{ define "emailSettings" }}
+
+
+
Email Addresses
+
+ Commits authored using emails listed here will be associated with your Tangled profile.
+
+
+
+ {{ template "addEmailButton" . }}
+
+
+
+ {{ range .Emails }}
+ {{ template "user/settings/fragments/emailListing" (list $ .) }}
+ {{ else }}
+
+ no emails added yet
+
+ {{ end }}
+
+{{ end }}
+
+{{ define "addEmailButton" }}
+
+ {{ i "plus" "size-4" }}
+ add email
+
+
+ {{ template "addEmailModal" . }}
+
+{{ end}}
+
+{{ define "addEmailModal" }}
+
+{{ end }}
\ No newline at end of file
diff --git a/appview/pages/templates/user/settings/fragments/emailListing.html b/appview/pages/templates/user/settings/fragments/emailListing.html
new file mode 100644
index 00000000..da9c2956
--- /dev/null
+++ b/appview/pages/templates/user/settings/fragments/emailListing.html
@@ -0,0 +1,62 @@
+{{ define "user/settings/fragments/emailListing" }}
+ {{ $root := index . 0 }}
+ {{ $email := index . 1 }}
+
+
+
+ {{ i "mail" "w-4 h-4 text-gray-500 dark:text-gray-400" }}
+
+ {{ $email.Address }}
+
+
+ {{ if $email.Verified }}
+ verified
+ {{ else }}
+ unverified
+ {{ end }}
+ {{ if $email.Primary }}
+ primary
+ {{ end }}
+
+
+
+ added {{ template "repo/fragments/time" $email.CreatedAt }}
+
+
+
+ {{ if not $email.Verified }}
+
+ {{ i "rotate-cw" "w-4 h-4" }}
+ resend
+
+ {{ end }}
+ {{ if and (not $email.Primary) $email.Verified }}
+
+ set as primary
+
+ {{ end }}
+ {{ if not $email.Primary }}
+
+ {{ i "trash-2" "w-5 h-5" }}
+ delete
+ {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
+ {{ end }}
+
+
+{{ end }}
diff --git a/appview/pages/templates/user/settings/fragments/keyListing.html b/appview/pages/templates/user/settings/fragments/keyListing.html
new file mode 100644
index 00000000..a645f3f9
--- /dev/null
+++ b/appview/pages/templates/user/settings/fragments/keyListing.html
@@ -0,0 +1,31 @@
+{{ define "user/settings/fragments/keyListing" }}
+ {{ $root := index . 0 }}
+ {{ $key := index . 1 }}
+
+
+
+ {{ i "key" "w-4" "h-4" }}
+
+ {{ $key.Name }}
+
+
+
+ {{ sshFingerprint $key.Key }}
+
+
+ added {{ template "repo/fragments/time" $key.Created }}
+
+
+
+ {{ i "trash-2" "w-5 h-5" }}
+ delete
+ {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
+
+{{ end }}
diff --git a/appview/pages/templates/user/settings/fragments/sidebar.html b/appview/pages/templates/user/settings/fragments/sidebar.html
new file mode 100644
index 00000000..d5a39600
--- /dev/null
+++ b/appview/pages/templates/user/settings/fragments/sidebar.html
@@ -0,0 +1,16 @@
+{{ define "user/settings/fragments/sidebar" }}
+ {{ $active := .Tab }}
+ {{ $tabs := .Tabs }}
+
+{{ end }}
\ No newline at end of file
diff --git a/appview/pages/templates/user/settings/keys.html b/appview/pages/templates/user/settings/keys.html
new file mode 100644
index 00000000..6d6e8552
--- /dev/null
+++ b/appview/pages/templates/user/settings/keys.html
@@ -0,0 +1,101 @@
+{{ define "title" }}{{ .Tab }} settings{{ end }}
+
+{{ define "content" }}
+
+
+
+
+ {{ template "user/settings/fragments/sidebar" . }}
+
+
+ {{ template "sshKeysSettings" . }}
+
+
+
+{{ end }}
+
+{{ define "sshKeysSettings" }}
+
+
+
SSH Keys
+
+ SSH public keys added here will be broadcasted to knots that you are a member of,
+ allowing you to push to repositories there.
+
+
+
+ {{ template "addKeyButton" . }}
+
+
+
+ {{ range .PubKeys }}
+ {{ template "user/settings/fragments/keyListing" (list $ .) }}
+ {{ else }}
+
+ no keys added yet
+
+ {{ end }}
+
+{{ end }}
+
+{{ define "addKeyButton" }}
+
+ {{ i "plus" "size-4" }}
+ add key
+
+
+ {{ template "addKeyModal" . }}
+
+{{ end}}
+
+{{ define "addKeyModal" }}
+
+{{ end }}
diff --git a/appview/pages/templates/user/settings/profile.html b/appview/pages/templates/user/settings/profile.html
new file mode 100644
index 00000000..e5c195df
--- /dev/null
+++ b/appview/pages/templates/user/settings/profile.html
@@ -0,0 +1,64 @@
+{{ define "title" }}{{ .Tab }} settings{{ end }}
+
+{{ define "content" }}
+
+
+
+
+ {{ template "user/settings/fragments/sidebar" . }}
+
+
+ {{ template "profileInfo" . }}
+
+
+
+{{ end }}
+
+{{ define "profileInfo" }}
+
+
+
Profile
+
+ Your account information from your AT Protocol identity.
+
+
+
+
+
+
+
+
+ {{ if .LoggedInUser.Handle }}
+
+ @{{ .LoggedInUser.Handle }}
+
+
+ Handle
+
+ {{ end }}
+
+
+
+
+
+ {{ .LoggedInUser.Did }}
+
+
+ Decentralized Identifier (DID)
+
+
+
+
+
+
+ {{ .LoggedInUser.Pds }}
+
+
+ Personal Data Server (PDS)
+
+
+
+
+{{ end }}
diff --git a/appview/settings/settings.go b/appview/settings/settings.go
index 23a533e7..acc1cf68 100644
--- a/appview/settings/settings.go
+++ b/appview/settings/settings.go
@@ -33,19 +33,33 @@ type Settings struct {
Config *config.Config
}
+type tab = map[string]any
+
+var (
+ settingsTabs []tab = []tab{
+ {"Name": "profile", "Icon": "user"},
+ {"Name": "keys", "Icon": "key"},
+ {"Name": "emails", "Icon": "mail"},
+ }
+)
+
func (s *Settings) Router() http.Handler {
r := chi.NewRouter()
r.Use(middleware.AuthMiddleware(s.OAuth))
- r.Get("/", s.settings)
+ // settings pages
+ r.Get("/", s.profileSettings)
+ r.Get("/profile", s.profileSettings)
r.Route("/keys", func(r chi.Router) {
+ r.Get("/", s.keysSettings)
r.Put("/", s.keys)
r.Delete("/", s.keys)
})
r.Route("/emails", func(r chi.Router) {
+ r.Get("/", s.emailsSettings)
r.Put("/", s.emails)
r.Delete("/", s.emails)
r.Get("/verify", s.emailsVerify)
@@ -56,22 +70,43 @@ func (s *Settings) Router() http.Handler {
return r
}
-func (s *Settings) settings(w http.ResponseWriter, r *http.Request) {
+func (s *Settings) profileSettings(w http.ResponseWriter, r *http.Request) {
+ user := s.OAuth.GetUser(r)
+
+ s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{
+ LoggedInUser: user,
+ Tabs: settingsTabs,
+ Tab: "profile",
+ })
+}
+
+func (s *Settings) keysSettings(w http.ResponseWriter, r *http.Request) {
user := s.OAuth.GetUser(r)
pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Did)
if err != nil {
log.Println(err)
}
+ s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{
+ LoggedInUser: user,
+ PubKeys: pubKeys,
+ Tabs: settingsTabs,
+ Tab: "keys",
+ })
+}
+
+func (s *Settings) emailsSettings(w http.ResponseWriter, r *http.Request) {
+ user := s.OAuth.GetUser(r)
emails, err := db.GetAllEmails(s.Db, user.Did)
if err != nil {
log.Println(err)
}
- s.Pages.Settings(w, pages.SettingsParams{
+ s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{
LoggedInUser: user,
- PubKeys: pubKeys,
Emails: emails,
+ Tabs: settingsTabs,
+ Tab: "emails",
})
}
@@ -201,7 +236,7 @@ func (s *Settings) emails(w http.ResponseWriter, r *http.Request) {
return
}
- s.Pages.HxLocation(w, "/settings")
+ s.Pages.HxLocation(w, "/settings/emails")
return
}
}
@@ -244,7 +279,7 @@ func (s *Settings) emailsVerify(w http.ResponseWriter, r *http.Request) {
return
}
- http.Redirect(w, r, "/settings", http.StatusSeeOther)
+ http.Redirect(w, r, "/settings/emails", http.StatusSeeOther)
}
func (s *Settings) emailsVerifyResend(w http.ResponseWriter, r *http.Request) {
@@ -339,7 +374,7 @@ func (s *Settings) emailsPrimary(w http.ResponseWriter, r *http.Request) {
return
}
- s.Pages.HxLocation(w, "/settings")
+ s.Pages.HxLocation(w, "/settings/emails")
}
func (s *Settings) keys(w http.ResponseWriter, r *http.Request) {
@@ -410,7 +445,7 @@ func (s *Settings) keys(w http.ResponseWriter, r *http.Request) {
return
}
- s.Pages.HxLocation(w, "/settings")
+ s.Pages.HxLocation(w, "/settings/keys")
return
case http.MethodDelete:
@@ -455,7 +490,7 @@ func (s *Settings) keys(w http.ResponseWriter, r *http.Request) {
}
log.Println("deleted successfully")
- s.Pages.HxLocation(w, "/settings")
+ s.Pages.HxLocation(w, "/settings/keys")
return
}
}