Skip to content

Commit

Permalink
feat(ui): Add sorting support and add proxy filter
Browse files Browse the repository at this point in the history
  • Loading branch information
dadav committed Dec 29, 2024
1 parent 306fea0 commit dc8113c
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 46 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Flags:
--dev enables dev mode
--drop-privileges drops privileges to the given user/group
--fallback-proxy string optional comma separated list of fallback upstream proxy urls
--proxy-prefixes string url prefixes to proxy (default "/v3")
--group string give control to this group or gid (requires root)
-h, --help help for serve
--import-proxied-releases add every proxied modules to local store
Expand Down Expand Up @@ -177,6 +178,8 @@ dev: false
drop-privileges: false
# List of comma separated upstream forge(s) to use when local requests return 404
fallback-proxy:
# The prefixes of requests to send to the proxies. Multiple entries must be separated by comma.
proxy-prefixes: /v3
# Import proxied modules into local backend.
import-proxied-releases: false
# Path to local modules.
Expand Down Expand Up @@ -213,6 +216,7 @@ GORGE_CORS="*"
GORGE_DEV=false
GORGE_DROP_PRIVILEGES=false
GORGE_FALLBACK_PROXY=""
GORGE_PROXY_PREFIXES=/v3
GORGE_IMPORT_PROXIED_RELEASES=false
GORGE_MODULESDIR=~/.gorge/modules
GORGE_MODULES_SCAN_SEC=0
Expand Down
14 changes: 12 additions & 2 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,20 @@ You can also enable the caching functionality to speed things up.`,
proxies := strings.Split(config.FallbackProxyUrl, ",")
slices.Reverse(proxies)

proxyPrefixes := strings.Split(config.ProxyPrefixes, ",")

for _, proxy := range proxies {
r.Use(customMiddleware.ProxyFallback(
proxy,
func(status int) bool {
return status == http.StatusNotFound
func(r *http.Request, status int) bool {
shouldProxy := false
for _, prefix := range proxyPrefixes {
if strings.HasPrefix(r.URL.Path, strings.TrimSpace(prefix)) {
shouldProxy = true
break
}
}
return shouldProxy && status == http.StatusNotFound
},
func(r *http.Response) {
r.Header.Add("X-Proxied-To", proxy)
Expand Down Expand Up @@ -395,6 +404,7 @@ func init() {
serveCmd.Flags().BoolVar(&config.DropPrivileges, "drop-privileges", false, "drops privileges to the given user/group")
serveCmd.Flags().BoolVar(&config.UI, "ui", false, "enables the web ui")
serveCmd.Flags().StringVar(&config.CachePrefixes, "cache-prefixes", "/v3/files", "url prefixes to cache")
serveCmd.Flags().StringVar(&config.ProxyPrefixes, "proxy-prefixes", "/v3", "url prefixes to proxy")
serveCmd.Flags().StringVar(&config.JwtSecret, "jwt-secret", "changeme", "jwt secret")
serveCmd.Flags().StringVar(&config.JwtTokenPath, "jwt-token-path", "~/.gorge/token", "jwt token path")
serveCmd.Flags().StringVar(&config.TlsCertPath, "tls-cert", "", "path to tls cert file")
Expand Down
2 changes: 2 additions & 0 deletions defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ dev: false
drop-privileges: false
# List of comma separated upstream forge(s) to use when local requests return 404
fallback-proxy:
# The prefixes of requests to send to the proxies. Multiple entries must be separated by comma.
proxy-prefixes: /v3
# Import proxied modules into local backend.
import-proxied-releases: false
# Path to local modules.
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var (
FallbackProxyUrl string
NoCache bool
CachePrefixes string
ProxyPrefixes string
CacheByFullRequestURI bool
CacheMaxAge int64
ImportProxiedReleases bool
Expand Down
4 changes: 2 additions & 2 deletions internal/middleware/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
}
}

func ProxyFallback(upstreamHost string, forwardToProxy func(int) bool, proxiedResponseCb func(*http.Response)) func(next http.Handler) http.Handler {
func ProxyFallback(upstreamHost string, forwardToProxy func(*http.Request, int) bool, proxiedResponseCb func(*http.Response)) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Store original headers before any modifications
Expand All @@ -58,7 +58,7 @@ func ProxyFallback(upstreamHost string, forwardToProxy func(int) bool, proxiedRe
capturedResponseWriter := NewCapturedResponseWriter(w)
next.ServeHTTP(capturedResponseWriter, r)

if forwardToProxy(capturedResponseWriter.status) {
if forwardToProxy(r, capturedResponseWriter.status) {
log.Log.Infof("Forwarding request to %s\n", upstreamHost)
u, err := url.Parse(upstreamHost)
if err != nil {
Expand Down
49 changes: 49 additions & 0 deletions internal/v3/ui/assets/js/table-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
function sortTable(tableId, columnIndex) {
var table = document.getElementById(tableId);
var switching = true;
var dir = "asc";
var switchcount = 0;

while (switching) {
switching = false;
var rows = table.rows;

for (var i = 1; i < (rows.length - 1); i++) {
var shouldSwitch = false;
var x = rows[i].getElementsByTagName("TD")[columnIndex];
var y = rows[i + 1].getElementsByTagName("TD")[columnIndex];

var xContent = x.innerHTML.toLowerCase();
var yContent = y.innerHTML.toLowerCase();

// Try to convert to numbers if the content looks numeric
if (xContent.match(/^[\d./]+$/) && yContent.match(/^[\d./]+$/)) {
xContent = parseFloat(xContent.replace(/[^\d.-]/g, '')) || xContent;
yContent = parseFloat(yContent.replace(/[^\d.-]/g, '')) || yContent;
}

if (dir === "asc") {
if (xContent > yContent) {
shouldSwitch = true;
break;
}
} else if (dir === "desc") {
if (xContent < yContent) {
shouldSwitch = true;
break;
}
}
}

if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount++;
} else {
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
4 changes: 4 additions & 0 deletions internal/v3/ui/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
max-width: 50%;
}

th[onclick] {
cursor: pointer;
}

/* https://codepen.io/irunatbullets/pen/MWwyVOw */
.switch {
display: inline-block;
Expand Down
7 changes: 4 additions & 3 deletions internal/v3/ui/components/author.templ
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
templ AuthorView(modules []*gen.Module) {
<h3>{ modules[0].Owner.Username }</h3>
<h4>Modules</h4>
<table>
<table id="modulesTable">
<thead>
<tr>
<th scope="col">Module</th>
<th scope="col">Version</th>
<th scope="col" onclick="sortTable('modulesTable', 0)">Module</th>
<th scope="col" onclick="sortTable('modulesTable', 1)">Version</th>
</tr>
</thead>
<tbody>
Expand All @@ -28,4 +28,5 @@ templ AuthorView(modules []*gen.Module) {
}
</tbody>
</table>
<script src="/assets/js/table-sort.js"></script>
}
4 changes: 2 additions & 2 deletions internal/v3/ui/components/author_templ.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions internal/v3/ui/components/search.templ
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ templ SearchView(query string, modules []*gen.Module) {
hx-swap="outerHTML"
hx-replace-url="true"
/>
<table class="table">
<table class="table" id="searchTable">
<thead>
<tr>
<th scope="col">Module</th>
<th scope="col">Author</th>
<th scope="col">Version</th>
<th scope="col" onclick="sortTable('searchTable', 0)">Module</th>
<th scope="col" onclick="sortTable('searchTable', 1)">Author</th>
<th scope="col" onclick="sortTable('searchTable', 2)">Version</th>
</tr>
</thead>
<tbody id="search-results">
Expand All @@ -40,6 +40,7 @@ templ SearchView(query string, modules []*gen.Module) {
}
</tbody>
</table>
<script src="/assets/js/table-sort.js"></script>
</div>
}

Expand Down
10 changes: 5 additions & 5 deletions internal/v3/ui/components/search_templ.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 21 additions & 10 deletions internal/v3/ui/components/statistics.templ
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ package components

import (
customMiddleware "github.com/dadav/gorge/internal/middleware"
"sort"
"strconv"
"time"
)

func sortedKeys(stats *customMiddleware.Statistics) []string {
keys := []string{}
for k := range stats.ConnectionsPerEndpoint {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}

templ StatisticsView(stats *customMiddleware.Statistics) {
<div>
<h3>Statistics</h3>
Expand All @@ -15,24 +25,24 @@ templ StatisticsView(stats *customMiddleware.Statistics) {
<p>TotalResponseTime: { stats.TotalResponseTime.String() }</p>
<p>TotalCacheHits: { strconv.Itoa(stats.TotalCacheHits) }</p>
<p>TotalCacheMisses: { strconv.Itoa(stats.TotalCacheMisses) }</p>
<table>
<table id="statsTable">
<thead>
<tr>
<th>Path</th>
<th>Connections</th>
<th>Proxied Connections</th>
<th>Average ResponseTime</th>
<th>Total ResponseTime</th>
<th>Cache (Hits/Misses)</th>
<th onclick="sortTable('statsTable', 0)" style="cursor: pointer;">Path</th>
<th onclick="sortTable('statsTable', 1)" style="cursor: pointer;">Connections</th>
<th onclick="sortTable('statsTable', 2)" style="cursor: pointer;">Proxied Connections</th>
<th onclick="sortTable('statsTable', 3)" style="cursor: pointer;">Average ResponseTime</th>
<th onclick="sortTable('statsTable', 4)" style="cursor: pointer;">Total ResponseTime</th>
<th onclick="sortTable('statsTable', 5)" style="cursor: pointer;">Cache (Hits/Misses)</th>
</tr>
</thead>
<tbody>
for path, connections := range stats.ConnectionsPerEndpoint {
for _, path := range sortedKeys(stats) {
<tr>
<td>{ path }</td>
<td>{ strconv.Itoa(connections) }</td>
<td>{ strconv.Itoa(stats.ConnectionsPerEndpoint[path]) }</td>
<td>{ strconv.Itoa(stats.ProxiedConnectionsPerEndpoint[path]) }</td>
<td>{ (stats.ResponseTimePerEndpoint[path] / time.Duration(connections)).String() }</td>
<td>{ (stats.ResponseTimePerEndpoint[path] / time.Duration(stats.ConnectionsPerEndpoint[path])).String() }</td>
<td>{ stats.ResponseTimePerEndpoint[path].String() }</td>
if stats.CacheHitsPerEndpoint[path] > 0 || stats.CacheMissesPerEndpoint[path] > 0 {
<td>{ strconv.Itoa(stats.CacheHitsPerEndpoint[path]) }/{ strconv.Itoa(stats.CacheMissesPerEndpoint[path]) }</td>
Expand All @@ -43,5 +53,6 @@ templ StatisticsView(stats *customMiddleware.Statistics) {
}
</tbody>
</table>
<script src="/assets/js/table-sort.js"></script>
</div>
}
Loading

0 comments on commit dc8113c

Please sign in to comment.