From 05ec85115ff987b9b8bb56c9c830be99badff84e Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Thu, 31 Jul 2025 16:12:10 +0100 Subject: [PATCH] appview/pages: rework repo settings page Change-Id: yssxzpkyorwvyzyrwnvztrntmxmqmnps Signed-off-by: oppiliappan --- appview/pages/pages.go | 44 +++ appview/pages/templates/repo/settings.html | 306 ++++++++-------- .../pages/templates/repo/settings/access.html | 110 ++++++ .../settings/fragments/secretListing.html | 29 ++ .../repo/settings/fragments/sidebar.html | 16 + .../templates/repo/settings/general.html | 68 ++++ .../templates/repo/settings/pipelines.html | 135 +++++++ appview/repo/repo.go | 346 +++++++++++++----- appview/reporesolver/resolver.go | 7 +- appview/state/router.go | 3 +- input.css | 3 +- 11 files changed, 803 insertions(+), 264 deletions(-) create mode 100644 appview/pages/templates/repo/settings/access.html create mode 100644 appview/pages/templates/repo/settings/fragments/secretListing.html create mode 100644 appview/pages/templates/repo/settings/fragments/sidebar.html create mode 100644 appview/pages/templates/repo/settings/general.html create mode 100644 appview/pages/templates/repo/settings/pipelines.html diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 88c13b6..0c845df 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -702,6 +702,50 @@ func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error { return p.executeRepo("repo/settings", w, params) } +type RepoGeneralSettingsParams struct { + LoggedInUser *oauth.User + RepoInfo repoinfo.RepoInfo + Active string + Tabs []map[string]any + Tab string + Branches []types.Branch +} + +func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error { + params.Active = "settings" + return p.executeRepo("repo/settings/general", w, params) +} + +type RepoAccessSettingsParams struct { + LoggedInUser *oauth.User + RepoInfo repoinfo.RepoInfo + Active string + Tabs []map[string]any + Tab string + Collaborators []Collaborator +} + +func (p *Pages) RepoAccessSettings(w io.Writer, params RepoAccessSettingsParams) error { + params.Active = "settings" + return p.executeRepo("repo/settings/access", w, params) +} + +type RepoPipelineSettingsParams struct { + LoggedInUser *oauth.User + RepoInfo repoinfo.RepoInfo + Active string + Tabs []map[string]any + Tab string + Spindles []string + CurrentSpindle string + Secrets []map[string]any +} + +func (p *Pages) RepoPipelineSettings(w io.Writer, params RepoPipelineSettingsParams) error { + params.Active = "settings" + return p.executeRepo("repo/settings/pipelines", w, params) +} + type RepoIssuesParams struct { LoggedInUser *oauth.User RepoInfo repoinfo.RepoInfo diff --git a/appview/pages/templates/repo/settings.html b/appview/pages/templates/repo/settings.html index 8f252b5..cb6b2b7 100644 --- a/appview/pages/templates/repo/settings.html +++ b/appview/pages/templates/repo/settings.html @@ -1,182 +1,168 @@ {{ define "title" }}settings · {{ .RepoInfo.FullName }}{{ end }} + {{ define "repoContent" }} -
- Collaborators -
+ {{ template "collaboratorSettings" . }} + {{ template "branchSettings" . }} + {{ template "dangerZone" . }} + {{ template "spindleSelector" . }} + {{ template "spindleSecrets" . }} +{{ end }} -
- {{ range .Collaborators }} -
- - {{ didOrHandle .Did .Handle }} - -
- - {{ .Role }} - -
-
- {{ end }} -
+{{ define "collaboratorSettings" }} +
+ Collaborators +
- {{ if .RepoInfo.Roles.CollaboratorInviteAllowed }} -
+ {{ range .Collaborators }} +
{{ end }} + + {{ if .RepoInfo.Roles.CollaboratorInviteAllowed }}
- -
- - -
+ + +
+ {{ end }} +{{ end }} - {{ if .RepoInfo.Roles.IsOwner }} +{{ define "dangerZone" }} + {{ if .RepoInfo.Roles.RepoDeleteAllowed }}
- -
- - -
+ hx-confirm="Are you sure you want to delete this repository?" + hx-delete="/{{ $.RepoInfo.FullName }}/settings/delete" + class="mt-6" + hx-indicator="#delete-repo-spinner"> + + + + Deleting a repository is irreversible and permanent. +
- {{ end }} + {{ end }} +{{ end }} - {{ if $.CurrentSpindle }} -
- Secrets -
+{{ define "branchSettings" }} +
+ +
+ + +
+
+{{ end }} -
- {{ range $idx, $secret := .Secrets }} - {{ with $secret }} -
- {{ .Key }} created on {{ .CreatedAt }} by {{ .CreatedBy }} -
- {{ end }} +{{ define "spindleSelector" }} + {{ if .RepoInfo.Roles.IsOwner }} +
+ +
+ - - + + +
+
+ {{ end }} +{{ end }} - - - {{ end }} +{{ define "spindleSecrets" }} + {{ if $.CurrentSpindle }} +
+ Secrets +
- {{ if .RepoInfo.Roles.RepoDeleteAllowed }} +
+ {{ range $idx, $secret := .Secrets }} + {{ with $secret }} +
+ {{ .Key }} created on {{ .CreatedAt }} by {{ .CreatedBy }} +
+ {{ end }} + {{ end }} +
- - - - Deleting a repository is irreversible and permanent. - -
- {{ end }} + hx-indicator="#add-secret-spinner"> + + + + + + + {{ end }} {{ end }} diff --git a/appview/pages/templates/repo/settings/access.html b/appview/pages/templates/repo/settings/access.html new file mode 100644 index 0000000..010c5c6 --- /dev/null +++ b/appview/pages/templates/repo/settings/access.html @@ -0,0 +1,110 @@ +{{ define "title" }}{{ .Tab }} settings · {{ .RepoInfo.FullName }}{{ end }} + +{{ define "repoContent" }} +
+
+ {{ template "repo/settings/fragments/sidebar" . }} +
+
+ {{ template "collaboratorSettings" . }} +
+
+{{ end }} + +{{ define "collaboratorSettings" }} +
+
+

Collaborators

+

+ Any user added as a collaborator will be able to push commits and tags to this repository, upload releases, and workflows. +

+
+ {{ template "collaboratorsGrid" . }} +
+{{ end }} + +{{ define "collaboratorsGrid" }} +
+ {{ if .RepoInfo.Roles.CollaboratorInviteAllowed }} + {{ template "addCollaboratorButton" . }} + {{ end }} + {{ range .Collaborators }} +
+
+ {{ .Handle }} + + +
+
+ {{ end }} +
+{{ end }} + +{{ define "addCollaboratorButton" }} + +
+ {{ template "addCollaboratorModal" . }} +
+{{ end }} + +{{ define "addCollaboratorModal" }} +
+ +

Collaborators can push to this repository.

+ +
+ + +
+
+
+{{ end }} diff --git a/appview/pages/templates/repo/settings/fragments/secretListing.html b/appview/pages/templates/repo/settings/fragments/secretListing.html new file mode 100644 index 0000000..e5aab5e --- /dev/null +++ b/appview/pages/templates/repo/settings/fragments/secretListing.html @@ -0,0 +1,29 @@ +{{ define "repo/settings/fragments/secretListing" }} + {{ $root := index . 0 }} + {{ $secret := index . 1 }} +
+
+ + {{ $secret.Key }} + +
+ added by + {{ template "user/fragments/picHandleLink" $secret.CreatedBy }} + + {{ template "repo/fragments/shortTimeAgo" $secret.CreatedAt }} +
+
+ +
+{{ end }} diff --git a/appview/pages/templates/repo/settings/fragments/sidebar.html b/appview/pages/templates/repo/settings/fragments/sidebar.html new file mode 100644 index 0000000..90bbf95 --- /dev/null +++ b/appview/pages/templates/repo/settings/fragments/sidebar.html @@ -0,0 +1,16 @@ +{{ define "repo/settings/fragments/sidebar" }} + {{ $active := .Tab }} + {{ $tabs := .Tabs }} +
+ {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} + {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} + {{ range $tabs }} + +
+ {{ i .Icon "size-4" }} + {{ .Name }} +
+
+ {{ end }} +
+{{ end }} diff --git a/appview/pages/templates/repo/settings/general.html b/appview/pages/templates/repo/settings/general.html new file mode 100644 index 0000000..65a1a6a --- /dev/null +++ b/appview/pages/templates/repo/settings/general.html @@ -0,0 +1,68 @@ +{{ define "title" }}{{ .Tab }} settings · {{ .RepoInfo.FullName }}{{ end }} + +{{ define "repoContent" }} +
+
+ {{ template "repo/settings/fragments/sidebar" . }} +
+
+ {{ template "branchSettings" . }} + {{ template "deleteRepo" . }} +
+
+{{ end }} + +{{ define "branchSettings" }} +
+
+

Default Branch

+

+ The default branch is considered the “base” branch in your repository, + against which all pull requests and code commits are automatically made, + unless you specify a different branch. +

+
+
+ + +
+
+{{ end }} + +{{ define "deleteRepo" }} + {{ if .RepoInfo.Roles.RepoDeleteAllowed }} +
+
+

Delete Repository

+

+ Deleting a repository is irreversible and permanent. Be certain before deleting a repository. +

+
+
+ +
+
+ {{ end }} +{{ end }} diff --git a/appview/pages/templates/repo/settings/pipelines.html b/appview/pages/templates/repo/settings/pipelines.html new file mode 100644 index 0000000..65e878d --- /dev/null +++ b/appview/pages/templates/repo/settings/pipelines.html @@ -0,0 +1,135 @@ +{{ define "title" }}{{ .Tab }} settings · {{ .RepoInfo.FullName }}{{ end }} + +{{ define "repoContent" }} +
+
+ {{ template "repo/settings/fragments/sidebar" . }} +
+
+ {{ template "spindleSettings" . }} + {{ if $.CurrentSpindle }} + {{ template "secretSettings" . }} + {{ end }} +
+
+
+{{ end }} + +{{ define "spindleSettings" }} +
+
+

Spindle

+

+ Choose a spindle to execute your workflows on. Spindles can be + selfhosted, + + click to learn more. + +

+
+
+ + +
+
+{{ end }} + +{{ define "secretSettings" }} +
+
+

SECRETS

+

+ Secrets are accessible in workflow runs via environment variables. Anyone + with collaborator access to this repository can add and use secrets in + workflow runs. +

+
+
+ {{ template "addSecretButton" . }} +
+
+
+ {{ range .Secrets }} + {{ template "repo/settings/fragments/secretListing" (list $ .) }} + {{ else }} +
+ no secrets added yet +
+ {{ end }} +
+{{ end }} + +{{ define "addSecretButton" }} + +
+ {{ template "addSecretModal" . }} +
+{{ end}} + +{{ define "addSecretModal" }} +
+

ADD SECRET

+

Secrets are available as environment variables in the workflow.

+ + +
+ + +
+
+
+{{ end }} diff --git a/appview/repo/repo.go b/appview/repo/repo.go index 3cf93b8..6cf6995 100644 --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "log" + "log/slog" "net/http" "net/url" "slices" @@ -50,6 +51,7 @@ type Repo struct { db *db.DB enforcer *rbac.Enforcer notifier notify.Notifier + logger *slog.Logger } func New( @@ -62,6 +64,7 @@ func New( config *config.Config, notifier notify.Notifier, enforcer *rbac.Enforcer, + logger *slog.Logger, ) *Repo { return &Repo{oauth: oauth, repoResolver: repoResolver, @@ -72,6 +75,7 @@ func New( db: db, notifier: notifier, enforcer: enforcer, + logger: logger, } } @@ -588,57 +592,59 @@ func (rp *Repo) RepoBlobRaw(w http.ResponseWriter, r *http.Request) { // modify the spindle configured for this repo func (rp *Repo) EditSpindle(w http.ResponseWriter, r *http.Request) { + user := rp.oauth.GetUser(r) + l := rp.logger.With("handler", "EditSpindle") + l = l.With("did", user.Did) + l = l.With("handle", user.Handle) + + errorId := "operation-error" + fail := func(msg string, err error) { + l.Error(msg, "err", err) + rp.pages.Notice(w, errorId, msg) + } + f, err := rp.repoResolver.Resolve(r) if err != nil { - log.Println("failed to get repo and knot", err) - w.WriteHeader(http.StatusBadRequest) + fail("Failed to resolve repo. Try again later", err) return } repoAt := f.RepoAt rkey := repoAt.RecordKey().String() if rkey == "" { - log.Println("invalid aturi for repo", err) - w.WriteHeader(http.StatusInternalServerError) + fail("Failed to resolve repo. Try again later", err) return } - user := rp.oauth.GetUser(r) - newSpindle := r.FormValue("spindle") client, err := rp.oauth.AuthorizedClient(r) if err != nil { - log.Println("failed to get client") - rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + fail("Failed to authorize. Try again later.", err) return } // ensure that this is a valid spindle for this user validSpindles, err := rp.enforcer.GetSpindlesForUser(user.Did) if err != nil { - log.Println("failed to get valid spindles") - rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + fail("Failed to find spindles. Try again later.", err) return } if !slices.Contains(validSpindles, newSpindle) { - log.Println("newSpindle not present in validSpindles", "newSpindle", newSpindle, "validSpindles", validSpindles) - rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + fail("Failed to configure spindle.", fmt.Errorf("%s is not a valid spindle: %q", newSpindle, validSpindles)) return } // optimistic update err = db.UpdateSpindle(rp.db, string(repoAt), newSpindle) if err != nil { - log.Println("failed to perform update-spindle query", err) - rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + fail("Failed to update spindle. Try again later.", err) return } ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, user.Did, rkey) if err != nil { - // failed to get record - rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, no record found on PDS.") + fail("Failed to update spindle, no record found on PDS.", err) return } _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ @@ -659,9 +665,7 @@ func (rp *Repo) EditSpindle(w http.ResponseWriter, r *http.Request) { }) if err != nil { - log.Println("failed to perform update-spindle query", err) - // failed to get record - rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, unable to save to PDS.") + fail("Failed to update spindle, unable to save to PDS.", err) return } @@ -671,96 +675,109 @@ func (rp *Repo) EditSpindle(w http.ResponseWriter, r *http.Request) { eventconsumer.NewSpindleSource(newSpindle), ) - w.Write(fmt.Append(nil, "spindle set to: ", newSpindle)) + rp.pages.HxRefresh(w) } func (rp *Repo) AddCollaborator(w http.ResponseWriter, r *http.Request) { + user := rp.oauth.GetUser(r) + l := rp.logger.With("handler", "AddCollaborator") + l = l.With("did", user.Did) + l = l.With("handle", user.Handle) + f, err := rp.repoResolver.Resolve(r) if err != nil { - log.Println("failed to get repo and knot", err) + l.Error("failed to get repo and knot", "err", err) return } + errorId := "add-collaborator-error" + fail := func(msg string, err error) { + l.Error(msg, "err", err) + rp.pages.Notice(w, errorId, msg) + } + collaborator := r.FormValue("collaborator") if collaborator == "" { - http.Error(w, "malformed form", http.StatusBadRequest) + fail("Invalid form.", nil) return } collaboratorIdent, err := rp.idResolver.ResolveIdent(r.Context(), collaborator) if err != nil { - w.Write([]byte("failed to resolve collaborator did to a handle")) + fail(fmt.Sprintf("'%s' is not a valid DID/handle.", collaborator), err) + return + } + + if collaboratorIdent.DID.String() == user.Did { + fail("You seem to be adding yourself as a collaborator.", nil) return } - log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot) - // TODO: create an atproto record for this + l = l.With("collaborator", collaboratorIdent.Handle) + l = l.With("knot", f.Knot) + l.Info("adding to knot") secret, err := db.GetRegistrationKey(rp.db, f.Knot) if err != nil { - log.Printf("no key found for domain %s: %s\n", f.Knot, err) + fail("Failed to add to knot.", err) return } ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev) if err != nil { - log.Println("failed to create client to ", f.Knot) + fail("Failed to add to knot.", err) return } ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String()) if err != nil { - log.Printf("failed to make request to %s: %s", f.Knot, err) + fail("Knot was unreachable.", err) return } if ksResp.StatusCode != http.StatusNoContent { - w.Write(fmt.Append(nil, "knotserver failed to add collaborator: ", err)) + fail(fmt.Sprintf("Knot returned unexpected status code: %d.", ksResp.StatusCode), nil) return } tx, err := rp.db.BeginTx(r.Context(), nil) if err != nil { - log.Println("failed to start tx") - w.Write(fmt.Append(nil, "failed to add collaborator: ", err)) + fail("Failed to add collaborator.", err) return } defer func() { tx.Rollback() err = rp.enforcer.E.LoadPolicy() if err != nil { - log.Println("failed to rollback policies") + fail("Failed to add collaborator.", err) } }() err = rp.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo()) if err != nil { - w.Write(fmt.Append(nil, "failed to add collaborator: ", err)) + fail("Failed to add collaborator permissions.", err) return } err = db.AddCollaborator(rp.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot) if err != nil { - w.Write(fmt.Append(nil, "failed to add collaborator: ", err)) + fail("Failed to add collaborator.", err) return } err = tx.Commit() if err != nil { - log.Println("failed to commit changes", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + fail("Failed to add collaborator.", err) return } err = rp.enforcer.E.SavePolicy() if err != nil { - log.Println("failed to update ACLs", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + fail("Failed to update collaborator permissions.", err) return } - w.Write(fmt.Append(nil, "added collaborator: ", collaboratorIdent.Handle.String())) - + rp.pages.HxRefresh(w) } func (rp *Repo) DeleteRepo(w http.ResponseWriter, r *http.Request) { @@ -913,6 +930,11 @@ func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { } func (rp *Repo) Secrets(w http.ResponseWriter, r *http.Request) { + user := rp.oauth.GetUser(r) + l := rp.logger.With("handler", "Secrets") + l = l.With("handle", user.Handle) + l = l.With("did", user.Did) + f, err := rp.repoResolver.Resolve(r) if err != nil { log.Println("failed to get repo and knot", err) @@ -948,8 +970,10 @@ func (rp *Repo) Secrets(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPut: + errorId := "add-secret-error" + value := r.FormValue("value") - if key == "" { + if value == "" { w.WriteHeader(http.StatusBadRequest) return } @@ -964,11 +988,14 @@ func (rp *Repo) Secrets(w http.ResponseWriter, r *http.Request) { }, ) if err != nil { - log.Println("request didnt run", "err", err) + l.Error("Failed to add secret.", "err", err) + rp.pages.Notice(w, errorId, "Failed to add secret.") return } case http.MethodDelete: + errorId := "operation-error" + err = tangled.RepoRemoveSecret( r.Context(), spindleClient, @@ -978,82 +1005,205 @@ func (rp *Repo) Secrets(w http.ResponseWriter, r *http.Request) { }, ) if err != nil { - log.Println("request didnt run", "err", err) + l.Error("Failed to delete secret.", "err", err) + rp.pages.Notice(w, errorId, "Failed to delete secret.") return } } + + rp.pages.HxRefresh(w) } +type tab = map[string]any + +var ( + // would be great to have ordered maps right about now + settingsTabs []tab = []tab{ + {"Name": "general", "Icon": "sliders-horizontal"}, + {"Name": "access", "Icon": "users"}, + {"Name": "pipelines", "Icon": "layers-2"}, + } +) + func (rp *Repo) RepoSettings(w http.ResponseWriter, r *http.Request) { + tabVal := r.URL.Query().Get("tab") + if tabVal == "" { + tabVal = "general" + } + + switch tabVal { + case "general": + rp.generalSettings(w, r) + + case "access": + rp.accessSettings(w, r) + + case "pipelines": + rp.pipelineSettings(w, r) + } + + // user := rp.oauth.GetUser(r) + // repoCollaborators, err := f.Collaborators(r.Context()) + // if err != nil { + // log.Println("failed to get collaborators", err) + // } + + // isCollaboratorInviteAllowed := false + // if user != nil { + // ok, err := rp.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.DidSlashRepo()) + // if err == nil && ok { + // isCollaboratorInviteAllowed = true + // } + // } + + // us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) + // if err != nil { + // log.Println("failed to create unsigned client", err) + // return + // } + + // result, err := us.Branches(f.OwnerDid(), f.RepoName) + // if err != nil { + // log.Println("failed to reach knotserver", err) + // return + // } + + // // all spindles that this user is a member of + // spindles, err := rp.enforcer.GetSpindlesForUser(user.Did) + // if err != nil { + // log.Println("failed to fetch spindles", err) + // return + // } + + // var secrets []*tangled.RepoListSecrets_Secret + // if f.Spindle != "" { + // if spindleClient, err := rp.oauth.ServiceClient( + // r, + // oauth.WithService(f.Spindle), + // oauth.WithLxm(tangled.RepoListSecretsNSID), + // oauth.WithDev(rp.config.Core.Dev), + // ); err != nil { + // log.Println("failed to create spindle client", err) + // } else if resp, err := tangled.RepoListSecrets(r.Context(), spindleClient, f.RepoAt.String()); err != nil { + // log.Println("failed to fetch secrets", err) + // } else { + // secrets = resp.Secrets + // } + // } + + // rp.pages.RepoSettings(w, pages.RepoSettingsParams{ + // LoggedInUser: user, + // RepoInfo: f.RepoInfo(user), + // Collaborators: repoCollaborators, + // IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed, + // Branches: result.Branches, + // Spindles: spindles, + // CurrentSpindle: f.Spindle, + // Secrets: secrets, + // }) +} + +func (rp *Repo) generalSettings(w http.ResponseWriter, r *http.Request) { f, err := rp.repoResolver.Resolve(r) + user := rp.oauth.GetUser(r) + + us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) if err != nil { - log.Println("failed to get repo and knot", err) + log.Println("failed to create unsigned client", err) return } - switch r.Method { - case http.MethodGet: - // for now, this is just pubkeys - user := rp.oauth.GetUser(r) - repoCollaborators, err := f.Collaborators(r.Context()) - if err != nil { - log.Println("failed to get collaborators", err) - } + result, err := us.Branches(f.OwnerDid(), f.RepoName) + if err != nil { + log.Println("failed to reach knotserver", err) + return + } - isCollaboratorInviteAllowed := false - if user != nil { - ok, err := rp.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.DidSlashRepo()) - if err == nil && ok { - isCollaboratorInviteAllowed = true - } - } + rp.pages.RepoGeneralSettings(w, pages.RepoGeneralSettingsParams{ + LoggedInUser: user, + RepoInfo: f.RepoInfo(user), + Branches: result.Branches, + Tabs: settingsTabs, + Tab: "general", + }) +} - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) - if err != nil { - log.Println("failed to create unsigned client", err) - return - } +func (rp *Repo) accessSettings(w http.ResponseWriter, r *http.Request) { + f, err := rp.repoResolver.Resolve(r) + user := rp.oauth.GetUser(r) - result, err := us.Branches(f.OwnerDid(), f.RepoName) - if err != nil { - log.Println("failed to reach knotserver", err) - return - } + repoCollaborators, err := f.Collaborators(r.Context()) + if err != nil { + log.Println("failed to get collaborators", err) + } - // all spindles that this user is a member of - spindles, err := rp.enforcer.GetSpindlesForUser(user.Did) - if err != nil { - log.Println("failed to fetch spindles", err) - return - } + rp.pages.RepoAccessSettings(w, pages.RepoAccessSettingsParams{ + LoggedInUser: user, + RepoInfo: f.RepoInfo(user), + Tabs: settingsTabs, + Tab: "access", + Collaborators: repoCollaborators, + }) +} - var secrets []*tangled.RepoListSecrets_Secret - if f.Spindle != "" { - if spindleClient, err := rp.oauth.ServiceClient( - r, - oauth.WithService(f.Spindle), - oauth.WithLxm(tangled.RepoListSecretsNSID), - oauth.WithDev(rp.config.Core.Dev), - ); err != nil { - log.Println("failed to create spindle client", err) - } else if resp, err := tangled.RepoListSecrets(r.Context(), spindleClient, f.RepoAt.String()); err != nil { - log.Println("failed to fetch secrets", err) - } else { - secrets = resp.Secrets - } +func (rp *Repo) pipelineSettings(w http.ResponseWriter, r *http.Request) { + f, err := rp.repoResolver.Resolve(r) + user := rp.oauth.GetUser(r) + + // all spindles that this user is a member of + spindles, err := rp.enforcer.GetSpindlesForUser(user.Did) + if err != nil { + log.Println("failed to fetch spindles", err) + return + } + + var secrets []*tangled.RepoListSecrets_Secret + if f.Spindle != "" { + if spindleClient, err := rp.oauth.ServiceClient( + r, + oauth.WithService(f.Spindle), + oauth.WithLxm(tangled.RepoListSecretsNSID), + oauth.WithDev(rp.config.Core.Dev), + ); err != nil { + log.Println("failed to create spindle client", err) + } else if resp, err := tangled.RepoListSecrets(r.Context(), spindleClient, f.RepoAt.String()); err != nil { + log.Println("failed to fetch secrets", err) + } else { + secrets = resp.Secrets } + } + + slices.SortFunc(secrets, func(a, b *tangled.RepoListSecrets_Secret) int { + return strings.Compare(a.Key, b.Key) + }) - rp.pages.RepoSettings(w, pages.RepoSettingsParams{ - LoggedInUser: user, - RepoInfo: f.RepoInfo(user), - Collaborators: repoCollaborators, - IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed, - Branches: result.Branches, - Spindles: spindles, - CurrentSpindle: f.Spindle, - Secrets: secrets, + var dids []string + for _, s := range secrets { + dids = append(dids, s.CreatedBy) + } + resolvedIdents := rp.idResolver.ResolveIdents(r.Context(), dids) + + // convert to a more manageable form + var niceSecret []map[string]any + for id, s := range secrets { + when, _ := time.Parse(time.RFC3339, s.CreatedAt) + niceSecret = append(niceSecret, map[string]any{ + "Id": id, + "Key": s.Key, + "CreatedAt": when, + "CreatedBy": resolvedIdents[id].Handle.String(), }) } + + rp.pages.RepoPipelineSettings(w, pages.RepoPipelineSettingsParams{ + LoggedInUser: user, + RepoInfo: f.RepoInfo(user), + Tabs: settingsTabs, + Tab: "pipelines", + Spindles: spindles, + CurrentSpindle: f.Spindle, + Secrets: niceSecret, + }) } func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) { diff --git a/appview/reporesolver/resolver.go b/appview/reporesolver/resolver.go index ad2fdd9..c622c29 100644 --- a/appview/reporesolver/resolver.go +++ b/appview/reporesolver/resolver.go @@ -149,11 +149,12 @@ func (f *ResolvedRepo) Collaborators(ctx context.Context) ([]pages.Collaborator, for _, item := range repoCollaborators { // currently only two roles: owner and member var role string - if item[3] == "repo:owner" { + switch item[3] { + case "repo:owner": role = "owner" - } else if item[3] == "repo:collaborator" { + case "repo:collaborator": role = "collaborator" - } else { + default: continue } diff --git a/appview/state/router.go b/appview/state/router.go index d164a0b..60728d2 100644 --- a/appview/state/router.go +++ b/appview/state/router.go @@ -208,7 +208,8 @@ func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler { } func (s *State) RepoRouter(mw *middleware.Middleware) http.Handler { - repo := repo.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.notifier, s.enforcer) + logger := log.New("repo") + repo := repo.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.notifier, s.enforcer, logger) return repo.Router(mw) } diff --git a/input.css b/input.css index 816ee76..5ac9a13 100644 --- a/input.css +++ b/input.css @@ -100,8 +100,7 @@ .prose img { display: inline; - margin-left: 0; - margin-right: 0; + margin: 0; vertical-align: middle; } } -- 2.43.0