internal/httpd/settings.go
152 lines · 4945 bytes
1package httpd
2
3import (
4 "fmt"
5 "net/http"
6 "strings"
7
8 "gitbay.org/gitbay/internal/control"
9 "gitbay.org/gitbay/internal/gitutil"
10 "gitbay.org/gitbay/internal/store"
11)
12
13// Repository settings for repo admins. Every control dispatches the
14// command the CLI runs; the page only groups them. Destructive lifecycle
15// — delete and transfer — stays on the CLI, where a typed confirmation
16// is the norm.
17
18type settingsPage struct {
19 repoPage
20 Topics []string
21 Branches []gitutil.Ref
22 DepsEnabled bool
23 Deps control.DepsOut
24 Runners []store.RepoRunner
25 Notice string
26}
27
28func (s *Server) settingsForm(w http.ResponseWriter, r *http.Request, u store.User) {
29 repo, ok := s.repoForUser(w, r, u, policyCanAdmin)
30 if !ok {
31 return
32 }
33 p, ok := s.repoFor(w, r, "")
34 if !ok {
35 return
36 }
37 p.Tab = "settings"
38 topics, _ := s.st.ListTopics(repo.ID)
39 branches, _ := gitutil.Refs(p.Dir, "heads")
40 // The toggle's state comes from the store; what the last run found
41 // comes from the command, so the page shows the same report the CLI
42 // prints (#164).
43 var deps control.DepsOut
44 s.runControlInto(u, []string{"repo", "deps", "status", repo.Path()}, &deps)
45 var runners []store.RepoRunner
46 s.runControlInto(u, []string{"repo", "runner", "list", repo.Path()}, &runners)
47 s.render(w, "settings.html", settingsPage{
48 repoPage: p, Topics: topics, Branches: branches,
49 DepsEnabled: deps.Enabled, Deps: deps,
50 Runners: runners,
51 Notice: s.takeFlash(w, r),
52 })
53}
54
55func (s *Server) settingsRedirect(w http.ResponseWriter, r *http.Request, msg string) {
56 dest := fmt.Sprintf("/%s/%s/settings", r.PathValue("owner"), r.PathValue("repo"))
57 s.setFlash(w, msg)
58 http.Redirect(w, r, dest, http.StatusSeeOther)
59}
60
61// settingsSubmit routes one form to its command. Keeping the mapping in
62// one place makes what the page can reach obvious.
63func (s *Server) settingsSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
64 repo := r.PathValue("owner") + "/" + r.PathValue("repo")
65 v := func(k string) string { return strings.TrimSpace(r.FormValue(k)) }
66
67 var argv []string
68 switch r.FormValue("field") {
69 case "description":
70 argv = []string{"repo", "settings", "description", repo, v("description")}
71 case "website":
72 argv = []string{"repo", "settings", "website", repo, v("website")}
73 case "visibility":
74 argv = []string{"repo", "settings", "visibility", repo, v("visibility")}
75 case "default-branch":
76 argv = []string{"repo", "settings", "default-branch", repo, v("default-branch")}
77 case "git-daemon":
78 argv = []string{"repo", "settings", "git-daemon", repo, onOff(v("git-daemon"))}
79 case "require-checks":
80 argv = []string{"repo", "settings", "require-checks", repo, onOff(v("require-checks"))}
81 case "require-resolved":
82 argv = []string{"repo", "settings", "require-resolved", repo, onOff(v("require-resolved"))}
83 case "require-codeowners":
84 argv = []string{"repo", "settings", "require-codeowners", repo, onOff(v("require-codeowners"))}
85 case "require-mr":
86 argv = []string{"repo", "settings", "require-mr", repo, onOff(v("require-mr"))}
87 case "require-signed":
88 argv = []string{"repo", "settings", "require-signed", repo, onOff(v("require-signed"))}
89 case "require-approvals":
90 argv = []string{"repo", "settings", "require-approvals", repo, v("approvals")}
91 case "protect":
92 argv = []string{"repo", "settings", "protect", repo, v("branch")}
93 case "unprotect":
94 argv = []string{"repo", "settings", "unprotect", repo, v("branch")}
95 case "protect-tag":
96 argv = []string{"repo", "settings", "protect-tag", repo, v("glob")}
97 case "unprotect-tag":
98 argv = []string{"repo", "settings", "unprotect-tag", repo, v("glob")}
99 case "deps":
100 verb := "disable"
101 if v("deps") == "on" {
102 verb = "enable"
103 }
104 argv = []string{"repo", "deps", verb, repo}
105 case "archive":
106 verb := "archive"
107 if v("archive") != "on" {
108 verb = "unarchive"
109 }
110 argv = []string{"repo", verb, repo}
111 case "topics":
112 if add := strings.Fields(v("add")); len(add) > 0 {
113 argv = append([]string{"repo", "topics", "add", repo}, add...)
114 } else if rm := strings.Fields(v("remove")); len(rm) > 0 {
115 argv = append([]string{"repo", "topics", "remove", repo}, rm...)
116 } else {
117 s.settingsRedirect(w, r, "name at least one topic")
118 return
119 }
120 case "runner-add":
121 body := v("key")
122 if body == "" {
123 s.settingsRedirect(w, r, "paste the runner's public key")
124 return
125 }
126 msg, ok := s.runControlStdin(u, []string{"repo", "runner", "add", repo}, body+"\n")
127 if ok {
128 msg = ""
129 }
130 s.settingsRedirect(w, r, msg)
131 return
132 case "runner-remove":
133 argv = []string{"repo", "runner", "remove", repo, v("fingerprint")}
134 default:
135 s.settingsRedirect(w, r, "unknown setting")
136 return
137 }
138
139 _, msg, ok := s.runControl(u, argv)
140 if ok {
141 msg = ""
142 }
143 s.settingsRedirect(w, r, msg)
144}
145
146// onOff normalises a checkbox to the on|off the commands take.
147func onOff(v string) string {
148 if v == "on" || v == "true" {
149 return "on"
150 }
151 return "off"
152}