From cd82d079e37512c5cfca6ae07309a37f2d12257a Mon Sep 17 00:00:00 2001 From: Rogee Date: Thu, 30 Jul 2026 16:55:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(ws):=20=E5=8F=91=E9=80=81=20WebSocket=20?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E7=BA=A7=20Ping=20=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=B8=A7=E9=98=B2=E6=AD=A2=2060s=20=E5=90=8E=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E6=96=AD=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit writePump 只发 ActionCable 文本 JSON ping,从不发 websocket.PingMessage, 导致 readPump 的 PongHandler 永远不触发,ReadDeadline(60s) 到期后连接 被强制关闭。前端每 60 秒弹出"离线""重连"通知。 修复:在 ticker 中先发 PingMessage 控制帧(浏览器自动回 Pong 重置 ReadDeadline),再发 ActionCable 文本 ping(保活 ConnectionMonitor)。 同时包含此前会话中的其他变更: - 移除安全设置(SAML)页面及路由 - 新增超级管理员控制台前端页面 - platform agent_bots 路由使用 Platform* handler - 超级管理员入口改为 SPA 内路由跳转 --- backend/internal/handler/ws/handler.go | 16 +- backend/internal/router/router.go | 11 +- .../app/javascript/dashboard/api/platform.js | 110 +++++ .../components-next/sidebar/Sidebar.vue | 6 - .../sidebar/SidebarProfileMenu.vue | 4 +- .../dashboard/i18n/locale/en/index.js | 2 + .../dashboard/i18n/locale/en/settings.json | 73 ---- .../dashboard/i18n/locale/en/superAdmin.json | 164 ++++++++ .../dashboard/i18n/locale/zh_CN/index.js | 2 + .../dashboard/i18n/locale/zh_CN/settings.json | 73 ---- .../i18n/locale/zh_CN/superAdmin.json | 164 ++++++++ .../routes/dashboard/dashboard.routes.js | 2 + .../settings/security/security.routes.js | 35 -- .../dashboard/settings/settings.routes.js | 2 - .../routes/dashboard/super_admin/Layout.vue | 136 ++++++ .../dashboard/super_admin/accounts/Index.vue | 236 +++++++++++ .../dashboard/super_admin/agentBots/Index.vue | 252 +++++++++++ .../super_admin/components/FormDialog.vue | 142 +++++++ .../super_admin/components/PageHeader.vue | 84 ++++ .../dashboard/super_admin/configs/Index.vue | 240 +++++++++++ .../dashboard/super_admin/dashboard/Index.vue | 143 +++++++ .../super_admin/superAdmin.routes.js | 53 +++ .../dashboard/super_admin/users/Index.vue | 255 ++++++++++++ .../app/javascript/dashboard/store/index.js | 2 + .../dashboard/store/modules/platform.js | 394 ++++++++++++++++++ .../dashboard/store/mutation-types.js | 19 + 26 files changed, 2421 insertions(+), 199 deletions(-) create mode 100644 frontend/app/javascript/dashboard/api/platform.js create mode 100644 frontend/app/javascript/dashboard/i18n/locale/en/superAdmin.json create mode 100644 frontend/app/javascript/dashboard/i18n/locale/zh_CN/superAdmin.json delete mode 100644 frontend/app/javascript/dashboard/routes/dashboard/settings/security/security.routes.js create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/Layout.vue create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/accounts/Index.vue create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/agentBots/Index.vue create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/components/FormDialog.vue create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/components/PageHeader.vue create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/configs/Index.vue create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/dashboard/Index.vue create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/superAdmin.routes.js create mode 100644 frontend/app/javascript/dashboard/routes/dashboard/super_admin/users/Index.vue create mode 100644 frontend/app/javascript/dashboard/store/modules/platform.js diff --git a/backend/internal/handler/ws/handler.go b/backend/internal/handler/ws/handler.go index aebea0b0..09b385b2 100644 --- a/backend/internal/handler/ws/handler.go +++ b/backend/internal/handler/ws/handler.go @@ -194,7 +194,19 @@ func (h *Handler) writePump(client *Client) { } case <-ticker.C: - // Send ActionCable-level ping message (JSON text frame). + // Send WebSocket protocol-level ping control frame. + // The browser automatically responds with a Pong, which triggers + // the PongHandler in readPump and resets the ReadDeadline. + // Without this, the ReadDeadline (PongWait=60s) expires and the + // connection is forcibly closed, causing the client to show + // "offline" / "reconnecting" notifications every ~60 seconds. + client.Conn.SetWriteDeadline(time.Now().Add(WriteWait)) + if err := client.Conn.WriteMessage(websocket.PingMessage, nil); err != nil { + logger.L().Errorf("ws: ws-ping control frame failed for user=%d: %v", client.UserID, err) + return + } + + // Also send ActionCable-level ping message (JSON text frame). // The JS ConnectionMonitor expects periodic ping messages to // keep the connection alive (staleThreshold = 6s by default). pingMsg, _ := json.Marshal(PingFrame{ @@ -203,7 +215,7 @@ func (h *Handler) writePump(client *Client) { }) client.Conn.SetWriteDeadline(time.Now().Add(WriteWait)) if err := client.Conn.WriteMessage(websocket.TextMessage, pingMsg); err != nil { - logger.L().Errorf("ws: ping write failed for user=%d: %v", client.UserID, err) + logger.L().Errorf("ws: actioncable ping write failed for user=%d: %v", client.UserID, err) return } } diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index 4ce808d1..2eda4b3d 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -2040,11 +2040,12 @@ func registerPlatformRoutes(g *gin.RouterGroup, h *Handlers) { // Agent Bot CRUD (super-admin, global bots without account_id) // Reference: Chatwoot namespace :agent_bots under :platform_app - g.GET("/agent_bots", h.AgentBot.List) - g.POST("/agent_bots", h.AgentBot.Create) - g.GET("/agent_bots/:id", h.AgentBot.Get) - g.PUT("/agent_bots/:id", h.AgentBot.Update) - g.DELETE("/agent_bots/:id", h.AgentBot.Delete) + // Use Platform* handlers — these don't require account_id from URL params. + g.GET("/agent_bots", h.AgentBot.PlatformList) + g.POST("/agent_bots", h.AgentBot.PlatformCreate) + g.GET("/agent_bots/:id", h.AgentBot.PlatformGet) + g.PUT("/agent_bots/:id", h.AgentBot.PlatformUpdate) + g.DELETE("/agent_bots/:id", h.AgentBot.PlatformDelete) g.POST("/agent_bots/:id/reset_token", h.AgentBot.ResetToken) g.POST("/agent_bots/:id/reset_secret", h.AgentBot.ResetSecret) g.POST("/agent_bots/:id/delete_avatar", h.AgentBot.DeleteAvatar) diff --git a/frontend/app/javascript/dashboard/api/platform.js b/frontend/app/javascript/dashboard/api/platform.js new file mode 100644 index 00000000..5433470c --- /dev/null +++ b/frontend/app/javascript/dashboard/api/platform.js @@ -0,0 +1,110 @@ +/* global axios */ + +/** + * Platform API client for Super Admin endpoints. + * All endpoints are under /platform/api/v1/ and require super_admin auth. + */ +class PlatformAPI { + constructor() { + this.baseURL = '/platform/api/v1'; + } + + // ── Accounts ────────────────────────────────────────── + getAccounts(params = {}) { + return axios.get(`${this.baseURL}/accounts`, { params }); + } + + getAccount(id) { + return axios.get(`${this.baseURL}/accounts/${id}`); + } + + createAccount(data) { + return axios.post(`${this.baseURL}/accounts`, data); + } + + updateAccount(id, data) { + return axios.patch(`${this.baseURL}/accounts/${id}`, data); + } + + deleteAccount(id) { + return axios.delete(`${this.baseURL}/accounts/${id}`); + } + + // ── Users ───────────────────────────────────────────── + getUsers(params = {}) { + return axios.get(`${this.baseURL}/users`, { params }); + } + + getUser(id) { + return axios.get(`${this.baseURL}/users/${id}`); + } + + createUser(data) { + return axios.post(`${this.baseURL}/users`, data); + } + + updateUser(id, data) { + return axios.patch(`${this.baseURL}/users/${id}`, data); + } + + deleteUser(id) { + return axios.delete(`${this.baseURL}/users/${id}`); + } + + // ── Account Users ───────────────────────────────────── + getAccountUsers(accountId) { + return axios.get( + `${this.baseURL}/accounts/${accountId}/account_users` + ); + } + + createAccountUser(accountId, data) { + return axios.post( + `${this.baseURL}/accounts/${accountId}/account_users`, + data + ); + } + + deleteAccountUser(accountId, userId) { + return axios.delete( + `${this.baseURL}/accounts/${userId}/account_users`, + { data: { user_id: userId } } + ); + } + + // ── Agent Bots (platform-level) ─────────────────────── + getAgentBots() { + return axios.get(`${this.baseURL}/agent_bots`); + } + + createAgentBot(data) { + return axios.post(`${this.baseURL}/agent_bots`, data); + } + + updateAgentBot(id, data) { + return axios.put(`${this.baseURL}/agent_bots/${id}`, data); + } + + deleteAgentBot(id) { + return axios.delete(`${this.baseURL}/agent_bots/${id}`); + } + + // ── Installation Config ─────────────────────────────── + getInstallationConfigs(params = {}) { + return axios.get(`${this.baseURL}/installation_configs`, { params }); + } + + createInstallationConfig(data) { + return axios.post(`${this.baseURL}/installation_configs`, data); + } + + updateInstallationConfig(id, data) { + return axios.put(`${this.baseURL}/installation_configs/${id}`, data); + } + + deleteInstallationConfig(id) { + return axios.delete(`${this.baseURL}/installation_configs/${id}`); + } +} + +export default new PlatformAPI(); diff --git a/frontend/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/frontend/app/javascript/dashboard/components-next/sidebar/Sidebar.vue index 85e2a15c..746b1d51 100644 --- a/frontend/app/javascript/dashboard/components-next/sidebar/Sidebar.vue +++ b/frontend/app/javascript/dashboard/components-next/sidebar/Sidebar.vue @@ -783,12 +783,6 @@ const menuItems = computed(() => { icon: 'i-lucide-workflow', to: accountScopedRoute('conversation_workflow_index'), }, - { - name: 'Settings Security', - label: t('SIDEBAR.SECURITY'), - icon: 'i-lucide-shield', - to: accountScopedRoute('security_settings_index'), - }, { name: 'Settings Billing', label: t('SIDEBAR.BILLING'), diff --git a/frontend/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue b/frontend/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue index ffe576e3..37f479c8 100644 --- a/frontend/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue +++ b/frontend/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue @@ -104,9 +104,7 @@ const menuItems = computed(() => { showOnCustomBrandedInstance: true, label: t('SIDEBAR_ITEMS.SUPER_ADMIN_CONSOLE'), icon: 'i-lucide-castle', - link: '/super_admin', - nativeLink: true, - target: '_blank', + link: { name: 'super_admin_dashboard' }, }, { show: true, diff --git a/frontend/app/javascript/dashboard/i18n/locale/en/index.js b/frontend/app/javascript/dashboard/i18n/locale/en/index.js index 6246a1a6..a8b85d9b 100644 --- a/frontend/app/javascript/dashboard/i18n/locale/en/index.js +++ b/frontend/app/javascript/dashboard/i18n/locale/en/index.js @@ -35,6 +35,7 @@ import settings from './settings.json'; import signup from './signup.json'; import sla from './sla.json'; import snooze from './snooze.json'; +import superAdmin from './superAdmin.json'; import teamsSettings from './teamsSettings.json'; import whatsappTemplates from './whatsappTemplates.json'; import contentTemplates from './contentTemplates.json'; @@ -79,6 +80,7 @@ export default { ...signup, ...sla, ...snooze, + ...superAdmin, ...teamsSettings, ...whatsappTemplates, ...contentTemplates, diff --git a/frontend/app/javascript/dashboard/i18n/locale/en/settings.json b/frontend/app/javascript/dashboard/i18n/locale/en/settings.json index 93e9c7cd..ac871ef0 100644 --- a/frontend/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/frontend/app/javascript/dashboard/i18n/locale/en/settings.json @@ -381,7 +381,6 @@ "INFO_SHORT": "Automatically mark offline when you aren't using the app." }, "DOCS": "Read docs", - "SECURITY": "Security", "CAPTAIN_AI": "Copilot Configuration", "CONVERSATION_WORKFLOW": "Conversation Workflow" }, @@ -568,78 +567,6 @@ } } }, - "SECURITY_SETTINGS": { - "TITLE": "Security", - "DESCRIPTION": "Manage your account security settings.", - "LINK_TEXT": "Learn more about SAML SSO", - "SAML_DISABLED_MESSAGE": "SAML SSO is currently disabled. Please contact your administrator to enable this feature.", - "SAML": { - "TITLE": "SAML SSO", - "NOTE": "Configure SAML single sign-on for your account. Users will authenticate through your identity provider instead of using email/password.", - "ACS_URL": { - "LABEL": "ACS URL", - "TOOLTIP": "Assertion Consumer Service URL - Configure this URL in your IdP as the destination for SAML responses" - }, - "SSO_URL": { - "LABEL": "SSO URL", - "HELP": "The URL where SAML authentication requests will be sent", - "PLACEHOLDER": "https://your-idp.com/saml/sso" - }, - "CERTIFICATE": { - "LABEL": "Signing certificate in PEM format", - "HELP": "The public certificate from your identity provider used to verify SAML responses", - "PLACEHOLDER": "-----BEGIN CERTIFICATE-----\nMIIC..." - }, - "FINGERPRINT": { - "LABEL": "Fingerprint", - "TOOLTIP": "SHA-1 fingerprint of the certificate - Use this to verify the certificate in your IdP configuration" - }, - "COPY_SUCCESS": "Copied to clipboard", - "SP_ENTITY_ID": { - "LABEL": "SP Entity ID", - "HELP": "Unique identifier for this application as a service provider (auto-generated).", - "TOOLTIP": "Unique identifier for Chatwoot as the Service Provider - Configure this in your IdP settings" - }, - "IDP_ENTITY_ID": { - "LABEL": "Identity Provider Entity ID", - "HELP": "Unique identifier for your identity provider (usually found in IdP configuration)", - "PLACEHOLDER": "https://your-idp.com/saml" - }, - "UPDATE_BUTTON": "Update SAML Settings", - "API": { - "SUCCESS": "SAML settings updated successfully", - "ERROR": "Failed to update SAML settings", - "ERROR_LOADING": "Failed to load SAML settings", - "DISABLED": "SAML settings disabled successfully" - }, - "VALIDATION": { - "REQUIRED_FIELDS": "SSO URL, Identity Provider Entity ID, and Certificate are required fields", - "SSO_URL_ERROR": "Please enter a valid SSO URL", - "CERTIFICATE_ERROR": "Certificate is required", - "IDP_ENTITY_ID_ERROR": "Identity Provider Entity ID is required" - }, - "ENTERPRISE_PAYWALL": { - "AVAILABLE_ON": "The SAML SSO feature is only available in the Enterprise plans.", - "UPGRADE_PROMPT": "Upgrade to an Enterprise plan to access SAML single sign-on and other advanced security features.", - "ASK_ADMIN": "Please reach out to your administrator for the upgrade." - }, - "PAYWALL": { - "TITLE": "Upgrade to enable SAML SSO", - "AVAILABLE_ON": "The SAML SSO feature is only available in the Enterprise plans.", - "UPGRADE_PROMPT": "Upgrade your plan to get access to SAML single sign-on and other advanced features.", - "UPGRADE_NOW": "Upgrade now", - "CANCEL_ANYTIME": "You can change or cancel your plan anytime" - }, - "ATTRIBUTE_MAPPING": { - "TITLE": "SAML Attribute Setup", - "DESCRIPTION": "The following attribute mappings must be configured in your identity provider" - }, - "INFO_SECTION": { - "TITLE": "Service Provider Information", - "TOOLTIP": "Copy these values and configure them in your Identity Provider to establish the SAML connection" - } - } - }, "CONVERSATION_WORKFLOW": { "INDEX": { "HEADER": { diff --git a/frontend/app/javascript/dashboard/i18n/locale/en/superAdmin.json b/frontend/app/javascript/dashboard/i18n/locale/en/superAdmin.json new file mode 100644 index 00000000..7e76ab59 --- /dev/null +++ b/frontend/app/javascript/dashboard/i18n/locale/en/superAdmin.json @@ -0,0 +1,164 @@ +{ + "SUPER_ADMIN": { + "SIDEBAR": { + "TITLE": "Super Admin", + "DASHBOARD": "Dashboard", + "ACCOUNTS": "Accounts", + "USERS": "Users", + "AGENT_BOTS": "Agent Bots", + "CONFIGS": "Installation Config", + "BACK_TO_DASHBOARD": "Back to Dashboard" + }, + "NO_ACCESS": { + "TITLE": "Access Denied", + "MESSAGE": "This page is only accessible to super admins." + }, + "COMMON": { + "EDIT": "Edit", + "DELETE": "Delete", + "CONFIRM_DELETE": "Confirm Delete", + "CANCEL": "Cancel" + }, + "DASHBOARD": { + "TITLE": "Platform Dashboard", + "DESCRIPTION": "Platform-wide statistics and overview", + "STATS": { + "ACCOUNTS": "Total Accounts", + "USERS": "Total Users" + }, + "RECENT_ACCOUNTS": "Recent Accounts" + }, + "ACCOUNTS": { + "TITLE": "Account Management", + "DESCRIPTION": "Manage all accounts on the platform", + "SEARCH": "Search accounts...", + "EMPTY": "No accounts found", + "TABLE": { + "NAME": "Name", + "STATUS": "Status", + "ACTIONS": "Actions" + }, + "CREATE": { + "TITLE": "Create Account", + "SUCCESS": "Account created successfully" + }, + "EDIT": { + "TITLE": "Edit Account", + "SUCCESS": "Account updated successfully" + }, + "DELETE": { + "TITLE": "Delete Account", + "MESSAGE": "Are you sure you want to delete account \"{name}\"? This action cannot be undone.", + "SUCCESS": "Account deleted successfully" + }, + "FIELDS": { + "NAME": "Account Name", + "NAME_PLACEHOLDER": "Enter account name" + }, + "ERROR": "Operation failed, please try again" + }, + "USERS": { + "TITLE": "User Management", + "DESCRIPTION": "Manage all users on the platform", + "SEARCH": "Search users...", + "EMPTY": "No users found", + "TABLE": { + "NAME": "User", + "TYPE": "Type", + "ACTIONS": "Actions" + }, + "CREATE": { + "TITLE": "Create User", + "SUCCESS": "User created successfully" + }, + "EDIT": { + "TITLE": "Edit User", + "SUCCESS": "User updated successfully" + }, + "DELETE": { + "TITLE": "Delete User", + "MESSAGE": "Are you sure you want to delete user \"{name}\"? This action cannot be undone.", + "SUCCESS": "User deleted successfully" + }, + "FIELDS": { + "NAME": "Name", + "NAME_PLACEHOLDER": "Enter user name", + "EMAIL": "Email", + "PASSWORD": "Password", + "PASSWORD_HINT": "Leave empty to keep current password", + "TYPE": "User Type", + "TYPE_USER": "Regular User", + "TYPE_SUPER_ADMIN": "Super Admin", + "TYPE_HINT": "Set to SuperAdmin to grant super admin privileges" + }, + "ERROR": "Operation failed, please try again" + }, + "AGENT_BOTS": { + "TITLE": "Agent Bot Management", + "DESCRIPTION": "Manage platform-level agent bots", + "SEARCH": "Search agent bots...", + "EMPTY": "No agent bots found", + "GLOBAL_BADGE": "System Bot", + "TABLE": { + "NAME": "Bot", + "URL": "Webhook URL", + "ACTIONS": "Actions" + }, + "CREATE": { + "TITLE": "Create Agent Bot", + "SUCCESS": "Agent bot created successfully" + }, + "EDIT": { + "TITLE": "Edit Agent Bot", + "SUCCESS": "Agent bot updated successfully" + }, + "DELETE": { + "TITLE": "Delete Agent Bot", + "MESSAGE": "Are you sure you want to delete agent bot \"{name}\"?", + "SUCCESS": "Agent bot deleted successfully" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DESCRIPTION_PLACEHOLDER": "Enter bot description", + "URL": "Webhook URL", + "TYPE": "Type", + "TYPE_HINT": "Defaults to webhook" + }, + "ERROR": "Operation failed, please try again" + }, + "CONFIGS": { + "TITLE": "Installation Config", + "DESCRIPTION": "Manage platform-wide key-value configuration", + "SEARCH": "Search configs...", + "EMPTY": "No configuration items found", + "TABLE": { + "NAME": "Name", + "VALUE": "Value", + "ACTIONS": "Actions" + }, + "CREATE": { + "TITLE": "Create Config", + "SUCCESS": "Configuration created successfully" + }, + "EDIT": { + "TITLE": "Edit Config", + "SUCCESS": "Configuration updated successfully" + }, + "DELETE": { + "TITLE": "Delete Config", + "MESSAGE": "Are you sure you want to delete config \"{name}\"?", + "SUCCESS": "Configuration deleted successfully" + }, + "FIELDS": { + "NAME": "Config Name", + "NAME_HINT": "Use uppercase snake_case, e.g. INSTALLATION_NAME", + "VALUE": "Config Value", + "VALUE_PLACEHOLDER": "Enter config value", + "LOCKED": "Locked", + "LOCKED_HINT": "When locked, non-super-admins cannot modify" + }, + "ERROR": "Operation failed, please try again" + } + } +} diff --git a/frontend/app/javascript/dashboard/i18n/locale/zh_CN/index.js b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/index.js index 3837393b..542ef79b 100644 --- a/frontend/app/javascript/dashboard/i18n/locale/zh_CN/index.js +++ b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/index.js @@ -35,6 +35,7 @@ import setNewPassword from './setNewPassword.json'; import settings from './settings.json'; import signup from './signup.json'; import sla from './sla.json'; +import superAdmin from './superAdmin.json'; import teamsSettings from './teamsSettings.json'; import whatsappTemplates from './whatsappTemplates.json'; @@ -76,6 +77,7 @@ export default { ...settings, ...signup, ...sla, + ...superAdmin, ...teamsSettings, ...whatsappTemplates, }; diff --git a/frontend/app/javascript/dashboard/i18n/locale/zh_CN/settings.json b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/settings.json index 82fd94d2..035753e8 100644 --- a/frontend/app/javascript/dashboard/i18n/locale/zh_CN/settings.json +++ b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/settings.json @@ -381,7 +381,6 @@ "INFO_SHORT": "当您不使用应用程序时自动标记离线。" }, "DOCS": "阅读文档", - "SECURITY": "安全", "CAPTAIN_AI": "Copilot 配置", "CONVERSATION_WORKFLOW": "会话工作流" }, @@ -568,78 +567,6 @@ } } }, - "SECURITY_SETTINGS": { - "TITLE": "安全", - "DESCRIPTION": "管理您的账户安全设置。", - "LINK_TEXT": "了解更多关于 SAML SSO 的信息", - "SAML_DISABLED_MESSAGE": "SAML SSO is currently disabled. Please contact your administrator to enable this feature.", - "SAML": { - "TITLE": "SAML SSO", - "NOTE": "Configure SAML single sign-on for your account. Users will authenticate through your identity provider instead of using email/password.", - "ACS_URL": { - "LABEL": "ACS URL", - "TOOLTIP": "Assertion Consumer Service URL - Configure this URL in your IdP as the destination for SAML responses" - }, - "SSO_URL": { - "LABEL": "SSO URL", - "HELP": "The URL where SAML authentication requests will be sent", - "PLACEHOLDER": "https://your-idp.com/saml/sso" - }, - "CERTIFICATE": { - "LABEL": "Signing certificate in PEM format", - "HELP": "The public certificate from your identity provider used to verify SAML responses", - "PLACEHOLDER": "-----BEGIN CERTIFICATE-----\nMIIC..." - }, - "FINGERPRINT": { - "LABEL": "Fingerprint", - "TOOLTIP": "SHA-1 fingerprint of the certificate - Use this to verify the certificate in your IdP configuration" - }, - "COPY_SUCCESS": "已复制到剪贴板", - "SP_ENTITY_ID": { - "LABEL": "SP Entity ID", - "HELP": "Unique identifier for this application as a service provider (auto-generated).", - "TOOLTIP": "Unique identifier for Chatwoot as the Service Provider - Configure this in your IdP settings" - }, - "IDP_ENTITY_ID": { - "LABEL": "Identity Provider Entity ID", - "HELP": "Unique identifier for your identity provider (usually found in IdP configuration)", - "PLACEHOLDER": "https://your-idp.com/saml" - }, - "UPDATE_BUTTON": "更新 SAML 设置", - "API": { - "SUCCESS": "SAML settings updated successfully", - "ERROR": "Failed to update SAML settings", - "ERROR_LOADING": "Failed to load SAML settings", - "DISABLED": "SAML settings disabled successfully" - }, - "VALIDATION": { - "REQUIRED_FIELDS": "SSO URL, Identity Provider Entity ID, and Certificate are required fields", - "SSO_URL_ERROR": "Please enter a valid SSO URL", - "CERTIFICATE_ERROR": "Certificate is required", - "IDP_ENTITY_ID_ERROR": "Identity Provider Entity ID is required" - }, - "ENTERPRISE_PAYWALL": { - "AVAILABLE_ON": "The SAML SSO feature is only available in the Enterprise plans.", - "UPGRADE_PROMPT": "Upgrade to an Enterprise plan to access SAML single sign-on and other advanced security features.", - "ASK_ADMIN": "请联系您的管理员进行升级。" - }, - "PAYWALL": { - "TITLE": "Upgrade to enable SAML SSO", - "AVAILABLE_ON": "The SAML SSO feature is only available in the Enterprise plans.", - "UPGRADE_PROMPT": "Upgrade your plan to get access to SAML single sign-on and other advanced features.", - "UPGRADE_NOW": "立即升级", - "CANCEL_ANYTIME": "您可以随时更改或取消您的计划" - }, - "ATTRIBUTE_MAPPING": { - "TITLE": "SAML Attribute Setup", - "DESCRIPTION": "The following attribute mappings must be configured in your identity provider" - }, - "INFO_SECTION": { - "TITLE": "Service Provider Information", - "TOOLTIP": "Copy these values and configure them in your Identity Provider to establish the SAML connection" - } - } - }, "CONVERSATION_WORKFLOW": { "INDEX": { "HEADER": { diff --git a/frontend/app/javascript/dashboard/i18n/locale/zh_CN/superAdmin.json b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/superAdmin.json new file mode 100644 index 00000000..5a5cf8d9 --- /dev/null +++ b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/superAdmin.json @@ -0,0 +1,164 @@ +{ + "SUPER_ADMIN": { + "SIDEBAR": { + "TITLE": "超级管理控制台", + "DASHBOARD": "仪表盘", + "ACCOUNTS": "账户管理", + "USERS": "用户管理", + "AGENT_BOTS": "机器人管理", + "CONFIGS": "安装配置", + "BACK_TO_DASHBOARD": "返回工作台" + }, + "NO_ACCESS": { + "TITLE": "无权访问", + "MESSAGE": "此页面仅限超级管理员访问。" + }, + "COMMON": { + "EDIT": "编辑", + "DELETE": "删除", + "CONFIRM_DELETE": "确认删除", + "CANCEL": "取消" + }, + "DASHBOARD": { + "TITLE": "平台仪表盘", + "DESCRIPTION": "平台全局统计与概览", + "STATS": { + "ACCOUNTS": "账户总数", + "USERS": "用户总数" + }, + "RECENT_ACCOUNTS": "最近账户" + }, + "ACCOUNTS": { + "TITLE": "账户管理", + "DESCRIPTION": "管理平台上的所有账户", + "SEARCH": "搜索账户...", + "EMPTY": "暂无账户", + "TABLE": { + "NAME": "账户名称", + "STATUS": "状态", + "ACTIONS": "操作" + }, + "CREATE": { + "TITLE": "创建账户", + "SUCCESS": "账户创建成功" + }, + "EDIT": { + "TITLE": "编辑账户", + "SUCCESS": "账户更新成功" + }, + "DELETE": { + "TITLE": "删除账户", + "MESSAGE": "确定要删除账户「{name}」吗?此操作不可撤销。", + "SUCCESS": "账户删除成功" + }, + "FIELDS": { + "NAME": "账户名称", + "NAME_PLACEHOLDER": "输入账户名称" + }, + "ERROR": "操作失败,请重试" + }, + "USERS": { + "TITLE": "用户管理", + "DESCRIPTION": "管理平台上的所有用户", + "SEARCH": "搜索用户...", + "EMPTY": "暂无用户", + "TABLE": { + "NAME": "用户", + "TYPE": "类型", + "ACTIONS": "操作" + }, + "CREATE": { + "TITLE": "创建用户", + "SUCCESS": "用户创建成功" + }, + "EDIT": { + "TITLE": "编辑用户", + "SUCCESS": "用户更新成功" + }, + "DELETE": { + "TITLE": "删除用户", + "MESSAGE": "确定要删除用户「{name}」吗?此操作不可撤销。", + "SUCCESS": "用户删除成功" + }, + "FIELDS": { + "NAME": "姓名", + "NAME_PLACEHOLDER": "输入用户姓名", + "EMAIL": "邮箱", + "PASSWORD": "密码", + "PASSWORD_HINT": "留空则不修改密码", + "TYPE": "用户类型", + "TYPE_USER": "普通用户", + "TYPE_SUPER_ADMIN": "超级管理员", + "TYPE_HINT": "设置为 SuperAdmin 可授予超级管理员权限" + }, + "ERROR": "操作失败,请重试" + }, + "AGENT_BOTS": { + "TITLE": "机器人管理", + "DESCRIPTION": "管理平台级 Agent Bot", + "SEARCH": "搜索机器人...", + "EMPTY": "暂无机器人", + "GLOBAL_BADGE": "系统机器人", + "TABLE": { + "NAME": "机器人", + "URL": "Webhook URL", + "ACTIONS": "操作" + }, + "CREATE": { + "TITLE": "创建机器人", + "SUCCESS": "机器人创建成功" + }, + "EDIT": { + "TITLE": "编辑机器人", + "SUCCESS": "机器人更新成功" + }, + "DELETE": { + "TITLE": "删除机器人", + "MESSAGE": "确定要删除机器人「{name}」吗?", + "SUCCESS": "机器人删除成功" + }, + "FIELDS": { + "NAME": "名称", + "DESCRIPTION": "描述", + "DESCRIPTION_PLACEHOLDER": "输入机器人描述", + "URL": "Webhook URL", + "TYPE": "类型", + "TYPE_HINT": "默认为 webhook" + }, + "ERROR": "操作失败,请重试" + }, + "CONFIGS": { + "TITLE": "安装配置", + "DESCRIPTION": "管理平台全局键值配置", + "SEARCH": "搜索配置...", + "EMPTY": "暂无配置项", + "TABLE": { + "NAME": "配置名称", + "VALUE": "值", + "ACTIONS": "操作" + }, + "CREATE": { + "TITLE": "创建配置", + "SUCCESS": "配置创建成功" + }, + "EDIT": { + "TITLE": "编辑配置", + "SUCCESS": "配置更新成功" + }, + "DELETE": { + "TITLE": "删除配置", + "MESSAGE": "确定要删除配置「{name}」吗?", + "SUCCESS": "配置删除成功" + }, + "FIELDS": { + "NAME": "配置名称", + "NAME_HINT": "使用大写下划线格式,如 INSTALLATION_NAME", + "VALUE": "配置值", + "VALUE_PLACEHOLDER": "输入配置值", + "LOCKED": "锁定", + "LOCKED_HINT": "锁定后非超级管理员不可修改" + }, + "ERROR": "操作失败,请重试" + } + } +} diff --git a/frontend/app/javascript/dashboard/routes/dashboard/dashboard.routes.js b/frontend/app/javascript/dashboard/routes/dashboard/dashboard.routes.js index 9a460386..9e6e03d0 100644 --- a/frontend/app/javascript/dashboard/routes/dashboard/dashboard.routes.js +++ b/frontend/app/javascript/dashboard/routes/dashboard/dashboard.routes.js @@ -9,6 +9,7 @@ import { frontendURL } from '../../helper/URLHelper'; import helpcenterRoutes from './helpcenter/helpcenter.routes'; import campaignsRoutes from './campaigns/campaigns.routes'; import { routes as captainRoutes } from './captain/captain.routes'; +import { routes as superAdminRoutes } from './super_admin/superAdmin.routes'; import AppContainer from './Dashboard.vue'; import Suspended from './suspended/Index.vue'; import NoAccounts from './noAccounts/Index.vue'; @@ -47,6 +48,7 @@ export default { ...notificationRoutes, ...helpcenterRoutes.routes, ...campaignsRoutes.routes, + ...superAdminRoutes, ], }, { diff --git a/frontend/app/javascript/dashboard/routes/dashboard/settings/security/security.routes.js b/frontend/app/javascript/dashboard/routes/dashboard/settings/security/security.routes.js deleted file mode 100644 index 93d26e8c..00000000 --- a/frontend/app/javascript/dashboard/routes/dashboard/settings/security/security.routes.js +++ /dev/null @@ -1,35 +0,0 @@ -import { frontendURL } from '../../../../helper/URLHelper'; -import SettingsWrapper from '../SettingsWrapper.vue'; - -const SecurityIndex = { - render() { - return null; - }, -}; - -export default { - routes: [ - { - path: frontendURL('accounts/:accountId/settings/security'), - meta: { - permissions: ['administrator'], - }, - component: SettingsWrapper, - props: { - headerTitle: 'SECURITY_SETTINGS.TITLE', - icon: 'i-lucide-shield', - showNewButton: false, - }, - children: [ - { - path: '', - name: 'security_settings_index', - component: SecurityIndex, - meta: { - permissions: ['administrator'], - }, - }, - ], - }, - ], -}; diff --git a/frontend/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js b/frontend/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js index e277a0ef..a699855b 100644 --- a/frontend/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js +++ b/frontend/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js @@ -23,7 +23,6 @@ import sla from './sla/sla.routes'; import teams from './teams/teams.routes'; import customRoles from './customRoles/customRole.routes'; import profile from './profile/profile.routes'; -import security from './security/security.routes'; import conversationWorkflow from './conversationWorkflow/conversationWorkflow.routes'; import captain from './captain/captain.routes'; @@ -64,7 +63,6 @@ export default { ...teams.routes, ...customRoles.routes, ...profile.routes, - ...security.routes, ...conversationWorkflow.routes, ...captain.routes, ], diff --git a/frontend/app/javascript/dashboard/routes/dashboard/super_admin/Layout.vue b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/Layout.vue new file mode 100644 index 00000000..4cdb0811 --- /dev/null +++ b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/Layout.vue @@ -0,0 +1,136 @@ + + + diff --git a/frontend/app/javascript/dashboard/routes/dashboard/super_admin/accounts/Index.vue b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/accounts/Index.vue new file mode 100644 index 00000000..0585a2aa --- /dev/null +++ b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/accounts/Index.vue @@ -0,0 +1,236 @@ + + + diff --git a/frontend/app/javascript/dashboard/routes/dashboard/super_admin/agentBots/Index.vue b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/agentBots/Index.vue new file mode 100644 index 00000000..bd61f381 --- /dev/null +++ b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/agentBots/Index.vue @@ -0,0 +1,252 @@ + + + diff --git a/frontend/app/javascript/dashboard/routes/dashboard/super_admin/components/FormDialog.vue b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/components/FormDialog.vue new file mode 100644 index 00000000..cf5b919a --- /dev/null +++ b/frontend/app/javascript/dashboard/routes/dashboard/super_admin/components/FormDialog.vue @@ -0,0 +1,142 @@ + + +