diff --git a/constants/constants.go b/constants/constants.go index 3d9911a..24fdfd9 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -20,6 +20,11 @@ const FFMPEG_COMMANDS_TEMPLATE_NAME = "ffmpeg_commands.tmpl" 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 ( TASK_TYPE_HEALTHCHECK = iota TASK_TYPE_TRANSCODE diff --git a/server/task.go b/server/task.go index 5964cd1..d536b1f 100644 --- a/server/task.go +++ b/server/task.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "net/http" + "slices" "strconv" "time" @@ -20,6 +21,11 @@ type TasksData struct { FfmpegCommands []FfmpegCommand Tasks []TaskDisplay Stats TaskStats + OrderBy string + OrderAsc bool + Limit uint + Page uint + Count uint } type TaskStats struct { @@ -56,6 +62,8 @@ type TaskDB struct { 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) { if r.Method == "POST" { err := createTask(r.Context(), r) @@ -114,7 +122,52 @@ func handleTasks(w http.ResponseWriter, r *http.Request) { } 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 { slog.ErrorContext(r.Context(), "Query Tasks", "err", err) 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), }) } + data.Count = uint(len(tasks)) buf := bytes.Buffer{} err = templates.ExecuteTemplate(&buf, constants.TASKS_TEMPLATE_NAME, data) diff --git a/static/js/table_sort.js b/static/js/table_sort.js new file mode 100644 index 0000000..a37972a --- /dev/null +++ b/static/js/table_sort.js @@ -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; +} diff --git a/tmpl/tasks.tmpl b/tmpl/tasks.tmpl index dc6f4f9..ee3edc5 100644 --- a/tmpl/tasks.tmpl +++ b/tmpl/tasks.tmpl @@ -1,4 +1,5 @@ {{template "head"}} +

New Tasks

@@ -42,16 +43,25 @@ Total: {{.Stats.TotalCount}}

Tasks

+ + + - - - - - - - - + + + + + + + + {{range $t := .Tasks}} @@ -82,4 +92,8 @@ Total: {{.Stats.TotalCount}} {{end}}
IDLibraryWorkerTypeFFmpeg CommandStatusFileUpdated AtIDLibraryWorkerTypeFFmpeg CommandStatusFileUpdated At
+{{if ne .Page 0 }}{{end}} +{{if ne .Page 0 }}{{end}} +Page {{.Page}} +{{if ne .Count 0}}{{end}} {{template "tail"}} \ No newline at end of file