devices: added page
This commit is contained in:
parent
81031dc2aa
commit
b33b672cb6
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/stephenafamo/bob"
|
"github.com/stephenafamo/bob"
|
||||||
"github.com/stephenafamo/bob/dialect/sqlite"
|
"github.com/stephenafamo/bob/dialect/sqlite"
|
||||||
|
@ -12,24 +13,37 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DevicesListParams struct {
|
type DevicesListParams struct {
|
||||||
All bool
|
|
||||||
Q string
|
Q string
|
||||||
|
Status int
|
||||||
|
|
||||||
Limit int64
|
Limit int64
|
||||||
Offset int64
|
Offset int64
|
||||||
OrderBy string
|
OrderBy string
|
||||||
Sort string
|
Sort string
|
||||||
Active bool
|
}
|
||||||
|
|
||||||
|
func (dlp *DevicesListParams) FillFromQuery(query Queryable) {
|
||||||
|
dlp.Status, _ = strconv.Atoi(query.Get("status"))
|
||||||
|
if dlp.Status > 2 {
|
||||||
|
dlp.Status = 2
|
||||||
|
}
|
||||||
|
dlp.Q = query.Get("q")
|
||||||
|
|
||||||
|
dlp.Limit, _ = strconv.ParseInt(query.Get("limit"), 10, 64)
|
||||||
|
if dlp.Limit < 1 {
|
||||||
|
dlp.Limit = 20
|
||||||
|
}
|
||||||
|
dlp.Offset, _ = strconv.ParseInt(query.Get("offset"), 10, 64)
|
||||||
|
if dlp.Offset < 0 {
|
||||||
|
dlp.Offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
dlp.OrderBy = query.Get("order_by")
|
||||||
|
dlp.Sort = query.Get("sort")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dlp DevicesListParams) Query() (expr []bob.Mod[*dialect.SelectQuery]) {
|
func (dlp DevicesListParams) Query() (expr []bob.Mod[*dialect.SelectQuery]) {
|
||||||
expr = append(expr, dlp.CountQuery()...)
|
expr = append(expr, dlp.CountQuery()...)
|
||||||
if dlp.Active {
|
|
||||||
expr = append(expr, models.SelectWhere.Devices.Enable.EQ(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
if dlp.All {
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
if dlp.Limit > 0 {
|
if dlp.Limit > 0 {
|
||||||
expr = append(expr, sm.Limit(dlp.Limit))
|
expr = append(expr, sm.Limit(dlp.Limit))
|
||||||
|
@ -54,8 +68,8 @@ func (dlp DevicesListParams) Query() (expr []bob.Mod[*dialect.SelectQuery]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dlp DevicesListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
|
func (dlp DevicesListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
|
||||||
if dlp.Active {
|
if dlp.Status > 0 {
|
||||||
expr = append(expr, models.SelectWhere.Devices.Enable.EQ(1))
|
expr = append(expr, models.SelectWhere.Devices.Enable.EQ(int32(dlp.Status-1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if dlp.Q != "" {
|
if dlp.Q != "" {
|
||||||
|
|
|
@ -3,8 +3,6 @@ package routes
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tigorlazuardi/redmage/api"
|
"github.com/tigorlazuardi/redmage/api"
|
||||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||||
|
@ -15,9 +13,10 @@ func (routes *Routes) APIDeviceList(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx, span := tracer.Start(r.Context(), "*Routes.APIDeviceList")
|
ctx, span := tracer.Start(r.Context(), "*Routes.APIDeviceList")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
query := parseApiDeviceListQueries(r)
|
var params api.DevicesListParams
|
||||||
|
params.FillFromQuery(r.URL.Query())
|
||||||
|
|
||||||
result, err := routes.API.DevicesList(ctx, query)
|
result, err := routes.API.DevicesList(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
code, message := errs.HTTPMessage(err)
|
code, message := errs.HTTPMessage(err)
|
||||||
rw.WriteHeader(code)
|
rw.WriteHeader(code)
|
||||||
|
@ -29,22 +28,3 @@ func (routes *Routes) APIDeviceList(rw http.ResponseWriter, r *http.Request) {
|
||||||
log.New(ctx).Err(err).Error("failed to marshal json api devices")
|
log.New(ctx).Err(err).Error("failed to marshal json api devices")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseApiDeviceListQueries(req *http.Request) (params api.DevicesListParams) {
|
|
||||||
params.All, _ = strconv.ParseBool(req.FormValue("all"))
|
|
||||||
params.Offset, _ = strconv.ParseInt(req.FormValue("offset"), 10, 64)
|
|
||||||
params.Limit, _ = strconv.ParseInt(req.FormValue("limit"), 10, 64)
|
|
||||||
params.Q = req.FormValue("q")
|
|
||||||
params.OrderBy = req.FormValue("order")
|
|
||||||
params.Sort = strings.ToLower(req.FormValue("sort"))
|
|
||||||
|
|
||||||
if params.Limit < 1 {
|
|
||||||
params.Limit = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.OrderBy == "" {
|
|
||||||
params.OrderBy = "name"
|
|
||||||
}
|
|
||||||
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
36
server/routes/page_devices.go
Normal file
36
server/routes/page_devices.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||||
|
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||||
|
"github.com/tigorlazuardi/redmage/views"
|
||||||
|
"github.com/tigorlazuardi/redmage/views/devicesview"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (routes *Routes) PageDevices(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx, start := tracer.Start(req.Context(), "*Routes.PageDevices")
|
||||||
|
defer start.End()
|
||||||
|
|
||||||
|
vc := views.NewContext(routes.Config, req)
|
||||||
|
var data devicesview.Data
|
||||||
|
data.Params.FillFromQuery(req.URL.Query())
|
||||||
|
|
||||||
|
result, err := routes.API.DevicesList(ctx, data.Params)
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to query devices")
|
||||||
|
code, message := errs.HTTPMessage(err)
|
||||||
|
rw.WriteHeader(code)
|
||||||
|
data.Error = message
|
||||||
|
if err := devicesview.Devices(vc, data).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render devices error view")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.Devices = result.Devices
|
||||||
|
data.Total = result.Total
|
||||||
|
|
||||||
|
if err := devicesview.Devices(vc, data).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render devices view")
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,6 +81,7 @@ func (routes *Routes) registerWWWRoutes(router chi.Router) {
|
||||||
r.Get("/subreddits/details/{name}", routes.PageSubredditsDetails)
|
r.Get("/subreddits/details/{name}", routes.PageSubredditsDetails)
|
||||||
r.Get("/subreddits/add", routes.PageSubredditsAdd)
|
r.Get("/subreddits/add", routes.PageSubredditsAdd)
|
||||||
r.Get("/config", routes.PageConfig)
|
r.Get("/config", routes.PageConfig)
|
||||||
|
r.Get("/devices", routes.PageDevices)
|
||||||
r.Get("/schedules", routes.PageScheduleHistory)
|
r.Get("/schedules", routes.PageScheduleHistory)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
80
views/devicesview/devicesview.templ
Normal file
80
views/devicesview/devicesview.templ
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package devicesview
|
||||||
|
|
||||||
|
import "github.com/tigorlazuardi/redmage/views"
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/components"
|
||||||
|
import "github.com/tigorlazuardi/redmage/models"
|
||||||
|
import "github.com/tigorlazuardi/redmage/api"
|
||||||
|
import "strconv"
|
||||||
|
import "fmt"
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Error string
|
||||||
|
Devices models.DeviceSlice
|
||||||
|
Total int64
|
||||||
|
Params api.DevicesListParams
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Devices(c *views.Context, data Data) {
|
||||||
|
@components.Doctype() {
|
||||||
|
@components.Head(c, components.HeadTitle("Redmage - Devices"))
|
||||||
|
@components.Body(c) {
|
||||||
|
@DevicesContent(c, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ DevicesContent(c *views.Context, data Data) {
|
||||||
|
<main class="prose min-w-full">
|
||||||
|
@components.Container() {
|
||||||
|
if data.Error != "" {
|
||||||
|
@components.ErrorToast(data.Error)
|
||||||
|
} else {
|
||||||
|
<h1>Devices</h1>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<h2>{ strconv.FormatInt(data.Total, 10) } Devices</h2>
|
||||||
|
@devicesList(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ devicesList(data Data) {
|
||||||
|
<div class="grid md:grid-cols-2 gap-4">
|
||||||
|
for _, device := range data.Devices {
|
||||||
|
<a
|
||||||
|
href={ templ.URL(fmt.Sprintf("/devices/details/%s", device.Slug)) }
|
||||||
|
class={ utils.CXX(
|
||||||
|
"card bg-base-100 no-underline text-primary hover:bg-base-200 shadow-xl rounded-xl top-0 hover:-top-1 transition-all", true,
|
||||||
|
"bg-base-300 hover:bg-base-300", device.Enable == 0,
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2 class="my-auto">{ device.Name }</h2>
|
||||||
|
<span class="text-sm self-end italic font-normal">{ device.Slug }</span>
|
||||||
|
<p class="text-xs my-auto text-end">{ fmt.Sprintf("%.0f \u00d7 %.0f", device.ResolutionX, device.ResolutionY) } px</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-4">
|
||||||
|
if device.NSFW == 1 {
|
||||||
|
<div class="badge badge-accent">NSFW</div>
|
||||||
|
}
|
||||||
|
<div class="badge badge-secondary">Tolerance: { fmt.Sprintf("%.2f", device.AspectRatioTolerance) }</div>
|
||||||
|
if device.MaxX > 0 {
|
||||||
|
<div class="badge badge-primary">Max Width: { strconv.Itoa(int(device.MaxX)) }px</div>
|
||||||
|
}
|
||||||
|
if device.MaxY > 0 {
|
||||||
|
<div class="badge badge-primary">Max Height: { strconv.Itoa(int(device.MaxY)) }px</div>
|
||||||
|
}
|
||||||
|
if device.MinX > 0 {
|
||||||
|
<div class="badge badge-primary">Min Width: { strconv.Itoa(int(device.MinX)) }px</div>
|
||||||
|
}
|
||||||
|
if device.MinY > 0 {
|
||||||
|
<div class="badge badge-primary">Min Height: { strconv.Itoa(int(device.MinY)) }px</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import "strings"
|
||||||
|
|
||||||
// CX is a helper function to generate a string of class names based
|
// CX is a helper function to generate a string of class names based
|
||||||
// on a map of class names and their conditions.
|
// on a map of class names and their conditions.
|
||||||
|
//
|
||||||
|
// CX is not guaranteed to be ordered, use CXX for that.
|
||||||
func CX(classes map[string]bool) string {
|
func CX(classes map[string]bool) string {
|
||||||
b := strings.Builder{}
|
b := strings.Builder{}
|
||||||
for class, condition := range classes {
|
for class, condition := range classes {
|
||||||
|
@ -15,3 +17,24 @@ func CX(classes map[string]bool) string {
|
||||||
|
|
||||||
return strings.TrimSpace(b.String())
|
return strings.TrimSpace(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CXX takes an alternating string and boolean arguments.
|
||||||
|
// Odd values must be string and even values must be boolean.
|
||||||
|
//
|
||||||
|
// Function panics if above conditions are not fulfilled.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// utils.CXX("my-0", true, "my-1", cond)
|
||||||
|
func CXX(classAndConds ...any) string {
|
||||||
|
s := strings.Builder{}
|
||||||
|
for i, j := 0, 1; j < len(classAndConds); i, j = i+2, j+2 {
|
||||||
|
class := classAndConds[i].(string)
|
||||||
|
b := classAndConds[j].(bool)
|
||||||
|
if b {
|
||||||
|
s.WriteString(class)
|
||||||
|
s.WriteByte(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue