feat(admin): add dashboard overview charts and log pagination
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m40s
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m40s
Enhance the admin panel by introducing visual overview charts for upload and storage trends, along with status bars for system metrics. Additionally, implement pagination for the admin logs view, allowing users to navigate through log entries with configurable page sizes. Corresponding CSS styles have been added for the new charts, metrics grid, and pagination controls.
This commit is contained in:
@@ -14,27 +14,40 @@ import (
|
||||
"warpbox.dev/backend/libs/web"
|
||||
)
|
||||
|
||||
const adminFilesPageSize = 25
|
||||
const adminFilesDefaultPageSize = 50
|
||||
|
||||
var adminFilesPageSizes = []int{25, 50, 100, 200}
|
||||
|
||||
type adminFilesData struct {
|
||||
Stats services.AdminStats
|
||||
Section string
|
||||
PageTitle string
|
||||
Boxes []adminBoxView
|
||||
Query string
|
||||
Sort string
|
||||
Dir string
|
||||
Page int
|
||||
TotalPages int
|
||||
Total int
|
||||
RangeFrom int
|
||||
RangeTo int
|
||||
Columns []adminFilesColumn
|
||||
PageLinks []adminFilesPageLink
|
||||
HasPrev bool
|
||||
HasNext bool
|
||||
PrevHref string
|
||||
NextHref string
|
||||
Stats services.AdminStats
|
||||
Section string
|
||||
PageTitle string
|
||||
Boxes []adminBoxView
|
||||
Query string
|
||||
Sort string
|
||||
Dir string
|
||||
Page int
|
||||
PerPage int
|
||||
PerPageOptions []int
|
||||
TotalPages int
|
||||
Total int
|
||||
RangeFrom int
|
||||
RangeTo int
|
||||
Columns []adminFilesColumn
|
||||
PageLinks []adminFilesPageLink
|
||||
HasPrev bool
|
||||
HasNext bool
|
||||
PrevHref string
|
||||
NextHref string
|
||||
}
|
||||
|
||||
// adminFilesQuery captures the listing state that every paginated link must
|
||||
// preserve.
|
||||
type adminFilesQuery struct {
|
||||
Query string
|
||||
Sort string
|
||||
Dir string
|
||||
Per int
|
||||
}
|
||||
|
||||
type adminFilesColumn struct {
|
||||
@@ -153,8 +166,11 @@ func (a *App) AdminFiles(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
sortAdminFileRows(rows, sortKey, dir)
|
||||
|
||||
perPage := normalizePageSize(r.URL.Query().Get("per"), adminFilesDefaultPageSize, adminFilesPageSizes)
|
||||
state := adminFilesQuery{Query: query, Sort: sortKey, Dir: dir, Per: perPage}
|
||||
|
||||
total := len(rows)
|
||||
totalPages := (total + adminFilesPageSize - 1) / adminFilesPageSize
|
||||
totalPages := (total + perPage - 1) / perPage
|
||||
if totalPages < 1 {
|
||||
totalPages = 1
|
||||
}
|
||||
@@ -165,11 +181,11 @@ func (a *App) AdminFiles(w http.ResponseWriter, r *http.Request) {
|
||||
if page > totalPages {
|
||||
page = totalPages
|
||||
}
|
||||
start := (page - 1) * adminFilesPageSize
|
||||
start := (page - 1) * perPage
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
end := start + adminFilesPageSize
|
||||
end := start + perPage
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
@@ -200,24 +216,26 @@ func (a *App) AdminFiles(w http.ResponseWriter, r *http.Request) {
|
||||
Description: "Manage Warpbox uploads.",
|
||||
CurrentUser: a.currentPublicUser(r),
|
||||
Data: adminFilesData{
|
||||
Stats: stats,
|
||||
Section: "files",
|
||||
PageTitle: "Files",
|
||||
Boxes: views,
|
||||
Query: query,
|
||||
Sort: sortKey,
|
||||
Dir: dir,
|
||||
Page: page,
|
||||
TotalPages: totalPages,
|
||||
Total: total,
|
||||
RangeFrom: rangeFrom,
|
||||
RangeTo: end,
|
||||
Columns: adminFilesColumns(query, sortKey, dir),
|
||||
PageLinks: adminFilesPageLinks(query, sortKey, dir, page, totalPages),
|
||||
HasPrev: page > 1,
|
||||
HasNext: page < totalPages,
|
||||
PrevHref: adminFilesHref(query, sortKey, dir, page-1),
|
||||
NextHref: adminFilesHref(query, sortKey, dir, page+1),
|
||||
Stats: stats,
|
||||
Section: "files",
|
||||
PageTitle: "Files",
|
||||
Boxes: views,
|
||||
Query: query,
|
||||
Sort: sortKey,
|
||||
Dir: dir,
|
||||
Page: page,
|
||||
PerPage: perPage,
|
||||
PerPageOptions: adminFilesPageSizes,
|
||||
TotalPages: totalPages,
|
||||
Total: total,
|
||||
RangeFrom: rangeFrom,
|
||||
RangeTo: end,
|
||||
Columns: adminFilesColumns(state, sortKey, dir),
|
||||
PageLinks: adminFilesPageLinks(state, page, totalPages),
|
||||
HasPrev: page > 1,
|
||||
HasNext: page < totalPages,
|
||||
PrevHref: adminFilesHref(state, page-1),
|
||||
NextHref: adminFilesHref(state, page+1),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -274,7 +292,7 @@ func sortAdminFileRows(rows []adminFileRow, sortKey, dir string) {
|
||||
})
|
||||
}
|
||||
|
||||
func adminFilesColumns(query, sortKey, dir string) []adminFilesColumn {
|
||||
func adminFilesColumns(state adminFilesQuery, sortKey, dir string) []adminFilesColumn {
|
||||
defs := []struct{ Key, Label string }{
|
||||
{"id", "Box"},
|
||||
{"owner", "Owner"},
|
||||
@@ -291,9 +309,12 @@ func adminFilesColumns(query, sortKey, dir string) []adminFilesColumn {
|
||||
if sorted && dir == "asc" {
|
||||
nextDir = "desc"
|
||||
}
|
||||
colState := state
|
||||
colState.Sort = def.Key
|
||||
colState.Dir = nextDir
|
||||
columns = append(columns, adminFilesColumn{
|
||||
Label: def.Label,
|
||||
Href: adminFilesHref(query, def.Key, nextDir, 1),
|
||||
Href: adminFilesHref(colState, 1),
|
||||
Sorted: sorted,
|
||||
Ascending: dir == "asc",
|
||||
})
|
||||
@@ -301,7 +322,7 @@ func adminFilesColumns(query, sortKey, dir string) []adminFilesColumn {
|
||||
return columns
|
||||
}
|
||||
|
||||
func adminFilesPageLinks(query, sortKey, dir string, page, totalPages int) []adminFilesPageLink {
|
||||
func adminFilesPageLinks(state adminFilesQuery, page, totalPages int) []adminFilesPageLink {
|
||||
links := make([]adminFilesPageLink, 0, 5)
|
||||
const window = 2
|
||||
for p := page - window; p <= page+window; p++ {
|
||||
@@ -310,23 +331,26 @@ func adminFilesPageLinks(query, sortKey, dir string, page, totalPages int) []adm
|
||||
}
|
||||
links = append(links, adminFilesPageLink{
|
||||
Page: p,
|
||||
Href: adminFilesHref(query, sortKey, dir, p),
|
||||
Href: adminFilesHref(state, p),
|
||||
Active: p == page,
|
||||
})
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
func adminFilesHref(query, sortKey, dir string, page int) string {
|
||||
func adminFilesHref(state adminFilesQuery, page int) string {
|
||||
values := url.Values{}
|
||||
if query != "" {
|
||||
values.Set("q", query)
|
||||
if state.Query != "" {
|
||||
values.Set("q", state.Query)
|
||||
}
|
||||
if sortKey != "" && sortKey != "created" {
|
||||
values.Set("sort", sortKey)
|
||||
if state.Sort != "" && state.Sort != "created" {
|
||||
values.Set("sort", state.Sort)
|
||||
}
|
||||
if dir != "" && dir != "desc" {
|
||||
values.Set("dir", dir)
|
||||
if state.Dir != "" && state.Dir != "desc" {
|
||||
values.Set("dir", state.Dir)
|
||||
}
|
||||
if state.Per > 0 && state.Per != adminFilesDefaultPageSize {
|
||||
values.Set("per", strconv.Itoa(state.Per))
|
||||
}
|
||||
if page > 1 {
|
||||
values.Set("page", strconv.Itoa(page))
|
||||
@@ -337,6 +361,21 @@ func adminFilesHref(query, sortKey, dir string, page int) string {
|
||||
return "/admin/files?" + values.Encode()
|
||||
}
|
||||
|
||||
// normalizePageSize parses a requested page size, falling back to def when the
|
||||
// value is missing or not one of the allowed sizes.
|
||||
func normalizePageSize(raw string, def int, allowed []int) int {
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
for _, size := range allowed {
|
||||
if size == parsed {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (a *App) AdminEditBox(w http.ResponseWriter, r *http.Request) {
|
||||
if !a.requireAdmin(w, r) {
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user