feat(alarm): rename event views to alarm, add alarm routes and i18n, extend login and settings nav

This commit is contained in:
pnoker
2026-06-19 08:27:05 +08:00
parent de0cda0841
commit a129c90fa1
35 changed files with 492 additions and 334 deletions
+4 -13
View File
@@ -42,8 +42,8 @@ export const revokeMcpConnection = (id: string) =>
export const replaceMcpConnectionTools = (connectionId: string, toolIds: string[]) =>
httpPost<R<boolean>>(`${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<R<number>>(`${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<R<McpToolRecord[]>>(`${API_MCP_BASE}/tool/list`, query);
export const listMcpAudit = (
@@ -62,13 +62,4 @@ export const listMcpAudit = (
riskLevel?: string;
limit?: number;
} = {}
) =>
httpPost<R<McpAuditRecord[]>>(`${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<R<McpAuditRecord[]>>(`${API_MCP_BASE}/audit/list`, undefined, { params });
+2
View File
@@ -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);
+12
View File
@@ -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',
+7 -1
View File
@@ -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);
+7
View File
@@ -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;
+17
View File
@@ -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',
+17
View File
@@ -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: '抱歉, 你无权访问该页面',
+40 -4
View File
@@ -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',
+245 -85
View File
@@ -28,41 +28,55 @@ export interface SettingsBreadcrumbParent {
}
export const SETTINGS_TITLE_KEYS: Record<string, string> = {
// 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<string, string> = {
export const SETTINGS_FALLBACK_ICON: Record<string, string> = {
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<string, string> = {
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<string, string> = {
// 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<string, string> = {
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<string, string> = {
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<string, string> = {
// 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<string, SettingsBreadcrumbParent[]> = {
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<string, SettingsBreadcrumbParen
alarmParent,
{ path: '/settings/alarm/history', titleKey: 'nav.settingsAlarmHistory', code: 'settingsAlarmHistory' },
],
settingsModelConfig: [modelParent],
settingsModelProvider: [modelParent],
settingsModelConfigDetail: [
modelParent,
{ path: '/settings/model/config', titleKey: 'nav.settingsModelConfig', code: 'settingsModelConfig' },
@@ -319,8 +483,4 @@ export const SETTINGS_BREADCRUMB_PARENTS: Record<string, SettingsBreadcrumbParen
modelParent,
{ path: '/settings/model/provider', titleKey: 'nav.settingsModelProvider', code: 'settingsModelProvider' },
],
settingsAlarmOverview: [alarmParent],
settingsPointAlarm: [alarmParent],
settingsCommandHistory: [commandParent],
settingsEventHistory: [eventParent],
};
+1
View File
@@ -21,6 +21,7 @@ export interface Login {
salt?: string;
password?: string;
token?: string;
newPassword?: string;
}
/** Attribute information */
+19 -2
View File
@@ -20,9 +20,10 @@ import router from '@/config/router';
import i18n from '@/config/i18n';
import { ElLoading } from 'element-plus';
import { cancelToken, generateSalt, generateToken } from '@/api/token';
import { cancelToken, changePassword, generateSalt, generateToken } from '@/api/token';
import { AUTH_HEADERS } from '@/config/constant/common';
import { PASSWORD_CHANGE_CODES } from '@/config/constant/axios';
import type { Login } from '@/config/types';
import { getStorage, removeStorage, setStorage } from '@/utils/storageUtil';
import { failMessage } from '@/utils/notificationUtil';
@@ -91,13 +92,28 @@ export const useAuthStore = defineStore('auth', () => {
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,
};
});
+99 -3
View File
@@ -69,16 +69,45 @@
</div>
</div>
</div>
<el-dialog v-model="changePasswordVisible" :title="t('login.changePasswordTitle')" width="420px">
<el-alert :title="changePasswordHint" :closable="false" type="warning" show-icon class="mb-4" />
<el-form ref="changePasswordRef" :model="changePasswordData" :rules="changePasswordRule" label-width="0">
<el-form-item prop="newPassword">
<el-input
v-model="changePasswordData.newPassword"
:placeholder="t('login.newPasswordPlaceholder')"
type="password"
show-password
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="changePasswordData.confirmPassword"
:placeholder="t('login.confirmPasswordPlaceholder')"
type="password"
show-password
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :loading="changePasswordLoading" type="primary" @click="handleChangePassword">
{{ t('login.changePasswordSubmit') }}
</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, unref } from 'vue';
import { computed, reactive, ref, unref } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import { Box, Lock, User } from '@element-plus/icons-vue';
import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/store';
import { PASSWORD_CHANGE_CODES } from '@/config/constant/axios';
import { successMessage, failMessage } from '@/utils/notificationUtil';
import Particles from '@/components/particles/Particles.vue';
@@ -146,12 +175,79 @@
loading.value = true;
try {
await authStore.login(reactiveData.formData);
} catch {
// authStore.login already shows failMessage
} catch (error) {
const code = (error as { code?: string })?.code;
if (code && PASSWORD_CHANGE_CODES.includes(code as never)) {
openChangePassword(code);
}
// other failures already surface failMessage inside authStore.login
} finally {
loading.value = false;
}
};
// Password change flow (mandatory change / expired password)
const changePasswordVisible = ref(false);
const changePasswordLoading = ref(false);
const changePasswordCode = ref('');
const changePasswordRef = ref<FormInstance>();
const changePasswordData = reactive({ newPassword: '', confirmPassword: '' });
const changePasswordHint = computed(() =>
changePasswordCode.value === 'R4032' ? t('login.passwordExpired') : t('login.changePasswordRequired')
);
const changePasswordRule = reactive<FormRules>({
newPassword: [
{ required: true, message: t('login.passwordRequired'), trigger: 'blur' },
{ min: 6, message: t('login.passwordMin'), trigger: 'blur' },
],
confirmPassword: [
{
validator: (_rule, value, callback) =>
value === changePasswordData.newPassword
? callback()
: callback(new Error(t('login.confirmPasswordMismatch'))),
trigger: 'blur',
},
],
});
const openChangePassword = (code: string) => {
changePasswordCode.value = code;
changePasswordData.newPassword = '';
changePasswordData.confirmPassword = '';
changePasswordVisible.value = true;
};
const handleChangePassword = async () => {
const form = unref(changePasswordRef);
if (!form) {
return;
}
try {
await form.validate();
} catch {
return;
}
changePasswordLoading.value = true;
try {
await authStore.changeLoginPassword({
tenant: reactiveData.formData.tenant,
name: reactiveData.formData.name,
password: reactiveData.formData.password,
newPassword: changePasswordData.newPassword,
});
changePasswordVisible.value = false;
successMessage(t('login.changePasswordSuccess'));
reactiveData.formData.password = changePasswordData.newPassword;
await handleLogin();
} catch {
failMessage(t('login.changePasswordFailed'));
} finally {
changePasswordLoading.value = false;
}
};
</script>
<style lang="scss" scoped>
+10 -225
View File
@@ -61,12 +61,7 @@
getSettingsActiveName,
getSettingsDefaultOpeneds,
getSettingsRouteName,
SETTINGS_ALARM_CHILDREN,
SETTINGS_COMMAND_CHILDREN,
SETTINGS_EVENT_CHILDREN,
SETTINGS_FALLBACK_ICON,
SETTINGS_FALLBACK_SIDEBAR,
SETTINGS_MODEL_CHILDREN,
SETTINGS_TITLE_KEYS,
type SettingsNavNode,
} from '@/config/settingsNav';
@@ -98,40 +93,12 @@
children: node.children?.map(toSidebarItem),
});
const fallbackItems = (): SidebarItem[] => SETTINGS_FALLBACK_SIDEBAR.map(toSidebarItem);
// Static fallback shown when the menu API is unreachable or still loading.
// `icon` holds the globally-registered element-plus icon component name.
const modelGroup = (children?: SidebarItem[]): SidebarItem => ({
name: 'settingsModel',
title: t('nav.settingsModel'),
icon: SETTINGS_FALLBACK_ICON.settingsModel,
children: children?.length ? children : SETTINGS_MODEL_CHILDREN.map(toSidebarItem),
});
const alarmGroup = (children?: SidebarItem[]): SidebarItem => ({
name: 'settingsAlarm',
title: t('nav.settingsAlarm'),
icon: SETTINGS_FALLBACK_ICON.settingsAlarm,
children: children?.length ? children : SETTINGS_ALARM_CHILDREN.map(toSidebarItem),
});
const eventGroup = (children?: SidebarItem[]): SidebarItem => ({
name: 'settingsEvent',
title: t('nav.settingsEvent'),
icon: SETTINGS_FALLBACK_ICON.settingsEvent,
children: children?.length ? children : SETTINGS_EVENT_CHILDREN.map(toSidebarItem),
});
const commandGroup = (children?: SidebarItem[]): SidebarItem => ({
name: 'settingsCommand',
title: t('nav.settingsCommand'),
icon: SETTINGS_FALLBACK_ICON.settingsCommand,
children: children?.length ? children : SETTINGS_COMMAND_CHILDREN.map(toSidebarItem),
});
const fallbackItems = (): SidebarItem[] => SETTINGS_FALLBACK_SIDEBAR.map(toSidebarItem);
const menuTitle = (node: any) => {
const titleKey = node.menuCode === 'settingsEvent' ? 'nav.settingsEvent' : SETTINGS_TITLE_KEYS[node.menuCode];
const titleKey = SETTINGS_TITLE_KEYS[node.menuCode];
return titleKey ? t(titleKey) : resolveMenuTitle(node);
};
@@ -150,200 +117,18 @@
const sidebarItems = computed<SidebarItem[]>(() => {
const settings = menuStore.findByCode('settings');
const children = settings?.children || [];
if (!children.length) return menuStore.loaded ? [] : fallbackItems();
const items = children
.slice()
.sort((a, b) => (a.menuIndex ?? 0) - (b.menuIndex ?? 0))
.map(mapMenuNode);
if (menuStore.loaded) {
return items;
// The menu API is authoritative once loaded; the DB already encodes the group tree.
return children.length
? children
.slice()
.sort((a, b) => (a.menuIndex ?? 0) - (b.menuIndex ?? 0))
.map(mapMenuNode)
: [];
}
ensureDirectItem(items, { name: 'settingsGroup', title: t('nav.settingsGroup'), icon: 'Grid' }, [
'settingsLabel',
'settingsModel',
'settingsEvent',
'settingsAbout',
]);
ensureDirectItem(items, { name: 'settingsPrincipal', title: t('nav.settingsPrincipal'), icon: 'Avatar' }, [
'settingsTenantMembership',
'settingsRole',
'settingsRolePrincipalBind',
'settingsResource',
'settingsApi',
'settingsMenu',
'settingsAbout',
]);
ensureDirectItem(
items,
{ name: 'settingsTenantMembership', title: t('nav.settingsTenantMembership'), icon: 'OfficeBuilding' },
[
'settingsLocalCredential',
'settingsRole',
'settingsRolePrincipalBind',
'settingsResource',
'settingsApi',
'settingsAbout',
]
);
ensureDirectItem(
items,
{ name: 'settingsLocalCredential', title: t('nav.settingsLocalCredential'), icon: 'Lock' },
[
'settingsIdentityAudit',
'settingsRole',
'settingsRolePrincipalBind',
'settingsResource',
'settingsApi',
'settingsAbout',
]
);
ensureDirectItem(
items,
{ name: 'settingsIdentityAudit', title: t('nav.settingsIdentityAudit'), icon: 'DocumentChecked' },
['settingsRole', 'settingsRolePrincipalBind', 'settingsResource', 'settingsApi', 'settingsAbout']
);
ensureDirectItem(
items,
{ name: 'settingsRolePrincipalBind', title: t('nav.settingsRolePrincipalBind'), icon: 'Link' },
['settingsResource', 'settingsApi', 'settingsMenu', 'settingsAbout']
);
ensureDirectItem(items, { name: 'settingsLabel', title: t('nav.settingsLabel'), icon: 'CollectionTag' }, [
'settingsAbout',
]);
ensureDirectItem(items, { name: 'settingsServiceAccount', title: t('nav.settingsServiceAccount'), icon: 'Key' }, [
'settingsMcpServer',
'settingsAbout',
]);
ensureDirectItem(items, { name: 'settingsMcpServer', title: t('nav.settingsMcpServer'), icon: 'Connection' }, [
'settingsMcpAudit',
'settingsAbout',
]);
ensureDirectItem(items, { name: 'settingsMcpAudit', title: t('nav.settingsMcpAudit'), icon: 'Document' }, [
'settingsAbout',
]);
ensureDirectItem(items, { name: 'settingsAbout', title: t('nav.settingsAbout'), icon: 'InfoFilled' }, []);
ensureAlarmGroup(items);
ensureModelGroup(items);
ensureCommandGroup(items);
ensureEventGroup(items);
return items;
return fallbackItems();
});
const hasItem = (items: SidebarItem[], name: string): boolean => {
return items.some((item) => item.name === name || hasItem(item.children || [], name));
};
const insertBefore = (items: SidebarItem[], item: SidebarItem, beforeNames: string[]) => {
const index = items.findIndex((candidate) => beforeNames.includes(candidate.name));
if (index >= 0) {
items.splice(index, 0, item);
} else {
items.push(item);
}
};
const ensureDirectItem = (items: SidebarItem[], item: SidebarItem, beforeNames: string[]) => {
if (hasItem(items, item.name)) return;
insertBefore(items, item, beforeNames);
};
const ensureModelGroup = (items: SidebarItem[]) => {
const extracted: SidebarItem[] = [];
for (const name of SETTINGS_MODEL_CHILDREN.map((item) => item.name)) {
const index = items.findIndex((item) => item.name === name);
if (index >= 0) {
const removed = items.splice(index, 1)[0];
if (removed) extracted.push(removed);
}
}
const model = items.find((item) => item.name === 'settingsModel');
if (model) {
model.children = model.children || [];
if (!hasItem(model.children, 'settingsModelConfig')) {
model.children.push(
extracted.find((item) => item.name === 'settingsModelConfig') || {
name: 'settingsModelConfig',
title: t('nav.settingsModelConfig'),
icon: SETTINGS_FALLBACK_ICON.settingsModelConfig,
}
);
}
if (!hasItem(model.children, 'settingsModelProvider')) {
model.children.push(
extracted.find((item) => item.name === 'settingsModelProvider') || {
name: 'settingsModelProvider',
title: t('nav.settingsModelProvider'),
icon: SETTINGS_FALLBACK_ICON.settingsModelProvider,
}
);
}
return;
}
insertBefore(items, modelGroup(extracted), ['settingsEvent', 'settingsAbout']);
};
const ensureAlarmGroup = (items: SidebarItem[]) => {
const alarmChildNames = SETTINGS_ALARM_CHILDREN.map((item) => item.name);
const extracted: SidebarItem[] = [];
for (const name of alarmChildNames) {
const index = items.findIndex((item) => item.name === name);
if (index >= 0) {
const removed = items.splice(index, 1)[0];
if (removed) extracted.push(removed);
}
}
const alarm = items.find((item) => item.name === 'settingsAlarm');
if (alarm) {
alarm.children = alarm.children?.length ? alarm.children : alarmGroup(extracted).children;
return;
}
insertBefore(items, alarmGroup(extracted), ['settingsModel', 'settingsEvent', 'settingsAbout']);
};
const ensureCommandGroup = (items: SidebarItem[]) => {
const commandChildNames = SETTINGS_COMMAND_CHILDREN.map((item) => item.name);
const extracted: SidebarItem[] = [];
for (const name of commandChildNames) {
const index = items.findIndex((item) => item.name === name);
if (index >= 0) {
const removed = items.splice(index, 1)[0];
if (removed) extracted.push(removed);
}
}
const command = items.find((item) => item.name === 'settingsCommand');
if (command) {
command.children = command.children?.length ? command.children : commandGroup(extracted).children;
return;
}
insertBefore(items, commandGroup(extracted), ['settingsAbout']);
};
const ensureEventGroup = (items: SidebarItem[]) => {
const eventChildNames = SETTINGS_EVENT_CHILDREN.map((item) => item.name);
const extracted: SidebarItem[] = [];
for (const name of eventChildNames) {
const index = items.findIndex((item) => item.name === name);
if (index >= 0) {
const removed = items.splice(index, 1)[0];
if (removed) extracted.push(removed);
}
}
const event = items.find((item) => item.name === 'settingsEvent');
if (event) {
event.children = event.children?.length ? event.children : eventGroup(extracted).children;
return;
}
insertBefore(items, eventGroup(extracted), ['settingsAbout']);
};
const activeMenu = computed(() => {
const name = String(route.name || 'settingsUser');
return getSettingsActiveName(name);
+1 -1
View File
@@ -475,7 +475,7 @@
try {
const res = await listMcpTool({
keyword: reactiveData.toolKeyword,
risk_level: reactiveData.toolRisk,
riskLevel: reactiveData.toolRisk,
limit: 500,
});
reactiveData.tools = res.data || [];
@@ -3847,6 +3847,16 @@ exports[`API wrapper contracts > token > cancelToken maps to its transport contr
}
`;
exports[`API wrapper contracts > token > changePassword maps to its transport contract 1`] = `
{
"args": [
"api/v3/auth/token/change_password",
undefined,
],
"transport": "httpPost",
}
`;
exports[`API wrapper contracts > token > checkTokenValid maps to its transport contract 1`] = `
{
"args": [
@@ -3880,6 +3890,7 @@ exports[`API wrapper contracts > token > generateToken maps to its transport con
exports[`API wrapper contracts > token > has every export covered by the contract matrix 1`] = `
[
"cancelToken",
"changePassword",
"checkTokenValid",
"generateSalt",
"generateToken",