feat: add folder filtering support to job reports and update UI filters
This commit is contained in:
+16
-4
@@ -2,6 +2,7 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Watch Folders
|
// Watch Folders
|
||||||
@@ -151,22 +152,33 @@ func AddJobReport(j Job, status string, encodedSize int64, sizeSaved int64, proc
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetJobReports(limit, offset int, statusFilter string) ([]JobReport, int, error) {
|
func GetJobReports(limit, offset int, statusFilter string, folderFilter string) ([]JobReport, int, error) {
|
||||||
var total int
|
var total int
|
||||||
queryArgs := []interface{}{}
|
queryArgs := []interface{}{}
|
||||||
countQuery := `SELECT COUNT(*) FROM job_reports`
|
countQuery := `SELECT COUNT(*) FROM job_reports`
|
||||||
selectQuery := `SELECT id, file_path, media_type, status, original_size, encoded_size, size_saved, processing_time, target_resolution, ffmpeg_flags, error_message, created_at FROM job_reports`
|
selectQuery := `SELECT id, file_path, media_type, status, original_size, encoded_size, size_saved, processing_time, target_resolution, ffmpeg_flags, error_message, created_at FROM job_reports`
|
||||||
whereClause := ""
|
|
||||||
|
whereClauses := []string{}
|
||||||
|
|
||||||
if statusFilter != "" && statusFilter != "all" {
|
if statusFilter != "" && statusFilter != "all" {
|
||||||
if statusFilter == "exclude_skipped" {
|
if statusFilter == "exclude_skipped" {
|
||||||
whereClause = ` WHERE status != 'skipped'`
|
whereClauses = append(whereClauses, `status != 'skipped'`)
|
||||||
} else {
|
} else {
|
||||||
whereClause = ` WHERE status = ?`
|
whereClauses = append(whereClauses, `status = ?`)
|
||||||
queryArgs = append(queryArgs, statusFilter)
|
queryArgs = append(queryArgs, statusFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if folderFilter != "" && folderFilter != "all" {
|
||||||
|
whereClauses = append(whereClauses, `(file_path LIKE ? OR file_path = ?)`)
|
||||||
|
queryArgs = append(queryArgs, folderFilter+"/%", folderFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
whereClause := ""
|
||||||
|
if len(whereClauses) > 0 {
|
||||||
|
whereClause = " WHERE " + strings.Join(whereClauses, " AND ")
|
||||||
|
}
|
||||||
|
|
||||||
if err := DB.QueryRow(countQuery+whereClause, queryArgs...).Scan(&total); err != nil {
|
if err := DB.QueryRow(countQuery+whereClause, queryArgs...).Scan(&total); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-1
@@ -281,6 +281,7 @@ func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) {
|
|||||||
limitStr := r.URL.Query().Get("limit")
|
limitStr := r.URL.Query().Get("limit")
|
||||||
pageStr := r.URL.Query().Get("page")
|
pageStr := r.URL.Query().Get("page")
|
||||||
statusFilter := r.URL.Query().Get("status")
|
statusFilter := r.URL.Query().Get("status")
|
||||||
|
folderFilterIDStr := r.URL.Query().Get("folder")
|
||||||
|
|
||||||
limit := 50
|
limit := 50
|
||||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||||
@@ -294,7 +295,21 @@ func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
offset := (page - 1) * limit
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
reports, total, err := db.GetJobReports(limit, offset, statusFilter)
|
folders, _ := db.GetWatchFolders()
|
||||||
|
|
||||||
|
folderFilterPath := ""
|
||||||
|
folderFilterID := 0
|
||||||
|
if fID, err := strconv.Atoi(folderFilterIDStr); err == nil && fID > 0 {
|
||||||
|
folderFilterID = fID
|
||||||
|
for _, f := range folders {
|
||||||
|
if f.ID == fID {
|
||||||
|
folderFilterPath = f.FolderPath
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reports, total, err := db.GetJobReports(limit, offset, statusFilter, folderFilterPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -316,6 +331,8 @@ func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) {
|
|||||||
PrevPage int
|
PrevPage int
|
||||||
NextPage int
|
NextPage int
|
||||||
FilterStatus string
|
FilterStatus string
|
||||||
|
FilterFolder int
|
||||||
|
Folders []db.WatchFolder
|
||||||
Limit int
|
Limit int
|
||||||
}{
|
}{
|
||||||
AuthEnabled: s.cfg.Auth.Username != "",
|
AuthEnabled: s.cfg.Auth.Username != "",
|
||||||
@@ -328,6 +345,8 @@ func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) {
|
|||||||
PrevPage: page - 1,
|
PrevPage: page - 1,
|
||||||
NextPage: page + 1,
|
NextPage: page + 1,
|
||||||
FilterStatus: statusFilter,
|
FilterStatus: statusFilter,
|
||||||
|
FilterFolder: folderFilterID,
|
||||||
|
Folders: folders,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,21 @@
|
|||||||
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;">
|
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;">
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<label for="status-filter" style="font-size: 0.875rem; color: var(--text-secondary);">Status:</label>
|
<label for="status-filter" style="font-size: 0.875rem; color: var(--text-secondary);">Status:</label>
|
||||||
<select id="status-filter" class="filter-select" onchange="window.location.href='?status=' + this.value">
|
<select id="status-filter" class="filter-select" onchange="updateFilter('status', this.value)">
|
||||||
<option value="all" {{if eq .FilterStatus "all"}}selected{{end}}>All</option>
|
<option value="all" {{if eq .FilterStatus "all"}}selected{{end}}>All</option>
|
||||||
<option value="exclude_skipped" {{if eq .FilterStatus "exclude_skipped"}}selected{{end}}>Exclude Skipped</option>
|
<option value="exclude_skipped" {{if eq .FilterStatus "exclude_skipped"}}selected{{end}}>Exclude Skipped</option>
|
||||||
<option value="success" {{if eq .FilterStatus "success"}}selected{{end}}>Success Only</option>
|
<option value="success" {{if eq .FilterStatus "success"}}selected{{end}}>Success Only</option>
|
||||||
<option value="failed" {{if eq .FilterStatus "failed"}}selected{{end}}>Failed Only</option>
|
<option value="failed" {{if eq .FilterStatus "failed"}}selected{{end}}>Failed Only</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<label for="folder-filter" style="font-size: 0.875rem; color: var(--text-secondary); margin-left: 0.5rem;">Folder:</label>
|
||||||
|
<select id="folder-filter" class="filter-select" onchange="updateFilter('folder', this.value)">
|
||||||
|
<option value="0" {{if eq .FilterFolder 0}}selected{{end}}>All Folders</option>
|
||||||
|
{{$filterFolder := .FilterFolder}}
|
||||||
|
{{range .Folders}}
|
||||||
|
<option value="{{.ID}}" {{if eq $filterFolder .ID}}selected{{end}}>{{.FolderPath}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="history-search" class="form-control" placeholder="Search page..." onkeyup="filterHistory()" style="max-width: 200px;">
|
<input type="text" id="history-search" class="form-control" placeholder="Search page..." onkeyup="filterHistory()" style="max-width: 200px;">
|
||||||
<span style="color: var(--text-secondary); font-size: 0.875rem; white-space: nowrap;">Total Records: <span id="record-count">{{.Total}}</span></span>
|
<span style="color: var(--text-secondary); font-size: 0.875rem; white-space: nowrap;">Total Records: <span id="record-count">{{.Total}}</span></span>
|
||||||
@@ -64,7 +73,7 @@
|
|||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem;">
|
||||||
<div>
|
<div>
|
||||||
<label for="limit-select" style="color: var(--text-secondary); font-size: 0.875rem; margin-right: 0.5rem;">Rows per page:</label>
|
<label for="limit-select" style="color: var(--text-secondary); font-size: 0.875rem; margin-right: 0.5rem;">Rows per page:</label>
|
||||||
<select id="limit-select" onchange="window.location.href='?limit=' + this.value + '&status={{.FilterStatus}}'" style="background: var(--surface); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; padding: 0.25rem 0.5rem;">
|
<select id="limit-select" onchange="updateFilter('limit', this.value)" style="background: var(--surface); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; padding: 0.25rem 0.5rem;">
|
||||||
<option value="10" {{if eq .Limit 10}}selected{{end}}>10</option>
|
<option value="10" {{if eq .Limit 10}}selected{{end}}>10</option>
|
||||||
<option value="50" {{if eq .Limit 50}}selected{{end}}>50</option>
|
<option value="50" {{if eq .Limit 50}}selected{{end}}>50</option>
|
||||||
<option value="100" {{if eq .Limit 100}}selected{{end}}>100</option>
|
<option value="100" {{if eq .Limit 100}}selected{{end}}>100</option>
|
||||||
@@ -72,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
||||||
{{if .HasPrev}}
|
{{if .HasPrev}}
|
||||||
<a href="?page={{.PrevPage}}&limit={{.Limit}}&status={{.FilterStatus}}" class="btn btn-sm btn-outline" style="text-decoration: none;">Previous</a>
|
<a href="?page={{.PrevPage}}&limit={{.Limit}}&status={{.FilterStatus}}&folder={{.FilterFolder}}" class="btn btn-sm btn-outline" style="text-decoration: none;">Previous</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button class="btn btn-sm btn-outline" disabled>Previous</button>
|
<button class="btn btn-sm btn-outline" disabled>Previous</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -80,7 +89,7 @@
|
|||||||
<span style="color: var(--text-secondary); font-size: 0.875rem;">Page {{.CurrentPage}} of {{.TotalPages}}</span>
|
<span style="color: var(--text-secondary); font-size: 0.875rem;">Page {{.CurrentPage}} of {{.TotalPages}}</span>
|
||||||
|
|
||||||
{{if .HasNext}}
|
{{if .HasNext}}
|
||||||
<a href="?page={{.NextPage}}&limit={{.Limit}}&status={{.FilterStatus}}" class="btn btn-sm btn-outline" style="text-decoration: none;">Next</a>
|
<a href="?page={{.NextPage}}&limit={{.Limit}}&status={{.FilterStatus}}&folder={{.FilterFolder}}" class="btn btn-sm btn-outline" style="text-decoration: none;">Next</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button class="btn btn-sm btn-outline" disabled>Next</button>
|
<button class="btn btn-sm btn-outline" disabled>Next</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -89,6 +98,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function updateFilter(key, value) {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set(key, value);
|
||||||
|
if (key !== 'limit' && key !== 'page') {
|
||||||
|
url.searchParams.set('page', '1');
|
||||||
|
}
|
||||||
|
window.location.href = url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
function filterHistory() {
|
function filterHistory() {
|
||||||
var input, filter, table, tr, td, i, txtValue;
|
var input, filter, table, tr, td, i, txtValue;
|
||||||
input = document.getElementById("history-search");
|
input = document.getElementById("history-search");
|
||||||
|
|||||||
Reference in New Issue
Block a user