mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-17 00:44:20 +00:00
View consented permissions
This commit is contained in:
+67
-16
@@ -56,26 +56,11 @@ func (s *Handlers) pageContribute(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "could not load commands", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Reuse the per-tier grouping; only commands in at least one tier are shown.
|
||||
groups := groupPermissions(catalog, idSet(adminIDs), idSet(memberIDs))
|
||||
var envelope []permGroup
|
||||
for _, g := range groups {
|
||||
var cmds []permChoice
|
||||
for _, c := range g.Commands {
|
||||
if c.AdminChecked || c.MemberChecked {
|
||||
cmds = append(cmds, c)
|
||||
}
|
||||
}
|
||||
if len(cmds) > 0 {
|
||||
envelope = append(envelope, permGroup{Name: g.Name, Commands: cmds})
|
||||
}
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"Repeater": rep,
|
||||
"Org": org,
|
||||
"Version": version,
|
||||
"Envelope": envelope,
|
||||
"Envelope": permEnvelope(catalog, idSet(adminIDs), idSet(memberIDs)),
|
||||
}
|
||||
|
||||
// If already contributed and behind the current version, show what changed
|
||||
@@ -104,6 +89,72 @@ func (s *Handlers) pageContribute(w http.ResponseWriter, r *http.Request) {
|
||||
s.Render(w, r, "contribute.html", data)
|
||||
}
|
||||
|
||||
// permEnvelope groups the catalog by category, keeping only commands granted to
|
||||
// at least one tier in the given admin/member id sets — the set of commands a
|
||||
// permission version actually allows.
|
||||
func permEnvelope(catalog []*store.Command, adminSet, memberSet map[int64]bool) []permGroup {
|
||||
var envelope []permGroup
|
||||
for _, g := range groupPermissions(catalog, adminSet, memberSet) {
|
||||
var cmds []permChoice
|
||||
for _, c := range g.Commands {
|
||||
if c.AdminChecked || c.MemberChecked {
|
||||
cmds = append(cmds, c)
|
||||
}
|
||||
}
|
||||
if len(cmds) > 0 {
|
||||
envelope = append(envelope, permGroup{Name: g.Name, Commands: cmds})
|
||||
}
|
||||
}
|
||||
return envelope
|
||||
}
|
||||
|
||||
// pageConsented shows, read-only, the exact commands this repeater is currently
|
||||
// consented to grant the org — the detail behind "consented to vN" on the
|
||||
// sharing page. It renders the consented version, which may lag the org's
|
||||
// current one.
|
||||
func (s *Handlers) pageConsented(w http.ResponseWriter, r *http.Request) {
|
||||
rep, orgID, ok := s.orgContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
org, err := s.Store.GetOrg(r.Context(), orgID)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
cvID, contributed, err := s.Store.ConsentedVersionID(r.Context(), orgID, rep.ID)
|
||||
if err != nil || !contributed {
|
||||
http.NotFound(w, r) // not contributed → nothing consented to view
|
||||
return
|
||||
}
|
||||
version, err := s.Store.VersionNumber(r.Context(), cvID)
|
||||
if err != nil {
|
||||
http.Error(w, "could not load policy", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
adminIDs, memberIDs, err := s.Store.VersionCommandIDs(r.Context(), cvID)
|
||||
if err != nil {
|
||||
http.Error(w, "could not load policy", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
catalog, err := s.Store.ListCommands(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, "could not load commands", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := map[string]any{
|
||||
"Repeater": rep,
|
||||
"Org": org,
|
||||
"Version": version,
|
||||
"Envelope": permEnvelope(catalog, idSet(adminIDs), idSet(memberIDs)),
|
||||
}
|
||||
// Note (with a re-consent link) when the org has moved past this version.
|
||||
if _, current, err := s.Store.CurrentVersion(r.Context(), orgID); err == nil && current > version {
|
||||
data["CurrentVersion"] = current
|
||||
}
|
||||
s.Render(w, r, "consented.html", data)
|
||||
}
|
||||
|
||||
// union returns the set union of two id sets.
|
||||
func union(a, b map[int64]bool) map[int64]bool {
|
||||
out := make(map[int64]bool, len(a)+len(b))
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{{define "title"}}{{.Repeater.Name}} · {{.Org.Name}} commands · MeshTender{{end}}
|
||||
{{define "header"}}
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<div class="page-pretitle">Consented to {{.Org.Name}} · v{{.Version}}</div>
|
||||
<h2 class="page-title">{{.Repeater.Name}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p class="text-secondary">
|
||||
These are the commands {{.Org.Name}}'s admins and members can run on this repeater over the mesh — the
|
||||
policy you consented to (v{{.Version}}). Org-admins also get everything members get.
|
||||
</p>
|
||||
|
||||
{{if .CurrentVersion}}
|
||||
<div class="alert alert-warning">
|
||||
{{.Org.Name}} has since published v{{.CurrentVersion}}. This repeater stays on v{{.Version}} until you
|
||||
<a class="alert-link" href="/repeaters/{{.Repeater.PublicID}}/orgs/{{.Org.Slug}}/contribute">review the changes and re-consent</a>.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Envelope}}
|
||||
{{range .Envelope}}
|
||||
<fieldset class="form-fieldset">
|
||||
<div class="form-label fw-bold text-capitalize">{{.Name}}</div>
|
||||
{{range .Commands}}
|
||||
<div class="d-flex align-items-center flex-wrap gap-2 py-1">
|
||||
<code class="flex-fill">{{.Template}}{{if .Risky}} <span class="badge bg-warning-lt">risky</span>{{end}}</code>
|
||||
{{if .AdminChecked}}<span class="badge bg-success-lt">admin</span>{{end}}
|
||||
{{if .MemberChecked}}<span class="badge bg-azure-lt">member</span>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</fieldset>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p class="text-secondary">This version grants no commands.</p>
|
||||
{{end}}
|
||||
|
||||
<a class="back-link mt-3" href="/repeaters/{{.Repeater.PublicID}}/share">{{template "icon-arrow-left" "me-1"}}Back to sharing</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -34,7 +34,7 @@
|
||||
{{range .Contributed}}
|
||||
<div class="list-group-item d-flex align-items-center flex-wrap gap-2 px-0">
|
||||
<span class="fw-bold">{{.OrgName}}</span>
|
||||
<span class="text-secondary">consented to v{{.ConsentedVersion}}{{if .NeedsReconsent}} · org is now on v{{.CurrentVersion}}{{end}}</span>
|
||||
<span class="text-secondary"><a class="link-secondary" href="/repeaters/{{$.Repeater.PublicID}}/orgs/{{.OrgSlug}}/consented">consented to v{{.ConsentedVersion}}</a>{{if .NeedsReconsent}} · org is now on v{{.CurrentVersion}}{{end}}</span>
|
||||
{{if .NeedsReconsent}}
|
||||
<a class="btn btn-sm" href="/repeaters/{{$.Repeater.PublicID}}/orgs/{{.OrgSlug}}/contribute">Review changes</a>
|
||||
{{end}}
|
||||
|
||||
@@ -140,6 +140,7 @@ func (s *Handlers) appRouter() chi.Router {
|
||||
r.Post("/repeaters/{id}/unshare", s.handleUnshare)
|
||||
r.Get("/repeaters/{id}/share/{userID}/commands", s.pageShareCommands)
|
||||
r.Post("/repeaters/{id}/share/{userID}/commands", s.handleSetShareCommands)
|
||||
r.Get("/repeaters/{id}/orgs/{orgID}/consented", s.pageConsented)
|
||||
r.Get("/repeaters/{id}/orgs/{orgID}/contribute", s.pageContribute)
|
||||
r.Post("/repeaters/{id}/orgs/{orgID}/contribute", s.handleContribute)
|
||||
r.Post("/repeaters/{id}/orgs/{orgID}/withdraw", s.handleWithdraw)
|
||||
|
||||
Reference in New Issue
Block a user