From a129c90fa197a0cee58faaa66f3296c22a75dbfc Mon Sep 17 00:00:00 2001 From: pnoker Date: Fri, 19 Jun 2026 08:27:05 +0800 Subject: [PATCH] feat(alarm): rename event views to alarm, add alarm routes and i18n, extend login and settings nav --- src/api/mcp.ts | 17 +- src/api/token.ts | 2 + src/components/layout/Layout.vue | 12 + src/config/axios/index.ts | 8 +- src/config/constant/axios.ts | 7 + src/config/i18n/locales/en.ts | 17 + src/config/i18n/locales/zh.ts | 17 + src/config/router/settings.ts | 44 ++- src/config/settingsNav.ts | 330 +++++++++++++----- src/config/types/common.ts | 1 + src/store/modules/auth.ts | 21 +- src/views/login/Login.vue | 102 +++++- src/views/settings/Settings.vue | 235 +------------ .../settings/{event => alarm}/DeviceEvent.vue | 0 .../settings/{event => alarm}/DriverEvent.vue | 0 .../settings/{event => alarm}/Overview.vue | 0 .../settings/{event => alarm}/PointEvent.vue | 0 .../components/AgingBacklog.vue | 0 .../components/AlertActivityHeatmap.vue | 0 .../components/AlertCorrelation.vue | 0 .../components/AlertStormSources.vue | 0 .../components/AlertTypePie.vue | 0 .../components/ChangeImpact.vue | 0 .../components/EventTable.vue | 0 .../components/EventTrendChart.vue | 0 .../components/FlappingSources.vue | 0 .../{event => alarm}/components/MttaMttr.vue | 0 .../components/PeerDeviation.vue | 0 .../components/PointCoverageGap.vue | 0 .../components/ProtocolHealth.vue | 0 .../components/RecentUnconfirmed.vue | 0 .../components/SilentSources.vue | 0 .../components/TopSourcesChart.vue | 0 src/views/settings/mcp/McpServer.vue | 2 +- .../__snapshots__/api-contracts.test.ts.snap | 11 + 35 files changed, 492 insertions(+), 334 deletions(-) rename src/views/settings/{event => alarm}/DeviceEvent.vue (100%) rename src/views/settings/{event => alarm}/DriverEvent.vue (100%) rename src/views/settings/{event => alarm}/Overview.vue (100%) rename src/views/settings/{event => alarm}/PointEvent.vue (100%) rename src/views/settings/{event => alarm}/components/AgingBacklog.vue (100%) rename src/views/settings/{event => alarm}/components/AlertActivityHeatmap.vue (100%) rename src/views/settings/{event => alarm}/components/AlertCorrelation.vue (100%) rename src/views/settings/{event => alarm}/components/AlertStormSources.vue (100%) rename src/views/settings/{event => alarm}/components/AlertTypePie.vue (100%) rename src/views/settings/{event => alarm}/components/ChangeImpact.vue (100%) rename src/views/settings/{event => alarm}/components/EventTable.vue (100%) rename src/views/settings/{event => alarm}/components/EventTrendChart.vue (100%) rename src/views/settings/{event => alarm}/components/FlappingSources.vue (100%) rename src/views/settings/{event => alarm}/components/MttaMttr.vue (100%) rename src/views/settings/{event => alarm}/components/PeerDeviation.vue (100%) rename src/views/settings/{event => alarm}/components/PointCoverageGap.vue (100%) rename src/views/settings/{event => alarm}/components/ProtocolHealth.vue (100%) rename src/views/settings/{event => alarm}/components/RecentUnconfirmed.vue (100%) rename src/views/settings/{event => alarm}/components/SilentSources.vue (100%) rename src/views/settings/{event => alarm}/components/TopSourcesChart.vue (100%) diff --git a/src/api/mcp.ts b/src/api/mcp.ts index ced1e187b..22a94aa93 100644 --- a/src/api/mcp.ts +++ b/src/api/mcp.ts @@ -42,8 +42,8 @@ export const revokeMcpConnection = (id: string) => export const replaceMcpConnectionTools = (connectionId: string, toolIds: string[]) => httpPost>(`${API_MCP_BASE}/connection/tools/replace`, { - connection_id: connectionId, - tool_ids: toolIds, + connectionId, + toolIds, }); export const listMcpConnectionTool = (id: string) => @@ -51,7 +51,7 @@ export const listMcpConnectionTool = (id: string) => export const refreshMcpToolCatalog = () => httpPost>(`${API_MCP_BASE}/tool/catalog/refresh`); -export const listMcpTool = (query: { keyword?: string; risk_level?: string; limit?: number } = {}) => +export const listMcpTool = (query: { keyword?: string; riskLevel?: string; limit?: number } = {}) => httpPost>(`${API_MCP_BASE}/tool/list`, query); export const listMcpAudit = ( @@ -62,13 +62,4 @@ export const listMcpAudit = ( riskLevel?: string; limit?: number; } = {} -) => - httpPost>(`${API_MCP_BASE}/audit/list`, undefined, { - params: { - principal_id: params.principalId, - tool_id: params.toolId, - status: params.status, - risk_level: params.riskLevel, - limit: params.limit, - }, - }); +) => httpPost>(`${API_MCP_BASE}/audit/list`, undefined, { params }); diff --git a/src/api/token.ts b/src/api/token.ts index 873250f6f..1c3d184a7 100644 --- a/src/api/token.ts +++ b/src/api/token.ts @@ -22,6 +22,8 @@ export const generateSalt = (login: Login) => httpPost(`${API_AUTH_BASE}/token/s export const generateToken = (login: Login) => httpPost(`${API_AUTH_BASE}/token/generate`, login); +export const changePassword = (login: Login) => httpPost(`${API_AUTH_BASE}/token/change_password`, login); + export const cancelToken = (login: Login) => httpPost(`${API_AUTH_BASE}/token/cancel`, login); export const checkTokenValid = (login: Login) => httpPost(`${API_AUTH_BASE}/token/check`, login); diff --git a/src/components/layout/Layout.vue b/src/components/layout/Layout.vue index 85983bdda..8f4b3013f 100644 --- a/src/components/layout/Layout.vue +++ b/src/components/layout/Layout.vue @@ -158,6 +158,12 @@ profileDetail: 'nav.profileDetail', profileEdit: 'nav.profileEdit', settings: 'nav.settings', + settingsIdentity: 'nav.settingsIdentity', + settingsAccess: 'nav.settingsAccess', + settingsEventCommand: 'nav.settingsEventCommand', + settingsAudit: 'nav.settingsAudit', + settingsIntegration: 'nav.settingsIntegration', + settingsSystem: 'nav.settingsSystem', settingsUser: 'nav.settingsUser', settingsPrincipal: 'nav.settingsPrincipal', settingsTenantMembership: 'nav.settingsTenantMembership', @@ -222,6 +228,12 @@ device: 'Management', pointValue: 'TrendCharts', settings: 'Setting', + settingsIdentity: 'User', + settingsAccess: 'Stamp', + settingsEventCommand: 'Operation', + settingsAudit: 'Files', + settingsIntegration: 'Share', + settingsSystem: 'Tools', settingsUser: 'User', settingsPrincipal: 'Avatar', settingsTenantMembership: 'OfficeBuilding', diff --git a/src/config/axios/index.ts b/src/config/axios/index.ts index 98b51aa94..b6a05701d 100644 --- a/src/config/axios/index.ts +++ b/src/config/axios/index.ts @@ -18,7 +18,7 @@ import type { AxiosInstance } from 'axios'; import axios, { type AxiosError, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios'; import { ElNotification } from 'element-plus'; -import { AXIOS_CONFIG, AXIOS_ERROR_MESSAGES } from '@/config/constant/axios'; +import { AXIOS_CONFIG, AXIOS_ERROR_MESSAGES, PASSWORD_CHANGE_CODES } from '@/config/constant/axios'; import { AUTH_HEADERS } from '@/config/constant/common'; import { failMessage, warnMessage } from '@/utils/notificationUtil'; import { getStorage, removeStorage } from '@/utils/storageUtil'; @@ -111,6 +111,12 @@ request.interceptors.response.use( return response.data; } + // Password change / expiry: a business outcome, not an error. Pass the payload + // through silently so the login flow can open the password change dialog. + if (PASSWORD_CHANGE_CODES.includes(response.data?.code)) { + return Promise.reject(response.data); + } + // Handle unauthorized access if (status === AXIOS_CONFIG.UNAUTHORIZED_STATUS) { warnMessage(AXIOS_ERROR_MESSAGES.UNAUTHORIZED, AXIOS_ERROR_MESSAGES.UNAUTHORIZED_TITLE); diff --git a/src/config/constant/axios.ts b/src/config/constant/axios.ts index e3bedc8c7..a92d6bac6 100644 --- a/src/config/constant/axios.ts +++ b/src/config/constant/axios.ts @@ -32,3 +32,10 @@ export const AXIOS_ERROR_MESSAGES = { UNAUTHORIZED_TITLE: 'Login credentials expired', REQUEST_ERROR: 'API request error. Please contact the system administrator.', } as const; + +/** + * Business response codes the interceptor must pass through silently so the calling + * flow can react (e.g. drive the user to the password change dialog) instead of the + * generic error toast. Mirrors backend ErrorCode (R4031 change required, R4032 expired). + */ +export const PASSWORD_CHANGE_CODES = ['R4031', 'R4032'] as const; diff --git a/src/config/i18n/locales/en.ts b/src/config/i18n/locales/en.ts index ec6e06c3b..c17f17265 100644 --- a/src/config/i18n/locales/en.ts +++ b/src/config/i18n/locales/en.ts @@ -118,6 +118,12 @@ export default { pointDetail: 'Point Detail', pointEdit: 'Point Edit', settings: 'Settings', + settingsIdentity: 'Identity', + settingsAccess: 'Access Control', + settingsEventCommand: 'Event & Command', + settingsAudit: 'Audit', + settingsIntegration: 'Integration', + settingsSystem: 'System', settingsUser: 'User', settingsRole: 'Role', settingsResource: 'Resource', @@ -207,6 +213,17 @@ export default { passwordMin: 'Password must be at least 6 characters', loading: 'Logging in, please wait...', failed: 'Login failed. Please check your credentials and try again.', + changePasswordTitle: 'Change Password', + changePasswordRequired: 'Your password must be changed before you can log in', + passwordExpired: 'Your password has expired. Please change it and log in again', + newPassword: 'New Password', + newPasswordPlaceholder: 'Please enter the new password', + confirmPassword: 'Confirm New Password', + confirmPasswordPlaceholder: 'Please re-enter the new password', + confirmPasswordMismatch: 'The two passwords do not match', + changePasswordSubmit: 'Submit and log in again', + changePasswordSuccess: 'Password changed. Please log in with the new password', + changePasswordFailed: 'Failed to change password. Please try again', }, error: { forbidden: 'Sorry, you do not have permission to access this page', diff --git a/src/config/i18n/locales/zh.ts b/src/config/i18n/locales/zh.ts index 592e258d5..be5c7c69f 100644 --- a/src/config/i18n/locales/zh.ts +++ b/src/config/i18n/locales/zh.ts @@ -116,6 +116,12 @@ export default { pointDetail: '位号详情', pointEdit: '位号编辑', settings: '设置', + settingsIdentity: '身份', + settingsAccess: '权限', + settingsEventCommand: '事件与指令', + settingsAudit: '审计', + settingsIntegration: '集成', + settingsSystem: '系统', settingsUser: '用户管理', settingsRole: '角色管理', settingsResource: '资源管理', @@ -205,6 +211,17 @@ export default { passwordMin: '密码长度最少为6位', loading: '登录中,请稍后...', failed: '登录失败,请检查您的账号信息后重试。', + changePasswordTitle: '修改密码', + changePasswordRequired: '当前密码需要修改后才能登录', + passwordExpired: '密码已过期,请修改后重新登录', + newPassword: '新密码', + newPasswordPlaceholder: '请输入新密码', + confirmPassword: '确认新密码', + confirmPasswordPlaceholder: '请再次输入新密码', + confirmPasswordMismatch: '两次输入的密码不一致', + changePasswordSubmit: '提交并重新登录', + changePasswordSuccess: '密码修改成功,请使用新密码登录', + changePasswordFailed: '密码修改失败,请重试', }, error: { forbidden: '抱歉, 你无权访问该页面', diff --git a/src/config/router/settings.ts b/src/config/router/settings.ts index 0219cf034..1ce6a2e66 100644 --- a/src/config/router/settings.ts +++ b/src/config/router/settings.ts @@ -123,6 +123,42 @@ const settingsRouter: RouteRecordRaw = { redirect: '/settings/command/history', meta: { title: 'Command' }, }, + { + name: 'settingsIdentity', + path: 'identity', + redirect: '/settings/user', + meta: { title: 'Identity' }, + }, + { + name: 'settingsAccess', + path: 'access', + redirect: '/settings/role', + meta: { title: 'Access Control' }, + }, + { + name: 'settingsEventCommand', + path: 'event-command', + redirect: '/settings/event/history', + meta: { title: 'Event & Command' }, + }, + { + name: 'settingsAudit', + path: 'audit', + redirect: '/settings/identity_audit', + meta: { title: 'Audit' }, + }, + { + name: 'settingsIntegration', + path: 'integration', + redirect: '/settings/mcp', + meta: { title: 'Integration' }, + }, + { + name: 'settingsSystem', + path: 'system', + redirect: '/settings/group', + meta: { title: 'System' }, + }, { name: 'settingsAlarmRule', path: 'alarm/rule', @@ -176,26 +212,26 @@ const settingsRouter: RouteRecordRaw = { name: 'settingsAlarmOverview', path: 'alarm/overview', meta: { title: 'Event Overview' }, - component: () => import('@/views/settings/event/Overview.vue'), + component: () => import('@/views/settings/alarm/Overview.vue'), }, { name: 'settingsDeviceAlarm', path: 'alarm/device', meta: { title: 'Device Alarm' }, - component: () => import('@/views/settings/event/DeviceEvent.vue'), + component: () => import('@/views/settings/alarm/DeviceEvent.vue'), }, { name: 'settingsDriverAlarm', path: 'alarm/driver', meta: { title: 'Driver Alarm' }, - component: () => import('@/views/settings/event/DriverEvent.vue'), + component: () => import('@/views/settings/alarm/DriverEvent.vue'), }, { name: 'settingsPointAlarm', path: 'alarm/point', meta: { title: 'Point Events' }, - component: () => import('@/views/settings/event/PointEvent.vue'), + component: () => import('@/views/settings/alarm/PointEvent.vue'), }, { name: 'settingsEventHistory', diff --git a/src/config/settingsNav.ts b/src/config/settingsNav.ts index f8081b825..ad04888e4 100644 --- a/src/config/settingsNav.ts +++ b/src/config/settingsNav.ts @@ -28,41 +28,55 @@ export interface SettingsBreadcrumbParent { } export const SETTINGS_TITLE_KEYS: Record = { + // group containers + settingsIdentity: 'nav.settingsIdentity', + settingsAccess: 'nav.settingsAccess', + settingsModel: 'nav.settingsModel', + settingsAlarm: 'nav.settingsAlarm', + settingsEventCommand: 'nav.settingsEventCommand', + settingsAudit: 'nav.settingsAudit', + settingsIntegration: 'nav.settingsIntegration', + settingsSystem: 'nav.settingsSystem', + // identity settingsUser: 'nav.settingsUser', settingsPrincipal: 'nav.settingsPrincipal', settingsTenantMembership: 'nav.settingsTenantMembership', settingsLocalCredential: 'nav.settingsLocalCredential', - settingsIdentityAudit: 'nav.settingsIdentityAudit', + settingsServiceAccount: 'nav.settingsServiceAccount', + // access control settingsRole: 'nav.settingsRole', settingsRolePrincipalBind: 'nav.settingsRolePrincipalBind', settingsResource: 'nav.settingsResource', settingsApi: 'nav.settingsApi', settingsMenu: 'nav.settingsMenu', - settingsGroup: 'nav.settingsGroup', - settingsLabel: 'nav.settingsLabel', - settingsAlarm: 'nav.settingsAlarm', + // model + settingsModelConfig: 'nav.settingsModelConfig', + settingsModelProvider: 'nav.settingsModelProvider', + // alarm settingsAlarmRule: 'nav.settingsAlarmRule', settingsAlarmNotify: 'nav.settingsAlarmNotify', settingsAlarmMessage: 'nav.settingsAlarmMessage', settingsAlarmChannel: 'nav.settingsAlarmChannel', settingsAlarmBind: 'nav.settingsAlarmBind', + settingsAlarmOverview: 'nav.settingsAlarmOverview', settingsAlarmState: 'nav.settingsAlarmState', settingsAlarmHistory: 'nav.settingsAlarmHistory', - settingsDeviceAlarm: 'nav.settingsDeviceAlarm', settingsDriverAlarm: 'nav.settingsDriverAlarm', - settingsModel: 'nav.settingsModel', - settingsModelConfig: 'nav.settingsModelConfig', - settingsModelProvider: 'nav.settingsModelProvider', - settingsEvent: 'nav.settingsEvent', - settingsAlarmOverview: 'nav.settingsAlarmOverview', + settingsDeviceAlarm: 'nav.settingsDeviceAlarm', settingsPointAlarm: 'nav.settingsPointAlarm', - settingsCommand: 'nav.settingsCommand', - settingsCommandHistory: 'nav.settingsCommandHistory', + // event & command settingsEventHistory: 'nav.settingsEventHistory', - settingsServiceAccount: 'nav.settingsServiceAccount', - settingsMcpServer: 'nav.settingsMcpServer', + settingsCommandHistory: 'nav.settingsCommandHistory', + // audit + settingsIdentityAudit: 'nav.settingsIdentityAudit', settingsMcpAudit: 'nav.settingsMcpAudit', + // integration + settingsMcpServer: 'nav.settingsMcpServer', + // system + settingsGroup: 'nav.settingsGroup', + settingsLabel: 'nav.settingsLabel', settingsAbout: 'nav.settingsAbout', + // detail pages settingsUserDetail: 'nav.settingsUserDetail', settingsRoleDetail: 'nav.settingsRoleDetail', settingsResourceDetail: 'nav.settingsResourceDetail', @@ -83,41 +97,55 @@ export const SETTINGS_TITLE_KEYS: Record = { export const SETTINGS_FALLBACK_ICON: Record = { settings: 'Setting', + // group containers + settingsIdentity: 'User', + settingsAccess: 'Stamp', + settingsModel: 'Cpu', + settingsAlarm: 'AlarmClock', + settingsEventCommand: 'Operation', + settingsAudit: 'Files', + settingsIntegration: 'Share', + settingsSystem: 'Tools', + // identity settingsUser: 'User', settingsPrincipal: 'Avatar', settingsTenantMembership: 'OfficeBuilding', settingsLocalCredential: 'Lock', - settingsIdentityAudit: 'DocumentChecked', + settingsServiceAccount: 'Ticket', + // access control settingsRole: 'UserFilled', settingsRolePrincipalBind: 'Link', settingsResource: 'Key', settingsApi: 'Link', settingsMenu: 'Menu', - settingsGroup: 'Grid', - settingsLabel: 'CollectionTag', - settingsAlarm: 'AlarmClock', + // model + settingsModelConfig: 'ChatDotRound', + settingsModelProvider: 'ChatLineSquare', + // alarm settingsAlarmRule: 'SetUp', settingsAlarmNotify: 'Bell', settingsAlarmMessage: 'Message', settingsAlarmChannel: 'Connection', settingsAlarmBind: 'Link', + settingsAlarmOverview: 'DataLine', settingsAlarmState: 'Monitor', settingsAlarmHistory: 'DocumentChecked', - settingsDeviceAlarm: 'Management', settingsDriverAlarm: 'Promotion', - settingsModel: 'Cpu', - settingsModelConfig: 'ChatDotRound', - settingsModelProvider: 'ChatLineSquare', - settingsEvent: 'Bell', - settingsAlarmOverview: 'DataLine', + settingsDeviceAlarm: 'Management', settingsPointAlarm: 'TrendCharts', - settingsCommand: 'Operation', - settingsCommandHistory: 'Document', + // event & command settingsEventHistory: 'Document', - settingsServiceAccount: 'Key', - settingsMcpServer: 'Connection', + settingsCommandHistory: 'Document', + // audit + settingsIdentityAudit: 'DocumentChecked', settingsMcpAudit: 'Document', + // integration + settingsMcpServer: 'Connection', + // system + settingsGroup: 'Grid', + settingsLabel: 'CollectionTag', settingsAbout: 'InfoFilled', + // detail pages settingsUserDetail: 'User', settingsRoleDetail: 'UserFilled', settingsResourceDetail: 'Key', @@ -136,18 +164,36 @@ export const SETTINGS_FALLBACK_ICON: Record = { settingsModelProviderDetail: 'ChatLineSquare', }; +export const SETTINGS_IDENTITY_CHILDREN: SettingsNavNode[] = [ + { name: 'settingsUser', titleKey: 'nav.settingsUser', icon: 'User' }, + { name: 'settingsPrincipal', titleKey: 'nav.settingsPrincipal', icon: 'Avatar' }, + { name: 'settingsTenantMembership', titleKey: 'nav.settingsTenantMembership', icon: 'OfficeBuilding' }, + { name: 'settingsLocalCredential', titleKey: 'nav.settingsLocalCredential', icon: 'Lock' }, + { name: 'settingsServiceAccount', titleKey: 'nav.settingsServiceAccount', icon: 'Ticket' }, +]; + +export const SETTINGS_ACCESS_CHILDREN: SettingsNavNode[] = [ + { name: 'settingsRole', titleKey: 'nav.settingsRole', icon: 'UserFilled' }, + { name: 'settingsRolePrincipalBind', titleKey: 'nav.settingsRolePrincipalBind', icon: 'Link' }, + { name: 'settingsResource', titleKey: 'nav.settingsResource', icon: 'Key' }, + { name: 'settingsApi', titleKey: 'nav.settingsApi', icon: 'Link' }, + { name: 'settingsMenu', titleKey: 'nav.settingsMenu', icon: 'Menu' }, +]; + export const SETTINGS_MODEL_CHILDREN: SettingsNavNode[] = [ { name: 'settingsModelConfig', titleKey: 'nav.settingsModelConfig', icon: 'ChatDotRound' }, { name: 'settingsModelProvider', titleKey: 'nav.settingsModelProvider', icon: 'ChatLineSquare' }, ]; +// Order reflects two intent groups: configuration first (rule/notify/message/channel/bind), +// then monitoring (overview/state/history + driver/device/point alarm views). export const SETTINGS_ALARM_CHILDREN: SettingsNavNode[] = [ - { name: 'settingsAlarmOverview', titleKey: 'nav.settingsAlarmOverview', icon: 'DataLine' }, { name: 'settingsAlarmRule', titleKey: 'nav.settingsAlarmRule', icon: 'SetUp' }, { name: 'settingsAlarmNotify', titleKey: 'nav.settingsAlarmNotify', icon: 'Bell' }, { name: 'settingsAlarmMessage', titleKey: 'nav.settingsAlarmMessage', icon: 'Message' }, { name: 'settingsAlarmChannel', titleKey: 'nav.settingsAlarmChannel', icon: 'Connection' }, { name: 'settingsAlarmBind', titleKey: 'nav.settingsAlarmBind', icon: 'Link' }, + { name: 'settingsAlarmOverview', titleKey: 'nav.settingsAlarmOverview', icon: 'DataLine' }, { name: 'settingsAlarmState', titleKey: 'nav.settingsAlarmState', icon: 'Monitor' }, { name: 'settingsAlarmHistory', titleKey: 'nav.settingsAlarmHistory', icon: 'DocumentChecked' }, { name: 'settingsDriverAlarm', titleKey: 'nav.settingsDriverAlarm', icon: 'Promotion' }, @@ -155,42 +201,63 @@ export const SETTINGS_ALARM_CHILDREN: SettingsNavNode[] = [ { name: 'settingsPointAlarm', titleKey: 'nav.settingsPointAlarm', icon: 'TrendCharts' }, ]; -export const SETTINGS_EVENT_CHILDREN: SettingsNavNode[] = [ +export const SETTINGS_EVENT_COMMAND_CHILDREN: SettingsNavNode[] = [ { name: 'settingsEventHistory', titleKey: 'nav.settingsEventHistory', icon: 'Document' }, -]; - -export const SETTINGS_COMMAND_CHILDREN: SettingsNavNode[] = [ { name: 'settingsCommandHistory', titleKey: 'nav.settingsCommandHistory', icon: 'Document' }, ]; -export const SETTINGS_FALLBACK_SIDEBAR: SettingsNavNode[] = [ - { name: 'settingsUser', titleKey: 'nav.settingsUser', icon: 'User' }, - { name: 'settingsPrincipal', titleKey: 'nav.settingsPrincipal', icon: 'Avatar' }, - { name: 'settingsTenantMembership', titleKey: 'nav.settingsTenantMembership', icon: 'OfficeBuilding' }, - { name: 'settingsLocalCredential', titleKey: 'nav.settingsLocalCredential', icon: 'Lock' }, +export const SETTINGS_AUDIT_CHILDREN: SettingsNavNode[] = [ { name: 'settingsIdentityAudit', titleKey: 'nav.settingsIdentityAudit', icon: 'DocumentChecked' }, - { name: 'settingsRole', titleKey: 'nav.settingsRole', icon: 'UserFilled' }, - { name: 'settingsRolePrincipalBind', titleKey: 'nav.settingsRolePrincipalBind', icon: 'Link' }, - { name: 'settingsModel', titleKey: 'nav.settingsModel', icon: 'Cpu', children: SETTINGS_MODEL_CHILDREN }, - { name: 'settingsCommand', titleKey: 'nav.settingsCommand', icon: 'Operation', children: SETTINGS_COMMAND_CHILDREN }, - { name: 'settingsEvent', titleKey: 'nav.settingsEvent', icon: 'Bell', children: SETTINGS_EVENT_CHILDREN }, - { name: 'settingsAlarm', titleKey: 'nav.settingsAlarm', icon: 'AlarmClock', children: SETTINGS_ALARM_CHILDREN }, - { name: 'settingsApi', titleKey: 'nav.settingsApi', icon: 'Link' }, - { name: 'settingsResource', titleKey: 'nav.settingsResource', icon: 'Key' }, - { name: 'settingsMenu', titleKey: 'nav.settingsMenu', icon: 'Menu' }, + { name: 'settingsMcpAudit', titleKey: 'nav.settingsMcpAudit', icon: 'Document' }, +]; + +export const SETTINGS_INTEGRATION_CHILDREN: SettingsNavNode[] = [ + { name: 'settingsMcpServer', titleKey: 'nav.settingsMcpServer', icon: 'Connection' }, +]; + +export const SETTINGS_SYSTEM_CHILDREN: SettingsNavNode[] = [ { name: 'settingsGroup', titleKey: 'nav.settingsGroup', icon: 'Grid' }, { name: 'settingsLabel', titleKey: 'nav.settingsLabel', icon: 'CollectionTag' }, - { name: 'settingsServiceAccount', titleKey: 'nav.settingsServiceAccount', icon: 'Key' }, - { name: 'settingsMcpServer', titleKey: 'nav.settingsMcpServer', icon: 'Connection' }, - { name: 'settingsMcpAudit', titleKey: 'nav.settingsMcpAudit', icon: 'Document' }, { name: 'settingsAbout', titleKey: 'nav.settingsAbout', icon: 'InfoFilled' }, ]; +export const SETTINGS_FALLBACK_SIDEBAR: SettingsNavNode[] = [ + { name: 'settingsIdentity', titleKey: 'nav.settingsIdentity', icon: 'User', children: SETTINGS_IDENTITY_CHILDREN }, + { name: 'settingsAccess', titleKey: 'nav.settingsAccess', icon: 'Stamp', children: SETTINGS_ACCESS_CHILDREN }, + { name: 'settingsModel', titleKey: 'nav.settingsModel', icon: 'Cpu', children: SETTINGS_MODEL_CHILDREN }, + { name: 'settingsAlarm', titleKey: 'nav.settingsAlarm', icon: 'AlarmClock', children: SETTINGS_ALARM_CHILDREN }, + { + name: 'settingsEventCommand', + titleKey: 'nav.settingsEventCommand', + icon: 'Operation', + children: SETTINGS_EVENT_COMMAND_CHILDREN, + }, + { name: 'settingsAudit', titleKey: 'nav.settingsAudit', icon: 'Files', children: SETTINGS_AUDIT_CHILDREN }, + { + name: 'settingsIntegration', + titleKey: 'nav.settingsIntegration', + icon: 'Share', + children: SETTINGS_INTEGRATION_CHILDREN, + }, + { name: 'settingsSystem', titleKey: 'nav.settingsSystem', icon: 'Tools', children: SETTINGS_SYSTEM_CHILDREN }, +]; + export const SETTINGS_ACTIVE_ALIAS: Record = { + // group container → its first child (used when a route resolves to a container name) + settingsIdentity: 'settingsUser', + settingsAccess: 'settingsRole', settingsModel: 'settingsModelConfig', + settingsAlarm: 'settingsAlarmRule', + settingsEventCommand: 'settingsEventHistory', + settingsAudit: 'settingsIdentityAudit', + settingsIntegration: 'settingsMcpServer', + settingsSystem: 'settingsGroup', + // legacy route containers kept for redirect compatibility + settingsCommand: 'settingsCommandHistory', + settingsEvent: 'settingsEventHistory', + // detail pages highlight their list sibling settingsModelConfigDetail: 'settingsModelConfig', settingsModelProviderDetail: 'settingsModelProvider', - settingsAlarm: 'settingsAlarmRule', settingsAlarmRuleDetail: 'settingsAlarmRule', settingsAlarmNotifyDetail: 'settingsAlarmNotify', settingsAlarmMessageDetail: 'settingsAlarmMessage', @@ -198,33 +265,68 @@ export const SETTINGS_ACTIVE_ALIAS: Record = { settingsAlarmBindDetail: 'settingsAlarmBind', settingsAlarmStateDetail: 'settingsAlarmState', settingsAlarmHistoryDetail: 'settingsAlarmHistory', - settingsCommand: 'settingsCommandHistory', + settingsUserDetail: 'settingsUser', + settingsRoleDetail: 'settingsRole', + settingsResourceDetail: 'settingsResource', + settingsApiDetail: 'settingsApi', + settingsMenuDetail: 'settingsMenu', settingsGroupDetail: 'settingsGroup', settingsLabelDetail: 'settingsLabel', }; export const SETTINGS_ROUTE_ALIAS: Record = { - settingsAlarm: 'settingsAlarmRule', + settingsIdentity: 'settingsUser', + settingsAccess: 'settingsRole', settingsModel: 'settingsModelConfig', + settingsAlarm: 'settingsAlarmRule', + settingsEventCommand: 'settingsEventHistory', + settingsAudit: 'settingsIdentityAudit', + settingsIntegration: 'settingsMcpServer', + settingsSystem: 'settingsGroup', settingsCommand: 'settingsCommandHistory', + settingsEvent: 'settingsEventHistory', }; export const SETTINGS_GROUP_OPENERS: Record = { + // identity + settingsUser: 'settingsIdentity', + settingsPrincipal: 'settingsIdentity', + settingsTenantMembership: 'settingsIdentity', + settingsLocalCredential: 'settingsIdentity', + settingsServiceAccount: 'settingsIdentity', + // access control + settingsRole: 'settingsAccess', + settingsRolePrincipalBind: 'settingsAccess', + settingsResource: 'settingsAccess', + settingsApi: 'settingsAccess', + settingsMenu: 'settingsAccess', + // model + settingsModelConfig: 'settingsModel', + settingsModelProvider: 'settingsModel', + // alarm settingsAlarmRule: 'settingsAlarm', settingsAlarmNotify: 'settingsAlarm', settingsAlarmMessage: 'settingsAlarm', settingsAlarmChannel: 'settingsAlarm', settingsAlarmBind: 'settingsAlarm', + settingsAlarmOverview: 'settingsAlarm', settingsAlarmState: 'settingsAlarm', settingsAlarmHistory: 'settingsAlarm', - settingsDeviceAlarm: 'settingsAlarm', settingsDriverAlarm: 'settingsAlarm', - settingsAlarmOverview: 'settingsAlarm', + settingsDeviceAlarm: 'settingsAlarm', settingsPointAlarm: 'settingsAlarm', - settingsModelConfig: 'settingsModel', - settingsModelProvider: 'settingsModel', - settingsCommandHistory: 'settingsCommand', - settingsEventHistory: 'settingsEvent', + // event & command + settingsEventHistory: 'settingsEventCommand', + settingsCommandHistory: 'settingsEventCommand', + // audit + settingsIdentityAudit: 'settingsAudit', + settingsMcpAudit: 'settingsAudit', + // integration + settingsMcpServer: 'settingsIntegration', + // system + settingsGroup: 'settingsSystem', + settingsLabel: 'settingsSystem', + settingsAbout: 'settingsSystem', }; export const getSettingsRouteName = (name: string): string => SETTINGS_ROUTE_ALIAS[name] || name; @@ -240,16 +342,16 @@ export const getSettingsTitleKey = (name: string): string | undefined => SETTING export const getSettingsLeafIconCode = (name: string): string => name; -const commandParent: SettingsBreadcrumbParent = { - path: '/settings/command/history', - titleKey: 'nav.settingsCommand', - code: 'settingsCommand', +const identityParent: SettingsBreadcrumbParent = { + path: '/settings/identity', + titleKey: 'nav.settingsIdentity', + code: 'settingsIdentity', }; -const alarmParent: SettingsBreadcrumbParent = { - path: '/settings/alarm', - titleKey: 'nav.settingsAlarm', - code: 'settingsAlarm', +const accessParent: SettingsBreadcrumbParent = { + path: '/settings/access', + titleKey: 'nav.settingsAccess', + code: 'settingsAccess', }; const modelParent: SettingsBreadcrumbParent = { @@ -258,29 +360,93 @@ const modelParent: SettingsBreadcrumbParent = { code: 'settingsModel', }; -const eventParent: SettingsBreadcrumbParent = { - path: '/settings/event', - titleKey: 'nav.settingsEvent', - code: 'settingsEvent', +const alarmParent: SettingsBreadcrumbParent = { + path: '/settings/alarm', + titleKey: 'nav.settingsAlarm', + code: 'settingsAlarm', +}; + +const eventCommandParent: SettingsBreadcrumbParent = { + path: '/settings/event-command', + titleKey: 'nav.settingsEventCommand', + code: 'settingsEventCommand', +}; + +const auditParent: SettingsBreadcrumbParent = { + path: '/settings/audit', + titleKey: 'nav.settingsAudit', + code: 'settingsAudit', +}; + +const integrationParent: SettingsBreadcrumbParent = { + path: '/settings/integration', + titleKey: 'nav.settingsIntegration', + code: 'settingsIntegration', +}; + +const systemParent: SettingsBreadcrumbParent = { + path: '/settings/system', + titleKey: 'nav.settingsSystem', + code: 'settingsSystem', }; export const SETTINGS_BREADCRUMB_PARENTS: Record = { - settingsUserDetail: [{ path: '/settings/user', titleKey: 'nav.settingsUser', code: 'settingsUser' }], - settingsRoleDetail: [{ path: '/settings/role', titleKey: 'nav.settingsRole', code: 'settingsRole' }], - settingsResourceDetail: [{ path: '/settings/resource', titleKey: 'nav.settingsResource', code: 'settingsResource' }], - settingsApiDetail: [{ path: '/settings/api', titleKey: 'nav.settingsApi', code: 'settingsApi' }], - settingsMenuDetail: [{ path: '/settings/menu', titleKey: 'nav.settingsMenu', code: 'settingsMenu' }], - settingsGroupDetail: [{ path: '/settings/group', titleKey: 'nav.settingsGroup', code: 'settingsGroup' }], - settingsLabelDetail: [{ path: '/settings/label', titleKey: 'nav.settingsLabel', code: 'settingsLabel' }], + // identity leaves + settingsUser: [identityParent], + settingsPrincipal: [identityParent], + settingsTenantMembership: [identityParent], + settingsLocalCredential: [identityParent], + settingsServiceAccount: [identityParent], + // access control leaves + settingsRole: [accessParent], + settingsRolePrincipalBind: [accessParent], + settingsResource: [accessParent], + settingsApi: [accessParent], + settingsMenu: [accessParent], + // model leaves + settingsModelConfig: [modelParent], + settingsModelProvider: [modelParent], + // alarm leaves settingsAlarmRule: [alarmParent], settingsAlarmNotify: [alarmParent], settingsAlarmMessage: [alarmParent], settingsAlarmChannel: [alarmParent], settingsAlarmBind: [alarmParent], + settingsAlarmOverview: [alarmParent], settingsAlarmState: [alarmParent], settingsAlarmHistory: [alarmParent], - settingsDeviceAlarm: [alarmParent], settingsDriverAlarm: [alarmParent], + settingsDeviceAlarm: [alarmParent], + settingsPointAlarm: [alarmParent], + // event & command leaves + settingsEventHistory: [eventCommandParent], + settingsCommandHistory: [eventCommandParent], + // audit leaves + settingsIdentityAudit: [auditParent], + settingsMcpAudit: [auditParent], + // integration leaves + settingsMcpServer: [integrationParent], + // system leaves + settingsGroup: [systemParent], + settingsLabel: [systemParent], + settingsAbout: [systemParent], + // detail pages: group → list → detail + settingsUserDetail: [identityParent, { path: '/settings/user', titleKey: 'nav.settingsUser', code: 'settingsUser' }], + settingsRoleDetail: [accessParent, { path: '/settings/role', titleKey: 'nav.settingsRole', code: 'settingsRole' }], + settingsResourceDetail: [ + accessParent, + { path: '/settings/resource', titleKey: 'nav.settingsResource', code: 'settingsResource' }, + ], + settingsApiDetail: [accessParent, { path: '/settings/api', titleKey: 'nav.settingsApi', code: 'settingsApi' }], + settingsMenuDetail: [accessParent, { path: '/settings/menu', titleKey: 'nav.settingsMenu', code: 'settingsMenu' }], + settingsGroupDetail: [ + systemParent, + { path: '/settings/group', titleKey: 'nav.settingsGroup', code: 'settingsGroup' }, + ], + settingsLabelDetail: [ + systemParent, + { path: '/settings/label', titleKey: 'nav.settingsLabel', code: 'settingsLabel' }, + ], settingsAlarmRuleDetail: [ alarmParent, { path: '/settings/alarm/rule', titleKey: 'nav.settingsAlarmRule', code: 'settingsAlarmRule' }, @@ -309,8 +475,6 @@ export const SETTINGS_BREADCRUMB_PARENTS: Record { token: tokenRes.data, }); await router.push({ name: 'home' }); - } catch { + } catch (error) { + const code = (error as { code?: string })?.code; + if (code && PASSWORD_CHANGE_CODES.includes(code as never)) { + // Password change required / expired: surface the code so the view can + // open the change-password dialog instead of reporting a failed login. + throw error; + } failMessage(i18n.global.t('login.failed')); } finally { loading.close(); } }; + const changeLoginPassword = async (form: LoginForm & { newPassword: string }) => { + await changePassword({ + tenant: form.tenant, + name: form.name, + password: form.password, + newPassword: form.newPassword, + }); + }; + const logout = async () => { const tenantValue = getStorage(AUTH_HEADERS.TENANT); const userValue = getStorage(AUTH_HEADERS.LOGIN); @@ -120,6 +136,7 @@ export const useAuthStore = defineStore('auth', () => { getName, // Actions login, + changeLoginPassword, logout, }; }); diff --git a/src/views/login/Login.vue b/src/views/login/Login.vue index 758fdff90..0323e7797 100644 --- a/src/views/login/Login.vue +++ b/src/views/login/Login.vue @@ -69,16 +69,45 @@ + + + + + + + + + + + + +