mirror of
https://github.com/System-End/cdn.git
synced 2026-04-19 16:18:17 +00:00
215 lines
7.8 KiB
Ruby
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
|