Skip to content

Commit 2ffd6dd

Browse files
Enzo DJABALIcloudlena
authored andcommitted
Implementing file search, pagination, bulk ops & URL S3 instance routing
1 parent 354a506 commit 2ffd6dd

File tree

12 files changed

+1098
-185
lines changed

12 files changed

+1098
-185
lines changed

internal/app/s3manager/bucket_view.go

Lines changed: 169 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,85 @@ import (
77
"net/http"
88
"path"
99
"regexp"
10+
"sort"
11+
"strconv"
1012
"strings"
1113
"time"
1214

1315
"github.com/minio/minio-go/v7"
1416
)
1517

18+
// objectWithIcon represents an S3 object with additional display properties
19+
type objectWithIcon struct {
20+
Key string
21+
Size int64
22+
LastModified time.Time
23+
Owner string
24+
Icon string
25+
IsFolder bool
26+
DisplayName string
27+
}
28+
1629
// HandleBucketView shows the details page of a bucket.
1730
func HandleBucketView(s3 S3, templates fs.FS, allowDelete bool, listRecursive bool, rootURL string) http.HandlerFunc {
18-
type objectWithIcon struct {
19-
Key string
20-
Size int64
21-
LastModified time.Time
22-
Owner string
23-
Icon string
24-
IsFolder bool
25-
DisplayName string
26-
}
27-
2831
type pageData struct {
29-
RootURL string
30-
BucketName string
31-
Objects []objectWithIcon
32-
AllowDelete bool
33-
Paths []string
34-
CurrentPath string
32+
RootURL string
33+
BucketName string
34+
Objects []objectWithIcon
35+
AllowDelete bool
36+
Paths []string
37+
CurrentPath string
38+
CurrentS3 *S3Instance
39+
S3Instances []*S3Instance
40+
HasError bool
41+
ErrorMessage string
42+
SortBy string
43+
SortOrder string
44+
Page int
45+
PerPage int
46+
TotalItems int
47+
TotalPages int
48+
HasPrevPage bool
49+
HasNextPage bool
50+
Search string
3551
}
3652

3753
return func(w http.ResponseWriter, r *http.Request) {
3854
regex := regexp.MustCompile(`\/buckets\/([^\/]*)\/?(.*)`)
39-
matches := regex.FindStringSubmatch(r.RequestURI)
55+
matches := regex.FindStringSubmatch(r.URL.Path)
4056
bucketName := matches[1]
4157
path := matches[2]
4258

59+
// Get sorting parameters from query string
60+
sortBy := r.URL.Query().Get("sortBy")
61+
sortOrder := r.URL.Query().Get("sortOrder")
62+
63+
// Default sorting
64+
if sortBy == "" {
65+
sortBy = "key"
66+
}
67+
if sortOrder == "" {
68+
sortOrder = "asc"
69+
}
70+
71+
// Get pagination parameters
72+
page := 1
73+
if pageStr := r.URL.Query().Get("page"); pageStr != "" {
74+
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
75+
page = p
76+
}
77+
}
78+
79+
perPage := 25
80+
if perPageStr := r.URL.Query().Get("perPage"); perPageStr != "" {
81+
if pp, err := strconv.Atoi(perPageStr); err == nil && pp > 0 {
82+
perPage = pp
83+
}
84+
}
85+
86+
// Get search parameter
87+
search := strings.TrimSpace(r.URL.Query().Get("search"))
88+
4389
var objs []objectWithIcon
4490
opts := minio.ListObjectsOptions{
4591
Recursive: listRecursive,
@@ -63,16 +109,91 @@ func HandleBucketView(s3 S3, templates fs.FS, allowDelete bool, listRecursive bo
63109
}
64110
objs = append(objs, obj)
65111
}
112+
113+
// Filter objects based on search query
114+
if search != "" {
115+
searchLower := strings.ToLower(search)
116+
filteredObjs := make([]objectWithIcon, 0)
117+
for _, obj := range objs {
118+
// Search in DisplayName and Key (case-insensitive)
119+
if strings.Contains(strings.ToLower(obj.DisplayName), searchLower) ||
120+
strings.Contains(strings.ToLower(obj.Key), searchLower) {
121+
filteredObjs = append(filteredObjs, obj)
122+
}
123+
}
124+
objs = filteredObjs
125+
}
126+
127+
// Sort objects based on sortBy and sortOrder
128+
sortObjects(objs, sortBy, sortOrder)
129+
130+
// Calculate pagination
131+
totalItems := len(objs)
132+
totalPages := (totalItems + perPage - 1) / perPage
133+
if totalPages == 0 {
134+
totalPages = 1
135+
}
136+
if page > totalPages {
137+
page = totalPages
138+
}
139+
140+
// Paginate objects
141+
start := (page - 1) * perPage
142+
end := start + perPage
143+
if start < 0 {
144+
start = 0
145+
}
146+
if end > totalItems {
147+
end = totalItems
148+
}
149+
if start < totalItems {
150+
objs = objs[start:end]
151+
} else {
152+
objs = []objectWithIcon{}
153+
}
154+
66155
data := pageData{
67-
RootURL: rootURL,
68-
BucketName: bucketName,
69-
Objects: objs,
70-
AllowDelete: allowDelete,
71-
Paths: removeEmptyStrings(strings.Split(path, "/")),
72-
CurrentPath: path,
156+
RootURL: rootURL,
157+
BucketName: bucketName,
158+
Objects: objs,
159+
AllowDelete: allowDelete,
160+
Paths: removeEmptyStrings(strings.Split(path, "/")),
161+
CurrentPath: path,
162+
CurrentS3: nil,
163+
S3Instances: nil,
164+
HasError: false,
165+
ErrorMessage: "",
166+
SortBy: sortBy,
167+
SortOrder: sortOrder,
168+
Page: page,
169+
PerPage: perPage,
170+
TotalItems: totalItems,
171+
TotalPages: totalPages,
172+
HasPrevPage: page > 1,
173+
HasNextPage: page < totalPages,
174+
Search: search,
175+
}
176+
177+
funcMap := template.FuncMap{
178+
"add": func(a, b int) int { return a + b },
179+
"sub": func(a, b int) int { return a - b },
180+
"mul": func(a, b int) int { return a * b },
181+
"min": func(a, b int) int {
182+
if a < b {
183+
return a
184+
}
185+
return b
186+
},
187+
"iterate": func(start, end int) []int {
188+
result := make([]int, 0, end-start)
189+
for i := start; i < end; i++ {
190+
result = append(result, i)
191+
}
192+
return result
193+
},
73194
}
74195

75-
t, err := template.ParseFS(templates, "layout.html.tmpl", "bucket.html.tmpl")
196+
t, err := template.New("").Funcs(funcMap).ParseFS(templates, "layout.html.tmpl", "bucket.html.tmpl")
76197
if err != nil {
77198
handleHTTPError(w, fmt.Errorf("error parsing template files: %w", err))
78199
return
@@ -114,3 +235,27 @@ func removeEmptyStrings(input []string) []string {
114235
}
115236
return result
116237
}
238+
239+
// sortObjects sorts the objects based on the specified field and order
240+
func sortObjects(objs []objectWithIcon, sortBy, sortOrder string) {
241+
sort.Slice(objs, func(i, j int) bool {
242+
var less bool
243+
switch sortBy {
244+
case "size":
245+
less = objs[i].Size < objs[j].Size
246+
case "owner":
247+
less = strings.ToLower(objs[i].Owner) < strings.ToLower(objs[j].Owner)
248+
case "lastModified":
249+
less = objs[i].LastModified.Before(objs[j].LastModified)
250+
case "key":
251+
fallthrough
252+
default:
253+
less = strings.ToLower(objs[i].DisplayName) < strings.ToLower(objs[j].DisplayName)
254+
}
255+
256+
if sortOrder == "desc" {
257+
return !less
258+
}
259+
return less
260+
})
261+
}

internal/app/s3manager/buckets_view.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,37 @@ import (
55
"html/template"
66
"io/fs"
77
"net/http"
8-
9-
"github.com/minio/minio-go/v7"
108
)
119

1210
// HandleBucketsView renders all buckets on an HTML page.
1311
func HandleBucketsView(s3 S3, templates fs.FS, allowDelete bool, rootURL string) http.HandlerFunc {
1412
type pageData struct {
15-
RootURL string
16-
Buckets []minio.BucketInfo
17-
AllowDelete bool
13+
RootURL string
14+
Buckets []interface{}
15+
AllowDelete bool
16+
CurrentS3 *S3Instance
17+
S3Instances []*S3Instance
18+
HasError bool
19+
ErrorMessage string
1820
}
1921

2022
return func(w http.ResponseWriter, r *http.Request) {
2123
buckets, err := s3.ListBuckets(r.Context())
2224

25+
data := pageData{
26+
RootURL: rootURL,
27+
AllowDelete: allowDelete,
28+
HasError: false,
29+
}
30+
2331
if err != nil {
2432
handleHTTPError(w, fmt.Errorf("error listing buckets: %w", err))
2533
return
2634
}
2735

28-
data := pageData{
29-
RootURL: rootURL,
30-
Buckets: buckets,
31-
AllowDelete: allowDelete,
36+
data.Buckets = make([]interface{}, len(buckets))
37+
for i, bucket := range buckets {
38+
data.Buckets[i] = bucket
3239
}
3340

3441
t, err := template.ParseFS(templates, "layout.html.tmpl", "buckets.html.tmpl")

0 commit comments

Comments
 (0)