From a741e9acf6e153e8e88f7b5f088ce2a59954e558 Mon Sep 17 00:00:00 2001 From: Feynman Date: Thu, 9 Apr 2026 09:19:38 +0800 Subject: [PATCH 1/5] refactor: update route handling in useCanvasOperation to use computed property for list routes and improve error logging in dataflow store --- packages/dag/src/EditorView.vue | 13 ++++++------ .../dag/src/composables/useCanvasOperation.ts | 20 ++++++++++++++++--- packages/dag/src/stores/dataflow.store.ts | 4 ++++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/dag/src/EditorView.vue b/packages/dag/src/EditorView.vue index c7eb66260..e1a9f3339 100644 --- a/packages/dag/src/EditorView.vue +++ b/packages/dag/src/EditorView.vue @@ -83,12 +83,13 @@ const init = async () => { if (taskId) { await initNodeType() - await dataflowStore.fetchDataflow(taskId) - // nextTick(() => { - // setTimeout(() => { - // canvasRef.value.fitViewWithOffset({ duration: 0, maxZoom: 1 }) - // }, 0) - // }) + const task = await dataflowStore.fetchDataflow(taskId) + + if (!task) { + ElMessage.error(t('packages_dag_mixins_editor_renwubucunzai')) + handlePageReturn() + return + } } else { let syncType let targetRoute diff --git a/packages/dag/src/composables/useCanvasOperation.ts b/packages/dag/src/composables/useCanvasOperation.ts index 1c09b5038..16df9ff3d 100644 --- a/packages/dag/src/composables/useCanvasOperation.ts +++ b/packages/dag/src/composables/useCanvasOperation.ts @@ -1771,8 +1771,22 @@ export function useCanvasOperation() { connHeartbeat: 'heartbeatTable', } + const listRoute = computed(() => { + const name = route.name as string + const map = { + DataflowEditor: 'dataflowList', + TaskMonitor: 'dataflowList', + MigrateEditor: 'migrateList', + MigrationMonitor: 'migrateList', + } + + return map[name] + }) + const handlePageReturn = () => { - const listRoute = listRouteMap[dataflow.value.syncType] + const routeName = dataflow.value.syncType + ? listRouteMap[dataflow.value.syncType] + : listRoute.value if (!dataflowStore.dag.nodes.length && dataflow.value.id) { Modal.confirm( @@ -1787,13 +1801,13 @@ export function useCanvasOperation() { deleteTask(dataflow.value.id) } router.push({ - name: listRoute, + name: routeName, }) window.name = '' }) } else { router.push({ - name: listRoute, + name: routeName, }) window.name = '' } diff --git a/packages/dag/src/stores/dataflow.store.ts b/packages/dag/src/stores/dataflow.store.ts index 120085cbb..d12c8777a 100644 --- a/packages/dag/src/stores/dataflow.store.ts +++ b/packages/dag/src/stores/dataflow.store.ts @@ -268,6 +268,10 @@ export const useDataflowStore = defineStore('dataflow', () => { dag.value.nodes = nodes dag.value.edges = edges + + return response + } catch (error) { + console.error(error) } finally { taskLoading.value = false } From 9ebba8b38ed344ed28b71f87997a9004dca98d8e Mon Sep 17 00:00:00 2001 From: Feynman Date: Fri, 10 Apr 2026 08:34:54 +0800 Subject: [PATCH 2/5] refactor: convert Dashboard.vue to Composition API, streamline data handling, and enhance task dashboard integration --- apps/daas/src/views/dashboard/Dashboard.vue | 1850 ++++++++--------- packages/api/src/core/task.ts | 85 + packages/assets/styles/utilities.scss | 20 + packages/styles/src/design/tokens/default.css | 4 + packages/types/src/daas-components.d.ts | 14 + 5 files changed, 948 insertions(+), 1025 deletions(-) diff --git a/apps/daas/src/views/dashboard/Dashboard.vue b/apps/daas/src/views/dashboard/Dashboard.vue index b17ecd7c1..c26e2b57a 100644 --- a/apps/daas/src/views/dashboard/Dashboard.vue +++ b/apps/daas/src/views/dashboard/Dashboard.vue @@ -1,926 +1,667 @@ - diff --git a/packages/api/src/core/task.ts b/packages/api/src/core/task.ts index da3b5f449..9cadd7c3c 100644 --- a/packages/api/src/core/task.ts +++ b/packages/api/src/core/task.ts @@ -442,3 +442,88 @@ export function checkTaskMemoryHeap(taskId: string) { isSafe: boolean }>(`${BASE_URL}/checkTaskMemoryHeap/${taskId}`) } + +// ── Task Dashboard ────────────────────────────────────────────────── + +export interface TaskDashboardQuery { + type: string + step: number + dashboardType: string + top: number + startAt: number + endAt: number +} + +export interface TaskDashboardActiveTasks { + total: number + running: number + error: number + maxLag: number + minLag: number +} + +export interface TaskDashboardThroughput { + current: number + peak: number + dataRate: number + changeRate: number +} + +export interface TaskDashboardConnectedDb { + id: string + name: string + tableCount: number +} + +export interface TaskDashboardApiRequests { + total: number + failed: number + errorRate: number + avgTime: number +} + +export interface TaskDashboardTrendSeries { + ts: number[] + values: number[] +} + +export interface TaskDashboardTopTask { + taskId: string + taskName: string + latency: number + throughput: number +} + +export interface TaskDashboardVo { + query: TaskDashboardQuery + summary: { + activeTasks: TaskDashboardActiveTasks + totalThroughput: TaskDashboardThroughput + connectedDbs: { + total: number + items: TaskDashboardConnectedDb[] + } + apiRequests: TaskDashboardApiRequests + } + trends: { + throughput: TaskDashboardTrendSeries + apiRequests: TaskDashboardTrendSeries + } + tops: { + topLaggingTasks: TaskDashboardTopTask[] + topThroughputTasks: TaskDashboardTopTask[] + } +} + +export interface TaskDashboardParams { + type?: 'minute' | 'hours' | 'days' + step?: number + dashboardType?: string + top?: number +} + +export function fetchTaskDashboard(params?: TaskDashboardParams) { + return requestClient.get(`${BASE_URL}/dashboard`, { + params, + }) +} diff --git a/packages/assets/styles/utilities.scss b/packages/assets/styles/utilities.scss index 2245899bb..fc9010025 100644 --- a/packages/assets/styles/utilities.scss +++ b/packages/assets/styles/utilities.scss @@ -12852,6 +12852,10 @@ grid-template-columns: repeat(3, minmax(0, 1fr)); } +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + .grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } @@ -13189,3 +13193,19 @@ .bg-components-kbd-bg-gray { background-color: var(--color-components-kbd-bg-gray); } + +.text-blue-500 { + color: var(--color-blue-500); +} + +.text-indigo-500 { + color: var(--color-indigo-500); +} + +.text-teal-500 { + color: var(--color-teal-500); +} + +.text-purple-500 { + color: var(--color-purple-500); +} diff --git a/packages/styles/src/design/tokens/default.css b/packages/styles/src/design/tokens/default.css index b0c941fc2..ad78f83b1 100644 --- a/packages/styles/src/design/tokens/default.css +++ b/packages/styles/src/design/tokens/default.css @@ -40,6 +40,7 @@ --color-blue-50: oklch(97% 0.014 254.604); --color-blue-200: oklch(88.2% 0.059 254.128); --color-blue-300: oklch(80.9% 0.105 251.813); + --color-blue-500: oklch(62.3% 0.214 259.815); --color-blue-600: oklch(54.6% 0.245 262.881); --color-blue-800: oklch(42.4% 0.199 265.638); --color-blue-950: oklch(28.2% 0.091 267.935); @@ -49,6 +50,9 @@ --color-orange-600: oklch(64.6% 0.222 41.116); --color-orange-800: oklch(47% 0.157 37.304); --color-orange-950: oklch(26.6% 0.079 36.259); + --color-indigo-500: oklch(58.5% 0.233 277.117); + --color-teal-500: oklch(70.4% 0.14 182.503); + --color-purple-500: oklch(62.7% 0.265 303.9); --text-dark: #1d2129; --text-normal: #333c4a; diff --git a/packages/types/src/daas-components.d.ts b/packages/types/src/daas-components.d.ts index 9d31fd0f4..b53aa470e 100644 --- a/packages/types/src/daas-components.d.ts +++ b/packages/types/src/daas-components.d.ts @@ -90,7 +90,9 @@ declare module 'vue' { ElTreeV2: typeof import('element-plus/es')['ElTreeV2'] ElUpload: typeof import('element-plus/es')['ElUpload'] IFluentFolderLink16Regular: typeof import('~icons/fluent/folder-link16-regular')['default'] + ILucideActivity: typeof import('~icons/lucide/activity')['default'] ILucideAlertCircle: typeof import('~icons/lucide/alert-circle')['default'] + ILucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] ILucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] ILucideArrowRight: typeof import('~icons/lucide/arrow-right')['default'] ILucideBadgeAlert: typeof import('~icons/lucide/badge-alert')['default'] @@ -99,6 +101,7 @@ declare module 'vue' { ILucideCalendarDays: typeof import('~icons/lucide/calendar-days')['default'] ILucideCheck: typeof import('~icons/lucide/check')['default'] ILucideCheckCheck: typeof import('~icons/lucide/check-check')['default'] + ILucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] ILucideChevronDown: typeof import('~icons/lucide/chevron-down')['default'] ILucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default'] ILucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] @@ -124,6 +127,7 @@ declare module 'vue' { ILucideFolder: typeof import('~icons/lucide/folder')['default'] ILucideFolderClosed: typeof import('~icons/lucide/folder-closed')['default'] ILucideFolderOpen: typeof import('~icons/lucide/folder-open')['default'] + ILucideFoldVertical: typeof import('~icons/lucide/fold-vertical')['default'] ILucideGitBranch: typeof import('~icons/lucide/git-branch')['default'] ILucideGitCompareArrows: typeof import('~icons/lucide/git-compare-arrows')['default'] ILucideGithub: typeof import('~icons/lucide/github')['default'] @@ -165,14 +169,17 @@ declare module 'vue' { ILucideTable: typeof import('~icons/lucide/table')['default'] ILucideTag: typeof import('~icons/lucide/tag')['default'] ILucideTrash2: typeof import('~icons/lucide/trash2')['default'] + ILucideTrendingUp: typeof import('~icons/lucide/trending-up')['default'] ILucideTriangleAlert: typeof import('~icons/lucide/triangle-alert')['default'] ILucideUndo: typeof import('~icons/lucide/undo')['default'] ILucideUndo2: typeof import('~icons/lucide/undo2')['default'] + ILucideUnfoldVertical: typeof import('~icons/lucide/unfold-vertical')['default'] ILucideUpload: typeof import('~icons/lucide/upload')['default'] ILucideUserRound: typeof import('~icons/lucide/user-round')['default'] ILucideWandSparkles: typeof import('~icons/lucide/wand-sparkles')['default'] ILucideWorkflow: typeof import('~icons/lucide/workflow')['default'] ILucideX: typeof import('~icons/lucide/x')['default'] + ILucideXCircle: typeof import('~icons/lucide/x-circle')['default'] ILucideZoomIn: typeof import('~icons/lucide/zoom-in')['default'] ILucideZoomOut: typeof import('~icons/lucide/zoom-out')['default'] IMingcuteAddFill: typeof import('~icons/mingcute/add-fill')['default'] @@ -288,7 +295,9 @@ declare global { const ElTreeV2: typeof import('element-plus/es')['ElTreeV2'] const ElUpload: typeof import('element-plus/es')['ElUpload'] const IFluentFolderLink16Regular: typeof import('~icons/fluent/folder-link16-regular')['default'] + const ILucideActivity: typeof import('~icons/lucide/activity')['default'] const ILucideAlertCircle: typeof import('~icons/lucide/alert-circle')['default'] + const ILucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] const ILucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] const ILucideArrowRight: typeof import('~icons/lucide/arrow-right')['default'] const ILucideBadgeAlert: typeof import('~icons/lucide/badge-alert')['default'] @@ -297,6 +306,7 @@ declare global { const ILucideCalendarDays: typeof import('~icons/lucide/calendar-days')['default'] const ILucideCheck: typeof import('~icons/lucide/check')['default'] const ILucideCheckCheck: typeof import('~icons/lucide/check-check')['default'] + const ILucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] const ILucideChevronDown: typeof import('~icons/lucide/chevron-down')['default'] const ILucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default'] const ILucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] @@ -322,6 +332,7 @@ declare global { const ILucideFolder: typeof import('~icons/lucide/folder')['default'] const ILucideFolderClosed: typeof import('~icons/lucide/folder-closed')['default'] const ILucideFolderOpen: typeof import('~icons/lucide/folder-open')['default'] + const ILucideFoldVertical: typeof import('~icons/lucide/fold-vertical')['default'] const ILucideGitBranch: typeof import('~icons/lucide/git-branch')['default'] const ILucideGitCompareArrows: typeof import('~icons/lucide/git-compare-arrows')['default'] const ILucideGithub: typeof import('~icons/lucide/github')['default'] @@ -363,14 +374,17 @@ declare global { const ILucideTable: typeof import('~icons/lucide/table')['default'] const ILucideTag: typeof import('~icons/lucide/tag')['default'] const ILucideTrash2: typeof import('~icons/lucide/trash2')['default'] + const ILucideTrendingUp: typeof import('~icons/lucide/trending-up')['default'] const ILucideTriangleAlert: typeof import('~icons/lucide/triangle-alert')['default'] const ILucideUndo: typeof import('~icons/lucide/undo')['default'] const ILucideUndo2: typeof import('~icons/lucide/undo2')['default'] + const ILucideUnfoldVertical: typeof import('~icons/lucide/unfold-vertical')['default'] const ILucideUpload: typeof import('~icons/lucide/upload')['default'] const ILucideUserRound: typeof import('~icons/lucide/user-round')['default'] const ILucideWandSparkles: typeof import('~icons/lucide/wand-sparkles')['default'] const ILucideWorkflow: typeof import('~icons/lucide/workflow')['default'] const ILucideX: typeof import('~icons/lucide/x')['default'] + const ILucideXCircle: typeof import('~icons/lucide/x-circle')['default'] const ILucideZoomIn: typeof import('~icons/lucide/zoom-in')['default'] const ILucideZoomOut: typeof import('~icons/lucide/zoom-out')['default'] const IMingcuteAddFill: typeof import('~icons/mingcute/add-fill')['default'] From 3d188c00a7e53320c74b2288543020b382eebecc Mon Sep 17 00:00:00 2001 From: Feynman Date: Sun, 12 Apr 2026 18:35:56 +0800 Subject: [PATCH 3/5] refactor: enhance Dashboard.vue with improved data fetching, task options, and time range handling --- apps/daas/src/views/dashboard/Dashboard.vue | 249 ++++++++++++-------- 1 file changed, 147 insertions(+), 102 deletions(-) diff --git a/apps/daas/src/views/dashboard/Dashboard.vue b/apps/daas/src/views/dashboard/Dashboard.vue index c26e2b57a..810fad344 100644 --- a/apps/daas/src/views/dashboard/Dashboard.vue +++ b/apps/daas/src/views/dashboard/Dashboard.vue @@ -8,9 +8,10 @@ import { type TaskDashboardTopTask, type TaskDashboardVo, } from '@tap/api/src/core/task' -import { getProcessInfo } from '@tap/api/src/core/workers' +import { fetchWorkers, getProcessInfo } from '@tap/api/src/core/workers' import PageContainer from '@tap/business/src/components/PageContainer.vue' import Chart from '@tap/component/src/chart/Chart.vue' +import { calcUnit } from '@tap/shared/src/number' import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { STATUS_MAP as DASHBOARD_STATUS_MAP } from './const' @@ -31,7 +32,16 @@ const trendsTimeRange = ref<'5min' | '1h' | '24h'>('24h') // Top Tasks state const topTaskTab = ref<'lagging' | 'throughput'>('lagging') +const topTaskTabOptions = [ + { label: 'Most Lagging', value: 'lagging' }, + { label: 'Highest Throughput', value: 'throughput' }, +] const topTaskLimit = ref<5 | 10 | 20>(5) +const topTaskLimitOptions = [ + { label: 'Top 5', value: 5 }, + { label: 'Top 10', value: 10 }, + { label: 'Top 20', value: 20 }, +] // Cluster / Agent data interface AgentNode extends ClusterState { @@ -85,7 +95,7 @@ const apiChartOption = computed(() => { }) function formatTimeLabel(ts: number): string { - const d = new Date(ts) + const d = new Date(Number(`${ts}000`)) const hh = String(d.getHours()).padStart(2, '0') const mm = String(d.getMinutes()).padStart(2, '0') return `${hh}:${mm}` @@ -245,18 +255,22 @@ function getStatusLabel(type: string) { return (DASHBOARD_STATUS_MAP as any)[type] || '-' } +// ── Helpers ──────────────────────────────────────────── +function rangeToTimeParams(range: string): { + type: 'minute' | 'hours' | 'days' +} { + if (range === '5m' || range === '5min') return { type: 'minute' } + if (range === '1h') return { type: 'hours' } + return { type: 'days' } +} + // ── Data fetching ────────────────────────────────────── async function fetchDashboardData() { loading.value = true try { const [dashboard, clusterData] = await Promise.all([ fetchTaskDashboard({ - type: - trendsTimeRange.value === '5min' - ? 'minute' - : trendsTimeRange.value === '1h' - ? 'hours' - : 'days', + ...rangeToTimeParams(trendsTimeRange.value), top: topTaskLimit.value, }).catch(() => null), fetchClusterStates({ type: 'dashboard' }).catch(() => ({ items: [] })), @@ -265,7 +279,7 @@ async function fetchDashboardData() { if (dashboard) dashboardData.value = dashboard // Process cluster data - const processIdSet = new Set() + const processIds: string[] = [] const items: AgentNode[] = (clusterData?.items || []).map((item: any) => { const node: AgentNode = { ...item } if (node.status !== 'running') { @@ -278,32 +292,115 @@ async function fetchDashboardData() { } if (node.systemInfo?.process_id) { node.processId = node.systemInfo.process_id - processIdSet.add(node.systemInfo.process_id) + processIds.push(node.systemInfo.process_id) } return node }) - agentNodes.value = items - if (processIdSet.size > 0) { + // Fetch worker usage rates (CPU / Memory) — same approach as Cluster.vue + if (processIds.length > 0) { try { - const processData = await getProcessInfo(Array.from(processIdSet)) - for (const id of Object.keys(processData)) { - agentRunningTask.value[id] = (processData as any)[id].runningTaskNum + const [workerResponse, processData] = await Promise.all([ + fetchWorkers({ + where: { + process_id: { inq: processIds }, + worker_type: 'connector', + }, + }).catch(() => null), + getProcessInfo(processIds).catch(() => null), + ]) + + // Build metricValues map from worker data + const metricMap: Record< + string, + { CpuUsage: string; HeapMemoryUsage: string } + > = {} + if (workerResponse?.items?.length) { + for (const w of workerResponse.items) { + if (w.metricValues) { + metricMap[(w as any).process_id] = { + CpuUsage: `${(((w.metricValues as any).CpuUsage ?? 0) * 100).toFixed(2)}%`, + HeapMemoryUsage: `${(((w.metricValues as any).HeapMemoryUsage ?? 0) * 100).toFixed(2)}%`, + } + } + } + } + + // Apply metricValues onto each node + for (const node of items) { + if (node.processId && metricMap[node.processId]) { + node.metricValues = metricMap[node.processId] + } + } + + // Running task counts + if (processData) { + for (const id of Object.keys(processData)) { + agentRunningTask.value[id] = (processData as any)[id].runningTaskNum + } } } catch { /* ignore */ } } + agentNodes.value = items + lastUpdated.value = new Date().toLocaleTimeString() } finally { loading.value = false } } -function onTrendsTimeChange(range: '5min' | '1h' | '24h') { - trendsTimeRange.value = range - fetchDashboardData() +// ── Partial fetchers (only re-fetch the changed section) ── +async function fetchPartial( + dashboardType: string, + range: string, + top?: number, +) { + try { + const result = await fetchTaskDashboard({ + ...rangeToTimeParams(range), + dashboardType, + ...(top != null ? { top } : {}), + }) + if (!result) return + // Merge partial result into existing data + const prev = dashboardData.value + if (!prev) { + dashboardData.value = result + return + } + if (result.summary) { + prev.summary = { ...prev.summary, ...result.summary } + } + if (result.trends) { + // Only overwrite non-empty trend arrays + if (result.trends.throughput?.ts?.length) { + prev.trends = { ...prev.trends, throughput: result.trends.throughput } + } + if (result.trends.apiRequests?.ts?.length) { + prev.trends = { ...prev.trends, apiRequests: result.trends.apiRequests } + } + } + if (result.tops) { + prev.tops = result.tops + } + } catch { + /* ignore */ + } +} + +function onApiTimeRangeChange() { + fetchPartial('apiRequests', apiTimeRange.value) +} + +function onTrendsTimeChange() { + fetchPartial('trends', trendsTimeRange.value) +} + +function onTopTaskLimitChange() { + fetchPartial('tops', trendsTimeRange.value, topTaskLimit.value) } // ── Navigation ───────────────────────────────────────── @@ -340,7 +437,7 @@ onBeforeUnmount(() => {
Active Tasks -
+
@@ -369,7 +466,7 @@ onBeforeUnmount(() => {
Total Throughput -
+
@@ -377,16 +474,16 @@ onBeforeUnmount(() => {
{{ - formatNumber(throughput?.current ?? 0) + (throughput?.current ?? 0).toFixed(2) }} events/sec
Peak {{ formatNumber(throughput?.peak ?? 0) }}Peak {{ (throughput?.peak ?? 0).toFixed(2) }} Data {{ throughput?.dataRate ?? 0 }} MB/sData {{ calcUnit(throughput?.dataRate ?? 0, 'b') }}/s

{ " > {{ throughput.changeRate > 0 ? '+' : '' - }}{{ throughput.changeRate }}% vs last hour + }}{{ throughput.changeRate.toFixed(2) }}% vs last hour

@@ -405,7 +502,7 @@ onBeforeUnmount(() => {
Connected DBs -
+
@@ -422,7 +519,7 @@ onBeforeUnmount(() => { v-for="db in (connectedDbs?.items || []).slice(0, 3)" :key="db.id" class="dashboard__tag dashboard__tag--blue" - >{{ db.name }} {{ db.tableCount }} tbls{{ db.name }} {{ db.tableCount }} tbls
@@ -432,19 +529,15 @@ onBeforeUnmount(() => {
API Requests -
- -
+
- +
@@ -470,16 +563,12 @@ onBeforeUnmount(() => {

System Trends

-
- -
+
@@ -506,31 +595,18 @@ onBeforeUnmount(() => {

Top Tasks

-
- - -
-
-
- +
+
@@ -751,37 +827,6 @@ onBeforeUnmount(() => { font-weight: 500; } - // ── Time / Tab switcher ──────────────────────────────── - &__time-switcher, - &__tab-switcher { - display: inline-flex; - background-color: #f1f5f9; - border-radius: 0.375rem; - padding: 2px; - - button { - padding: 0.125rem 0.5rem; - border: none; - background: none; - border-radius: 0.25rem; - font-size: 0.6875rem; - font-weight: 500; - color: #64748b; - cursor: pointer; - transition: all 0.15s ease; - - &.active { - background-color: #fff; - color: #0f172a; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); - } - - &:hover:not(.active) { - color: #334155; - } - } - } - // ── Nodes badge ──────────────────────────────────────── &__nodes-badge { display: inline-block; From 3367eb544a358db851edcbd4dd012fc8d7a2baeb Mon Sep 17 00:00:00 2001 From: Feynman Date: Mon, 13 Apr 2026 09:14:36 +0800 Subject: [PATCH 4/5] feat: add ODH dashboard localization support in English, Chinese, and Traditional Chinese --- apps/daas/src/i18n/langs/en.js | 39 +++++ apps/daas/src/i18n/langs/zh-CN.js | 39 +++++ apps/daas/src/i18n/langs/zh-TW.js | 39 +++++ apps/daas/src/views/dashboard/Dashboard.vue | 171 ++++++++++++++------ 4 files changed, 237 insertions(+), 51 deletions(-) diff --git a/apps/daas/src/i18n/langs/en.js b/apps/daas/src/i18n/langs/en.js index 21d7ddb00..d84c19511 100644 --- a/apps/daas/src/i18n/langs/en.js +++ b/apps/daas/src/i18n/langs/en.js @@ -224,6 +224,45 @@ export default { dashboard_error: 'Number of tasks with errors in verification', dashboard_no_data_here: 'There is no data here~', dashboard_no_statistics: 'No {0} statistics yet', + // Dashboard ODH + dashboard_odh_active_tasks: 'Active Tasks', + dashboard_odh_running: 'Running', + dashboard_odh_error: 'Error', + dashboard_odh_max_lag: 'Max Lag', + dashboard_odh_min_lag: 'Min Lag', + dashboard_odh_total_throughput: 'Total Throughput', + dashboard_odh_events_sec: 'events/sec', + dashboard_odh_peak: 'Peak', + dashboard_odh_data: 'Data', + dashboard_odh_vs_last_hour: 'vs last hour', + dashboard_odh_connected_dbs: 'Connected DBs', + dashboard_odh_sources: 'sources', + dashboard_odh_tbls: 'tbls', + dashboard_odh_api_requests: 'API Requests', + dashboard_odh_failed: 'Failed', + dashboard_odh_rate: 'Rate', + dashboard_odh_avg_time: 'Avg Time', + dashboard_odh_system_trends: 'System Trends', + dashboard_odh_throughput_chart: 'Throughput (events/sec)', + dashboard_odh_api_chart: 'API Requests (req/sec)', + dashboard_odh_no_data_in_range: 'No data in this time range', + dashboard_odh_top_tasks: 'Top Tasks', + dashboard_odh_most_lagging: 'Most Lagging', + dashboard_odh_highest_throughput: 'Highest Throughput', + dashboard_odh_top_n: 'Top {0}', + dashboard_odh_task_name: 'Task Name', + dashboard_odh_latency: 'Latency', + dashboard_odh_throughput: 'Throughput', + dashboard_odh_no_task_data: 'No task data', + dashboard_odh_agent_cluster: 'Agent Cluster Status', + dashboard_odh_nodes_total: '{0} Nodes Total', + dashboard_odh_agent_name: 'Agent Name', + dashboard_odh_status: 'Status', + dashboard_odh_cpu_usage: 'CPU Usage', + dashboard_odh_memory_usage: 'Memory Usage', + dashboard_odh_running_tasks: 'Running Tasks', + dashboard_odh_no_agents: 'No agents found', + dashboard_odh_just_now: 'Just now', // 元数据管理 metadata_db: 'Owned library', metadata_change_name: 'Rename', diff --git a/apps/daas/src/i18n/langs/zh-CN.js b/apps/daas/src/i18n/langs/zh-CN.js index 36e6a33dd..e8937ed25 100644 --- a/apps/daas/src/i18n/langs/zh-CN.js +++ b/apps/daas/src/i18n/langs/zh-CN.js @@ -217,6 +217,45 @@ export default { dashboard_error: '校验出错的任务数', dashboard_no_data_here: '这里没用数据哦~', dashboard_no_statistics: '暂无{0}统计', + // Dashboard ODH + dashboard_odh_active_tasks: '活跃任务', + dashboard_odh_running: '运行中', + dashboard_odh_error: '错误', + dashboard_odh_max_lag: '最大延迟', + dashboard_odh_min_lag: '最小延迟', + dashboard_odh_total_throughput: '总吞吐量', + dashboard_odh_events_sec: '事件/秒', + dashboard_odh_peak: '峰值', + dashboard_odh_data: '数据', + dashboard_odh_vs_last_hour: '较上一小时', + dashboard_odh_connected_dbs: '已连接数据库', + dashboard_odh_sources: '个数据源', + dashboard_odh_tbls: '张表', + dashboard_odh_api_requests: 'API 请求', + dashboard_odh_failed: '失败', + dashboard_odh_rate: '错误率', + dashboard_odh_avg_time: '平均耗时', + dashboard_odh_system_trends: '系统趋势', + dashboard_odh_throughput_chart: '吞吐量(事件/秒)', + dashboard_odh_api_chart: 'API 请求(请求/秒)', + dashboard_odh_no_data_in_range: '该时间范围内暂无数据', + dashboard_odh_top_tasks: '任务排行', + dashboard_odh_most_lagging: '延迟最高', + dashboard_odh_highest_throughput: '吞吐量最高', + dashboard_odh_top_n: '前 {0}', + dashboard_odh_task_name: '任务名称', + dashboard_odh_latency: '延迟', + dashboard_odh_throughput: '吞吐量', + dashboard_odh_no_task_data: '暂无任务数据', + dashboard_odh_agent_cluster: 'Agent 集群状态', + dashboard_odh_nodes_total: '共 {0} 个节点', + dashboard_odh_agent_name: 'Agent 名称', + dashboard_odh_status: '状态', + dashboard_odh_cpu_usage: 'CPU 使用率', + dashboard_odh_memory_usage: '内存使用率', + dashboard_odh_running_tasks: '运行中任务', + dashboard_odh_no_agents: '暂无 Agent', + dashboard_odh_just_now: '刚刚', // 元数据管理 metadata_db: '所属库', metadata_change_name: '改名', diff --git a/apps/daas/src/i18n/langs/zh-TW.js b/apps/daas/src/i18n/langs/zh-TW.js index 3a81b1147..a740da68c 100644 --- a/apps/daas/src/i18n/langs/zh-TW.js +++ b/apps/daas/src/i18n/langs/zh-TW.js @@ -217,6 +217,45 @@ export default { dashboard_error: '校驗出錯的任務數', dashboard_no_data_here: '這裡沒用數據哦~', dashboard_no_statistics: '暫無{0}統計', + // Dashboard ODH + dashboard_odh_active_tasks: '活躍任務', + dashboard_odh_running: '運行中', + dashboard_odh_error: '錯誤', + dashboard_odh_max_lag: '最大延遲', + dashboard_odh_min_lag: '最小延遲', + dashboard_odh_total_throughput: '總吞吐量', + dashboard_odh_events_sec: '事件/秒', + dashboard_odh_peak: '峰值', + dashboard_odh_data: '數據', + dashboard_odh_vs_last_hour: '較上一小時', + dashboard_odh_connected_dbs: '已連接資料庫', + dashboard_odh_sources: '個資料源', + dashboard_odh_tbls: '張表', + dashboard_odh_api_requests: 'API 請求', + dashboard_odh_failed: '失敗', + dashboard_odh_rate: '錯誤率', + dashboard_odh_avg_time: '平均耗時', + dashboard_odh_system_trends: '系統趨勢', + dashboard_odh_throughput_chart: '吞吐量(事件/秒)', + dashboard_odh_api_chart: 'API 請求(請求/秒)', + dashboard_odh_no_data_in_range: '該時間範圍內暫無數據', + dashboard_odh_top_tasks: '任務排行', + dashboard_odh_most_lagging: '延遲最高', + dashboard_odh_highest_throughput: '吞吐量最高', + dashboard_odh_top_n: '前 {0}', + dashboard_odh_task_name: '任務名稱', + dashboard_odh_latency: '延遲', + dashboard_odh_throughput: '吞吐量', + dashboard_odh_no_task_data: '暫無任務數據', + dashboard_odh_agent_cluster: 'Agent 叢集狀態', + dashboard_odh_nodes_total: '共 {0} 個節點', + dashboard_odh_agent_name: 'Agent 名稱', + dashboard_odh_status: '狀態', + dashboard_odh_cpu_usage: 'CPU 使用率', + dashboard_odh_memory_usage: '記憶體使用率', + dashboard_odh_running_tasks: '運行中任務', + dashboard_odh_no_agents: '暫無 Agent', + dashboard_odh_just_now: '剛剛', // 元數據管理 metadata_db: '所屬庫', metadata_change_name: '改名', diff --git a/apps/daas/src/views/dashboard/Dashboard.vue b/apps/daas/src/views/dashboard/Dashboard.vue index 810fad344..2c947a22d 100644 --- a/apps/daas/src/views/dashboard/Dashboard.vue +++ b/apps/daas/src/views/dashboard/Dashboard.vue @@ -11,16 +11,18 @@ import { import { fetchWorkers, getProcessInfo } from '@tap/api/src/core/workers' import PageContainer from '@tap/business/src/components/PageContainer.vue' import Chart from '@tap/component/src/chart/Chart.vue' +import { useI18n } from '@tap/i18n' import { calcUnit } from '@tap/shared/src/number' import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { STATUS_MAP as DASHBOARD_STATUS_MAP } from './const' const router = useRouter() +const { t } = useI18n() // ── State ────────────────────────────────────────────── const loading = ref(false) -const lastUpdated = ref('Just now') +const lastUpdated = ref(t('dashboard_odh_just_now')) const dashboardData = ref(null) let refreshTimer: ReturnType | null = null @@ -32,16 +34,16 @@ const trendsTimeRange = ref<'5min' | '1h' | '24h'>('24h') // Top Tasks state const topTaskTab = ref<'lagging' | 'throughput'>('lagging') -const topTaskTabOptions = [ - { label: 'Most Lagging', value: 'lagging' }, - { label: 'Highest Throughput', value: 'throughput' }, -] +const topTaskTabOptions = computed(() => [ + { label: t('dashboard_odh_most_lagging'), value: 'lagging' }, + { label: t('dashboard_odh_highest_throughput'), value: 'throughput' }, +]) const topTaskLimit = ref<5 | 10 | 20>(5) -const topTaskLimitOptions = [ - { label: 'Top 5', value: 5 }, - { label: 'Top 10', value: 10 }, - { label: 'Top 20', value: 20 }, -] +const topTaskLimitOptions = computed(() => [ + { label: t('dashboard_odh_top_n', [5]), value: 5 }, + { label: t('dashboard_odh_top_n', [10]), value: 10 }, + { label: t('dashboard_odh_top_n', [20]), value: 20 }, +]) // Cluster / Agent data interface AgentNode extends ClusterState { @@ -78,26 +80,38 @@ const connectedDbs = computed(() => dashboardData.value?.summary?.connectedDbs) // ── KPI: API Requests ────────────────────────────────── const apiRequests = computed(() => dashboardData.value?.summary?.apiRequests) +const hasApiTrendsData = computed(() => { + const trends = dashboardData.value?.trends?.apiRequests + return (trends?.ts?.length ?? 0) > 0 +}) // ── System Trends: ECharts options ───────────────────── const throughputChartOption = computed(() => { const trends = dashboardData.value?.trends?.throughput if (!trends?.ts?.length) return buildAreaChartOption([], [], '#6366f1') - const xData = trends.ts.map((t) => formatTimeLabel(t)) - return buildAreaChartOption(xData, trends.values, '#6366f1') + // const showSeconds = trendsTimeRange.value === '5min' + const xData = trends.ts.map((t) => formatTimeLabel(t, true)) + const yData = trends.values.map((v: number) => Number(v.toFixed(2))) + return buildAreaChartOption(xData, yData, '#6366f1') }) const apiChartOption = computed(() => { const trends = dashboardData.value?.trends?.apiRequests if (!trends?.ts?.length) return buildLineChartOption([], [], '#a855f7') - const xData = trends.ts.map((t) => formatTimeLabel(t)) - return buildLineChartOption(xData, trends.values, '#a855f7') + // const showSeconds = apiTimeRange.value === '5m' + const xData = trends.ts.map((t) => formatTimeLabel(t, true)) + const yData = trends.values.map((v: number) => Number(v.toFixed(2))) + return buildLineChartOption(xData, yData, '#a855f7') }) -function formatTimeLabel(ts: number): string { +function formatTimeLabel(ts: number, showSeconds = false): string { const d = new Date(Number(`${ts}000`)) const hh = String(d.getHours()).padStart(2, '0') const mm = String(d.getMinutes()).padStart(2, '0') + if (showSeconds) { + const ss = String(d.getSeconds()).padStart(2, '0') + return `${hh}:${mm}:${ss}` + } return `${hh}:${mm}` } @@ -436,7 +450,9 @@ onBeforeUnmount(() => {
- Active Tasks + {{ + t('dashboard_odh_active_tasks') + }}
@@ -448,16 +464,20 @@ onBeforeUnmount(() => {
Running {{ activeTasks?.running ?? 0 }}{{ t('dashboard_odh_running') }} + {{ activeTasks?.running ?? 0 }} Error {{ activeTasks?.error ?? 0 }}{{ t('dashboard_odh_error') }} + {{ activeTasks?.error ?? 0 }} Max Lag {{ formatLag(activeTasks?.maxLag ?? 0) }}{{ t('dashboard_odh_max_lag') }} + {{ formatLag(activeTasks?.maxLag ?? 0) }} Min Lag {{ formatLag(activeTasks?.minLag ?? 0) }}{{ t('dashboard_odh_min_lag') }} + {{ formatLag(activeTasks?.minLag ?? 0) }}
@@ -465,7 +485,9 @@ onBeforeUnmount(() => {
- Total Throughput + {{ + t('dashboard_odh_total_throughput') + }}
@@ -476,14 +498,18 @@ onBeforeUnmount(() => { {{ (throughput?.current ?? 0).toFixed(2) }} - events/sec + {{ + t('dashboard_odh_events_sec') + }}
Peak {{ (throughput?.peak ?? 0).toFixed(2) }}{{ t('dashboard_odh_peak') }} + {{ (throughput?.peak ?? 0).toFixed(2) }} Data {{ calcUnit(throughput?.dataRate ?? 0, 'b') }}/s{{ t('dashboard_odh_data') }} + {{ calcUnit(throughput?.dataRate ?? 0, 'b') }}/s

{ " > {{ throughput.changeRate > 0 ? '+' : '' - }}{{ throughput.changeRate.toFixed(2) }}% vs last hour + }}{{ throughput.changeRate.toFixed(2) }}% + {{ t('dashboard_odh_vs_last_hour') }}

- Connected DBs + {{ + t('dashboard_odh_connected_dbs') + }}
@@ -512,14 +541,19 @@ onBeforeUnmount(() => { {{ connectedDbs?.total ?? 0 }} - sources + {{ + t('dashboard_odh_sources') + }}
{{ db.name }} {{ db.tableCount }} tbls{{ db.name }} + {{ db.tableCount }} {{ t('dashboard_odh_tbls') }}
@@ -528,7 +562,9 @@ onBeforeUnmount(() => {
- API Requests + {{ + t('dashboard_odh_api_requests') + }} {
Failed {{ apiRequests?.failed ?? 0 }}{{ t('dashboard_odh_failed') }} + {{ apiRequests?.failed ?? 0 }} Rate {{ apiRequests?.errorRate ?? 0 }}%{{ t('dashboard_odh_rate') }} + {{ apiRequests?.errorRate ?? 0 }}% Avg Time {{ apiRequests?.avgTime ?? 0 }}ms{{ t('dashboard_odh_avg_time') }} + {{ apiRequests?.avgTime ?? 0 }}ms
@@ -562,7 +601,9 @@ onBeforeUnmount(() => {
-

System Trends

+

+ {{ t('dashboard_odh_system_trends') }} +

{

- Throughput (events/sec) + {{ t('dashboard_odh_throughput_chart') }}

@@ -581,10 +622,20 @@ onBeforeUnmount(() => {

- API Requests (req/sec) + {{ t('dashboard_odh_api_chart') }}

- + +
+ {{ + t('dashboard_odh_no_data_in_range') + }} +
@@ -594,7 +645,9 @@ onBeforeUnmount(() => {
-

Top Tasks

+

+ {{ t('dashboard_odh_top_tasks') }} +

{ # - Task Name - Latency - Throughput + {{ t('dashboard_odh_task_name') }} + {{ t('dashboard_odh_latency') }} + {{ t('dashboard_odh_throughput') }} @@ -637,27 +690,39 @@ onBeforeUnmount(() => {
- +
-

Agent Cluster Status

- {{ agentNodes.length }} Nodes Total +

+ {{ t('dashboard_odh_agent_cluster') }} +

+ {{ + t('dashboard_odh_nodes_total', [agentNodes.length]) + }}
- - - - - + + + + + @@ -737,7 +802,11 @@ onBeforeUnmount(() => {
Agent NameStatusCPU UsageMemory UsageRunning Tasks + {{ t('dashboard_odh_agent_name') }} + {{ t('dashboard_odh_status') }}{{ t('dashboard_odh_cpu_usage') }} + {{ t('dashboard_odh_memory_usage') }} + + {{ t('dashboard_odh_running_tasks') }} +
- +
From 4383000162f02ccace07c88355c1384450c3f07a Mon Sep 17 00:00:00 2001 From: Feynman Date: Mon, 13 Apr 2026 15:46:00 +0800 Subject: [PATCH 5/5] refactor: update Dashboard.vue --- apps/daas/src/views/dashboard/Dashboard.vue | 17 ++++++++++++++++- packages/api/src/core/task.ts | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/daas/src/views/dashboard/Dashboard.vue b/apps/daas/src/views/dashboard/Dashboard.vue index 2c947a22d..2077ea20d 100644 --- a/apps/daas/src/views/dashboard/Dashboard.vue +++ b/apps/daas/src/views/dashboard/Dashboard.vue @@ -212,6 +212,15 @@ const topTasks = computed(() => { return (list || []).slice(0, topTaskLimit.value) }) +function goToMonitor(task: TaskDashboardTopTask) { + const route = + task.syncType === 'migrate' + ? { name: 'MigrationMonitor', params: { id: task.taskId } } + : { name: 'TaskMonitor', params: { id: task.taskId } } + const href = router.resolve(route).href + window.open(href, '_blank') +} + function latencyClass(ms: number): string { const s = ms / 1000 if (s > 60) return 'badge--red' @@ -675,7 +684,13 @@ onBeforeUnmount(() => { {{ idx + 1 }} - {{ task.taskName }} + + {{ task.taskName }} +