diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/graphql/graphiql/graphiql.go | 17 | ||||
-rw-r--r-- | pkg/api/graphql/graphiql/graphiql.templ | 33 | ||||
-rw-r--r-- | pkg/app/handler/packages/qareport.templ | 24 | ||||
-rw-r--r-- | pkg/app/handler/packages/show.templ | 33 | ||||
-rw-r--r-- | pkg/app/handler/user/arches.templ | 153 | ||||
-rw-r--r-- | pkg/app/handler/user/general.templ | 93 | ||||
-rw-r--r-- | pkg/app/handler/user/maintainers.templ | 95 | ||||
-rw-r--r-- | pkg/app/handler/user/packages.templ | 407 | ||||
-rw-r--r-- | pkg/app/handler/user/preferences.go | 245 | ||||
-rw-r--r-- | pkg/app/handler/user/preferences.templ | 94 | ||||
-rw-r--r-- | pkg/app/handler/user/useflags.templ | 93 | ||||
-rw-r--r-- | pkg/app/handler/user/utils.go | 71 | ||||
-rw-r--r-- | pkg/app/serve.go | 10 | ||||
-rw-r--r-- | pkg/models/userpreferences.go | 58 |
14 files changed, 1004 insertions, 422 deletions
diff --git a/pkg/api/graphql/graphiql/graphiql.go b/pkg/api/graphql/graphiql/graphiql.go deleted file mode 100644 index afc58df..0000000 --- a/pkg/api/graphql/graphiql/graphiql.go +++ /dev/null @@ -1,17 +0,0 @@ -package graphiql - -import ( - "html/template" - "net/http" - "soko/pkg/config" -) - -func Show(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "text/html") - - templates := template.Must( - template.New("graphiql"). - ParseGlob("web/templates/api/explore/*.tmpl")) - - templates.ExecuteTemplate(w, "graphiql.tmpl", template.URL(config.GraphiqlEndpoint())) -} diff --git a/pkg/api/graphql/graphiql/graphiql.templ b/pkg/api/graphql/graphiql/graphiql.templ new file mode 100644 index 0000000..0b3399d --- /dev/null +++ b/pkg/api/graphql/graphiql/graphiql.templ @@ -0,0 +1,33 @@ +package graphiql + +import "net/http" +import "soko/pkg/config" + +templ show() { + <!DOCTYPE html> + <html lang="en"> + <head> + <title>GraphiQL - Gentoo Packages</title> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"/> + <meta name="theme-color" content="#54487a"/> + <meta name="description" content="Gentoo Packages GraphiQL GraphQL API Explorer"/> + <link rel="icon" href="https://packages.gentoo.org/favicon.ico" type="image/x-icon"/> + <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> + <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> + <link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css"/> + </head> + <body> + <div id="graphiql"> + Loading... + </div> + <script src="https://unpkg.com/graphiql@0.17.5/graphiql.min.js" type="application/javascript"></script> + @templ.Raw(`<script>window.graphqlEndpoint = '` + config.GraphiqlEndpoint() + `';</script>`) + <script src="/assets/graphiql.js" type="application/javascript"></script> + </body> + </html> +} + +func Show(w http.ResponseWriter, r *http.Request) { + show().Render(r.Context(), w) +} diff --git a/pkg/app/handler/packages/qareport.templ b/pkg/app/handler/packages/qareport.templ index 4b70458..29603a1 100644 --- a/pkg/app/handler/packages/qareport.templ +++ b/pkg/app/handler/packages/qareport.templ @@ -11,13 +11,11 @@ templ qaReport(pkg *models.Package, userPreferences *models.UserPreferences) { <span class="text-muted">All Versions</span> <ul class="list-group"> for _, res := range pkg.PkgCheckResults { - if res.Version == "" && userPreferences.ContainsPkgcheckClass(res.Class) { - <li class="list-group-item"> - <strong>{ res.Class }</strong> - <br/> - <span class="kk-version kk-cell-sep-right text-muted">{ res.Message }</span> - </li> - } + <li class="list-group-item"> + <strong>{ res.Class }</strong> + <br/> + <span class="kk-version kk-cell-sep-right text-muted">{ res.Message }</span> + </li> } </ul> </li> @@ -26,13 +24,11 @@ templ qaReport(pkg *models.Package, userPreferences *models.UserPreferences) { <span class="text-muted">{ ver.Version }</span> <ul class="list-group"> for _, res := range ver.PkgCheckResults { - if userPreferences.ContainsPkgcheckClass(res.Class) { - <li class="list-group-item"> - <strong>{ res.Class }</strong> - <br/> - <span class="kk-version kk-cell-sep-right text-muted">{ res.Message }</span> - </li> - } + <li class="list-group-item"> + <strong>{ res.Class }</strong> + <br/> + <span class="kk-version kk-cell-sep-right text-muted">{ res.Message }</span> + </li> } </ul> </li> diff --git a/pkg/app/handler/packages/show.templ b/pkg/app/handler/packages/show.templ index 8d71781..3ef4341 100644 --- a/pkg/app/handler/packages/show.templ +++ b/pkg/app/handler/packages/show.templ @@ -1,13 +1,12 @@ package packages -import "slices" import "strconv" import "soko/pkg/app/handler/packages/components" import "soko/pkg/app/layout" import "soko/pkg/models" func showViewTabs(pkg *models.Package, userPreference *models.PackagesPreferences) []layout.SubTab { - securityBugs, nonSecurityBugs := countBugs(pkg) + securityBugs, nonSecurityBugs := countBugs(pkg) return []layout.SubTab{ { Name: "Overview", @@ -78,14 +77,12 @@ templ tabbedHeader(pkg *models.Package, currentSubTab string, userPreference *mo <div class="col-md-12 pt-4 mt-1"> <nav class="nav kk-package-nav"> for _, tab := range showViewTabs(pkg, userPreference) { - if slices.Contains(userPreference.Tabs.Visible, tab.Name) { - <a class={ "nav-link", templ.KV("active", tab.Name == currentSubTab) } href={ tab.Link }> - <i class={ tab.Icon } aria-hidden="true"></i> { tab.Name } - if tab.BadgeValue != "" { - <span class="ml-1 badge badge-pill kk-misc-badge">{ tab.BadgeValue }</span> - } - </a> - } + <a class={ "nav-link", templ.KV("active", tab.Name == currentSubTab) } href={ tab.Link }> + <i class={ tab.Icon } aria-hidden="true"></i> { tab.Name } + if tab.BadgeValue != "" { + <span class="ml-1 badge badge-pill kk-misc-badge">{ tab.BadgeValue }</span> + } + </a> } </nav> </div> @@ -98,19 +95,19 @@ templ tabbedHeader(pkg *models.Package, currentSubTab string, userPreference *mo func collectAllBugs(pkg *models.Package) (generalCount, stabilizationCount, keywordingCount int, bugs []*models.Bug) { bugs = make([]*models.Bug, 0, len(pkg.Bugs)) - handled := make(map[string]struct{}, len(pkg.Bugs)) + handled := make(map[string]struct{}, len(pkg.Bugs)) for _, bug := range pkg.Bugs { if bug.Component == "Current packages" { generalCount++ bugs = append(bugs, bug) - handled[bug.Id] = struct{}{} + handled[bug.Id] = struct{}{} } } for _, ver := range pkg.Versions { for _, bug := range ver.Bugs { - if _, found := handled[bug.Id]; found { - continue - } + if _, found := handled[bug.Id]; found { + continue + } if bug.Component == "Stabilization" { stabilizationCount++ bugs = append(bugs, bug) @@ -118,7 +115,7 @@ func collectAllBugs(pkg *models.Package) (generalCount, stabilizationCount, keyw keywordingCount++ bugs = append(bugs, bug) } - handled[bug.Id] = struct{}{} + handled[bug.Id] = struct{}{} } } return @@ -143,8 +140,8 @@ templ show(pkg *models.Package, currentSubTab string, userPreferences models.Use <div class="tab-content" id="myTabContent"> <div class="container mb-5 tab-pane fade show active" id="overview" role="tabpanel" aria-labelledby="overview-tab"> switch currentSubTab { - case "QA report": - @qaReport(pkg, &userPreferences) + case "QA report": + @qaReport(pkg, &userPreferences) case "Pull requests": @components.PullRequests(len(pkg.PullRequests) > 0, pkg.PullRequests) case "Bugs": diff --git a/pkg/app/handler/user/arches.templ b/pkg/app/handler/user/arches.templ new file mode 100644 index 0000000..7f9fa50 --- /dev/null +++ b/pkg/app/handler/user/arches.templ @@ -0,0 +1,153 @@ +package user + +import "encoding/base64" +import "encoding/json" +import "net/http" +import "slices" +import "time" +import "soko/pkg/app/utils" +import "soko/pkg/models" + +func splitArches(selected []string) (firstColumn, secondColumn []string) { + allArches := models.GetAllKeywords() + remainingFirstColumn := len(allArches) - (len(allArches) / 2) - len(selected) + for _, arch := range allArches { + if !slices.Contains(selected, arch) { + if len(firstColumn) < remainingFirstColumn { + firstColumn = append(firstColumn, arch) + } else { + secondColumn = append(secondColumn, arch) + } + } + } + return +} + +templ archesTwoColumns(selected []string) { + if firstColumn, secondColumn := splitArches(selected); true { + <ul id="example1" class="list-group col-6"> + for _, arch := range selected { + <li class="list-group-item"> + <div class="form-check form-check-inline w-100"> + <input type="checkbox" id={ "visible-arches-" + arch } name="visible-arches" value={ arch } checked/> + <label class="form-check-label ml-1" for={ "visible-arches-" + arch }>{ arch }</label> <i class="fa fa-arrows ml-auto" aria-hidden="true"></i> + </div> + </li> + } + for _, arch := range firstColumn { + <li class="list-group-item"> + <div class="form-check form-check-inline w-100"> + <input type="checkbox" id={ "visible-arches-" + arch } name="visible-arches" value={ arch }/> + <label class="form-check-label ml-1" for={ "visible-arches-" + arch }>{ arch }</label> <i class="fa fa-arrows ml-auto" aria-hidden="true"></i> + </div> + </li> + } + </ul> + <ul id="example2" class="list-group col-6"> + for _, arch := range secondColumn { + <li class="list-group-item"> + <div class="form-check form-check-inline w-100"> + <input type="checkbox" id={ "visible-arches-" + arch } name="visible-arches" value={ arch }/> + <label class="form-check-label ml-1" for={ "visible-arches-" + arch }>{ arch }</label> <i class="fa fa-arrows ml-auto" aria-hidden="true"></i> + </div> + </li> + } + </ul> + } +} + +templ arches(preferences models.ArchesPreferences) { + <div class="row"> + <div class="col-2 mt-1"> + <div class="nav flex-column" role="tablist" aria-orientation="vertical" style="position: fixed;"> + <a class="nav-link user-pref-nav-link active" id="keywords-tab" href="#keywords" aria-controls="overview-settings">Keywords</a> + <a class="nav-link user-pref-nav-link" id="defaults-tab" href="#defaults">Defaults</a> + </div> + </div> + <div class="col-10 mt-1"> + <form method="post" action="/user/preferences/arches/visible"> + <h3 class="" id="keywords">Keywords</h3> + <hr class="mt-1"/> + <div class="row pl-3"> + @archesTwoColumns(preferences.Visible) + </div> + <h3 class="mt-5" id="defaults">Defaults</h3> + <hr class="mt-1"/> + <div class="card"> + <div class="card-body"> + <div class="row"> + <div class="col-6"> + Default arch + <select class="form-control" style="max-width: 200px;display: inline;" name="arches-default-arch" id="arches-default-arch"> + for _, arch := range models.GetAllKeywords() { + <option + value={ arch } + if preferences.DefaultArch == arch { + selected + } + >{ arch }</option> + } + </select> + </div> + <div class="col-6"> + Default page + <select class="form-control" style="max-width: 150px;display: inline;" name="arches-default-page" id="arches-default-page"> + <option + value="keyworded" + if preferences.DefaultPage == "keyworded" { + selected + } + >keyworded</option> + <option + value="stable" + if preferences.DefaultPage == "stable" { + selected + } + >newly stable</option> + </select> + </div> + </div> + </div> + </div> + <div class="row"> + <div class="col-12 mt-4"> + <button type="submit" class="float-right btn btn-sm btn-primary">Save</button> + <a class="float-right btn btn-sm btn-outline-danger mr-2" href="/user/preferences/arches/reset">Reset to Defaults</a> + </div> + </div> + </form> + </div> + </div> + @sortableScript() +} + +func Arches(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetUserPreferences(r) + r.ParseForm() + // visible arches + visibleArches := r.Form["visible-arches"] + userPreferences.Arches.Visible = visibleArches + // default arch + defaultArch := r.Form.Get("arches-default-arch") + userPreferences.Arches.DefaultArch = defaultArch + // default arches page + defaultPage := r.Form.Get("arches-default-page") + userPreferences.Arches.DefaultPage = defaultPage + // store cookie + encodedUserPreferences, err := json.Marshal(&userPreferences.Arches) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_arches", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/arches", http.StatusSeeOther) +} + +func ResetArches(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetDefaultUserPreferences() + encodedUserPreferences, err := json.Marshal(&userPreferences.Arches) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_arches", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/arches", http.StatusSeeOther) +} diff --git a/pkg/app/handler/user/general.templ b/pkg/app/handler/user/general.templ new file mode 100644 index 0000000..799354b --- /dev/null +++ b/pkg/app/handler/user/general.templ @@ -0,0 +1,93 @@ +package user + +import "encoding/base64" +import "encoding/json" +import "net/http" +import "time" +import "soko/pkg/app/utils" +import "soko/pkg/models" + +templ general(preferences models.GeneralPreferences) { + <form method="post" action="/user/preferences/general/layout"> + <div class="row"> + <div class="col-5 offset-1 mt-1"> + <div class="card" style="background: transparent;"> + <div class="card-body"> + <img id="img1" alt="Recently Added Packages (default)" src="/assets/pgo3.png" style="width: 100%;cursor: pointer;"/> + </div> + </div> + <div class="text-center mt-2"> + <div class="form-check text-center form-check-inline" style="text-overflow: ellipsis;overflow: hidden;"> + <input + type="radio" + id="classicLandingpageLayout" + name="landingpage-layout" + value="classic" + if preferences.LandingPageLayout == "classic" { + checked + } + /> + <label class="form-check-label ml-1" for="classicLandingpageLayout" style="overflow:hidden;text-overflow: ellipsis;" title="Recently Added Packages (default)">Recently Added Packages <i>(default)</i></label> + </div> + </div> + </div> + <div class="col-5 mt-1"> + <div class="card" style="background: transparent;"> + <div class="card-body"> + <img id="img2" alt="Recently Visited Packages" src="/assets/pgo4.png" style="width: 100%;cursor: pointer;"/> + </div> + </div> + <div class="text-center mt-2"> + <div class="form-check text-center form-check-inline" style="text-overflow: ellipsis;overflow: hidden;"> + <input + type="radio" + id="fullLandingpageLayout" + name="landingpage-layout" + value="full" + if preferences.LandingPageLayout == "full" { + checked + } + /> + <label class="form-check-label ml-1" for="fullLandingpageLayout" style="overflow:hidden;text-overflow: ellipsis;" title="Recently Visited Packages">Recently Visited Packages</label> + </div> + </div> + </div> + <div class="col-10 offset-1 mt-4"> + <button type="submit" class="float-right btn btn-sm btn-primary">Save</button> + <a class="float-right btn btn-sm btn-outline-danger mr-2" href="/user/preferences/general/reset">Reset to Defaults</a> + </div> + </div> + </form> + <div id="myModal" class="modal"> + <span class="close">×</span> + <img class="modal-content" id="img01"/> + <div id="caption"></div> + </div> +} + +func General(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetUserPreferences(r) + r.ParseForm() + // landing page layout + layout := r.Form.Get("landingpage-layout") + if layout == "classic" || layout == "full" { + userPreferences.General.LandingPageLayout = layout + } + // store cookie + encodedUserPreferences, err := json.Marshal(&userPreferences.General) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_general", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/general", http.StatusSeeOther) +} + +func ResetGeneral(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetDefaultUserPreferences() + encodedUserPreferences, err := json.Marshal(&userPreferences.General) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_general", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/general", http.StatusSeeOther) +} diff --git a/pkg/app/handler/user/maintainers.templ b/pkg/app/handler/user/maintainers.templ new file mode 100644 index 0000000..263cb67 --- /dev/null +++ b/pkg/app/handler/user/maintainers.templ @@ -0,0 +1,95 @@ +package user + +import "encoding/base64" +import "encoding/json" +import "net/http" +import "time" +import "soko/pkg/app/utils" +import "soko/pkg/models" +import "slices" +import "soko/pkg/database" + +func splitAllProjects() [][]models.Project { + var projects []models.Project + database.DBCon.Model(&projects).Column("name", "email").Select() + split := (3 + len(projects)) / 4 + return [][]models.Project{projects[:split], projects[split : 2*split], projects[2*split : 3*split], projects[3*split:]} +} + +templ maintainers(preferences models.MaintainersPreferences) { + <form method="post" action="/user/preferences/maintainers/edit"> + <div class="row"> + <div class="col-12"> + <h3 class="mt-0" id="qa-report">Include Project Packages</h3> + <div class="form-check form-check-inline" style="text-overflow: ellipsis;overflow: hidden; width: 100%;"> + <input + type="checkbox" + id="include-packages" + name="include-packages" + value="true" + if preferences.IncludeProjectPackages { + checked + } + /> + <label class="form-check-label ml-1" for="include-packages" style="overflow:hidden;text-overflow: ellipsis;" title="">Include packages of projects the maintainer is part of</label> + </div> + <i>If this option is enabled, all packages, QA reports, pull requests and bugs of projects a maintainer is part of will be displayed as well on the maintainer page. That is, if <i>Larry</i> is part of the <i>Python</i> project, all packages, QA reports, pull requests and bugs of the Python project will be displayed as well on the maintainer page of <i>Larry</i>.<br/>Below you can furthermore specify projects that will be excluded on the maintainer page. E.g. if Larry is furthermore part of the proxy-maintainers project, and the project is marked below, packages of the proxy maintainers project won't be shown on Larry's maintainer page.</i> + </div> + <div class="col-12"> + <h3 class="mt-4 pt-3" id="qa-report">Excluded Projects</h3> + </div> + for _, projects := range splitAllProjects() { + <div class="col-3"> + for _, project := range projects { + <li class="list-group-item"> + <div class="form-check form-check-inline" style="text-overflow: ellipsis;overflow: hidden; width: 100%;height:21px;"> + <input + type="checkbox" + id={ "excluded-projects-" + project.Email } + name="excluded-projects" + value={ project.Email } + if slices.Contains(preferences.ExcludedProjects, project.Email) { + checked + } + /> + <label class="form-check-label ml-1" for={ "excluded-projects-" + project.Email } style="overflow:hidden;text-overflow: ellipsis;height:21px;" title={ project.Name }>{ project.Name }</label> + </div> + </li> + } + </div> + } + <div class="col-12 mt-4"> + <button type="submit" class="float-right btn btn-sm btn-primary">Save</button> + <a class="float-right btn btn-sm btn-outline-danger mr-2" href="/user/preferences/maintainers/reset">Reset to Defaults</a> + </div> + </div> + </form> +} + +func Maintainers(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetUserPreferences(r) + r.ParseForm() + // excluded projects + excludedProjects := r.Form["excluded-projects"] + userPreferences.Maintainers.ExcludedProjects = excludedProjects + // include projects? + includePackages := r.Form.Get("include-packages") + userPreferences.Maintainers.IncludeProjectPackages = includePackages == "true" + // store cookie + encodedUserPreferences, err := json.Marshal(&userPreferences.Maintainers) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_maintainers", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/maintainers", http.StatusSeeOther) +} + +func ResetMaintainers(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetDefaultUserPreferences() + encodedUserPreferences, err := json.Marshal(&userPreferences.Maintainers) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_maintainers", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/maintainers", http.StatusSeeOther) +} diff --git a/pkg/app/handler/user/packages.templ b/pkg/app/handler/user/packages.templ new file mode 100644 index 0000000..dd01068 --- /dev/null +++ b/pkg/app/handler/user/packages.templ @@ -0,0 +1,407 @@ +package user + +import "encoding/base64" +import "encoding/json" +import "net/http" +import "slices" +import "strconv" +import "time" +import "soko/pkg/app/utils" +import "soko/pkg/models" + +templ packages(preferences models.PackagesPreferences) { + <div class="row"> + <div class="col-2 mt-1"> + <div class="nav flex-column" role="tablist" aria-orientation="vertical" style="position: fixed;"> + <a class="nav-link user-pref-nav-link active" id="overview-tab" href="#overview" aria-controls="overview-settings">Overview</a> + <a class="nav-link user-pref-nav-link" id="dependencies-tab" href="#dependencies">Dependencies</a> + <a class="nav-link user-pref-nav-link" id="pull-requests-tab" href="#pull-requests" aria-controls="pull-requests-settings">Pull requests</a> + <a class="nav-link user-pref-nav-link" id="bugs-tab" href="#bugs" aria-controls="bugs-settings">Bugs</a> + <a class="nav-link user-pref-nav-link" id="security-tab" href="#security" aria-controls="security-settings">Security</a> + <a class="nav-link user-pref-nav-link" id="changelog-tab" href="#changelog" aria-controls="changelog-settings">Changelog</a> + <a class="nav-link user-pref-nav-link" id="tabs-tab" href="#tabs" aria-controls="tabs-settings">Tabs</a> + </div> + </div> + <div class="col-9 mt-1"> + <form method="post" action="/user/preferences/packages/edit"> + <h3 id="overview">Overview</h3> + <hr class="mt-1"/> + <h4 class="mb-1">Layout</h4> + <div class="row"> + <div class="col-6 mt-1"> + <div class="card" style="background: transparent;"> + <div class="card-body"> + <img id="img1" alt="Versions + Metadata (default)" src="/assets/pgo2.png" style="width: 100%;cursor: pointer;"/> + </div> + </div> + <div class="text-center mt-2"> + <div class="form-check text-center form-check-inline" style="text-overflow: ellipsis;overflow: hidden;"> + <input + type="radio" + id="minimalOverviewLayout" + name="overview-layout" + value="minimal" + if preferences.Overview.Layout == "minimal" { + checked + } + /> + <label class="form-check-label ml-1" for="minimalOverviewLayout" style="overflow:hidden;text-overflow: ellipsis;" title="Versions + Metadata (default)">Versions + Metadata <i>(default)</i></label> + </div> + </div> + </div> + <div class="col-6 mt-1"> + <div class="card" style="background: transparent;"> + <div class="card-body"> + <img id="img2" alt="Versions + Metadata + Changelog" src="/assets/pgo1.png" onclick="document.getElementById('fullOverviewLayout').checked = true;" style="width: 100%;cursor: pointer;"/> + </div> + </div> + <div class="text-center mt-2"> + <div class="form-check text-center form-check-inline" style="text-overflow: ellipsis;overflow: hidden;"> + <input + type="radio" + id="fullOverviewLayout" + name="overview-layout" + value="full" + if preferences.Overview.Layout == "full" { + checked + } + /> + <label class="form-check-label ml-1" for="fullOverviewLayout" style="overflow:hidden;text-overflow: ellipsis;" title="Versions + Metadata + Changelog">Versions + Metadata + Changelog</label> + </div> + </div> + </div> + </div> + <h4 class="mt-4 mb-1">Keywords</h4> + <div class="row pl-3"> + @archesTwoColumns(preferences.Overview.Keywords) + </div> + <h4 class="mt-4 mb-1">EAPI version</h4> + <div class="card"> + <div class="card-body"> + Show + <select class="form-control form-control-sm ml-2" style="max-width: 100px;display: inline;" name="overview-eapi" id="overview-eapi"> + <option + value="none" + if preferences.Overview.EAPI == "none" { + selected + } + >none</option> + <option + value="column" + if preferences.Overview.EAPI == "column" { + selected + } + >in column</option> + <option + value="inline" + if preferences.Overview.EAPI == "inline" { + selected + } + >inline</option> + </select> + </div> + </div> + <h4 class="mt-4 mb-1">Outdated Versions</h4> + <div class="card"> + <div class="card-body"> + <div class="form-check form-check-inline"> + <input + type="checkbox" + class="" + id="overview-showOutdated" + name="overview-showOutdated" + value="true" + if preferences.Overview.ShowOutdated { + checked + } + /> + <label class="form-check-label ml-1" for="overview-showOutdated">Show Outdated warnings?</label> + </div> + </div> + </div> + <h4 class="mt-4 mb-1">Metadata</h4> + <div class="card"> + <div class="card-body"> + <div class="row"> + <div class="col-4"> + <div class="form-check form-check-inline"> + <input + type="checkbox" + name="overview-metadata-fields" + id="overview-metadata-fields-homepage" + value="homepage" + if slices.Contains(preferences.Overview.MetadataFields, "homepage") { + checked + } + /> + <label class="form-check-label ml-1" for="overview-metadata-fields-homepage">Homepage</label> + </div> + </div> + <div class="col-4"> + <div class="form-check form-check-inline"> + <input + type="checkbox" + name="overview-metadata-fields" + id="overview-metadata-fields-upstream" + value="upstream" + if slices.Contains(preferences.Overview.MetadataFields, "upstream") { + checked + } + /> + <label class="form-check-label ml-1" for="overview-metadata-fields-upstream">Upstream</label> + </div> + </div> + <div class="col-4"> + <div class="form-check form-check-inline"> + <input + type="checkbox" + name="overview-metadata-fields" + id="overview-metadata-fields-longdescription" + value="longdescription" + if slices.Contains(preferences.Overview.MetadataFields, "longdescription") { + checked + } + /> + <label class="form-check-label ml-1" for="overview-metadata-fields-longdescription">Longdescription</label> + </div> + </div> + <div class="col-4"> + <div class="form-check form-check-inline"> + <input + type="checkbox" + name="overview-metadata-fields" + id="overview-metadata-fields-useflags" + value="useflags" + if slices.Contains(preferences.Overview.MetadataFields, "useflags") { + checked + } + /> + <label class="form-check-label ml-1" for="overview-metadata-fields-useflags">USE flags</label> + </div> + </div> + <div class="col-4"> + <div class="form-check form-check-inline"> + <input + type="checkbox" + name="overview-metadata-fields" + id="overview-metadata-fields-license" + value="license" + if slices.Contains(preferences.Overview.MetadataFields, "license") { + checked + } + /> + <label class="form-check-label ml-1" for="overview-metadata-fields-license">License</label> + </div> + </div> + <div class="col-4"> + <div class="form-check form-check-inline"> + <input + type="checkbox" + name="overview-metadata-fields" + id="overview-metadata-fields-maintainers" + value="maintainers" + if slices.Contains(preferences.Overview.MetadataFields, "maintainers") { + checked + } + /> + <label class="form-check-label ml-1" for="overview-metadata-fields-maintainers">Maintainers</label> + </div> + </div> + </div> + </div> + </div> + <h4 class="mt-4 mb-1">Changelog</h4> + <div class="card"> + <div class="card-body"> + <div class="row"> + <div class="col-6"> + Layout + <select class="form-control form-control-sm ml-1" style="max-width: 100px;display: inline;" name="overview-changelog-type" id="overview-changelog-type" disabled> + <option value="compact">default</option> + </select> + </div> + <div class="col-6"> + Size <input type="number" name="overview-changelog-size" class="form-control form-control-sm ml-1" style="width:150px;display: inline;" value={ strconv.Itoa(preferences.Overview.ChangelogLength) }/> + </div> + </div> + </div> + </div> + <h3 class="mt-5" id="dependencies">Dependencies</h3> + <hr class="mt-1"/> + <div class="card"> + <div class="card-body"> + Default Page + <select class="form-control form-control-sm ml-1" style="max-width: 200px;display: inline;" name="dependencies-default-page" id="dependencies-default-page"> + <option + value="dependencies" + if preferences.Dependencies.Default == "dependencies" { + selected + } + >dependencies</option> + <option + value="reverse-dependencies" + if preferences.Dependencies.Default == "reverse-dependencies" { + selected + } + >reverse-dependencies</option> + </select> + </div> + </div> + <h3 class="mt-5" id="pull-requests">Pull requests</h3> + <hr class="mt-1"/> + <div class="card"> + <div class="card-body"> + Layout + <select class="form-control form-control-sm ml-1" style="max-width: 200px;display: inline;" name="pullrequests-layout" id="pullrequests-layout" disabled> + <option + value="default" + if preferences.PullRequests.Layout == "default" { + selected + } + >default</option> + </select> + </div> + </div> + <h3 class="mt-5" id="bugs">Bugs</h3> + <hr class="mt-1"/> + <div class="card"> + <div class="card-body"> + Layout + <select class="form-control form-control-sm ml-1" style="max-width: 200px;display: inline;" name="bugs-layout" id="bugs-layout" disabled> + <option + value="default" + if preferences.PullRequests.Layout == "default" { + selected + } + >default</option> + </select> + </div> + </div> + <h3 class="mt-5" id="security">Security</h3> + <hr class="mt-1"/> + <div class="card"> + <div class="card-body"> + <div class="row"> + <div class="col-6"> + Layout + <select class="form-control form-control-sm ml-1" style="max-width: 100px;display: inline;" name="security-layout" id="security-layout" disabled> + <option value="default">default</option> + </select> + </div> + <div class="col-6"> + <div class="form-check form-check-inline pt-2"> + <input + type="checkbox" + name="security-show-glsas" + id="security-show-glsas" + class="disabled" + value="true" + if preferences.Security.ShowGLSAs { + selected + } + disabled + /> + <label class="form-check-label ml-1" style="color:grey;">Show GLSAs</label> + </div> + </div> + </div> + </div> + </div> + <h3 class="mt-5" id="changelog">Changelog</h3> + <hr class="mt-1"/> + <div class="card"> + <div class="card-body"> + <div class="row"> + <div class="col-6"> + Layout + <select class="form-control form-control-sm ml-1" style="max-width: 100px;display: inline;" name="changelog-type" id="changelog-type" disabled> + <option value="compact">default</option> + </select> + </div> + <div class="col-6"> + Size <input type="number" name="changelog-size" class="form-control form-control-sm" style="display: inline;width: 150px;" value={ strconv.Itoa(preferences.Changelog.Size) }/> + </div> + </div> + </div> + </div> + <div class="row"> + <div class="col-12 mt-4"> + <button type="submit" class="float-right btn btn-sm btn-primary">Save</button> + <a class="float-right btn btn-sm btn-outline-danger mr-2" href="/user/preferences/packages/reset">Reset to Defaults</a> + </div> + </div> + </form> + </div> + </div> + <div id="myModal" class="modal"> + <span class="close">×</span> + <img class="modal-content" id="img01"/> + <div id="caption"></div> + </div> +} + +func EditPackagesPreferences(w http.ResponseWriter, r *http.Request) { + + userPreferences := utils.GetUserPreferences(r) + + r.ParseForm() + + // Overview: Layout + overviewLayout := r.Form.Get("overview-layout") + if overviewLayout == "minimal" || overviewLayout == "full" { + userPreferences.Packages.Overview.Layout = overviewLayout + } + + // Overview: Keywords + overviewKeywords := r.Form["overview-keywords"] + userPreferences.Packages.Overview.Keywords = overviewKeywords + + // EAPI + showEAPI := r.Form.Get("overview-eapi") + if showEAPI == "none" || showEAPI == "column" || showEAPI == "inline" { + userPreferences.Packages.Overview.EAPI = showEAPI + } + + // Overview: Show Outdated + userPreferences.Packages.Overview.ShowOutdated = r.Form.Get("overview-showOutdated") == "true" + + // Overview: Metadata fields + overviewMetadataFields := r.Form["overview-metadata-fields"] + userPreferences.Packages.Overview.MetadataFields = overviewMetadataFields + + // Overview: Changelog + changelogSize, err := strconv.Atoi(r.Form.Get("overview-changelog-size")) + if err == nil { + if changelogSize < 100 { + userPreferences.Packages.Overview.ChangelogLength = changelogSize + } else { + userPreferences.Packages.Overview.ChangelogLength = 100 + } + } + + // Dependencies + defaultDependenciesPage := r.Form.Get("dependencies-default-page") + if defaultDependenciesPage == "dependencies" || defaultDependenciesPage == "reverse-dependencies" { + userPreferences.Packages.Dependencies.Default = defaultDependenciesPage + } + + // + // Store cookie + // + encodedUserPreferences, err := json.Marshal(&userPreferences.Packages) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_packages", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/packages", http.StatusSeeOther) +} + +func ResetPackages(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetDefaultUserPreferences() + encodedUserPreferences, err := json.Marshal(&userPreferences.Packages) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_packages", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/packages", http.StatusSeeOther) +} diff --git a/pkg/app/handler/user/preferences.go b/pkg/app/handler/user/preferences.go deleted file mode 100644 index 8c5deec..0000000 --- a/pkg/app/handler/user/preferences.go +++ /dev/null @@ -1,245 +0,0 @@ -package user - -import ( - b64 "encoding/base64" - "encoding/json" - "net/http" - "soko/pkg/app/utils" - "soko/pkg/config" - "soko/pkg/database" - "soko/pkg/models" - "strconv" - "strings" - "time" -) - -// Preferences renders a template to show the user preferences page -func Preferences(w http.ResponseWriter, r *http.Request) { - - pageName := "general" - - if strings.HasSuffix(r.URL.Path, "/general") { - pageName = "general" - } else if strings.HasSuffix(r.URL.Path, "/packages") { - pageName = "packages" - } else if strings.HasSuffix(r.URL.Path, "/maintainers") { - pageName = "maintainers" - } else if strings.HasSuffix(r.URL.Path, "/useflags") { - pageName = "useflags" - } else if strings.HasSuffix(r.URL.Path, "/arches") { - pageName = "arches" - } - - var allProjects []*models.Project - database.DBCon.Model(&allProjects).Select() - - renderUserTemplate(w, r, allProjects, pageName, "preferences") -} - -func EditPackagesPreferences(w http.ResponseWriter, r *http.Request) { - - userPreferences := utils.GetUserPreferences(r) - - r.ParseForm() - - // Overview: Layout - overviewLayout := r.Form.Get("overview-layout") - if overviewLayout == "minimal" || overviewLayout == "full" { - userPreferences.Packages.Overview.Layout = overviewLayout - } - - // Overview: Keywords - overviewKeywords := r.Form["overview-keywords"] - userPreferences.Packages.Overview.Keywords = overviewKeywords - - // EAPI - showEAPI := r.Form.Get("overview-eapi") - if showEAPI == "none" || showEAPI == "column" || showEAPI == "inline" { - userPreferences.Packages.Overview.EAPI = showEAPI - } - - // Overview: Show Outdated - userPreferences.Packages.Overview.ShowOutdated = r.Form.Get("overview-showOutdated") == "true" - - // Overview: Metadata fields - overviewMetadataFields := r.Form["overview-metadata-fields"] - userPreferences.Packages.Overview.MetadataFields = overviewMetadataFields - - // Overview: Changelog - changelogSize, err := strconv.Atoi(r.Form.Get("overview-changelog-size")) - if err == nil { - if changelogSize < 100 { - userPreferences.Packages.Overview.ChangelogLength = changelogSize - } else { - userPreferences.Packages.Overview.ChangelogLength = 100 - } - } - - // Dependencies - defaultDependenciesPage := r.Form.Get("dependencies-default-page") - if defaultDependenciesPage == "dependencies" || defaultDependenciesPage == "reverse-dependencies" { - userPreferences.Packages.Dependencies.Default = defaultDependenciesPage - } - - // QA Report - qaReportClasses := r.Form["qareport-classes"] - excludedQAReportClasses := []int{} - for i := 0; i <= 190; i++ { - if !contains(qaReportClasses, strconv.Itoa(i)) { - excludedQAReportClasses = append(excludedQAReportClasses, i) - } - } - userPreferences.Packages.QAReport.ExcludedWarningClasses = excludedQAReportClasses - - // Tabs - visibleTabs := r.Form["visible-tabs"] - userPreferences.Packages.Tabs.Visible = visibleTabs - - // - // Store cookie - // - encodedUserPreferences, err := json.Marshal(&userPreferences.Packages) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_packages", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/packages", http.StatusSeeOther) -} - -func ResetPackages(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetDefaultUserPreferences() - encodedUserPreferences, err := json.Marshal(&userPreferences.Packages) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_packages", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/packages", http.StatusSeeOther) -} - -func General(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetUserPreferences(r) - r.ParseForm() - // landing page layout - layout := r.Form.Get("landingpage-layout") - if layout == "classic" || layout == "full" { - userPreferences.General.LandingPageLayout = layout - } - // store cookie - encodedUserPreferences, err := json.Marshal(&userPreferences.General) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_general", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/general", http.StatusSeeOther) -} - -func ResetGeneral(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetDefaultUserPreferences() - encodedUserPreferences, err := json.Marshal(&userPreferences.General) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_general", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/general", http.StatusSeeOther) -} - -func Useflags(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetUserPreferences(r) - r.ParseForm() - // default use flag page - layout := r.Form.Get("useflag-default-page") - if layout == "bubble" || layout == "search" { - userPreferences.Useflags.Layout = layout - } - // store cookie - encodedUserPreferences, err := json.Marshal(&userPreferences.Useflags) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_useflags", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/useflags", http.StatusSeeOther) -} - -func ResetUseflags(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetDefaultUserPreferences() - encodedUserPreferences, err := json.Marshal(&userPreferences.Useflags) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_useflags", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/useflags", http.StatusSeeOther) -} - -func Arches(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetUserPreferences(r) - r.ParseForm() - // visible arches - visibleArches := r.Form["visible-arches"] - userPreferences.Arches.Visible = visibleArches - // default arch - defaultArch := r.Form.Get("arches-default-arch") - userPreferences.Arches.DefaultArch = defaultArch - // default arches page - defaultPage := r.Form.Get("arches-default-page") - userPreferences.Arches.DefaultPage = defaultPage - // store cookie - encodedUserPreferences, err := json.Marshal(&userPreferences.Arches) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_arches", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/arches", http.StatusSeeOther) -} - -func ResetArches(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetDefaultUserPreferences() - encodedUserPreferences, err := json.Marshal(&userPreferences.Arches) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_arches", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/arches", http.StatusSeeOther) -} - -func Maintainers(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetUserPreferences(r) - r.ParseForm() - // excluded projects - excludedProjects := r.Form["excluded-projects"] - userPreferences.Maintainers.ExcludedProjects = excludedProjects - // include projects? - includePackages := r.Form.Get("include-packages") - userPreferences.Maintainers.IncludeProjectPackages = includePackages == "true" - // store cookie - encodedUserPreferences, err := json.Marshal(&userPreferences.Maintainers) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_maintainers", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/maintainers", http.StatusSeeOther) -} - -func ResetMaintainers(w http.ResponseWriter, r *http.Request) { - userPreferences := utils.GetDefaultUserPreferences() - encodedUserPreferences, err := json.Marshal(&userPreferences.Maintainers) - if err == nil { - sEnc := b64.StdEncoding.EncodeToString(encodedUserPreferences) - addCookie(w, "userpref_maintainers", "/", sEnc, 365*24*time.Hour) - } - http.Redirect(w, r, "/user/preferences/maintainers", http.StatusSeeOther) -} - -// addCookie will apply a new cookie to the response of a http request -// with the key/value specified. -func addCookie(w http.ResponseWriter, name, path, value string, ttl time.Duration) { - expire := time.Now().Add(ttl) - cookie := http.Cookie{ - Name: name, - Path: path, - Value: value, - Expires: expire, - HttpOnly: true, - Secure: config.DevMode() == "false", - } - http.SetCookie(w, &cookie) -} diff --git a/pkg/app/handler/user/preferences.templ b/pkg/app/handler/user/preferences.templ new file mode 100644 index 0000000..0b33d5c --- /dev/null +++ b/pkg/app/handler/user/preferences.templ @@ -0,0 +1,94 @@ +package user + +import "net/http" +import "time" +import "soko/pkg/app/layout" +import "soko/pkg/app/utils" +import "soko/pkg/config" +import "soko/pkg/models" + +var viewTabs = []layout.SubTab{ + { + Name: "General", + Link: "/user/preferences/general", + Icon: "fa fa-globe mr-1", + }, + { + Name: "Packages", + Link: "/user/preferences/packages", + Icon: "fa fa-cube mr-1", + }, + { + Name: "Maintainers", + Link: "/user/preferences/maintainers", + Icon: "fa fa-users mr-1", + }, + { + Name: "USE flags", + Link: "/user/preferences/useflags", + Icon: "fa fa-sliders mr-1", + }, + { + Name: "Architectures", + Link: "/user/preferences/arches", + Icon: "fa fa-server mr-1", + }, +} + +templ show(currentSubTab string, preferences models.UserPreferences) { + <div class="container mb-5"> + switch currentSubTab { + case "General": + @general(preferences.General) + case "Packages": + @packages(preferences.Packages) + case "Maintainers": + @maintainers(preferences.Maintainers) + case "USE flags": + @useflags(preferences.Useflags) + case "Architectures": + @arches(preferences.Arches) + } + </div> + <script src="/assets/userpref.js"></script> +} + +templ sortableScript() { + <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script> + <script> + if(document.getElementById("example1") != null && document.getElementById("example2") != null) { + new Sortable(example1, { + group: 'shared', + animation: 150, + ghostClass: 'bg-info' + }); + new Sortable(example2, { + group: 'shared', + animation: 150, + ghostClass: 'bg-info' + }); + } + </script> +} + +func Preferences(currentSubTab string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + layout.TabbedLayout("User", "preferences", "Preferences", "fa fa-fw fa-cog", "You can customize the page contents to your needs here", viewTabs, + currentSubTab, show(currentSubTab, utils.GetUserPreferences(r))).Render(r.Context(), w) + } +} + +// addCookie will apply a new cookie to the response of a http request +// with the key/value specified. +func addCookie(w http.ResponseWriter, name, path, value string, ttl time.Duration) { + expire := time.Now().Add(ttl) + cookie := http.Cookie{ + Name: name, + Path: path, + Value: value, + Expires: expire, + HttpOnly: true, + Secure: config.DevMode() == "false", + } + http.SetCookie(w, &cookie) +} diff --git a/pkg/app/handler/user/useflags.templ b/pkg/app/handler/user/useflags.templ new file mode 100644 index 0000000..438842c --- /dev/null +++ b/pkg/app/handler/user/useflags.templ @@ -0,0 +1,93 @@ +package user + +import "encoding/base64" +import "encoding/json" +import "net/http" +import "time" +import "soko/pkg/app/utils" +import "soko/pkg/models" + +templ useflags(preferences models.UseflagsPreferences) { + <form method="post" action="/user/preferences/useflags/edit"> + <div class="row"> + <div class="col-5 offset-1 mt-1"> + <div class="card" style="background: transparent;"> + <div class="card-body"> + <img id="img1" alt="Popular USE flags (default)" src="/assets/pgo6.png" style="width: 100%;cursor: pointer;"/> + </div> + </div> + <div class="text-center mt-2"> + <div class="form-check text-center form-check-inline" style="text-overflow: ellipsis;overflow: hidden;"> + <input + type="radio" + id="BubbleUseflagDefaultPage" + name="useflag-default-page" + value="bubble" + if preferences.Layout == "bubble" { + checked + } + /> + <label class="form-check-label ml-1" for="BubbleUseflagDefaultPage" style="overflow:hidden;text-overflow: ellipsis;" title="Popular USE flags (default)">Popular USE flags <i>(default)</i></label> + </div> + </div> + </div> + <div class="col-5 mt-1"> + <div class="card" style="background: transparent;"> + <div class="card-body"> + <img id="img2" alt="USE flags search" src="/assets/pgo5.png" style="width: 100%;cursor: pointer;"/> + </div> + </div> + <div class="text-center mt-2"> + <div class="form-check text-center form-check-inline" style="text-overflow: ellipsis;overflow: hidden;"> + <input + type="radio" + id="SearchUseflagDefaultPage" + name="useflag-default-page" + value="search" + if preferences.Layout == "search" { + checked + } + /> + <label class="form-check-label ml-1" for="SearchUseflagDefaultPage" style="overflow:hidden;text-overflow: ellipsis;" title="USE flags search">USE flags search</label> + </div> + </div> + </div> + <div class="col-10 offset-1 mt-4"> + <button type="submit" class="float-right btn btn-sm btn-primary">Save</button> + <a class="float-right btn btn-sm btn-outline-danger mr-2" href="/user/preferences/useflags/reset">Reset to Defaults</a> + </div> + </div> + </form> + <div id="myModal" class="modal"> + <span class="close">×</span> + <img class="modal-content" id="img01"/> + <div id="caption"></div> + </div> +} + +func Useflags(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetUserPreferences(r) + r.ParseForm() + // default use flag page + layout := r.Form.Get("useflag-default-page") + if layout == "bubble" || layout == "search" { + userPreferences.Useflags.Layout = layout + } + // store cookie + encodedUserPreferences, err := json.Marshal(&userPreferences.Useflags) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_useflags", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/useflags", http.StatusSeeOther) +} + +func ResetUseflags(w http.ResponseWriter, r *http.Request) { + userPreferences := utils.GetDefaultUserPreferences() + encodedUserPreferences, err := json.Marshal(&userPreferences.Useflags) + if err == nil { + sEnc := base64.StdEncoding.EncodeToString(encodedUserPreferences) + addCookie(w, "userpref_useflags", "/", sEnc, 365*24*time.Hour) + } + http.Redirect(w, r, "/user/preferences/useflags", http.StatusSeeOther) +} diff --git a/pkg/app/handler/user/utils.go b/pkg/app/handler/user/utils.go deleted file mode 100644 index 0372ef8..0000000 --- a/pkg/app/handler/user/utils.go +++ /dev/null @@ -1,71 +0,0 @@ -package user - -import ( - "html/template" - "net/http" - "soko/pkg/app/utils" - "soko/pkg/models" - "strings" -) - -// renderAboutTemplate renders a specific about template -func renderUserTemplate(w http.ResponseWriter, r *http.Request, allProjects []*models.Project, pageName, page string) { - templates := template.Must( - template.Must( - template.Must( - template.Must( - template.New(page).Funcs(template.FuncMap{ - "Contains": contains, - "ContainsInt": containsInt, - "CreateSlice": createSlice, - "GetPkgcheckClass": models.GetPkgcheckClass, - "add": func(a, b int) int { - return a + b - }, - }). - ParseGlob("web/templates/layout/*.tmpl")). - ParseGlob("web/templates/user/preferences/*.tmpl")). - ParseGlob("web/templates/user/userheader.tmpl")). - ParseGlob("web/templates/user/" + page + ".tmpl")) - - templates.ExecuteTemplate(w, page+".tmpl", getPageData(pageName, allProjects, utils.GetUserPreferences(r))) -} - -func contains(list []string, item string) bool { - return strings.Contains(" "+strings.Join(list, " ")+" ", " "+item+" ") -} - -func containsInt(list []int, item int) bool { - for _, v := range list { - if v == item { - return true - } - } - return false -} - -func createSlice(n int) []int { - slice := []int{} - for i := 0; i <= n; i++ { - slice = append(slice, i) - } - return slice -} - -// getPageData returns the data used -// in all about templates -func getPageData(pageName string, allProjects []*models.Project, preferences models.UserPreferences) interface{} { - return struct { - Header models.Header - Application models.Application - PageName string - Projects []*models.Project - UserPreferences models.UserPreferences - }{ - Header: models.Header{Title: "User – ", Tab: "user"}, - Application: utils.GetApplicationData(), - PageName: pageName, - Projects: allProjects, - UserPreferences: preferences, - } -} diff --git a/pkg/app/serve.go b/pkg/app/serve.go index f3e36e4..66bd220 100644 --- a/pkg/app/serve.go +++ b/pkg/app/serve.go @@ -78,9 +78,13 @@ func Serve() { redirect("/packages/stabilized.atom", "/packages/stable.atom") setRoute("/packages/search.atom", packages.SearchFeed) - setRoute("/user", user.Preferences) - setRoute("/user/preferences", user.Preferences) - setRoute("/user/preferences/", user.Preferences) + setRoute("/user", user.Preferences("General")) + setRoute("/user/preferences", user.Preferences("General")) + setRoute("/user/preferences/general", user.Preferences("General")) + setRoute("/user/preferences/packages", user.Preferences("Packages")) + setRoute("/user/preferences/maintainers", user.Preferences("Maintainers")) + setRoute("/user/preferences/useflags", user.Preferences("USE flags")) + setRoute("/user/preferences/arches", user.Preferences("Architectures")) setRoute("/user/preferences/general/layout", user.General) setRoute("/user/preferences/general/reset", user.ResetGeneral) diff --git a/pkg/models/userpreferences.go b/pkg/models/userpreferences.go index 3ed7e21..c31c6dc 100644 --- a/pkg/models/userpreferences.go +++ b/pkg/models/userpreferences.go @@ -19,12 +19,10 @@ type GeneralPreferences struct { type PackagesPreferences struct { Overview PackagesOverviewPreferences Dependencies PackagesDependenciesPreferences - QAReport PackagesQAReportPreferences PullRequests PackagesPullRequestsPreferences Bugs PackagesBugsPreferences Security PackagesSecurityPreferences Changelog PackagesChangelogPreferences - Tabs PackagesTabsPreferences } type PackagesOverviewPreferences struct { @@ -41,11 +39,6 @@ type PackagesDependenciesPreferences struct { Default string } -type PackagesQAReportPreferences struct { - ExcludedWarningClasses []int - ShowAll bool -} - type PackagesPullRequestsPreferences struct { Layout string } @@ -64,10 +57,6 @@ type PackagesChangelogPreferences struct { Size int } -type PackagesTabsPreferences struct { - Visible []string -} - type MaintainersPreferences struct { IncludeProjectPackages bool ExcludedProjects []string @@ -89,12 +78,10 @@ func GetDefaultUserPreferences() UserPreferences { userPreferences.Packages = PackagesPreferences{} userPreferences.Packages.Overview = PackagesOverviewPreferences{} userPreferences.Packages.Dependencies = PackagesDependenciesPreferences{} - userPreferences.Packages.QAReport = PackagesQAReportPreferences{} userPreferences.Packages.PullRequests = PackagesPullRequestsPreferences{} userPreferences.Packages.Bugs = PackagesBugsPreferences{} userPreferences.Packages.Security = PackagesSecurityPreferences{} userPreferences.Packages.Changelog = PackagesChangelogPreferences{} - userPreferences.Packages.Tabs = PackagesTabsPreferences{} userPreferences.Maintainers = MaintainersPreferences{} userPreferences.Useflags = UseflagsPreferences{} userPreferences.Arches = ArchesPreferences{} @@ -111,9 +98,6 @@ func GetDefaultUserPreferences() UserPreferences { userPreferences.Packages.Dependencies.Default = "dependencies" - userPreferences.Packages.QAReport.ExcludedWarningClasses = []int{} - userPreferences.Packages.QAReport.ShowAll = true - userPreferences.Packages.PullRequests.Layout = "default" userPreferences.Packages.Bugs.Layout = "default" @@ -124,8 +108,6 @@ func GetDefaultUserPreferences() UserPreferences { userPreferences.Packages.Changelog.Layout = "compact" userPreferences.Packages.Changelog.Size = 15 - userPreferences.Packages.Tabs.Visible = []string{"Overview", "Dependencies", "QA report", "Pull requests", "Bugs", "Security", "Changelog"} - userPreferences.Arches.Visible = []string{"amd64", "x86", "alpha", "arm", "arm64", "hppa", "ia64", "ppc", "ppc64", "riscv", "sparc"} userPreferences.Arches.DefaultArch = "amd64" userPreferences.Arches.DefaultPage = "keyworded" @@ -151,7 +133,7 @@ func (u *UserPreferences) Sanitize() { sanitizedKeywords := []string{} for _, keyword := range u.Packages.Overview.Keywords { - if strings.Contains(strings.Join(u.GetAllKeywords(), ","), keyword) { + if strings.Contains(strings.Join(GetAllKeywords(), ","), keyword) { sanitizedKeywords = append(sanitizedKeywords, keyword) } } @@ -201,23 +183,15 @@ func (u *UserPreferences) Sanitize() { u.Packages.Changelog.Size = 100 } - sanitizedTabs := []string{} - for _, tab := range u.Packages.Tabs.Visible { - if strings.Contains(strings.Join(defaultUserPreferences.Packages.Tabs.Visible, ","), tab) { - sanitizedTabs = append(sanitizedTabs, tab) - } - } - u.Packages.Tabs.Visible = sanitizedTabs - sanitizedVisibleArches := []string{} for _, keyword := range u.Arches.Visible { - if strings.Contains(strings.Join(u.GetAllKeywords(), ","), keyword) { + if strings.Contains(strings.Join(GetAllKeywords(), ","), keyword) { sanitizedVisibleArches = append(sanitizedVisibleArches, keyword) } } u.Arches.Visible = sanitizedVisibleArches - if !strings.Contains(strings.Join(u.GetAllKeywords(), ","), u.Arches.DefaultArch) { + if !strings.Contains(strings.Join(GetAllKeywords(), ","), u.Arches.DefaultArch) { u.Arches.DefaultArch = defaultUserPreferences.Arches.DefaultArch } @@ -230,34 +204,10 @@ func (u *UserPreferences) Sanitize() { } } -func (u UserPreferences) ContainsPkgcheckClass(class string) bool { - for _, v := range u.Packages.QAReport.ExcludedWarningClasses { - if GetPkgcheckClass(v) == class { - return false - } - } - return true -} - -func (u UserPreferences) GetAllKeywords() []string { +func GetAllKeywords() []string { return []string{"alpha", "amd64", "arm", "arm64", "hppa", "ia64", "loong", "m68k", "mips", "ppc", "ppc64", "riscv", "s390", "sparc", "x86", "amd64-linux", "arm-linux", "arm64-linux", "ppc64-linux", "x86-linux", "ppc-macos", "x64-macos", "sparc-solaris", "sparc64-solaris", "x64-solaris", "x86-solaris", "x64-winnt", "x86-winnt", "x64-cygwin"} } -func GetPkgcheckClass(number int) string { - pkgcheckClasses := []string{"AbsoluteSymlink", "ArchesWithoutProfiles", "BadCommitSummary", "BadDependency", "BadDescription", "BadFilename", "BadHomepage", "BadKeywords", "BadPackageUpdate", "BadProtocol", "BadWhitespaceCharacter", "BannedCharacter", "BannedEapi", "BannedEapiCommand", "BinaryFile", "CatBadlyFormedXml", "CatInvalidXml", "CatMetadataXmlEmptyElement", "CatMetadataXmlIndentation", "CatMetadataXmlInvalidCatRef", "CatMetadataXmlInvalidPkgRef", "CatMissingMetadataXml", "ConflictingAccountIdentifiers", "ConflictingChksums", "DeadUrl", "DeprecatedChksum", "DeprecatedDep", "DeprecatedEapi", "DeprecatedEapiCommand", "DeprecatedEclass", "DeprecatedInsinto", "DirectNoMaintainer", "DirectStableKeywords", "DoubleEmptyLine", "DoublePrefixInPath", "DroppedKeywords", "DroppedStableKeywords", "DroppedUnstableKeywords", "DuplicateEclassInherits", "DuplicateFiles", "DuplicateKeywords", "EbuildIncorrectCopyright", "EbuildInvalidCopyright", "EbuildInvalidLicenseHeader", "EbuildNonGentooAuthorsCopyright", "EbuildOldGentooCopyright", "EclassBashSyntaxError", "EclassIncorrectCopyright", "EclassInvalidCopyright", "EclassInvalidLicenseHeader", "EclassNonGentooAuthorsCopyright", "EclassOldGentooCopyright", "EmptyCategoryDir", "EmptyFile", "EmptyMaintainer", "EmptyPackageDir", "EmptyProject", "EqualVersions", "ExecutableFile", "HomepageInSrcUri", "HttpsUrlAvailable", "IncorrectCopyright", "InvalidBdepend", "InvalidCommitMessage", "InvalidCommitTag", "InvalidCopyright", "InvalidDepend", "InvalidEapi", "InvalidLicense", "InvalidLicenseHeader", "InvalidPN", "InvalidPdepend", "InvalidProperties", "InvalidRdepend", "InvalidRequiredUse", "InvalidRestrict", "InvalidSlot", "InvalidSrcUri", "InvalidUTF8", "InvalidUseFlags", "LaggingProfileEapi", "LaggingStable", "MaintainerWithoutProxy", "MatchingChksums", "MatchingGlobalUse", "MismatchedPN", "MismatchedPerlVersion", "MissingAccountIdentifier", "MissingChksum", "MissingLicense", "MissingLicenseFile", "MissingLicenseRestricts", "MissingManifest", "MissingPackageRevision", "MissingPythonEclass", "MissingSignOff", "MissingSlash", "MissingSlotDep", "MissingTestRestrict", "MissingUnpackerDep", "MissingUri", "MissingUseDepDefault", "MissingVirtualKeywords", "MovedPackageUpdate", "MultiMovePackageUpdate", "NoFinalNewline", "NonGentooAuthorsCopyright", "NonexistentBlocker", "NonexistentDeps", "NonexistentProfilePath", "NonexistentProjectMaintainer", "NonsolvableDepsInDev", "NonsolvableDepsInExp", "NonsolvableDepsInStable", "ObsoleteUri", "OldGentooCopyright", "OldMultiMovePackageUpdate", "OldPackageUpdate", "OutdatedBlocker", "OutsideRangeAccountIdentifier", "OverlappingKeywords", "PkgBadlyFormedXml", "PkgInvalidXml", "PkgMetadataXmlEmptyElement", "PkgMetadataXmlIndentation", "PkgMetadataXmlInvalidCatRef", "PkgMetadataXmlInvalidPkgRef", "PkgMissingMetadataXml", "PotentialGlobalUse", "PotentialLocalUse", "PotentialStable", "ProbableGlobalUse", "ProbableUseExpand", "ProfileError", "ProfileWarning", "PythonEclassError", "PythonMissingDeps", "PythonMissingRequiredUse", "PythonRuntimeDepInAnyR1", "RdependChange", "RedirectedUrl", "RedundantDodir", "RedundantLongDescription", "RedundantUriRename", "RedundantVersion", "RequiredUseDefaults", "SSLCertificateError", "SizeViolation", "SourcingError", "StableRequest", "StaleProxyMaintProject", "StaticSrcUri", "TarballAvailable", "TrailingEmptyLine", "UncheckableDep", "UnderscoreInUseFlag", "UnknownCategories", "UnknownKeywords", "UnknownLicenses", "UnknownManifest", "UnknownMirror", "UnknownPkgDirEntry", "UnknownProfilePackageKeywords", "UnknownProfilePackageUse", "UnknownProfilePackages", "UnknownProfileUse", "UnknownProperties", "UnknownRestrict", "UnknownUseFlags", "UnnecessaryLicense", "UnnecessaryManifest", "UnnecessarySlashStrip", "UnsortedKeywords", "UnstableOnly", "UnstatedIuse", "UnusedEclasses", "UnusedGlobalUse", "UnusedInMastersEclasses", "UnusedInMastersGlobalUse", "UnusedInMastersLicenses", "UnusedInMastersMirrors", "UnusedLicenses", "UnusedLocalUse", "UnusedMirrors", "UnusedProfileDirs", "VariableInHomepage", "VisibleVcsPkg", "VulnerablePackage", "WhitespaceFound", "WrongIndentFound", "WrongMaintainerType"} - return pkgcheckClasses[number] -} - -func GetPkgcheckClassIndex(class string) int { - pkgcheckClasses := []string{"AbsoluteSymlink", "ArchesWithoutProfiles", "BadCommitSummary", "BadDependency", "BadDescription", "BadFilename", "BadHomepage", "BadKeywords", "BadPackageUpdate", "BadProtocol", "BadWhitespaceCharacter", "BannedCharacter", "BannedEapi", "BannedEapiCommand", "BinaryFile", "CatBadlyFormedXml", "CatInvalidXml", "CatMetadataXmlEmptyElement", "CatMetadataXmlIndentation", "CatMetadataXmlInvalidCatRef", "CatMetadataXmlInvalidPkgRef", "CatMissingMetadataXml", "ConflictingAccountIdentifiers", "ConflictingChksums", "DeadUrl", "DeprecatedChksum", "DeprecatedDep", "DeprecatedEapi", "DeprecatedEapiCommand", "DeprecatedEclass", "DeprecatedInsinto", "DirectNoMaintainer", "DirectStableKeywords", "DoubleEmptyLine", "DoublePrefixInPath", "DroppedKeywords", "DroppedStableKeywords", "DroppedUnstableKeywords", "DuplicateEclassInherits", "DuplicateFiles", "DuplicateKeywords", "EbuildIncorrectCopyright", "EbuildInvalidCopyright", "EbuildInvalidLicenseHeader", "EbuildNonGentooAuthorsCopyright", "EbuildOldGentooCopyright", "EclassBashSyntaxError", "EclassIncorrectCopyright", "EclassInvalidCopyright", "EclassInvalidLicenseHeader", "EclassNonGentooAuthorsCopyright", "EclassOldGentooCopyright", "EmptyCategoryDir", "EmptyFile", "EmptyMaintainer", "EmptyPackageDir", "EmptyProject", "EqualVersions", "ExecutableFile", "HomepageInSrcUri", "HttpsUrlAvailable", "IncorrectCopyright", "InvalidBdepend", "InvalidCommitMessage", "InvalidCommitTag", "InvalidCopyright", "InvalidDepend", "InvalidEapi", "InvalidLicense", "InvalidLicenseHeader", "InvalidPN", "InvalidPdepend", "InvalidProperties", "InvalidRdepend", "InvalidRequiredUse", "InvalidRestrict", "InvalidSlot", "InvalidSrcUri", "InvalidUTF8", "InvalidUseFlags", "LaggingProfileEapi", "LaggingStable", "MaintainerWithoutProxy", "MatchingChksums", "MatchingGlobalUse", "MismatchedPN", "MismatchedPerlVersion", "MissingAccountIdentifier", "MissingChksum", "MissingLicense", "MissingLicenseFile", "MissingLicenseRestricts", "MissingManifest", "MissingPackageRevision", "MissingPythonEclass", "MissingSignOff", "MissingSlash", "MissingSlotDep", "MissingTestRestrict", "MissingUnpackerDep", "MissingUri", "MissingUseDepDefault", "MissingVirtualKeywords", "MovedPackageUpdate", "MultiMovePackageUpdate", "NoFinalNewline", "NonGentooAuthorsCopyright", "NonexistentBlocker", "NonexistentDeps", "NonexistentProfilePath", "NonexistentProjectMaintainer", "NonsolvableDepsInDev", "NonsolvableDepsInExp", "NonsolvableDepsInStable", "ObsoleteUri", "OldGentooCopyright", "OldMultiMovePackageUpdate", "OldPackageUpdate", "OutdatedBlocker", "OutsideRangeAccountIdentifier", "OverlappingKeywords", "PkgBadlyFormedXml", "PkgInvalidXml", "PkgMetadataXmlEmptyElement", "PkgMetadataXmlIndentation", "PkgMetadataXmlInvalidCatRef", "PkgMetadataXmlInvalidPkgRef", "PkgMissingMetadataXml", "PotentialGlobalUse", "PotentialLocalUse", "PotentialStable", "ProbableGlobalUse", "ProbableUseExpand", "ProfileError", "ProfileWarning", "PythonEclassError", "PythonMissingDeps", "PythonMissingRequiredUse", "PythonRuntimeDepInAnyR1", "RdependChange", "RedirectedUrl", "RedundantDodir", "RedundantLongDescription", "RedundantUriRename", "RedundantVersion", "RequiredUseDefaults", "SSLCertificateError", "SizeViolation", "SourcingError", "StableRequest", "StaleProxyMaintProject", "StaticSrcUri", "TarballAvailable", "TrailingEmptyLine", "UncheckableDep", "UnderscoreInUseFlag", "UnknownCategories", "UnknownKeywords", "UnknownLicenses", "UnknownManifest", "UnknownMirror", "UnknownPkgDirEntry", "UnknownProfilePackageKeywords", "UnknownProfilePackageUse", "UnknownProfilePackages", "UnknownProfileUse", "UnknownProperties", "UnknownRestrict", "UnknownUseFlags", "UnnecessaryLicense", "UnnecessaryManifest", "UnnecessarySlashStrip", "UnsortedKeywords", "UnstableOnly", "UnstatedIuse", "UnusedEclasses", "UnusedGlobalUse", "UnusedInMastersEclasses", "UnusedInMastersGlobalUse", "UnusedInMastersLicenses", "UnusedInMastersMirrors", "UnusedLicenses", "UnusedLocalUse", "UnusedMirrors", "UnusedProfileDirs", "VariableInHomepage", "VisibleVcsPkg", "VulnerablePackage", "WhitespaceFound", "WrongIndentFound", "WrongMaintainerType"} - for k, v := range pkgcheckClasses { - if v == class { - return k - } - } - return -1 -} - func createSlice(n int) []int { slice := []int{} for i := 0; i <= n; i++ { |