From fe6b27ed6ea10fc4dc85a06c95fc7d8467577989 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Tue, 28 Jul 2026 11:08:41 +0200 Subject: [PATCH] stats --- apps/api/app/admin/dashboard.rb | 65 +++++++++++++++++++++++++++++++-- apps/api/app/admin/downloads.rb | 64 ++++++++++++++++++++++++++++++++ apps/api/app/admin/releases.rb | 22 +++++++++++ apps/api/app/admin/softwares.rb | 34 +++++++++++++++++ apps/api/app/models/download.rb | 8 ++++ apps/api/app/models/release.rb | 2 +- 6 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 apps/api/app/admin/downloads.rb diff --git a/apps/api/app/admin/dashboard.rb b/apps/api/app/admin/dashboard.rb index 1dfeec4..9e85c63 100644 --- a/apps/api/app/admin/dashboard.rb +++ b/apps/api/app/admin/dashboard.rb @@ -1,9 +1,66 @@ ActiveAdmin.register_page "Dashboard" do - menu false + menu priority: 1 - controller do - def index - redirect_to "/admin/softwares" + content title: "Dashboard" do + columns do + column do + panel "Downloads Summary" do + dl style: "display:grid;grid-template-columns:1fr 1fr;gap:8px 16px;margin:0;" do + dt { strong "Total downloads" } + dd { b Download.count.to_s } + + dt { strong "Today" } + dd { b Download.where("created_at >= ?", Date.current.beginning_of_day).count.to_s } + + dt { strong "This week" } + dd { b Download.where("created_at >= ?", 1.week.ago).count.to_s } + + dt { strong "This month" } + dd { b Download.where("created_at >= ?", 1.month.ago).count.to_s } + end + end + end + end + + columns do + column do + panel "Top 10 Downloaded Files" do + table_for Download.group(:file_path) + .select("file_path, COUNT(*) as dl_count, MAX(created_at) as last_dl") + .order("dl_count DESC") + .limit(10) do + column("File") { |row| span row.file_path.truncate(60), style: "font-family:monospace;font-size:12px;" } + column("Downloads") { |row| row.dl_count } + column("Last") { |row| row.last_dl&.strftime("%Y-%m-%d %H:%M") } + end + end + end + + column do + panel "Top 5 Software by Downloads" do + rows = Download.joins(release: :software) + .group("softwares.title") + .order("count_all DESC") + .limit(5) + .count + table_for rows do + column("Software") { |title, _| title } + column("Downloads") { |_, count| count } + end + end + end + end + + columns do + column do + panel "Recent 15 Downloads" do + table_for Download.includes(release: :software).order(created_at: :desc).limit(15) do + column("File") { |d| link_to d.file_path.truncate(50), admin_download_path(d), style: "font-family:monospace;font-size:12px;" } + column("IP") { |d| d.ip_address } + column("Date") { |d| d.created_at.strftime("%Y-%m-%d %H:%M") } + end + end + end end end end diff --git a/apps/api/app/admin/downloads.rb b/apps/api/app/admin/downloads.rb new file mode 100644 index 0000000..6eadd89 --- /dev/null +++ b/apps/api/app/admin/downloads.rb @@ -0,0 +1,64 @@ +ActiveAdmin.register Download do + actions :index, :show + + menu priority: 6 + + scope :all, default: true + scope("Today") { |scope| scope.where("downloads.created_at >= ?", Date.current.beginning_of_day) } + scope("This week") { |scope| scope.where("downloads.created_at >= ?", 1.week.ago) } + scope("This month") { |scope| scope.where("downloads.created_at >= ?", 1.month.ago) } + scope("With release") { |scope| scope.where.not(release_id: nil) } + scope("Without release") { |scope| scope.where(release_id: nil) } + + config.sort_order = "created_at_desc" + + index do + id_column + column(:file_path) { |d| span d.file_path.truncate(50), style: "font-family:monospace;font-size:12px;" } + column("Software") { |d| d.release&.software&.title || "–" } + column("Release") { |d| d.release&.version || "–" } + column :ip_address + column("User-Agent") { |d| d.user_agent&.truncate(40) } + column("Referer") { |d| d.referer&.truncate(40) } + column :created_at + end + + filter :file_path_cont, label: "File path" + filter :release_software_title_cont, label: "Software" + filter :ip_address_cont, label: "IP address" + filter :user_agent_cont, label: "User-Agent" + filter :referer_cont, label: "Referer" + filter :created_at + + show do + attributes_table do + row :id + row(:file_path) { |d| span d.file_path, style: "font-family:monospace;" } + row("Software") do |d| + if d.release&.software + link_to d.release.software.title, admin_software_path(d.release.software) + else + "–" + end + end + row("Release") do |d| + if d.release + link_to d.release.version, admin_software_release_path(d.release.software, d.release) + else + "–" + end + end + row :ip_address + row :user_agent + row :referer + row :created_at + row :updated_at + end + end + + controller do + def scoped_collection + super.includes(release: :software) + end + end +end diff --git a/apps/api/app/admin/releases.rb b/apps/api/app/admin/releases.rb index 4206d56..d2d0b31 100644 --- a/apps/api/app/admin/releases.rb +++ b/apps/api/app/admin/releases.rb @@ -29,5 +29,27 @@ ActiveAdmin.register Release do row :created_at row :updated_at end + + panel "Downloads by File" do + file_stats = Download.where(release_id: resource.id) + .group(:file_path) + .select("file_path, COUNT(*) as dl_count, MAX(created_at) as last_dl") + .order("dl_count DESC") + + if file_stats.any? + div style: "margin-bottom:8px;" do + strong "Total downloads: " + b file_stats.sum(&:dl_count).to_s + end + + table_for file_stats do + column("File") { |row| span row.file_path, style: "font-family:monospace;font-size:12px;" } + column("Downloads") { |row| row.dl_count } + column("Last download") { |row| row.last_dl&.strftime("%Y-%m-%d %H:%M") } + end + else + para "No downloads for this release.", style: "color:#999;font-style:italic;" + end + end end end diff --git a/apps/api/app/admin/softwares.rb b/apps/api/app/admin/softwares.rb index 838facb..4cba021 100644 --- a/apps/api/app/admin/softwares.rb +++ b/apps/api/app/admin/softwares.rb @@ -41,6 +41,40 @@ ActiveAdmin.register Software do filter :status, as: :select, collection: %w[development demo released archived] filter :highlighted + sidebar "Download Statistics", only: :show do + sw = resource + total = Download.joins(:release).where(releases: { software_id: sw.id }).count + + div style: "margin-bottom:12px;" do + strong "Total downloads: " + span total.to_s, style: "font-size:18px;font-weight:bold;" + end + + sw.releases.each do |rel| + file_stats = Download.where(release_id: rel.id) + .group(:file_path) + .select("file_path, COUNT(*) as dl_count, MAX(created_at) as last_dl") + .order("dl_count DESC") + rel_total = file_stats.sum(&:dl_count) + + div style: "margin-bottom:16px;" do + h4 do + link_to "v#{rel.version}", admin_software_release_path(sw, rel) + span " (#{rel_total})", style: "color:#666;" + end + if file_stats.any? + table_for file_stats do + column("File") { |row| span row.file_path.split("/").last, style: "font-family:monospace;font-size:11px;" } + column("DL") { |row| row.dl_count } + column("Last") { |row| row.last_dl&.strftime("%m-%d") } + end + else + para "No downloads", style: "color:#999;font-style:italic;" + end + end + end + end + show title: proc { |sw| sw.title } do active_admin_form_for [ :admin, resource ], url: admin_software_path(resource), html: { method: :put, multipart: true } do |f| f.inputs "Details" do diff --git a/apps/api/app/models/download.rb b/apps/api/app/models/download.rb index 9b13636..36f1bbe 100644 --- a/apps/api/app/models/download.rb +++ b/apps/api/app/models/download.rb @@ -2,4 +2,12 @@ class Download < ApplicationRecord belongs_to :release, optional: true validates :file_path, presence: true + + def self.ransackable_attributes(auth_object = nil) + %w[created_at file_path id ip_address referer release_id updated_at user_agent] + end + + def self.ransackable_associations(auth_object = nil) + %w[release] + end end diff --git a/apps/api/app/models/release.rb b/apps/api/app/models/release.rb index 45f72de..fc6ba3a 100644 --- a/apps/api/app/models/release.rb +++ b/apps/api/app/models/release.rb @@ -21,6 +21,6 @@ class Release < ApplicationRecord end def self.ransackable_associations(auth_object = nil) - %w[software] + %w[downloads software] end end