Add Task Sorting and Pagination
This commit is contained in:
parent
1c48a43738
commit
1a46eaf51b
4 changed files with 119 additions and 9 deletions
|
@ -20,6 +20,11 @@ const FFMPEG_COMMANDS_TEMPLATE_NAME = "ffmpeg_commands.tmpl"
|
||||||
|
|
||||||
const FORM_FILE_KEY = "file"
|
const FORM_FILE_KEY = "file"
|
||||||
|
|
||||||
|
const SORT_ORDER_BY_PARAM = "order_by"
|
||||||
|
const SORT_ORDER_ASC_PARAM = "order_asc"
|
||||||
|
const QUERY_LIMIT_PARAM = "limit"
|
||||||
|
const QUERY_PAGE_PARAM = "page"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TASK_TYPE_HEALTHCHECK = iota
|
TASK_TYPE_HEALTHCHECK = iota
|
||||||
TASK_TYPE_TRANSCODE
|
TASK_TYPE_TRANSCODE
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -20,6 +21,11 @@ type TasksData struct {
|
||||||
FfmpegCommands []FfmpegCommand
|
FfmpegCommands []FfmpegCommand
|
||||||
Tasks []TaskDisplay
|
Tasks []TaskDisplay
|
||||||
Stats TaskStats
|
Stats TaskStats
|
||||||
|
OrderBy string
|
||||||
|
OrderAsc bool
|
||||||
|
Limit uint
|
||||||
|
Page uint
|
||||||
|
Count uint
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskStats struct {
|
type TaskStats struct {
|
||||||
|
@ -56,6 +62,8 @@ type TaskDB struct {
|
||||||
UpdatedAt time.Time `db:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var taskColumns = []string{"id", "library", "worker", "type", "ffmpeg_command", "status", "file", "updated_at"}
|
||||||
|
|
||||||
func handleTasks(w http.ResponseWriter, r *http.Request) {
|
func handleTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
err := createTask(r.Context(), r)
|
err := createTask(r.Context(), r)
|
||||||
|
@ -114,7 +122,52 @@ func handleTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
data.FfmpegCommands = ffmpegCommands
|
data.FfmpegCommands = ffmpegCommands
|
||||||
|
|
||||||
rows, err = db.Query(r.Context(), "SELECT t.id AS id, l.id AS library, t.worker_id AS worker, t.type AS type, fc.name AS ffmpeg_command, t.status AS status, f.path AS file, t.updated_at AS updated_at FROM tasks t INNER JOIN files f ON f.id = t.file_id INNER JOIN libraries l ON l.id = f.library_id INNER JOIN ffmpeg_commands fc ON fc.id = t.ffmpeg_command_id ORDER BY CASE t.type WHEN 3 THEN -1 ELSE t.type END, t.id")
|
query := r.URL.Query()
|
||||||
|
data.OrderBy = "updated_at"
|
||||||
|
if query.Has(constants.SORT_ORDER_BY_PARAM) {
|
||||||
|
if slices.Contains(taskColumns, query.Get(constants.SORT_ORDER_BY_PARAM)) {
|
||||||
|
data.OrderBy = query.Get(constants.SORT_ORDER_BY_PARAM)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Error Unknown Column in order_by: "+query.Get(constants.SORT_ORDER_BY_PARAM), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
orderDir := "DESC"
|
||||||
|
if query.Has(constants.SORT_ORDER_ASC_PARAM) {
|
||||||
|
orderDir = "ASC"
|
||||||
|
data.OrderAsc = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Limit = 100
|
||||||
|
if query.Has(constants.QUERY_LIMIT_PARAM) {
|
||||||
|
limit, err := strconv.Atoi(query.Get(constants.QUERY_LIMIT_PARAM))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error Parsing query limit: "+query.Get(constants.QUERY_LIMIT_PARAM), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if limit < 0 {
|
||||||
|
http.Error(w, "Error query limit smaller than 0: "+query.Get(constants.QUERY_LIMIT_PARAM), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.Limit = uint(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Page = 0
|
||||||
|
if query.Has(constants.QUERY_PAGE_PARAM) {
|
||||||
|
page, err := strconv.Atoi(query.Get(constants.QUERY_PAGE_PARAM))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error Parsing query page: "+query.Get(constants.QUERY_PAGE_PARAM), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if page < 0 {
|
||||||
|
http.Error(w, "Error query limit smaller than 0: "+query.Get(constants.QUERY_PAGE_PARAM), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.Page = uint(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err = db.Query(r.Context(), "SELECT t.id AS id, l.id AS library, t.worker_id AS worker, t.type AS type, fc.name AS ffmpeg_command, t.status AS status, f.path AS file, t.updated_at AS updated_at FROM tasks t INNER JOIN files f ON f.id = t.file_id INNER JOIN libraries l ON l.id = f.library_id INNER JOIN ffmpeg_commands fc ON fc.id = t.ffmpeg_command_id "+fmt.Sprintf("ORDER BY %v %v LIMIT %d OFFSET %d", data.OrderBy, orderDir, data.Limit, data.Page*data.Limit))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.ErrorContext(r.Context(), "Query Tasks", "err", err)
|
slog.ErrorContext(r.Context(), "Query Tasks", "err", err)
|
||||||
http.Error(w, "Error Query Tasks: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Error Query Tasks: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
@ -138,6 +191,7 @@ func handleTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
UpdatedAt: tasks[i].UpdatedAt.Format(time.DateTime),
|
UpdatedAt: tasks[i].UpdatedAt.Format(time.DateTime),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
data.Count = uint(len(tasks))
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
err = templates.ExecuteTemplate(&buf, constants.TASKS_TEMPLATE_NAME, data)
|
err = templates.ExecuteTemplate(&buf, constants.TASKS_TEMPLATE_NAME, data)
|
||||||
|
|
37
static/js/table_sort.js
Normal file
37
static/js/table_sort.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
const SORT_ORDER_BY_PARAM = "order_by"
|
||||||
|
const SORT_ORDER_ASC_PARAM = "order_asc"
|
||||||
|
const SORT_PAGE_PARAM = "page"
|
||||||
|
const SORT_LIMIT_PARAM = "limit"
|
||||||
|
|
||||||
|
function setTableSort(field) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
if (urlParams.get(SORT_ORDER_BY_PARAM) == field) {
|
||||||
|
if (urlParams.has(SORT_ORDER_ASC_PARAM)) {
|
||||||
|
urlParams.delete(SORT_ORDER_ASC_PARAM);
|
||||||
|
} else {
|
||||||
|
urlParams.set(SORT_ORDER_ASC_PARAM, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
urlParams.set(SORT_ORDER_BY_PARAM, field);
|
||||||
|
urlParams.delete(SORT_ORDER_ASC_PARAM);
|
||||||
|
}
|
||||||
|
//Reset Pagination
|
||||||
|
urlParams.delete(SORT_PAGE_PARAM);
|
||||||
|
window.location.search = urlParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLimit(number) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
urlParams.set(SORT_LIMIT_PARAM, number);
|
||||||
|
|
||||||
|
//Reset Pagination
|
||||||
|
urlParams.delete(SORT_PAGE_PARAM);
|
||||||
|
window.location.search = urlParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPage(number) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
urlParams.set(SORT_PAGE_PARAM, number);
|
||||||
|
window.location.search = urlParams;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
{{template "head"}}
|
{{template "head"}}
|
||||||
|
<script src="/static/js/table_sort.js"></script>
|
||||||
<h2>New Tasks</h2>
|
<h2>New Tasks</h2>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<label for="library">Library:</label>
|
<label for="library">Library:</label>
|
||||||
|
@ -42,16 +43,25 @@ Total: {{.Stats.TotalCount}}
|
||||||
|
|
||||||
|
|
||||||
<h2>Tasks</h2>
|
<h2>Tasks</h2>
|
||||||
|
<label for="limit">Limit:</label>
|
||||||
|
<select id="limit" name="limit" onchange="setLimit(this.value)">
|
||||||
|
<option selected value="{{.Limit}}">{{.Limit}}</option>
|
||||||
|
{{if ne .Limit 100 }}<option value="100">100</option>{{end}}
|
||||||
|
{{if ne .Limit 500 }}<option value="500">500</option>{{end}}
|
||||||
|
{{if ne .Limit 1000 }}<option value="1000">1000</option>{{end}}
|
||||||
|
{{if ne .Limit 10000 }}<option value="10000">10000</option>{{end}}
|
||||||
|
</select>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th onclick="setTableSort('id')">ID</th>
|
||||||
<th>Library</th>
|
<th onclick="setTableSort('library')">Library</th>
|
||||||
<th>Worker</th>
|
<th onclick="setTableSort('worker')">Worker</th>
|
||||||
<th>Type</th>
|
<th onclick="setTableSort('type')">Type</th>
|
||||||
<th>FFmpeg Command</th>
|
<th onclick="setTableSort('ffmpeg_command')">FFmpeg Command</th>
|
||||||
<th>Status</th>
|
<th onclick="setTableSort('status')">Status</th>
|
||||||
<th>File</th>
|
<th onclick="setTableSort('file')">File</th>
|
||||||
<th>Updated At</th>
|
<th onclick="setTableSort('updated_at')">Updated At</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{range $t := .Tasks}}
|
{{range $t := .Tasks}}
|
||||||
<tr onclick="window.location='/tasks/{{ $t.ID }}';">
|
<tr onclick="window.location='/tasks/{{ $t.ID }}';">
|
||||||
|
@ -82,4 +92,8 @@ Total: {{.Stats.TotalCount}}
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</table>
|
</table>
|
||||||
|
{{if ne .Page 0 }}<button type="button" onclick="setPage(0)">First Page</button>{{end}}
|
||||||
|
{{if ne .Page 0 }}<button type="button" onclick="setPage({{.Page}} -1)">Previous Page</button>{{end}}
|
||||||
|
Page {{.Page}}
|
||||||
|
{{if ne .Count 0}}<button type="button" onclick="setPage({{.Page}} +1)">Next Page</button>{{end}}
|
||||||
{{template "tail"}}
|
{{template "tail"}}
|
Loading…
Add table
Reference in a new issue