mirror of
https://github.com/chatwoot/chatwoot.git
synced 2026-06-04 21:02:35 +08:00
Adds a platform-wide status banner system to notify all users about external service outages. Super Admins can create, edit, and manage banners via the Super Admin console. Banners support markdown for links and are dismissible by users. <img width="1099" height="236" alt="image" src="https://github.com/user-attachments/assets/047a7994-d885-4a8a-b9c4-aeb32f15474a" /> ## How to test 1. Set `ENABLE_PLATFORM_BANNERS=true` in your environment 2. Go to Super Admin → Platform Banners 3. Create a banner with a message like: `Elevated error rates from Meta APIs. [Check status](https://metastatus.com)` 4. Select a banner type: `info` (blue), `warning` (amber), or `error` (red) 5. Visit the dashboard — the banner should appear at the top 6. Click "Dismiss" — the banner hides and stays dismissed across page reloads 7. Deactivate the banner in Super Admin — it disappears on next page load ## What changed - New `PlatformBanner` model with `banner_message`, `banner_type` (info/warning/error), and `active` flag - Super Admin CRUD via Administrate (controller, dashboard, routes, sidebar icon) - `DashboardController` serves active banners via `globalConfig` - `StatusBanner.vue` component renders banners with markdown support and per-banner localStorage dismiss - Feature gated behind `ENABLE_PLATFORM_BANNERS` env var 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
119 lines
3.8 KiB
Ruby
119 lines
3.8 KiB
Ruby
class DashboardController < ActionController::Base
|
|
include SwitchLocale
|
|
|
|
GLOBAL_CONFIG_KEYS = %w[
|
|
LOGO
|
|
LOGO_DARK
|
|
LOGO_THUMBNAIL
|
|
INSTALLATION_NAME
|
|
WIDGET_BRAND_URL
|
|
TERMS_URL
|
|
BRAND_URL
|
|
BRAND_NAME
|
|
PRIVACY_URL
|
|
DISPLAY_MANIFEST
|
|
CREATE_NEW_ACCOUNT_FROM_DASHBOARD
|
|
CHATWOOT_INBOX_TOKEN
|
|
API_CHANNEL_NAME
|
|
API_CHANNEL_THUMBNAIL
|
|
CLOUD_ANALYTICS_TOKEN
|
|
DIRECT_UPLOADS_ENABLED
|
|
MAXIMUM_FILE_UPLOAD_SIZE
|
|
HCAPTCHA_SITE_KEY
|
|
LOGOUT_REDIRECT_LINK
|
|
DISABLE_USER_PROFILE_UPDATE
|
|
DEPLOYMENT_ENV
|
|
INSTALLATION_PRICING_PLAN
|
|
].freeze
|
|
|
|
before_action :set_application_pack
|
|
before_action :set_global_config
|
|
before_action :set_dashboard_scripts
|
|
around_action :switch_locale
|
|
before_action :ensure_installation_onboarding, only: [:index]
|
|
before_action :render_hc_if_custom_domain, only: [:index]
|
|
before_action :ensure_html_format
|
|
layout 'vueapp'
|
|
|
|
def index; end
|
|
|
|
private
|
|
|
|
def ensure_html_format
|
|
render json: { error: 'Please use API routes instead of dashboard routes for JSON requests' }, status: :not_acceptable if request.format.json?
|
|
end
|
|
|
|
def set_global_config
|
|
@global_config = GlobalConfig.get(*GLOBAL_CONFIG_KEYS).merge(app_config)
|
|
end
|
|
|
|
def set_dashboard_scripts
|
|
@dashboard_scripts = sensitive_path? ? nil : GlobalConfig.get_value('DASHBOARD_SCRIPTS')
|
|
end
|
|
|
|
def ensure_installation_onboarding
|
|
redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)
|
|
end
|
|
|
|
def render_hc_if_custom_domain
|
|
domain = request.host
|
|
return if domain == URI.parse(ENV.fetch('FRONTEND_URL', '')).host
|
|
|
|
@portal = Portal.find_by(custom_domain: domain)
|
|
return unless @portal
|
|
|
|
@locale = @portal.default_locale
|
|
render 'public/api/v1/portals/show', layout: 'portal', portal: @portal and return
|
|
end
|
|
|
|
def app_config
|
|
{
|
|
APP_VERSION: Chatwoot.config[:version],
|
|
VAPID_PUBLIC_KEY: VapidService.public_key,
|
|
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'),
|
|
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
|
|
INSTAGRAM_APP_ID: GlobalConfigService.load('INSTAGRAM_APP_ID', ''),
|
|
TIKTOK_APP_ID: GlobalConfigService.load('TIKTOK_APP_ID', ''),
|
|
FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v18.0'),
|
|
WHATSAPP_APP_ID: GlobalConfigService.load('WHATSAPP_APP_ID', ''),
|
|
WHATSAPP_CONFIGURATION_ID: GlobalConfigService.load('WHATSAPP_CONFIGURATION_ID', ''),
|
|
IS_ENTERPRISE: ChatwootApp.enterprise?,
|
|
AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''),
|
|
GIT_SHA: GIT_HASH,
|
|
ALLOWED_LOGIN_METHODS: allowed_login_methods,
|
|
ACTIVE_PLATFORM_BANNERS: active_platform_banners
|
|
}
|
|
end
|
|
|
|
def active_platform_banners
|
|
return [] unless ChatwootApp.chatwoot_cloud?
|
|
|
|
PlatformBanner.active.order(created_at: :desc).as_json(only: %i[id banner_message banner_type updated_at])
|
|
end
|
|
|
|
def allowed_login_methods
|
|
methods = ['email']
|
|
methods << 'google_oauth' if GlobalConfigService.load('ENABLE_GOOGLE_OAUTH_LOGIN', 'true').to_s != 'false'
|
|
methods << 'saml' if ChatwootHub.pricing_plan != 'community' && GlobalConfigService.load('ENABLE_SAML_SSO_LOGIN', 'true').to_s != 'false'
|
|
methods
|
|
end
|
|
|
|
def set_application_pack
|
|
@application_pack = if request.path.include?('/auth') || request.path.include?('/login')
|
|
'v3app'
|
|
else
|
|
'dashboard'
|
|
end
|
|
end
|
|
|
|
def sensitive_path?
|
|
# dont load dashboard scripts on sensitive paths like password reset
|
|
sensitive_paths = [edit_user_password_path].freeze
|
|
|
|
# remove app prefix
|
|
current_path = request.path.gsub(%r{^/app}, '')
|
|
|
|
sensitive_paths.include?(current_path)
|
|
end
|
|
end
|