diff --git a/server/server.go b/server/server.go index 97a1f1d..73871a9 100644 --- a/server/server.go +++ b/server/server.go @@ -104,6 +104,7 @@ func Start(_conf config.Config, tmplFS embed.FS, staticFS embed.FS, migrationsFS mux.HandleFunc("/ffmpeg_commands", handleFfmpegCommands) mux.HandleFunc("/queue_enable", HandleSetQueueEnable) mux.HandleFunc("/stats", handleStats) + mux.HandleFunc("/stats/{id}", handleStats) mux.HandleFunc("/", handleIndex) diff --git a/server/stats.go b/server/stats.go index 3d870e3..d5c41fe 100644 --- a/server/stats.go +++ b/server/stats.go @@ -18,7 +18,9 @@ import ( ) type StatsDisplay struct { - Charts []ChartData + Charts []ChartData + Libraries []Library + SelectedLibrary string } type ChartData struct { @@ -83,14 +85,14 @@ func generatePie(name string, data []opts.PieData) ChartData { } } -func generateStats(ctx context.Context) ([]ChartData, error) { +func generateStats(ctx context.Context, library_id string) ([]ChartData, error) { data := []ChartData{} rows, err := db.Query(ctx, `SELECT COALESCE(jsonb_path_query_first(ffprobe_data, '$.streams[*] ? (@.codec_type == "video") ? (@.disposition.attached_pic == 0).codec_name')::text, 'Unknown') AS name, COUNT(*) AS value FROM files - WHERE ffprobe_data IS NOT NULL - GROUP BY 1;`) + WHERE ffprobe_data IS NOT NULL AND ($1 = "" OR library_id = $1) + GROUP BY 1;`, library_id) if err != nil { return nil, fmt.Errorf("Query Codecs: %w", err) } @@ -111,8 +113,8 @@ func generateStats(ctx context.Context) ([]ChartData, error) { rows, err = db.Query(ctx, `SELECT COALESCE(jsonb_path_query_first(ffprobe_data, '$.streams[*] ? (@.codec_type == "video") ? (@.disposition.attached_pic == 0).width')::text, 'Unknown') AS name, COUNT(*) AS value FROM files - WHERE ffprobe_data IS NOT NULL - GROUP BY 1;`) + WHERE ffprobe_data IS NOT NULL AND ($1 = "" OR library_id = $1) + GROUP BY 1;`, library_id) if err != nil { return nil, fmt.Errorf("Query Resolution: %w", err) } @@ -133,8 +135,8 @@ func generateStats(ctx context.Context) ([]ChartData, error) { rows, err = db.Query(ctx, `SELECT COALESCE(jsonb_path_query_first(ffprobe_data, '$.format.format_name')::text, 'Unknown') AS name, COUNT(*) AS value FROM files - WHERE ffprobe_data IS NOT NULL - GROUP BY 1;`) + WHERE ffprobe_data IS NOT NULL AND ($1 = "" OR library_id = $1) + GROUP BY 1;`, library_id) if err != nil { return nil, fmt.Errorf("Query Container: %w", err) } @@ -154,8 +156,8 @@ func generateStats(ctx context.Context) ([]ChartData, error) { rows, err = db.Query(ctx, `SELECT health AS id, COUNT(*) AS value - FROM files - GROUP BY 1;`) + FROM files WHERE ($1 = "" OR library_id = $1) + GROUP BY 1;`, library_id) if err != nil { return nil, fmt.Errorf("Query Health: %w", err) } @@ -175,8 +177,8 @@ func generateStats(ctx context.Context) ([]ChartData, error) { rows, err = db.Query(ctx, `SELECT transcode AS id, COUNT(*) AS value - FROM files - GROUP BY 1;`) + FROM files WHERE ($1 = "" OR library_id = $1) + GROUP BY 1;`, library_id) if err != nil { return nil, fmt.Errorf("Query Transcode: %w", err) } @@ -195,9 +197,10 @@ func generateStats(ctx context.Context) ([]ChartData, error) { data = append(data, generatePie("Transcode Status", res)) rows, err = db.Query(ctx, - `SELECT status AS id, COUNT(*) AS value - FROM tasks - GROUP BY 1;`) + `SELECT tasks.status AS id, COUNT(*) AS value + FROM tasks INNER JOIN files ON files.id = tasks.file_id + WHERE ($1 = "" OR files.library_id = $1) + GROUP BY 1;`, library_id) if err != nil { return nil, fmt.Errorf("Query Task Status: %w", err) } @@ -222,11 +225,11 @@ func generateStats(ctx context.Context) ([]ChartData, error) { } rows, err = db.Query(ctx, - `SELECT date_trunc('day', updated_at) date, status, COUNT(*) AS count - FROM tasks - WHERE updated_at > current_date - 7 AND (status = $1 OR status = $2) + `SELECT date_trunc('day', tasks.updated_at) date, tasks.status, COUNT(*) AS count + FROM tasks INNER JOIN files ON files.id = tasks.file_id + WHERE ($1 = "" OR files.library_id = $1) AND tasks.updated_at > tasks.current_date - 7 AND (tasks.status = $2 OR tasks.status = $3) GROUP BY 1,2 - ORDER BY date;`, constants.TASK_STATUS_SUCCESS, constants.TASK_STATUS_FAILED) + ORDER BY date;`, library_id, constants.TASK_STATUS_SUCCESS, constants.TASK_STATUS_FAILED) if err != nil { return nil, fmt.Errorf("Query Task Status Day: %w", err) } @@ -289,17 +292,36 @@ func generateStats(ctx context.Context) ([]ChartData, error) { } func handleStats(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") - data, err := generateStats(r.Context()) + data, err := generateStats(r.Context(), id) if err != nil { slog.ErrorContext(r.Context(), "Generate Stats:", "err", err) http.Error(w, "Generate Stats: "+err.Error(), http.StatusInternalServerError) return } + rows, err := db.Query(r.Context(), + `SELECT * + FROM libraries;`) + if err != nil { + slog.ErrorContext(r.Context(), "Query Libraries", "err", err) + http.Error(w, "Query Libraries: "+err.Error(), http.StatusInternalServerError) + return + } + + libraries, err := pgx.CollectRows(rows, pgx.RowToStructByName[Library]) + if err != nil { + slog.ErrorContext(r.Context(), "Collect Libraries", "err", err) + http.Error(w, "Collect Libraries: "+err.Error(), http.StatusInternalServerError) + return + } + buf := bytes.Buffer{} err = templates.ExecuteTemplate(&buf, constants.STATS_TEMPLATE_NAME, StatsDisplay{ - Charts: data, + Libraries: libraries, + SelectedLibrary: id, + Charts: data, }) if err != nil { slog.ErrorContext(r.Context(), "Executing Stats Template", "err", err) diff --git a/static/js/library_filter.js b/static/js/library_filter.js new file mode 100644 index 0000000..a08c51a --- /dev/null +++ b/static/js/library_filter.js @@ -0,0 +1,7 @@ +function setLibrary(library_id) { + if (library_id === "") { + } else { + window.location.href = "/stats"; + } + window.location.href = "/stats/" + library_id; +} diff --git a/tmpl/stats.tmpl b/tmpl/stats.tmpl index 8ea32da..7633753 100644 --- a/tmpl/stats.tmpl +++ b/tmpl/stats.tmpl @@ -1,5 +1,12 @@ {{template "head"}}