feat: add admin management features for cities

- Update menu labels for cities, countries, and regions
- Introduce new entities for states and subregions in Admin Panel
- Implement admin authentication for weather art generation
- Modify application controller to check for admin user
- Refactor view to display admin panel based on user permissions
- Update routes to include weather art generation action

These changes enhance the admin interface for better management of cities
and related entities. The new admin checks ensure that only authorized users
can generate weather art, improving security and functionality.
This commit is contained in:
songtianlun 2025-02-11 17:40:13 +08:00
parent 3237321db3
commit b2551361d7
14 changed files with 159 additions and 46 deletions

View File

@ -1,5 +1,5 @@
ActiveAdmin.register City do
menu label: "City Manager", parent: "系统管理"
menu label: "Cities", parent: "数据管理"
controller do
def find_resource
scoped_collection.friendly.find(params[:id])

View File

@ -1,5 +1,5 @@
ActiveAdmin.register Country do
menu label: "Country Manager", parent: "系统管理"
menu label: "Countries", parent: "数据管理"
controller do
def find_resource
scoped_collection.friendly.find(params[:id])

View File

@ -1,5 +1,5 @@
ActiveAdmin.register Region do
menu label: "Region Manager", parent: "系统管理"
menu label: "Regions", parent: "数据管理"
# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
#

19
app/admin/states.rb Normal file
View File

@ -0,0 +1,19 @@
ActiveAdmin.register State do
menu label: "States", parent: "数据管理"
# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
#
# Uncomment all parameters which should be permitted for assignment
#
# permit_params :name, :code, :country_id, :country_code, :fips_code, :iso2, :state_type, :level, :parent_id, :latitude, :longitude, :flag, :wiki_data_id
#
# or
#
# permit_params do
# permitted = [:name, :code, :country_id, :country_code, :fips_code, :iso2, :state_type, :level, :parent_id, :latitude, :longitude, :flag, :wiki_data_id]
# permitted << :other if params[:action] == 'create' && current_user.admin?
# permitted
# end
end

19
app/admin/subregions.rb Normal file
View File

@ -0,0 +1,19 @@
ActiveAdmin.register Subregion do
menu label: "SubRegions", parent: "数据管理"
# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
#
# Uncomment all parameters which should be permitted for assignment
#
# permit_params :name, :translations, :region_id, :flag, :wiki_data_id
#
# or
#
# permit_params do
# permitted = [:name, :translations, :region_id, :flag, :wiki_data_id]
# permitted << :other if params[:action] == 'create' && current_user.admin?
# permitted
# end
end

19
app/admin/users.rb Normal file
View File

@ -0,0 +1,19 @@
ActiveAdmin.register User do
menu label: "Users", parent: "数据管理"
# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
#
# Uncomment all parameters which should be permitted for assignment
#
# permit_params :email, :encrypted_password, :reset_password_token, :reset_password_sent_at, :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip, :confirmation_token, :confirmed_at, :confirmation_sent_at, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :admin
#
# or
#
# permit_params do
# permitted = [:email, :encrypted_password, :reset_password_token, :reset_password_sent_at, :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip, :confirmation_token, :confirmed_at, :confirmation_sent_at, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :admin]
# permitted << :other if params[:action] == 'create' && current_user.admin?
# permitted
# end
end

View File

@ -66,6 +66,13 @@ class ApplicationController < ActionController::Base
ahoy.track "Viewed Application", request.path_parameters
end
def authenticate_admin_user!
unless current_user&.admin?
flash[:alert] = "您没有权限访问该页面。"
redirect_to root_path
end
end
private
def set_locale

View File

@ -1,4 +1,7 @@
class CitiesController < ApplicationController
before_action :authenticate_user!, only: [:generate_weather_art]
before_action :require_admin, only: [:generate_weather_art]
def index
@regions = Region.includes(:countries).order(:name)
@cities = City.includes(:country, country: :region).order(:name)
@ -39,4 +42,26 @@ class CitiesController < ApplicationController
}
)
end
def generate_weather_art
@city = City.friendly.find(params[:id])
GenerateWeatherArtWorker.perform_async(@city.id)
respond_to do |format|
format.html do
flash[:notice] = "Weather art generation has been queued"
redirect_to @city
end
end
end
private
def require_admin
unless current_user&.admin?
flash[:error] = "You are not authorized to perform this action"
redirect_to root_path
end
end
end

View File

@ -22,7 +22,7 @@ module ApplicationHelper
}.to_json.html_safe if weather_art.image.attached?
end
def admin?
def current_user_is_admin?
current_user&.admin?
end
end

View File

@ -1,45 +1,58 @@
<% if admin? %>
<div class="card bg-warning/10 border border-warning/20 shadow-lg p-6">
<div class="flex items-center gap-3 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
<!-- 主要统计信息后面添加 -->
<% if current_user_is_admin? %>
<div class="mt-8 p-6 bg-base-100/90 backdrop-blur-sm shadow-xl rounded-box">
<h3 class="text-xl font-bold mb-4 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<h3 class="font-display font-bold text-lg">Admin Panel</h3>
Admin Panel
</h3>
<!-- 统计数据 -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<div class="stat bg-base-200/50 rounded-box">
<div class="stat-title">Success Rate</div>
<div class="stat-value text-primary text-2xl">
<%= "hello" %>%
</div>
<div class="stat-desc">Generation Success Rate</div>
</div>
<div class="stat bg-base-200/50 rounded-box">
<div class="stat-title">Last Generated</div>
<div class="stat-value text-2xl">
<%= time_ago_in_words(@city.weather_arts.last&.created_at) if @city.weather_arts.last %>
</div>
<div class="stat-desc">Time since last generation</div>
</div>
<div class="stat bg-base-200/50 rounded-box">
<div class="stat-title">Failed Attempts</div>
<div class="stat-value text-error text-2xl">
<%= "hello" %>
</div>
<div class="stat-desc">Total failed generations</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 统计数据 -->
<div class="space-y-4">
<h4 class="font-semibold">Statistics</h4>
<div class="stats stats-vertical lg:stats-horizontal shadow">
<div class="stat">
<div class="stat-title">Total Images</div>
<div class="stat-value"><%= @weather_art.city.weather_arts.count %></div>
</div>
<!-- 操作按钮 -->
<div class="flex flex-wrap gap-4">
<%= button_to generate_weather_art_city_path(@city),
method: :post,
class: "btn btn-primary gap-2" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Generate New Art
<% end %>
<div class="stat">
<div class="stat-title">Today's Images</div>
<div class="stat-value"><%= @weather_art.city.weather_arts.where("created_at >= ?", Time.zone.now.beginning_of_day).count %></div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="space-y-4">
<h4 class="font-semibold">Actions</h4>
<div class="flex flex-wrap gap-4">
<%= button_to "Generate New Art", "#",
method: :post,
data: {
controller: "generate-art",
action: "generate-art#generate",
city_id: @weather_art.city.id
},
class: "btn btn-primary" %>
<%= link_to "Edit City", edit_city_path(@weather_art.city), class: "btn btn-secondary" %>
</div>
</div>
<%= link_to edit_city_path(@city),
class: "btn btn-secondary gap-2" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Edit City
<% end %>
</div>
</div>
<% end %>

View File

@ -71,6 +71,8 @@
<div class="stat-desc mt-1">Total Weather Arts</div>
</div>
</div>
<%= render 'cities/admin_panel' %>
</div>
</div>

View File

@ -108,7 +108,8 @@ ActiveAdmin.setup do |config|
#
# This setting changes the method which Active Admin calls
# (within the application controller) to return the currently logged in user.
config.current_user_method = :current_admin_user
# config.current_user_method = :current_admin_user
config.current_user_method = :current_user
# == Logging Out
#
@ -120,13 +121,15 @@ ActiveAdmin.setup do |config|
# will call the method to return the path.
#
# Default:
config.logout_link_path = :destroy_admin_user_session_path
# config.logout_link_path = :destroy_admin_user_session_path
config.logout_link_path = :destroy_user_session_path
# This setting changes the http method used when rendering the
# link. For example :get, :delete, :put, etc..
#
# Default:
# config.logout_link_method = :get
config.logout_link_method = :delete
# == Root
#

View File

@ -24,7 +24,7 @@ Devise.setup do |config|
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class
# with default "from" parameter.
config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
config.mailer_sender = "noreply@mail.frytea.com"
# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'

View File

@ -7,6 +7,11 @@ Rails.application.routes.draw do
resources :cities, only: [ :index, :show ] do
resources :weather_arts, path: "weather", only: [ :show ], param: :slug
end
resources :cities do
member do
post :generate_weather_art, param: :slug
end
end
resources :arts, only: [ :index ]
# namespace :admin do
@ -25,7 +30,8 @@ Rails.application.routes.draw do
ActiveAdmin.routes(self)
# mount Sidekiq::Web => '/sidekiq'
authenticate :admin_user do
# authenticate :admin_user do
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => "/admin/tasks"
end
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html