From 40ed14593d7c115efe49caa0b7da47809181bf18 Mon Sep 17 00:00:00 2001 From: pnoker Date: Mon, 24 Aug 2026 17:48:41 +0800 Subject: [PATCH] refactor(web): enforce typed response envelopes and domain unions Default the R envelope to unknown so bare wrappers surface their data shape, remove the | string tail that voided alarm/attribute unions, add explicit generics to every dashboard/point/info wrapper, and move the alert row types plus RangeKey to a single dashboard source. Also fix mock seeds to match backend enums (PENDING, DINGTALK_BOT). --- dc3-web/src/api/dashboard/alert.ts | 52 +++++++---- dc3-web/src/api/dashboard/stats.ts | 18 ++-- dc3-web/src/api/dashboard/system.ts | 14 +-- dc3-web/src/api/dashboard/topology.ts | 4 +- dc3-web/src/api/driver.ts | 2 +- dc3-web/src/api/info.ts | 32 +++---- dc3-web/src/api/localCredential.ts | 2 +- dc3-web/src/api/menu.ts | 3 +- dc3-web/src/api/point.ts | 18 ++-- dc3-web/src/api/serviceAccount.ts | 4 +- dc3-web/src/api/token.ts | 2 +- .../components/segmented/RangeSegmented.vue | 3 +- dc3-web/src/composables/useEntityListPage.ts | 34 +++---- dc3-web/src/config/ambient/type.d.ts | 2 +- dc3-web/src/config/constant/enums.ts | 31 +++++++ dc3-web/src/config/types/alarm.ts | 12 +-- dc3-web/src/config/types/common.ts | 2 +- dc3-web/src/config/types/dashboard.ts | 81 +++++++++++++++-- dc3-web/src/config/types/entityList.ts | 10 ++- dc3-web/src/mock/seed/data.ts | 13 +-- dc3-web/src/views/device/edit/index.ts | 88 +++++++++++-------- .../views/home/components/ActivityHeatmap.vue | 2 +- .../views/home/components/AnalyticsTabs.vue | 2 +- .../views/home/components/LatencyChart.vue | 2 +- .../views/home/components/TopologySankey.vue | 2 +- .../src/views/home/components/TrendChart.vue | 2 +- .../views/settings/alarm/alarmEntityConfig.ts | 8 +- .../alarm/components/AlertStormSources.vue | 16 ++-- .../alarm/components/AlertTypePie.vue | 12 +-- .../alarm/components/RecentUnconfirmed.vue | 18 ++-- .../settings/alarm/useAlarmEntityPage.ts | 2 +- 31 files changed, 302 insertions(+), 191 deletions(-) diff --git a/dc3-web/src/api/dashboard/alert.ts b/dc3-web/src/api/dashboard/alert.ts index b2f695610..d2b76f4ea 100644 --- a/dc3-web/src/api/dashboard/alert.ts +++ b/dc3-web/src/api/dashboard/alert.ts @@ -17,44 +17,64 @@ import {httpGet, httpPost} from '@/api/common'; import {API_DATA_BASE} from '@/config/constant/api'; -import type {AlertPageQuery, AlertSource} from '@/config/types/dashboard'; +import type {PageResult} from '@/config/types'; +import type { + AlertActivityRow, + AlertEventRow, + AlertPageQuery, + AlertSource, + AlertStormRow, + AlertTopSourceRow, + AlertTrendRow, + AlertTypeRow, + AgingBacklog, + ChangeImpact, + CorrelationPair, + FlappingSource, + MttaTrend, + PeerDeviation, +} from '@/config/types/dashboard'; -export const alertPage = (body: AlertPageQuery = {}) => httpPost(`${API_DATA_BASE}/dashboard/alert/page`, body); +export const alertPage = (body: AlertPageQuery = {}) => + httpPost>>(`${API_DATA_BASE}/dashboard/alert/page`, body); export const alertConfirm = (source: AlertSource, id: string) => - httpPost(`${API_DATA_BASE}/dashboard/alert/confirm`, undefined, {params: {source, id}}); + httpPost>(`${API_DATA_BASE}/dashboard/alert/confirm`, undefined, {params: {source, id}}); export const alertUnconfirm = (source: AlertSource, id: string) => - httpPost(`${API_DATA_BASE}/dashboard/alert/unconfirm`, undefined, {params: {source, id}}); + httpPost>(`${API_DATA_BASE}/dashboard/alert/unconfirm`, undefined, {params: {source, id}}); export const alertBulkConfirm = (items: Array<{ source: AlertSource; id: string }>, confirm: boolean) => - httpPost(`${API_DATA_BASE}/dashboard/alert/bulk_confirm`, {items, confirm}); + httpPost>(`${API_DATA_BASE}/dashboard/alert/bulk_confirm`, {items, confirm}); -export const alertTrend = (days = 30) => httpGet(`${API_DATA_BASE}/dashboard/alert/trend`, {params: {days}}); +export const alertTrend = (days = 30) => + httpGet>(`${API_DATA_BASE}/dashboard/alert/trend`, {params: {days}}); export const alertTopSources = (days = 30, limit = 10) => - httpGet(`${API_DATA_BASE}/dashboard/alert/top_sources`, {params: {days, limit}}); + httpGet>(`${API_DATA_BASE}/dashboard/alert/top_sources`, {params: {days, limit}}); -export const alertActivity = (days = 7) => httpGet(`${API_DATA_BASE}/dashboard/alert/activity`, {params: {days}}); +export const alertActivity = (days = 7) => + httpGet>(`${API_DATA_BASE}/dashboard/alert/activity`, {params: {days}}); export const alertTypeDistribution = (days = 30) => - httpGet(`${API_DATA_BASE}/dashboard/alert/type_distribution`, {params: {days}}); + httpGet>(`${API_DATA_BASE}/dashboard/alert/type_distribution`, {params: {days}}); export const alertStormSources = (hours = 1, minCount = 10, limit = 10) => - httpGet(`${API_DATA_BASE}/dashboard/alert/storm_sources`, {params: {hours, min_count: minCount, limit}}); + httpGet>(`${API_DATA_BASE}/dashboard/alert/storm_sources`, {params: {hours, min_count: minCount, limit}}); export const alertFlapping = (hours = 6, minCount = 5, limit = 20) => - httpGet(`${API_DATA_BASE}/dashboard/alert/flapping`, {params: {hours, min_count: minCount, limit}}); + httpGet>(`${API_DATA_BASE}/dashboard/alert/flapping`, {params: {hours, min_count: minCount, limit}}); export const alertCorrelation = (hours = 24, windowSec = 30, limit = 15) => - httpGet(`${API_DATA_BASE}/dashboard/alert/correlation`, {params: {hours, window_sec: windowSec, limit}}); + httpGet>(`${API_DATA_BASE}/dashboard/alert/correlation`, {params: {hours, window_sec: windowSec, limit}}); export const alertPeerDeviation = (days = 7) => - httpGet(`${API_DATA_BASE}/dashboard/alert/peer_deviation`, {params: {days}}); + httpGet>(`${API_DATA_BASE}/dashboard/alert/peer_deviation`, {params: {days}}); -export const alertAging = () => httpGet(`${API_DATA_BASE}/dashboard/alert/aging`); +export const alertAging = () => httpGet>(`${API_DATA_BASE}/dashboard/alert/aging`); -export const alertMtta = (days = 30) => httpGet(`${API_DATA_BASE}/dashboard/alert/mtta`, {params: {days}}); +export const alertMtta = (days = 30) => + httpGet>(`${API_DATA_BASE}/dashboard/alert/mtta`, {params: {days}}); export const alertChangeImpact = (days = 30, limit = 30) => - httpGet(`${API_DATA_BASE}/dashboard/alert/change_impact`, {params: {days, limit}}); + httpGet>(`${API_DATA_BASE}/dashboard/alert/change_impact`, {params: {days, limit}}); diff --git a/dc3-web/src/api/dashboard/stats.ts b/dc3-web/src/api/dashboard/stats.ts index c2bb3e6e8..f843585d2 100644 --- a/dc3-web/src/api/dashboard/stats.ts +++ b/dc3-web/src/api/dashboard/stats.ts @@ -18,10 +18,15 @@ import {httpGet} from '@/api/common'; import {API_DATA_BASE, API_MANAGER_BASE} from '@/config/constant/api'; import type { + AlertActivityRow, DailyGrowthSummary, + DeviceStats, + DriverStats, Granularity, + StatsCountBucket, StatsTimeBucket, StatsTodaySummary, + StreamRow, TimeRangeParams, TopDimension, } from '@/config/types/dashboard'; @@ -32,23 +37,24 @@ export const statsTimeseries = (params: TimeRangeParams & { granularity?: Granul httpGet>(`${API_DATA_BASE}/dashboard/stats/timeseries`, {params: timeRangeParams(params)}); export const statsTop = (params: TimeRangeParams & { dimension?: TopDimension; limit?: number } = {}) => - httpGet(`${API_DATA_BASE}/dashboard/top`, {params: timeRangeParams(params)}); + httpGet>(`${API_DATA_BASE}/dashboard/top`, {params: timeRangeParams(params)}); -export const streamLatest = (size = 20) => httpGet(`${API_DATA_BASE}/dashboard/stream`, {params: {size}}); +export const streamLatest = (size = 20) => + httpGet>(`${API_DATA_BASE}/dashboard/stream`, {params: {size}}); export const statsLatency = (params: TimeRangeParams = {rangeKey: '24h'}) => - httpGet(`${API_DATA_BASE}/dashboard/stats/latency`, {params: timeRangeParams(params)}); + httpGet>(`${API_DATA_BASE}/dashboard/stats/latency`, {params: timeRangeParams(params)}); export const statsActivity = (params: TimeRangeParams = {rangeKey: '7d'}) => - httpGet(`${API_DATA_BASE}/dashboard/stats/activity`, {params: timeRangeParams(params)}); + httpGet>(`${API_DATA_BASE}/dashboard/stats/activity`, {params: timeRangeParams(params)}); export const dailyGrowth = (days = 7) => httpGet>(`${API_MANAGER_BASE}/dashboard/growth`, {params: {days}}); -export const driverStats = () => httpGet(`${API_MANAGER_BASE}/dashboard/driver/stats`); +export const driverStats = () => httpGet>(`${API_MANAGER_BASE}/dashboard/driver/stats`); export const deviceStats = (topN = 10) => - httpGet(`${API_MANAGER_BASE}/dashboard/device/stats`, {params: {top_n: topN}}); + httpGet>(`${API_MANAGER_BASE}/dashboard/device/stats`, {params: {top_n: topN}}); const timeRangeParams = (params: T) => { const {rangeKey, rangeHours, ...rest} = params; diff --git a/dc3-web/src/api/dashboard/system.ts b/dc3-web/src/api/dashboard/system.ts index cd83e8e45..f231cddc8 100644 --- a/dc3-web/src/api/dashboard/system.ts +++ b/dc3-web/src/api/dashboard/system.ts @@ -17,19 +17,21 @@ import {httpGet} from '@/api/common'; import {API_DATA_BASE} from '@/config/constant/api'; -import type {AlertStatsSummary} from '@/config/types/dashboard'; +import type {AlertEventRow, AlertStatsSummary, CoverageGap, ProtocolHealth, SilentSource} from '@/config/types/dashboard'; export const alertStats = () => httpGet>(`${API_DATA_BASE}/dashboard/alert/stats`); -export const alertLatest = (size = 10) => httpGet(`${API_DATA_BASE}/dashboard/alert/latest`, {params: {size}}); +export const alertLatest = (size = 10) => + httpGet>(`${API_DATA_BASE}/dashboard/alert/latest`, {params: {size}}); -export const systemHealth = () => httpGet(`${API_DATA_BASE}/dashboard/system/health`); +export const systemHealth = () => httpGet>>(`${API_DATA_BASE}/dashboard/system/health`); -export const protocolHealth = () => httpGet(`${API_DATA_BASE}/dashboard/protocol/health`); +export const protocolHealth = () => httpGet>(`${API_DATA_BASE}/dashboard/protocol/health`); export const silentSources = (baselineDays = 7, silentMinutes = 15, limit = 50) => - httpGet(`${API_DATA_BASE}/dashboard/silent/sources`, { + httpGet>(`${API_DATA_BASE}/dashboard/silent/sources`, { params: {baseline_days: baselineDays, silent_minutes: silentMinutes, limit}, }); -export const coverageGap = (limit = 100) => httpGet(`${API_DATA_BASE}/dashboard/coverage/gap`, {params: {limit}}); +export const coverageGap = (limit = 100) => + httpGet>(`${API_DATA_BASE}/dashboard/coverage/gap`, {params: {limit}}); diff --git a/dc3-web/src/api/dashboard/topology.ts b/dc3-web/src/api/dashboard/topology.ts index fa218c564..75db6d4d4 100644 --- a/dc3-web/src/api/dashboard/topology.ts +++ b/dc3-web/src/api/dashboard/topology.ts @@ -17,10 +17,10 @@ import {httpGet} from '@/api/common'; import {API_MANAGER_BASE} from '@/config/constant/api'; -import type {TopologyMode} from '@/config/types/dashboard'; +import type {TopologyMode, TopologyResponse} from '@/config/types/dashboard'; export const topology = (params: { mode?: TopologyMode; rangeKey?: string } = {}) => - httpGet(`${API_MANAGER_BASE}/dashboard/topology`, { + httpGet>(`${API_MANAGER_BASE}/dashboard/topology`, { params: { ...(params.mode ? {mode: params.mode} : {}), ...(params.rangeKey ? {range_key: params.rangeKey} : {}), diff --git a/dc3-web/src/api/driver.ts b/dc3-web/src/api/driver.ts index 8415ba87d..5aa9a810e 100644 --- a/dc3-web/src/api/driver.ts +++ b/dc3-web/src/api/driver.ts @@ -30,4 +30,4 @@ export const listDriver = >>(query: PageQuery) => httpPost(`${API_MANAGER_BASE}/driver/list`, query); export const listDriverStatus = (query: Record) => - httpPost(`${API_DATA_BASE}/driver/status/list`, query); + httpPost>>(`${API_DATA_BASE}/driver/status/list`, query); diff --git a/dc3-web/src/api/info.ts b/dc3-web/src/api/info.ts index 3889fe7ab..7bdb8f2fb 100644 --- a/dc3-web/src/api/info.ts +++ b/dc3-web/src/api/info.ts @@ -20,57 +20,57 @@ import {API_MANAGER_BASE} from '@/config/constant/api'; import type {CommandInfoForm, DriverInfoForm, EventInfoForm, PointInfoForm} from '@/config/types/manager'; export const addDriverInfo = (driverInfo: DriverInfoForm) => - httpPost(`${API_MANAGER_BASE}/driver_attribute_config/add`, driverInfo); + httpPost>(`${API_MANAGER_BASE}/driver_attribute_config/add`, driverInfo); export const updateDriverInfo = (driverInfo: DriverInfoForm) => - httpPost(`${API_MANAGER_BASE}/driver_attribute_config/update`, driverInfo); + httpPost>(`${API_MANAGER_BASE}/driver_attribute_config/update`, driverInfo); export const getDriverInfoByDeviceIdAndAttributeId = (deviceId: string, attributeId: string) => - httpGet(`${API_MANAGER_BASE}/driver_attribute_config/get_by_device_id_and_attribute_id`, { + httpGet>(`${API_MANAGER_BASE}/driver_attribute_config/get_by_device_id_and_attribute_id`, { params: {device_id: deviceId, attribute_id: attributeId}, }); export const listDriverInfoByDeviceId = (deviceId: string) => - httpGet(`${API_MANAGER_BASE}/driver_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); + httpGet>(`${API_MANAGER_BASE}/driver_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); export const addPointInfo = (pointInfo: PointInfoForm) => - httpPost(`${API_MANAGER_BASE}/point_attribute_config/add`, pointInfo); + httpPost>(`${API_MANAGER_BASE}/point_attribute_config/add`, pointInfo); export const updatePointInfo = (pointInfo: PointInfoForm) => - httpPost(`${API_MANAGER_BASE}/point_attribute_config/update`, pointInfo); + httpPost>(`${API_MANAGER_BASE}/point_attribute_config/update`, pointInfo); export const listPointInfoByDeviceIdAndPointId = (deviceId: string, pointId: string) => - httpGet(`${API_MANAGER_BASE}/point_attribute_config/list_by_device_id_and_point_id`, { + httpGet>(`${API_MANAGER_BASE}/point_attribute_config/list_by_device_id_and_point_id`, { params: {device_id: deviceId, point_id: pointId}, }); export const listPointInfoByDeviceId = (deviceId: string) => - httpGet(`${API_MANAGER_BASE}/point_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); + httpGet>(`${API_MANAGER_BASE}/point_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); export const addCommandInfo = (commandInfo: CommandInfoForm) => - httpPost(`${API_MANAGER_BASE}/command_attribute_config/add`, commandInfo); + httpPost>(`${API_MANAGER_BASE}/command_attribute_config/add`, commandInfo); export const updateCommandInfo = (commandInfo: CommandInfoForm) => - httpPost(`${API_MANAGER_BASE}/command_attribute_config/update`, commandInfo); + httpPost>(`${API_MANAGER_BASE}/command_attribute_config/update`, commandInfo); export const listCommandInfoByDeviceIdAndCommandId = (deviceId: string, commandId: string) => - httpGet(`${API_MANAGER_BASE}/command_attribute_config/list_by_device_id_and_command_id`, { + httpGet>(`${API_MANAGER_BASE}/command_attribute_config/list_by_device_id_and_command_id`, { params: {device_id: deviceId, command_id: commandId}, }); export const listCommandInfoByDeviceId = (deviceId: string) => - httpGet(`${API_MANAGER_BASE}/command_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); + httpGet>(`${API_MANAGER_BASE}/command_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); export const addEventInfo = (eventInfo: EventInfoForm) => - httpPost(`${API_MANAGER_BASE}/event_attribute_config/add`, eventInfo); + httpPost>(`${API_MANAGER_BASE}/event_attribute_config/add`, eventInfo); export const updateEventInfo = (eventInfo: EventInfoForm) => - httpPost(`${API_MANAGER_BASE}/event_attribute_config/update`, eventInfo); + httpPost>(`${API_MANAGER_BASE}/event_attribute_config/update`, eventInfo); export const listEventInfoByDeviceIdAndEventId = (deviceId: string, eventId: string) => - httpGet(`${API_MANAGER_BASE}/event_attribute_config/list_by_device_id_and_event_id`, { + httpGet>(`${API_MANAGER_BASE}/event_attribute_config/list_by_device_id_and_event_id`, { params: {device_id: deviceId, event_id: eventId}, }); export const listEventInfoByDeviceId = (deviceId: string) => - httpGet(`${API_MANAGER_BASE}/event_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); + httpGet>(`${API_MANAGER_BASE}/event_attribute_config/list_by_device_id`, {params: {device_id: deviceId}}); diff --git a/dc3-web/src/api/localCredential.ts b/dc3-web/src/api/localCredential.ts index f559cae49..f5744f6e7 100644 --- a/dc3-web/src/api/localCredential.ts +++ b/dc3-web/src/api/localCredential.ts @@ -27,7 +27,7 @@ export const addLocalCredential = crud.add; export const deleteLocalCredential = crud.delete; export const resetLocalCredentialPassword = (id: string, password: string) => - httpPost(`${API_LOCAL_CREDENTIAL_BASE}/reset_password`, undefined, {params: {id, password}}); + httpPost>(`${API_LOCAL_CREDENTIAL_BASE}/reset_password`, undefined, {params: {id, password}}); export const checkLoginNameAvailable = (name: string) => httpGet>(`${API_LOCAL_CREDENTIAL_BASE}/check`, {params: {name}}); diff --git a/dc3-web/src/api/menu.ts b/dc3-web/src/api/menu.ts index 15a043f48..88fbb5862 100644 --- a/dc3-web/src/api/menu.ts +++ b/dc3-web/src/api/menu.ts @@ -33,4 +33,5 @@ export const getMenuById = crud.getById; export const listMenu = crud.list; -export const listMenuTree = (query: PageQuery = {}) => httpPost(`${API_AUTH_BASE}/menu/list_tree`, query); +export const listMenuTree = (query: PageQuery = {}) => + httpPost>(`${API_AUTH_BASE}/menu/list_tree`, query); diff --git a/dc3-web/src/api/point.ts b/dc3-web/src/api/point.ts index 7458cb905..e38e44031 100644 --- a/dc3-web/src/api/point.ts +++ b/dc3-web/src/api/point.ts @@ -18,6 +18,7 @@ import {httpGet, httpPost} from '@/api/common'; import {createCrudApi} from '@/api/factory'; import {API_DATA_BASE, API_MANAGER_BASE} from '@/config/constant/api'; +import type {PageResult} from '@/config/types'; import type {PointForm, PointRecord} from '@/config/types/manager'; const crud = createCrudApi({base: API_MANAGER_BASE, entity: 'point'}); @@ -35,27 +36,28 @@ export const listPoint = crud.list; export const listPointByIds = (pointIds: string[]) => httpPost>>(`${API_MANAGER_BASE}/point/list_by_ids`, pointIds); -export const listPointUnit = (pointIds: string[]) => httpPost(`${API_MANAGER_BASE}/point/unit`, pointIds); +export const listPointUnit = (pointIds: string[]) => + httpPost>>(`${API_MANAGER_BASE}/point/unit`, pointIds); export const listPointByProfileId = (profileId: string) => - httpGet(`${API_MANAGER_BASE}/point/list_by_profile_id`, {params: {profile_id: profileId}}); + httpGet>(`${API_MANAGER_BASE}/point/list_by_profile_id`, {params: {profile_id: profileId}}); export const listPointByDeviceId = (deviceId: string) => - httpGet(`${API_MANAGER_BASE}/point/list_by_device_id`, {params: {device_id: deviceId}}); + httpGet>(`${API_MANAGER_BASE}/point/list_by_device_id`, {params: {device_id: deviceId}}); export const getPointValueLatest = (pointValue: Record) => - httpPost(`${API_DATA_BASE}/point_value/latest`, pointValue); + httpPost>>>(`${API_DATA_BASE}/point_value/latest`, pointValue); export const listPointValue = (pointValue: Record) => - httpPost(`${API_DATA_BASE}/point_value/list`, pointValue); + httpPost>>>(`${API_DATA_BASE}/point_value/list`, pointValue); export const listPointValueHistory = (deviceId: string, pointId: string, count = 100) => - httpGet(`${API_DATA_BASE}/point_value/list_history_by_device_id_and_point_id`, { + httpGet>(`${API_DATA_BASE}/point_value/list_history_by_device_id_and_point_id`, { params: {device_id: deviceId, point_id: pointId, count} }); export const readPointValue = (pointValueReadVO: Record) => - httpPost(`${API_DATA_BASE}/point_command/read`, pointValueReadVO); + httpPost>(`${API_DATA_BASE}/point_command/read`, pointValueReadVO); export const writePointValue = (pointValueWriteVO: Record) => - httpPost(`${API_DATA_BASE}/point_command/write`, pointValueWriteVO); + httpPost>(`${API_DATA_BASE}/point_command/write`, pointValueWriteVO); diff --git a/dc3-web/src/api/serviceAccount.ts b/dc3-web/src/api/serviceAccount.ts index e0177239d..4a01c7951 100644 --- a/dc3-web/src/api/serviceAccount.ts +++ b/dc3-web/src/api/serviceAccount.ts @@ -29,10 +29,10 @@ export const deleteServiceAccount = crud.delete; export const updateServiceAccount = crud.update; export const enableServiceAccount = (id: string) => - httpPost(`${API_SERVICE_ACCOUNT_BASE}/enable`, undefined, {params: {id}}); + httpPost>(`${API_SERVICE_ACCOUNT_BASE}/enable`, undefined, {params: {id}}); export const disableServiceAccount = (id: string) => - httpPost(`${API_SERVICE_ACCOUNT_BASE}/disable`, undefined, {params: {id}}); + httpPost>(`${API_SERVICE_ACCOUNT_BASE}/disable`, undefined, {params: {id}}); export const getServiceAccountById = crud.getById; diff --git a/dc3-web/src/api/token.ts b/dc3-web/src/api/token.ts index bdc26646c..c559c0bf5 100644 --- a/dc3-web/src/api/token.ts +++ b/dc3-web/src/api/token.ts @@ -19,7 +19,7 @@ import {httpPost} from '@/api/common'; import {API_AUTH_BASE} from '@/config/constant/api'; import type {Login} from '@/config/types'; -export const generateSalt = (login: Login) => httpPost(`${API_AUTH_BASE}/token/salt`, login); +export const generateSalt = (login: Login) => httpPost>(`${API_AUTH_BASE}/token/salt`, login); export const generateToken = (login: Login) => httpPost(`${API_AUTH_BASE}/token/generate`, login); diff --git a/dc3-web/src/components/segmented/RangeSegmented.vue b/dc3-web/src/components/segmented/RangeSegmented.vue index 1d500d875..4ea8d7f4f 100644 --- a/dc3-web/src/components/segmented/RangeSegmented.vue +++ b/dc3-web/src/components/segmented/RangeSegmented.vue @@ -23,13 +23,14 @@ import type {PropType} from 'vue'; import {computed} from 'vue'; import {useI18n} from 'vue-i18n'; +import type {RangeKey} from '@/config/types/dashboard'; /** * Presets the frontend sends as {@code rangeKey} — kept in sync with * backend {@link TimeRangeKeyEnum}. The empty-string sentinel is the * "no filter" choice, rendered only when {@code includeAll} is true. */ -export type RangeKey = '' | 'today' | '24h' | '7d' | '30d'; +export type {RangeKey}; const props = defineProps({ modelValue: { diff --git a/dc3-web/src/composables/useEntityListPage.ts b/dc3-web/src/composables/useEntityListPage.ts index ecc4316b8..75ce81879 100644 --- a/dc3-web/src/composables/useEntityListPage.ts +++ b/dc3-web/src/composables/useEntityListPage.ts @@ -22,8 +22,10 @@ import {useRouter} from 'vue-router'; import type {Order, PageQuery} from '@/config/types'; import type {EntityColumnConfig, EntityListConfig, EntityOption} from '@/config/types/entityList'; +import {ENUM_TAG_TYPE_MAP} from '@/config/constant/enums'; import {timestampLabel} from '@/utils/dateUtil'; import {prettyJson} from '@/utils/jsonUtil'; +import {logger} from '@/utils/log'; import {successMessage} from '@/utils/notificationUtil'; import {cleanSearchParams, resetSearchForm} from '@/utils/searchParamUtil'; @@ -160,7 +162,9 @@ export const useEntityListPage = (rawConfig: EntityListConfig) => { if (config.value.mode === 'tree') { state.rows = (res.data as Record[]) || []; } else { - const page = res.data || {}; + // Config-driven boundary: the envelope's data shape depends on the + // concrete config.list implementation, so narrow it once here. + const page = (res.data ?? {}) as { records?: Record[]; total?: number }; state.rows = page.records || []; state.page.total = Number(page.total || 0); } @@ -269,7 +273,10 @@ export const useEntityListPage = (rawConfig: EntityListConfig) => { const submit = () => { const addRequest = config.value.add; const updateRequest = config.value.update; - if (!addRequest || !updateRequest) return; + if (!addRequest || !updateRequest) { + logger.warn('Entity list action not configured', {add: Boolean(addRequest), update: Boolean(updateRequest)}); + return; + } formRef.value?.validate((valid) => { if (!valid) return; let data: Record; @@ -317,28 +324,7 @@ export const useEntityListPage = (rawConfig: EntityListConfig) => { const tagType = (value: unknown) => { const text = String(value ?? ''); - if ( - text === 'ENABLE' || - text === 'SUCCESS' || - text === 'NORMAL' || - text === 'AUTO' || - text === 'LOW' || - text === 'ACTIVE' - ) - return 'success'; - if ( - text === 'DISABLE' || - text === 'FAILED' || - text === 'FAILURE' || - text === 'ERROR' || - text === 'DENIED' || - text === 'FIRING' || - text === 'HIGH' - ) - return 'danger'; - if (text === 'PENDING' || text === 'RETRYING' || text === 'RECOVERED' || text === 'MEDIUM' || text === 'SUSPENDED') - return 'warning'; - return 'info'; + return ENUM_TAG_TYPE_MAP[text] || 'info'; }; const formatCell = (row: Record, column: EntityColumnConfig) => { diff --git a/dc3-web/src/config/ambient/type.d.ts b/dc3-web/src/config/ambient/type.d.ts index 2a378f29b..df640441f 100644 --- a/dc3-web/src/config/ambient/type.d.ts +++ b/dc3-web/src/config/ambient/type.d.ts @@ -18,7 +18,7 @@ /** * Standard response envelope. */ -declare type R = { +declare type R = { ok: boolean; code: string; message: string; diff --git a/dc3-web/src/config/constant/enums.ts b/dc3-web/src/config/constant/enums.ts index b5ff78171..341555f10 100644 --- a/dc3-web/src/config/constant/enums.ts +++ b/dc3-web/src/config/constant/enums.ts @@ -217,3 +217,34 @@ export const MCP_RISK_LEVEL_OPTIONS: EnumOption[] = [ {label: MCP_RISK_LEVELS.MEDIUM, value: MCP_RISK_LEVELS.MEDIUM}, {label: MCP_RISK_LEVELS.HIGH, value: MCP_RISK_LEVELS.HIGH}, ]; + +// Backend: PrincipalTypeEnum +export const PRINCIPAL_TYPE_OPTIONS: EnumOption[] = [ + {label: 'USER', value: 'USER'}, + {label: 'SERVICE_ACCOUNT', value: 'SERVICE_ACCOUNT'}, + {label: 'SYSTEM', value: 'SYSTEM'}, +]; + +/** + * Enum name → el-tag type mapping (kept in sync with backend enums). + */ +export const ENUM_TAG_TYPE_MAP: Record = { + ENABLE: 'success', + SUCCESS: 'success', + NORMAL: 'success', + AUTO: 'success', + LOW: 'success', + ACTIVE: 'success', + DISABLE: 'danger', + FAILED: 'danger', + FAILURE: 'danger', + ERROR: 'danger', + DENIED: 'danger', + FIRING: 'danger', + HIGH: 'danger', + PENDING: 'warning', + RETRYING: 'warning', + RECOVERED: 'warning', + MEDIUM: 'warning', + SUSPENDED: 'warning', +}; diff --git a/dc3-web/src/config/types/alarm.ts b/dc3-web/src/config/types/alarm.ts index 4f06b2946..1b5977c4c 100644 --- a/dc3-web/src/config/types/alarm.ts +++ b/dc3-web/src/config/types/alarm.ts @@ -19,12 +19,12 @@ * Alarm / notification data-domain types. */ -export type AlarmTargetTypeFlag = 'POINT' | 'DEVICE' | 'DRIVER' | string; -export type NotifyChannelTypeFlag = 'FEISHU_BOT' | 'WEBHOOK' | 'EMAIL' | string; -export type RuleStateFlag = 'NORMAL' | 'FIRING' | 'RECOVERED' | string; -export type NotifyHistoryStatusFlag = 'PENDING' | 'SUCCESS' | 'FAILED' | 'RETRYING' | 'SKIPPED' | string; -export type AutoConfirmFlag = 'AUTO' | 'MANUAL' | string; -export type EnableFlag = 'ENABLE' | 'DISABLE' | string; +export type AlarmTargetTypeFlag = 'POINT' | 'DEVICE' | 'DRIVER'; +export type NotifyChannelTypeFlag = 'FEISHU_BOT' | 'WEBHOOK' | 'EMAIL'; +export type RuleStateFlag = 'NORMAL' | 'FIRING' | 'RECOVERED'; +export type NotifyHistoryStatusFlag = 'PENDING' | 'SUCCESS' | 'FAILED' | 'RETRYING' | 'SKIPPED'; +export type AutoConfirmFlag = 'AUTO' | 'MANUAL'; +export type EnableFlag = 'ENABLE' | 'DISABLE'; export interface StructuredExt> { type?: string; diff --git a/dc3-web/src/config/types/common.ts b/dc3-web/src/config/types/common.ts index 31987bf9b..792fa052a 100644 --- a/dc3-web/src/config/types/common.ts +++ b/dc3-web/src/config/types/common.ts @@ -31,7 +31,7 @@ export interface Attribute { name: string; attributeName: string; attributeCode: string; - attributeTypeFlag?: 'STRING' | 'BYTE' | 'SHORT' | 'INT' | 'LONG' | 'FLOAT' | 'DOUBLE' | 'BOOLEAN' | string; + attributeTypeFlag?: 'STRING' | 'BYTE' | 'SHORT' | 'INT' | 'LONG' | 'FLOAT' | 'DOUBLE' | 'BOOLEAN'; defaultValue?: string; remark?: string; attributeExt?: Record; diff --git a/dc3-web/src/config/types/dashboard.ts b/dc3-web/src/config/types/dashboard.ts index 036cebfb5..7bdd74c3a 100644 --- a/dc3-web/src/config/types/dashboard.ts +++ b/dc3-web/src/config/types/dashboard.ts @@ -16,14 +16,9 @@ */ /** - * Dashboard / event-overview payload shapes. Keeping these here (not in - * api/dashboard.ts alongside the fetch functions) follows the project-wide - * convention described in CLAUDE.md: "config/entity/" is the interface-only - * module, always imported with `import type` under verbatimModuleSyntax. - * - *

Shared primitives (AlertSource, RangeKey) live here too so every - * card / API wrapper points at the same union instead of re-declaring - * `'device' | 'driver'` inline.

+ * Dashboard / event-overview payload shapes. Kept here (not inside the API + * wrappers) so every card and `api/dashboard/*` module points at one source + * of truth, always imported with `import type` under verbatimModuleSyntax. */ /** Three canonical alarm sources — point-level, device-level, driver-level. */ @@ -215,3 +210,73 @@ export interface DailyGrowthSummary { pointDailyCounts: number[]; profileDailyCounts: number[]; } + +// ---- Alert overview cards (previously declared inline in components) ---- + +export interface AlertEventRow { + id: string; + source: AlertSource; + sourceId: string; + createTime: string; + message?: string; +} + +export interface AlertStormRow { + source: AlertSource; + sourceId: string; + count: number; +} + +export interface AlertTypeRow { + type: string; + count: number; +} + +export interface AlertActivityRow { + /** 0..6 = Sun..Sat, matching Postgres EXTRACT(DOW). */ + dow: number; + hour: number; + count: number; +} + +export interface AlertTrendRow { + date: string; + source: string; + count: number; +} + +export interface AlertTopSourceRow { + name: string; + count: number; +} + +/** Bucket shape shared by statsTop / latency / enable-breakdown endpoints. */ +export interface StatsCountBucket { + entityId?: number; + key?: string; + bin?: number; + count: number; +} + +export interface StreamRow { + deviceId: string; + pointId: string; + driverId?: string; + // driverName / deviceName / pointName are populated server-side via metadata + // facades, so the feed renders the full tuple without extra lookups. + driverName?: string; + deviceName?: string; + pointName?: string; +} + +export interface DriverStats { + byEnable: { key: string; count: number }[]; + byType: { key: string; count: number }[]; + byService: { key: string; count: number }[]; +} + +export interface DeviceStats { + byEnable: { key: string; count: number }[]; + byProfile: { key: string; count: number }[]; + byDriver: { key: string; count: number }[]; +} diff --git a/dc3-web/src/config/types/entityList.ts b/dc3-web/src/config/types/entityList.ts index 14acd87c2..ffc2e54f2 100644 --- a/dc3-web/src/config/types/entityList.ts +++ b/dc3-web/src/config/types/entityList.ts @@ -88,6 +88,8 @@ export interface EntityRowAction { key: string; label: string; type?: 'primary' | 'success' | 'warning' | 'danger' | 'info'; + /** When set, the action renders inside el-popconfirm; onClick runs only after confirmation. */ + popconfirmTitle?: string; onClick: (row: Record) => void; } @@ -127,10 +129,10 @@ export interface EntityListConfig { /** Builds the submitted payload instead of using the default field assembly. */ toPayload?: (form: Record) => Record; - list: (query: PageQuery) => Promise; - add?: (payload: Record) => Promise; - update?: (payload: Record) => Promise; - remove?: (id: string) => Promise; + list: (query: PageQuery) => Promise>; + add?: (payload: Record) => Promise>; + update?: (payload: Record) => Promise>; + remove?: (id: string) => Promise>; detail?: { routeName: string }; // Detail route; omit to hide the detail action. extraActions?: EntityRowAction[]; diff --git a/dc3-web/src/mock/seed/data.ts b/dc3-web/src/mock/seed/data.ts index a8e8bf5fc..f4b2c49a1 100644 --- a/dc3-web/src/mock/seed/data.ts +++ b/dc3-web/src/mock/seed/data.ts @@ -19,6 +19,7 @@ import type { MessageRecord, NotifyChannelBindRecord, NotifyChannelRecord, + NotifyChannelTypeFlag, NotifyHistoryRecord, NotifyRecord, RuleRecord, @@ -40,7 +41,7 @@ const ext = (type: string, content: Record, version = 1) => ({ interface ChannelDef { name: string; code: string; - type: string; + type: NotifyChannelTypeFlag; credential: string; content: Record; } @@ -62,7 +63,7 @@ const channelDefs: ChannelDef[] = [ { name: '钉钉运维群机器人', code: 'dingtalk-ops', - type: 'DINGTALK_BOT', + type: 'WEBHOOK', credential: 'dingtalk-ops-bot', content: { signEnabled: true, @@ -193,7 +194,7 @@ const messageDefs: MessageDef[] = [ variables: ['severity', 'device', 'point', 'value', 'unit', 'threshold', 'triggerTime'], templates: [ { - channelType: 'DINGTALK_BOT', + channelType: 'WEBHOOK', payloadType: 'MARKDOWN', template: { title: '${severity} ${device} 告警', @@ -335,7 +336,7 @@ const ruleDefs: RuleDef[] = [ interface StateDef { ruleIdx: number; entity: string; - state: 'FIRING' | 'PENDING' | 'RECOVERED'; + state: 'FIRING' | 'NORMAL' | 'RECOVERED'; fingerprint: string; triggerCount: number; first: string; @@ -376,7 +377,7 @@ const stateDefs: StateDef[] = [ { ruleIdx: 2, entity: '5011', - state: 'PENDING', + state: 'NORMAL', fingerprint: 'fp-0a7b3e5d9c218f64', triggerCount: 0, first: '', @@ -474,7 +475,7 @@ const historyDefs: HistoryDef[] = [ retry: 2, time: '2026-08-05T09:30:00', request: { - channelType: 'DINGTALK_BOT', + channelType: 'WEBHOOK', target: 'https://oapi.dingtalk.com/robot/send?access_token=****', payloadType: 'MARKDOWN' }, diff --git a/dc3-web/src/views/device/edit/index.ts b/dc3-web/src/views/device/edit/index.ts index d75d3e4a4..602a62820 100644 --- a/dc3-web/src/views/device/edit/index.ts +++ b/dc3-web/src/views/device/edit/index.ts @@ -16,6 +16,7 @@ */ import {computed, defineComponent, onBeforeUnmount, onMounted, reactive, watch} from 'vue'; +import {ElMessageBox} from 'element-plus'; import type {FormItemRule, FormRules} from 'element-plus'; import {Search} from '@element-plus/icons-vue'; @@ -49,6 +50,7 @@ import { import type { Attribute, CommandInfoForm, + DriverInfoForm, CommandRecord, DeviceRecord, Dictionary, @@ -718,8 +720,8 @@ export default defineComponent({ listDriverInfoByDeviceId(reactiveData.id) .then((res) => { const formData: AttributeFormData = reactiveData.driverFormData; - res.data.forEach((info: { attributeId: string; id: any; configValue: any }) => { - const attributeCode = reactiveData.driverAttributeTable[info.attributeId]; + res.data.forEach((info: DriverInfoForm) => { + const attributeCode = reactiveData.driverAttributeTable[info.attributeId ?? '']; const attribute = reactiveData.driverAttributes.find((item) => item.attributeCode === attributeCode); if (attribute) { formData[attributeCode] = createAttributeFormItem(attribute, info.id, info.configValue); @@ -775,16 +777,14 @@ export default defineComponent({ return listPointInfoByDeviceId(reactiveData.id) .then((infoRes) => { - (infoRes.data || []).forEach( - (info: { pointId: string; attributeId: string; id: string; configValue: unknown }) => { - const attributeCode = reactiveData.pointAttributeTable[info.attributeId]; - const attribute = reactiveData.pointAttributes.find((item) => item.attributeCode === attributeCode); - const row = rowTable[info.pointId]; - if (row && attribute) { - row.attributes[attributeCode] = createPointAttributeCell(attribute, info.id, info.configValue); - } + (infoRes.data || []).forEach((info: PointInfoForm) => { + const attributeCode = reactiveData.pointAttributeTable[info.attributeId ?? '']; + const attribute = reactiveData.pointAttributes.find((item) => item.attributeCode === attributeCode); + const row = rowTable[String(info.pointId ?? '')]; + if (row && attribute) { + row.attributes[attributeCode] = createPointAttributeCell(attribute, info.id ?? '', info.configValue); } - ); + }); reactiveData.pointInfoData = rows; reactiveData.oldPointInfoData = clone(rows); }) @@ -839,16 +839,14 @@ export default defineComponent({ return listCommandInfoByDeviceId(reactiveData.id) .then((infoRes) => { - (infoRes.data || []).forEach( - (info: { commandId: string; attributeId: string; id: string; configValue: unknown }) => { - const attributeCode = reactiveData.commandAttributeTable[info.attributeId]; - const attribute = reactiveData.commandAttributes.find((item) => item.attributeCode === attributeCode); - const row = rowTable[String(info.commandId)]; - if (row && attribute) { - row.attributes[attributeCode] = createPointAttributeCell(attribute, info.id, info.configValue); - } + (infoRes.data || []).forEach((info: CommandInfoForm) => { + const attributeCode = reactiveData.commandAttributeTable[info.attributeId ?? '']; + const attribute = reactiveData.commandAttributes.find((item) => item.attributeCode === attributeCode); + const row = rowTable[String(info.commandId ?? '')]; + if (row && attribute) { + row.attributes[attributeCode] = createPointAttributeCell(attribute, info.id ?? '', info.configValue); } - ); + }); reactiveData.commandInfoData = rows; reactiveData.oldCommandInfoData = clone(rows); }) @@ -900,16 +898,14 @@ export default defineComponent({ return listEventInfoByDeviceId(reactiveData.id) .then((infoRes) => { - (infoRes.data || []).forEach( - (info: { eventId: string; attributeId: string; id: string; configValue: unknown }) => { - const attributeCode = reactiveData.eventAttributeTable[info.attributeId]; - const attribute = reactiveData.eventAttributes.find((item) => item.attributeCode === attributeCode); - const row = rowTable[String(info.eventId)]; - if (row && attribute) { - row.attributes[attributeCode] = createPointAttributeCell(attribute, info.id, info.configValue); - } + (infoRes.data || []).forEach((info: EventInfoForm) => { + const attributeCode = reactiveData.eventAttributeTable[info.attributeId ?? '']; + const attribute = reactiveData.eventAttributes.find((item) => item.attributeCode === attributeCode); + const row = rowTable[String(info.eventId ?? '')]; + if (row && attribute) { + row.attributes[attributeCode] = createPointAttributeCell(attribute, info.id ?? '', info.configValue); } - ); + }); reactiveData.eventInfoData = rows; reactiveData.oldEventInfoData = clone(rows); }) @@ -1095,8 +1091,13 @@ export default defineComponent({ }; try { - const res = cell.id ? await updatePointInfo(payload) : await addPointInfo(payload); - cell.id = String(res?.data?.id || cell.id || ''); + // Backend add/update return R (success code) without the new + // id, so there is nothing to read back from res.data. + if (cell.id) { + await updatePointInfo(payload); + } else { + await addPointInfo(payload); + } cell.originalValue = cell.configValue; cell.dirty = false; } catch (error) { @@ -1212,8 +1213,11 @@ export default defineComponent({ }; try { - const res = cell.id ? await updateCommandInfo(payload) : await addCommandInfo(payload); - cell.id = String(res?.data?.id || cell.id || ''); + if (cell.id) { + await updateCommandInfo(payload); + } else { + await addCommandInfo(payload); + } cell.originalValue = cell.configValue; cell.dirty = false; } catch (error) { @@ -1327,8 +1331,11 @@ export default defineComponent({ }; try { - const res = cell.id ? await updateEventInfo(payload) : await addEventInfo(payload); - cell.id = String(res?.data?.id || cell.id || ''); + if (cell.id) { + await updateEventInfo(payload); + } else { + await addEventInfo(payload); + } cell.originalValue = cell.configValue; cell.dirty = false; } catch (error) { @@ -1473,10 +1480,15 @@ export default defineComponent({ window.removeEventListener('beforeunload', warnBeforeUnload); }); - onBeforeRouteLeave((_to, _from, next) => { + onBeforeRouteLeave(async (_to, _from, next) => { if (totalDirtyCount.value > 0) { - const leave = window.confirm('You have unsaved changes. Are you sure you want to leave?'); - if (!leave) { + try { + await ElMessageBox.confirm(t('device.edit.unsavedConfirm'), t('common.confirm'), { + type: 'warning', + confirmButtonText: t('common.confirm'), + cancelButtonText: t('common.cancel'), + }); + } catch { next(false); return; } diff --git a/dc3-web/src/views/home/components/ActivityHeatmap.vue b/dc3-web/src/views/home/components/ActivityHeatmap.vue index ff945a82b..2c70f3346 100644 --- a/dc3-web/src/views/home/components/ActivityHeatmap.vue +++ b/dc3-web/src/views/home/components/ActivityHeatmap.vue @@ -31,7 +31,7 @@ import {Chart} from '@antv/g2'; import {statsActivity} from '@/api/dashboard'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; -import type {RangeKey} from '@/components/segmented/RangeSegmented.vue'; +import type {RangeKey} from '@/config/types/dashboard'; import RangeSegmented from '@/components/segmented/RangeSegmented.vue'; const {t} = useI18n(); diff --git a/dc3-web/src/views/home/components/AnalyticsTabs.vue b/dc3-web/src/views/home/components/AnalyticsTabs.vue index cdc3b6115..8b2193ea5 100644 --- a/dc3-web/src/views/home/components/AnalyticsTabs.vue +++ b/dc3-web/src/views/home/components/AnalyticsTabs.vue @@ -57,7 +57,7 @@ import {listDriverByIds} from '@/api/driver'; import {listPointByIds} from '@/api/point'; import {listProfileByIds} from '@/api/profile'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; -import type {RangeKey} from '@/components/segmented/RangeSegmented.vue'; +import type {RangeKey} from '@/config/types/dashboard'; import RangeSegmented from '@/components/segmented/RangeSegmented.vue'; type TabKey = 'deviceStatus' | 'protocol' | 'profile' | 'topDevice' | 'topPoint' | 'topDriver'; diff --git a/dc3-web/src/views/home/components/LatencyChart.vue b/dc3-web/src/views/home/components/LatencyChart.vue index 3e66dc780..013fe930d 100644 --- a/dc3-web/src/views/home/components/LatencyChart.vue +++ b/dc3-web/src/views/home/components/LatencyChart.vue @@ -31,7 +31,7 @@ import {Chart} from '@antv/g2'; import {statsLatency} from '@/api/dashboard'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; -import type {RangeKey} from '@/components/segmented/RangeSegmented.vue'; +import type {RangeKey} from '@/config/types/dashboard'; import RangeSegmented from '@/components/segmented/RangeSegmented.vue'; const {t} = useI18n(); diff --git a/dc3-web/src/views/home/components/TopologySankey.vue b/dc3-web/src/views/home/components/TopologySankey.vue index 8ef1b0b51..5de575615 100644 --- a/dc3-web/src/views/home/components/TopologySankey.vue +++ b/dc3-web/src/views/home/components/TopologySankey.vue @@ -96,7 +96,7 @@ import type { TopologyStats, } from '@/config/types/dashboard'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; -import type {RangeKey} from '@/components/segmented/RangeSegmented.vue'; +import type {RangeKey} from '@/config/types/dashboard'; import RangeSegmented from '@/components/segmented/RangeSegmented.vue'; const {t, locale} = useI18n(); diff --git a/dc3-web/src/views/home/components/TrendChart.vue b/dc3-web/src/views/home/components/TrendChart.vue index 9fe193498..de3112586 100644 --- a/dc3-web/src/views/home/components/TrendChart.vue +++ b/dc3-web/src/views/home/components/TrendChart.vue @@ -30,7 +30,7 @@ import {Chart} from '@antv/g2'; import {statsTimeseries} from '@/api/dashboard'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; -import type {RangeKey} from '@/components/segmented/RangeSegmented.vue'; +import type {RangeKey} from '@/config/types/dashboard'; import RangeSegmented from '@/components/segmented/RangeSegmented.vue'; const rangeKey = ref('24h'); diff --git a/dc3-web/src/views/settings/alarm/alarmEntityConfig.ts b/dc3-web/src/views/settings/alarm/alarmEntityConfig.ts index 318a52632..2cf3f1129 100644 --- a/dc3-web/src/views/settings/alarm/alarmEntityConfig.ts +++ b/dc3-web/src/views/settings/alarm/alarmEntityConfig.ts @@ -92,10 +92,10 @@ export interface AlarmEntityConfig { columns: AlarmColumnConfig[]; fields: AlarmFieldConfig[]; defaultForm: () => Record; - list: (query: PageQuery) => Promise; - add?: (payload: Record) => Promise; - update?: (payload: Record) => Promise; - remove?: (id: string) => Promise; + list: (query: PageQuery) => Promise>; + add?: (payload: Record) => Promise>; + update?: (payload: Record) => Promise>; + remove?: (id: string) => Promise>; } export const ALARM_DETAIL_ROUTE_MAP: Record = { diff --git a/dc3-web/src/views/settings/alarm/components/AlertStormSources.vue b/dc3-web/src/views/settings/alarm/components/AlertStormSources.vue index 91b33e66e..25bb8a6ca 100644 --- a/dc3-web/src/views/settings/alarm/components/AlertStormSources.vue +++ b/dc3-web/src/views/settings/alarm/components/AlertStormSources.vue @@ -57,13 +57,7 @@ import {alertStormSources} from '@/api/dashboard'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; import {useEntityNames} from '@/composables/useEntityNames'; import {jumpToSourceEvents} from '@/utils/jumpUtil'; -import type {AlertSource} from '@/config/types/dashboard'; - -interface StormRow { - source: AlertSource; - sourceId: string; - count: number; -} +import type {AlertSource, AlertStormRow} from '@/config/types/dashboard'; const props = defineProps({ limit: {type: Number, default: 10}, @@ -94,13 +88,13 @@ const windowKey = ref('24h'); const window = computed(() => WINDOW_SPECS[windowKey.value]); const loading = ref(false); -const rows = ref([]); +const rows = ref([]); const load = async () => { loading.value = true; try { const {hours, minCount} = window.value; - const res: { data?: StormRow[] } = await alertStormSources(hours, minCount, props.limit); + const res: { data?: AlertStormRow[] } = await alertStormSources(hours, minCount, props.limit); rows.value = res?.data ?? []; await resolveBySource(rows.value); } catch { @@ -113,9 +107,9 @@ const load = async () => { watch(windowKey, load); watch(locale, load); -const nameFor = (r: StormRow) => nameBySource(r.source, r.sourceId); +const nameFor = (r: AlertStormRow) => nameBySource(r.source, r.sourceId); -const onDrillIn = (row: StormRow) => jumpToSourceEvents(router, row.source, row.sourceId); +const onDrillIn = (row: AlertStormRow) => jumpToSourceEvents(router, row.source, row.sourceId); const sourceTagType = (s: AlertSource) => (s === 'device' ? 'primary' : s === 'driver' ? 'warning' : 'success'); const sourceLabel = (s: AlertSource) => { diff --git a/dc3-web/src/views/settings/alarm/components/AlertTypePie.vue b/dc3-web/src/views/settings/alarm/components/AlertTypePie.vue index 576f04040..8758985b8 100644 --- a/dc3-web/src/views/settings/alarm/components/AlertTypePie.vue +++ b/dc3-web/src/views/settings/alarm/components/AlertTypePie.vue @@ -36,16 +36,12 @@ import {Chart} from '@antv/g2'; import {alertTypeDistribution} from '@/api/dashboard'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; +import type {AlertTypeRow} from '@/config/types/dashboard'; const {t, locale} = useI18n(); -interface TypeRow { - type: string; - count: number; -} - const loading = ref(false); -const rows = ref([]); +const rows = ref([]); const chartRef = ref(); let chart: Chart | undefined; @@ -58,7 +54,7 @@ const labelFor = (type: string) => { return translated && translated !== key ? translated : type; }; -const render = (data: TypeRow[]) => { +const render = (data: AlertTypeRow[]) => { if (!chartRef.value) return; chart?.destroy(); chart = new Chart({container: chartRef.value, autoFit: true}); @@ -78,7 +74,7 @@ const render = (data: TypeRow[]) => { const load = async () => { loading.value = true; try { - const res: { data?: TypeRow[] } = await alertTypeDistribution(30); + const res: { data?: AlertTypeRow[] } = await alertTypeDistribution(30); rows.value = res?.data ?? []; await nextTick(); if (rows.value.length > 0) render(rows.value); diff --git a/dc3-web/src/views/settings/alarm/components/RecentUnconfirmed.vue b/dc3-web/src/views/settings/alarm/components/RecentUnconfirmed.vue index 0945cdf3d..63cadfaf3 100644 --- a/dc3-web/src/views/settings/alarm/components/RecentUnconfirmed.vue +++ b/dc3-web/src/views/settings/alarm/components/RecentUnconfirmed.vue @@ -57,26 +57,18 @@ import {useI18n} from 'vue-i18n'; import {alertPage} from '@/api/dashboard'; import DashboardCard from '@/components/card/dashboard/DashboardCard.vue'; import {useEntityNames} from '@/composables/useEntityNames'; -import type {AlertSource} from '@/config/types/dashboard'; - -interface Row { - id: string; - source: AlertSource; - sourceId: string; - createTime: string; - message?: string; -} +import type {AlertEventRow, AlertSource} from '@/config/types/dashboard'; const {t, locale} = useI18n(); const loading = ref(false); -const rows = ref([]); +const rows = ref([]); const {resolveBySource, nameBySource} = useEntityNames(); const load = async () => { loading.value = true; try { - const res: { data?: { records?: Row[] } } = await alertPage({confirmFlag: 0, current: 1, size: 5}); - const data: Row[] = res?.data?.records ?? []; + const res: { data?: { records?: AlertEventRow[] } } = await alertPage({confirmFlag: 0, current: 1, size: 5}); + const data: AlertEventRow[] = res?.data?.records ?? []; rows.value = data; await resolveBySource(data); } catch { @@ -86,7 +78,7 @@ const load = async () => { } }; -const nameFor = (r: Row) => nameBySource(r.source, r.sourceId); +const nameFor = (r: AlertEventRow) => nameBySource(r.source, r.sourceId); const sourceTagType = (s: AlertSource) => (s === 'device' ? 'primary' : s === 'driver' ? 'warning' : 'success'); const sourceColor = (s: AlertSource) => (s === 'device' ? '#409eff' : s === 'driver' ? '#e6a23c' : '#67c23a'); diff --git a/dc3-web/src/views/settings/alarm/useAlarmEntityPage.ts b/dc3-web/src/views/settings/alarm/useAlarmEntityPage.ts index dcaeb8a80..138e63318 100644 --- a/dc3-web/src/views/settings/alarm/useAlarmEntityPage.ts +++ b/dc3-web/src/views/settings/alarm/useAlarmEntityPage.ts @@ -133,7 +133,7 @@ export const useAlarmEntityPage = (props: AlarmEntityPageProps) => { activeConfig.value .list(query()) .then((res: R) => { - const page = res.data || {}; + const page = (res.data ?? {}) as { records?: AlarmEntity[]; total?: number }; state.rows = page.records || []; state.page.total = Number(page.total || 0); })