Commit b893d66995
Verified · cmc
internal/httpd/orglabels.go added +92
| @@ -0,0 +1,92 @@ | ||
| 1 | package httpd | |
| 2 | ||
| 3 | import ( | |
| 4 | "html/template" | |
| 5 | "net/http" | |
| 6 | ||
| 7 | "gitbay.org/gitbay/internal/control" | |
| 8 | "gitbay.org/gitbay/internal/store" | |
| 9 | ) | |
| 10 | ||
| 11 | // orgScope resolves the org for its labels or milestones page. Members | |
| 12 | // see it; anyone else only when some repository under the org is | |
| 13 | // readable. Everything else is not found, the same answer as for a | |
| 14 | // user owner or an unknown name. | |
| 15 | func (s *Server) orgScope(w http.ResponseWriter, r *http.Request) (store.Org, store.User, []int64, bool) { | |
| 16 | viewer := s.viewer(r) | |
| 17 | org, err := s.st.OrgByName(r.PathValue("owner")) | |
| 18 | if err != nil { | |
| 19 | s.notFound(w, r) | |
| 20 | return org, viewer, nil, false | |
| 21 | } | |
| 22 | readable, err := control.ReadableOrgRepoIDs(s.st, viewer, org.ID) | |
| 23 | if err != nil { | |
| 24 | http.Error(w, "internal error", http.StatusInternalServerError) | |
| 25 | return org, viewer, nil, false | |
| 26 | } | |
| 27 | role := "" | |
| 28 | if viewer.ID != 0 { | |
| 29 | role, _ = s.st.OrgRole(org.ID, viewer.ID) | |
| 30 | } | |
| 31 | if role == "" && len(readable) == 0 { | |
| 32 | s.notFound(w, r) | |
| 33 | return org, viewer, nil, false | |
| 34 | } | |
| 35 | return org, viewer, readable, true | |
| 36 | } | |
| 37 | ||
| 38 | func (s *Server) orgLabels(w http.ResponseWriter, r *http.Request) { | |
| 39 | org, viewer, readable, ok := s.orgScope(w, r) | |
| 40 | if !ok { | |
| 41 | return | |
| 42 | } | |
| 43 | labels, err := s.st.ListOrgLabels(org.ID, readable) | |
| 44 | if err != nil { | |
| 45 | http.Error(w, "internal error", http.StatusInternalServerError) | |
| 46 | return | |
| 47 | } | |
| 48 | stored := make(map[string]string, len(labels)) | |
| 49 | for _, l := range labels { | |
| 50 | stored[l.Name] = l.Color | |
| 51 | } | |
| 52 | s.render(w, "orglabels.html", struct { | |
| 53 | basePage | |
| 54 | Org string | |
| 55 | Labels []store.Label | |
| 56 | LabelColors map[string]template.CSS | |
| 57 | }{s.baseFor(viewer), org.Name, labels, colorStyles(stored)}) | |
| 58 | } | |
| 59 | ||
| 60 | func (s *Server) orgMilestones(w http.ResponseWriter, r *http.Request) { | |
| 61 | org, viewer, readable, ok := s.orgScope(w, r) | |
| 62 | if !ok { | |
| 63 | return | |
| 64 | } | |
| 65 | state := r.URL.Query().Get("state") | |
| 66 | if state != "closed" && state != "all" { | |
| 67 | state = "open" | |
| 68 | } | |
| 69 | ms, err := s.st.ListOrgMilestones(org.ID, state, readable) | |
| 70 | if err != nil { | |
| 71 | http.Error(w, "internal error", http.StatusInternalServerError) | |
| 72 | return | |
| 73 | } | |
| 74 | type msView struct { | |
| 75 | store.Milestone | |
| 76 | Percent int | |
| 77 | } | |
| 78 | var views []msView | |
| 79 | for _, m := range ms { | |
| 80 | v := msView{Milestone: m} | |
| 81 | if total := m.OpenItems + m.ClosedItems; total > 0 { | |
| 82 | v.Percent = m.ClosedItems * 100 / total | |
| 83 | } | |
| 84 | views = append(views, v) | |
| 85 | } | |
| 86 | s.render(w, "orgmilestones.html", struct { | |
| 87 | basePage | |
| 88 | Org string | |
| 89 | State string | |
| 90 | Milestones []msView | |
| 91 | }{s.baseFor(viewer), org.Name, state, views}) | |
| 92 | } | |
internal/httpd/routes.go +2
| @@ -71,6 +71,8 @@ func (s *Server) Routes() []Route { | ||
| 71 | 71 | Route{Method: "GET", Pattern: "/{owner}/{repo}/log.atom", Handler: s.logAtom}, |
| 72 | 72 | Route{Method: "GET", Pattern: "/{owner}/{repo}/log.atom/{ref...}", Handler: s.logAtom}, |
| 73 | 73 | Route{Method: "GET", Pattern: "/{owner}/activity.atom", Handler: s.ownerAtom}, |
| 74 | Route{Method: "GET", Pattern: "/{owner}/-/labels", Handler: s.orgLabels}, | |
| 75 | Route{Method: "GET", Pattern: "/{owner}/-/milestones", Handler: s.orgMilestones}, | |
| 74 | 76 | Route{Method: "GET", Pattern: "/{owner}/{repo}/builds", Handler: s.builds}, |
| 75 | 77 | Route{Method: "GET", Pattern: "/{owner}/{repo}/badge/build.svg", Handler: s.buildBadge}, |
| 76 | 78 | Route{Method: "GET", Pattern: "/{owner}/{repo}/badge/build.png", Handler: s.buildBadgePNG}, |
internal/web/templates/labels.html +4 −4
| @@ -5,18 +5,18 @@ | ||
| 5 | 5 | {{if .Labels}}<div class="tablewrap"><table class="keys"> |
| 6 | 6 | <tr class="cols"><th scope="col">label</th><th scope="col">colour</th><th scope="col">issues</th><th scope="col"></th></tr> |
| 7 | 7 | {{range .Labels}}<tr> |
| 8 | <td><a class="chip label" style="{{index $.LabelColors .Name}}" href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/issues?label={{.Name}}">{{.Name}}</a></td> | |
| 9 | <td>{{if $.CanWrite}}<form method="post" action="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/labels" class="inline"> | |
| 8 | <td><a class="chip label" style="{{index $.LabelColors .Name}}" href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/issues?label={{.Name}}">{{.Name}}</a>{{if .Org}} <span class="chip chip-neutral">org</span>{{end}}</td> | |
| 9 | <td>{{if and $.CanWrite (not .Org)}}<form method="post" action="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/labels" class="inline"> | |
| 10 | 10 | <input type="hidden" name="name" value="{{.Name}}"> |
| 11 | 11 | <input type="text" name="color" value="{{.Color}}" aria-label="Colour for {{.Name}}" placeholder="rrggbb" size="8"> |
| 12 | 12 | <button type="submit" class="btn">Save</button> |
| 13 | 13 | </form>{{else}}<span class="mono">{{if .Color}}{{.Color}}{{else}}—{{end}}</span>{{end}}</td> |
| 14 | 14 | <td>{{.Issues}}</td> |
| 15 | <td class="act">{{if $.CanWrite}}<form method="post" action="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/labels" class="inline"> | |
| 15 | <td class="act">{{if and $.CanWrite (not .Org)}}<form method="post" action="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/labels" class="inline"> | |
| 16 | 16 | <input type="hidden" name="action" value="remove"> |
| 17 | 17 | <input type="hidden" name="name" value="{{.Name}}"> |
| 18 | 18 | <button type="submit" class="linklike">Remove</button> |
| 19 | </form>{{end}}</td> | |
| 19 | </form>{{else if .Org}}<a href="/{{$.Repo.OwnerName}}/-/labels">org</a>{{end}}</td> | |
| 20 | 20 | </tr> |
| 21 | 21 | {{end}}</table></div> |
| 22 | 22 | {{else}}<p class="none">No labels yet.</p>{{end}} |
internal/web/templates/milestones.html +1 −1
| @@ -11,7 +11,7 @@ | ||
| 11 | 11 | <ul class="milestonelist"> |
| 12 | 12 | {{range .Milestones}}<li> |
| 13 | 13 | <div class="msmain"> |
| 14 | <p class="title">{{.Title}} <span class="chip {{if eq .State "open"}}chip-open{{else}}chip-done{{end}}">{{.State}}</span></p> | |
| 14 | <p class="title">{{.Title}} <span class="chip {{if eq .State "open"}}chip-open{{else}}chip-done{{end}}">{{.State}}</span>{{if .OrgID}} <span class="chip chip-neutral">org</span>{{end}}</p> | |
| 15 | 15 | {{if .Description}}<p class="desc">{{.Description}}</p>{{end}} |
| 16 | 16 | <p class="meta">{{if .DueDate}}due {{.DueDate}} · {{end}}{{.ClosedItems}} closed, {{.OpenItems}} open · {{.Percent}}%</p> |
| 17 | 17 | <div class="progress"><div class="bar" style="width: {{.Percent}}%"></div></div> |
internal/web/templates/orglabels.html added +14
| @@ -0,0 +1,14 @@ | ||
| 1 | {{define "title"}}labels · {{.Org}}{{end}} | |
| 2 | {{define "content"}} | |
| 3 | <h1><a href="/{{.Org}}">{{.Org}}</a> labels</h1> | |
| 4 | <p class="meta">Every repository under {{.Org}} sees these beside its own. Managed with <code>gitbay org label set {{.Org}} <label></code>; counts span the repositories you can read.</p> | |
| 5 | {{if .Labels}}<div class="tablewrap"><table class="keys"> | |
| 6 | <tr class="cols"><th scope="col">label</th><th scope="col">colour</th><th scope="col">issues</th></tr> | |
| 7 | {{range .Labels}}<tr> | |
| 8 | <td><span class="chip label" style="{{index $.LabelColors .Name}}">{{.Name}}</span></td> | |
| 9 | <td><span class="mono">{{if .Color}}{{.Color}}{{else}}—{{end}}</span></td> | |
| 10 | <td>{{.Issues}}</td> | |
| 11 | </tr> | |
| 12 | {{end}}</table></div> | |
| 13 | {{else}}<p class="none">No org labels yet.</p>{{end}} | |
| 14 | {{end}} | |
internal/web/templates/orgmilestones.html added +23
| @@ -0,0 +1,23 @@ | ||
| 1 | {{define "title"}}milestones · {{.Org}}{{end}} | |
| 2 | {{define "content"}} | |
| 3 | <div class="listhead"> | |
| 4 | <h1><a href="/{{.Org}}">{{.Org}}</a> milestones</h1> | |
| 5 | <nav class="filters"> | |
| 6 | <a {{if eq .State "open"}}class="active" aria-current="page" {{end}}href="?state=open">open</a> | |
| 7 | <a {{if eq .State "closed"}}class="active" aria-current="page" {{end}}href="?state=closed">closed</a> | |
| 8 | <a {{if eq .State "all"}}class="active" aria-current="page" {{end}}href="?state=all">all</a> | |
| 9 | </nav> | |
| 10 | </div> | |
| 11 | <p class="meta">Progress spans the repositories under {{.Org}} you can read.</p> | |
| 12 | <ul class="milestonelist"> | |
| 13 | {{range .Milestones}}<li> | |
| 14 | <div class="msmain"> | |
| 15 | <p class="title">{{.Title}} <span class="chip {{if eq .State "open"}}chip-open{{else}}chip-done{{end}}">{{.State}}</span></p> | |
| 16 | {{if .Description}}<p class="desc">{{.Description}}</p>{{end}} | |
| 17 | <p class="meta">{{if .DueDate}}due {{.DueDate}} · {{end}}{{.ClosedItems}} closed, {{.OpenItems}} open · {{.Percent}}%</p> | |
| 18 | <div class="progress"><div class="bar" style="width: {{.Percent}}%"></div></div> | |
| 19 | </div> | |
| 20 | </li> | |
| 21 | {{else}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}org milestones — create one with <code>gitbay org milestone create {{.Org}} "v1.0"</code></li>{{end}} | |
| 22 | </ul> | |
| 23 | {{end}} | |
internal/web/templates/owner.html +1
| @@ -7,6 +7,7 @@ | ||
| 7 | 7 | {{if .Profile.Links}}<p class="meta">{{range $i, $l := .Profile.Links}}{{if $i}} · {{end}}<a href="{{$l.URL}}" rel="nofollow me">{{if $l.Label}}{{$l.Label}}{{else}}{{$l.URL}}{{end}}</a>{{end}}</p>{{end}} |
| 8 | 8 | {{if .Orgs}}<p class="meta">member of {{range .Orgs}}<a class="memberchip" href="/{{.Name}}">{{.Name}}</a> {{end}}</p>{{end}} |
| 9 | 9 | {{if .Members}}<p class="meta">members {{range .Members}}<a class="memberchip" href="/{{.Name}}">{{.Name}} <span class="role">{{.Role}}</span></a> {{end}}</p>{{end}} |
| 10 | {{if eq .Kind "org"}}<p class="meta"><a href="/{{.Owner}}/-/labels">labels</a> · <a href="/{{.Owner}}/-/milestones">milestones</a></p>{{end}} | |
| 10 | 11 | </section> |
| 11 | 12 | {{if .AboutHTML}}<section class="readme"><div class="rendered">{{.AboutHTML}}</div></section>{{end}} |
| 12 | 13 | <section class="activity"> |