cdn/app/components/admin/users/show.rb
2026-01-30 14:59:51 -05:00

215 lines
7.8 KiB
Ruby

# frozen_string_literal: true
class Components::Admin::Users::Show < Components::Base
include Phlex::Rails::Helpers::LinkTo
include Phlex::Rails::Helpers::ButtonTo
def initialize(user:)
@user = user
end
def view_template
div(style: "max-width: 800px; margin: 0 auto; padding: 24px;") do
header_section
stats_section
quota_section
api_keys_section
uploads_section
end
end
private
def header_section
header(style: "margin-bottom: 24px;") do
div(style: "display: flex; justify-content: space-between; align-items: flex-start;") do
div do
div(style: "display: flex; align-items: center; gap: 8px; margin-bottom: 8px;") do
h1(style: "font-size: 2rem; font-weight: 600; margin: 0;") { @user.name || "Unnamed User" }
if @user.is_admin?
render(Primer::Beta::Label.new(scheme: :accent)) { plain "ADMIN" }
end
end
p(style: "color: var(--fgColor-muted, #656d76); margin: 0; font-size: 14px;") do
plain @user.email
plain " · "
code(style: "font-size: 12px;") { @user.public_id }
end
if @user.slack_id.present?
p(style: "color: var(--fgColor-muted); margin: 4px 0 0; font-size: 12px;") do
plain "Slack: "
code { @user.slack_id }
end
end
end
link_to admin_search_path, class: "btn" do
render Primer::Beta::Octicon.new(icon: :"arrow-left", mr: 1)
plain "Back to Search"
end
end
end
end
def stats_section
div(style: "display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px;") do
stat_card("Total Files", @user.total_files.to_s)
stat_card("Total Storage", @user.total_storage_formatted)
stat_card("Member Since", @user.created_at.strftime("%b %d, %Y"))
end
end
def stat_card(label, value)
render Primer::Beta::BorderBox.new do |box|
box.with_body(padding: :normal) do
div(style: "font-size: 12px; color: var(--fgColor-muted);") { label }
div(style: "font-size: 24px; font-weight: 600; margin-top: 4px;") { value }
end
end
end
def quota_section
quota_service = QuotaService.new(@user)
usage = quota_service.current_usage
policy = quota_service.current_policy
div(style: "margin-bottom: 24px;") do
h2(style: "font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;") { "Quota Management" }
render Primer::Beta::BorderBox.new do |box|
box.with_body(padding: :normal) do
# Current policy
div(style: "margin-bottom: 16px;") do
div(style: "display: flex; align-items: center; gap: 8px; margin-bottom: 8px;") do
span(style: "font-weight: 500;") { "Current Policy:" }
render(Primer::Beta::Label.new(scheme: quota_policy_scheme)) { policy.slug.to_s.humanize }
if @user.quota_policy.present?
render(Primer::Beta::Label.new(scheme: :accent)) { "Override" }
end
end
div(style: "font-size: 12px; color: var(--fgColor-muted);") do
plain "Per-file limit: #{helpers.number_to_human_size(policy.max_file_size)} · "
plain "Total storage: #{helpers.number_to_human_size(policy.max_total_storage)}"
end
end
# Usage stats
div(style: "margin-bottom: 16px;") do
div(style: "font-weight: 500; margin-bottom: 4px;") { "Storage Usage" }
div(style: "font-size: 14px; margin-bottom: 4px;") do
plain "#{helpers.number_to_human_size(usage[:storage_used])} / #{helpers.number_to_human_size(usage[:storage_limit])} "
span(style: "color: var(--fgColor-muted);") { "(#{usage[:percentage_used]}%)" }
end
# Progress bar
div(style: "background: var(--bgColor-muted); border-radius: 3px; height: 8px; overflow: hidden;") do
div(style: "background: #{progress_bar_color(usage[:percentage_used])}; height: 100%; width: #{[ usage[:percentage_used], 100 ].min}%;")
end
end
# Admin controls
form(action: helpers.admin_user_path(@user), method: :post, style: "display: flex; gap: 8px; align-items: center;") do
input(type: "hidden", name: "_method", value: "patch")
input(type: "hidden", name: "authenticity_token", value: helpers.form_authenticity_token)
render(Primer::Alpha::SelectPanel.new(
select_variant: :single,
fetch_strategy: :local,
dynamic_label: true,
dynamic_label_prefix: "Quota Policy",
form_arguments: { name: "user[quota_policy]" }
)) do |panel|
panel.with_show_button(scheme: :secondary, size: :small) { current_quota_label }
panel.with_item(label: "Auto-detect (via HCA)", content_arguments: { data: { value: "" } }, active: @user.quota_policy.nil?)
panel.with_item(label: "Verified", content_arguments: { data: { value: "verified" } }, active: @user.quota_policy == "verified")
panel.with_item(label: "Functionally Unlimited", content_arguments: { data: { value: "functionally_unlimited" } }, active: @user.quota_policy == "functionally_unlimited")
end
button(type: "submit", class: "btn btn-sm btn-primary") { "Set Policy" }
end
end
end
end
end
def quota_policy_scheme
case @user.quota_policy&.to_sym
when :functionally_unlimited
:success
when :verified
:accent
else
:default
end
end
def current_quota_label
case @user.quota_policy&.to_sym
when :functionally_unlimited
"Functionally Unlimited"
when :verified
"Verified"
else
"Auto-detect (via HCA)"
end
end
def progress_bar_color(percentage)
if percentage >= 100
"var(--bgColor-danger-emphasis)"
elsif percentage >= 80
"var(--bgColor-attention-emphasis)"
else
"var(--bgColor-success-emphasis)"
end
end
def api_keys_section
api_keys = @user.api_keys.recent
return if api_keys.empty?
div(style: "margin-bottom: 24px;") do
h2(style: "font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;") { "API Keys" }
render Primer::Beta::BorderBox.new do |box|
api_keys.each do |api_key|
box.with_row do
api_key_row(api_key)
end
end
end
end
end
def api_key_row(api_key)
div(style: "display: flex; justify-content: space-between; align-items: center;") do
div do
div(style: "font-weight: 500;") { api_key.name }
code(style: "font-size: 12px; color: var(--fgColor-muted);") { api_key.masked_token }
end
div(style: "display: flex; align-items: center; gap: 12px;") do
if api_key.revoked?
render(Primer::Beta::Label.new(scheme: :danger)) { plain "REVOKED" }
else
render(Primer::Beta::Label.new(scheme: :success)) { plain "ACTIVE" }
button_to helpers.admin_api_key_path(api_key), method: :delete, class: "btn btn-sm btn-danger", data: { confirm: "Revoke this API key?" } do
plain "Revoke"
end
end
span(style: "font-size: 12px; color: var(--fgColor-muted);") { api_key.created_at.strftime("%b %d, %Y") }
end
end
end
def uploads_section
uploads = @user.uploads.includes(:blob).order(created_at: :desc).limit(20)
return if uploads.empty?
div do
h2(style: "font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;") { "Recent Uploads" }
render Primer::Beta::BorderBox.new do |box|
uploads.each do |upload|
box.with_row do
render Components::Uploads::Row.new(upload: upload, compact: true, admin: true)
end
end
end
end
end
end