From 7c103306b3ac3ec4164839df946f81499555f141 Mon Sep 17 00:00:00 2001 From: Feynman Date: Thu, 4 Jun 2026 11:39:25 +0800 Subject: [PATCH 01/11] feat(task-rebalance): add task rebalance feature with localization support and routing integration --- apps/daas/src/i18n/langs/en.js | 58 ++ apps/daas/src/i18n/langs/zh-CN.js | 51 ++ apps/daas/src/i18n/langs/zh-TW.js | 51 ++ apps/daas/src/router/menu.ts | 5 + apps/daas/src/router/routes.ts | 23 + apps/daas/src/views/cluster/Cluster.vue | 50 +- .../src/views/cluster/TaskRebalanceDrawer.vue | 593 ++++++++++++++++++ apps/daas/src/views/role/Role.vue | 1 + .../daas/src/views/task-rebalance/History.vue | 571 +++++++++++++++++ .../src/views/task-rebalance/openapi.yaml | 389 ++++++++++++ packages/api/src/core/index.ts | 1 + packages/api/src/core/task-rebalance.ts | 152 +++++ 12 files changed, 1939 insertions(+), 6 deletions(-) create mode 100644 apps/daas/src/views/cluster/TaskRebalanceDrawer.vue create mode 100644 apps/daas/src/views/task-rebalance/History.vue create mode 100644 apps/daas/src/views/task-rebalance/openapi.yaml create mode 100644 packages/api/src/core/task-rebalance.ts diff --git a/apps/daas/src/i18n/langs/en.js b/apps/daas/src/i18n/langs/en.js index 37cecbf67..0e06a9cc6 100644 --- a/apps/daas/src/i18n/langs/en.js +++ b/apps/daas/src/i18n/langs/en.js @@ -2276,6 +2276,64 @@ export default { daas_cluster_engine_hostname: 'Hostname/IP', daas_cluster_connection_count: 'TCP Connection Count', daas_cluser_keyword_placeholder: 'Search for hostnames', + daas_task_rebalance_button: 'Task Rebalance', + daas_task_rebalance_title: 'Task Rebalance Plan', + daas_task_rebalance_subtitle: + 'Drag task cards onto a target engine to adjust the task distribution across engines', + daas_task_rebalance_online: 'Online', + daas_task_rebalance_offline: 'Offline', + daas_task_rebalance_memory: 'Memory Usage', + daas_task_rebalance_task_count: 'Tasks', + daas_task_rebalance_legend_move: 'Tasks to be moved', + daas_task_rebalance_legend_locked: 'Tasks that cannot be rebalanced', + daas_task_rebalance_cancel: 'Cancel', + daas_task_rebalance_confirm: 'Confirm Rebalance', + daas_task_rebalance_empty: 'No tasks available for rebalancing', + daas_task_rebalance_create_success: 'Rebalance plan created, migration started', + daas_task_rebalance_type_initial_sync: 'Full', + daas_task_rebalance_type_cdc: 'Incremental', + daas_task_rebalance_type_full_cdc: 'Full + Incremental', + daas_task_rebalance_reason_only_one_agent: + 'At least two available engines are required', + daas_task_rebalance_reason_agent_offline: 'The current engine is offline', + daas_task_rebalance_reason_status_error: + 'The task is not running and cannot be migrated', + daas_task_rebalance_reason_manual_agent: + 'A fixed engine is specified and cannot be migrated', + daas_task_rebalance_reason_incremental: + 'Initial sync in progress, cannot be migrated', + daas_task_rebalance_history_title: 'Task Rebalance Records', + daas_task_rebalance_history_list_title: 'History', + daas_task_rebalance_history_empty: 'No rebalance records', + daas_task_rebalance_history_empty_desc: + 'Records will appear here after a rebalance', + daas_task_rebalance_history_detail_empty: 'Select a record on the left', + daas_task_rebalance_history_task_total: '{count} tasks', + daas_task_rebalance_history_record_running: 'Running', + daas_task_rebalance_history_record_done: 'Completed', + daas_task_rebalance_history_record_partial: 'Partially Completed', + daas_task_rebalance_history_record_cancelled: 'Cancelled', + daas_task_rebalance_history_record_failed: 'Failed', + daas_task_rebalance_history_stat_success: 'Success', + daas_task_rebalance_history_stat_failed: 'Failed', + daas_task_rebalance_history_stat_skipped: 'Skipped', + daas_task_rebalance_history_stat_running: 'Running', + daas_task_rebalance_history_stat_pending: 'Pending', + daas_task_rebalance_history_col_task: 'Task Name', + daas_task_rebalance_history_col_source: 'Source Engine', + daas_task_rebalance_history_col_target: 'Target Engine', + daas_task_rebalance_history_col_status: 'Status', + daas_task_rebalance_history_col_error: 'Error', + daas_task_rebalance_history_col_action: 'Action', + daas_task_rebalance_history_cancel: 'Cancel', + daas_task_rebalance_history_cancel_remaining: 'Cancel Remaining', + daas_task_rebalance_history_cancel_remaining_confirm: + 'Cancel the remaining {count} pending tasks? This cannot be undone.', + daas_task_rebalance_history_cancel_success: 'Task cancelled', + daas_task_rebalance_history_cancel_remaining_success: + '{count} tasks cancelled', + daas_task_rebalance_history_progress: 'Execution Progress', + page_title_task_rebalance: 'Task Rebalance', webhook_alerts: 'Webhook Alerts', webhook_alerts_detail: 'Webhook Alert Details', diff --git a/apps/daas/src/i18n/langs/zh-CN.js b/apps/daas/src/i18n/langs/zh-CN.js index 59b378870..3d9a1c21b 100644 --- a/apps/daas/src/i18n/langs/zh-CN.js +++ b/apps/daas/src/i18n/langs/zh-CN.js @@ -2156,6 +2156,57 @@ export default { daas_cluster_engine_hostname: '主机名/IP', daas_cluster_connection_count: 'TCP 连接数', daas_cluser_keyword_placeholder: '搜索主机名', + daas_task_rebalance_button: '任务调度均衡', + daas_task_rebalance_title: '任务均衡方案', + daas_task_rebalance_subtitle: '拖动任务卡片到目标引擎,调整各引擎的任务分布', + daas_task_rebalance_online: '在线', + daas_task_rebalance_offline: '离线', + daas_task_rebalance_memory: '内存使用率', + daas_task_rebalance_task_count: '任务数', + daas_task_rebalance_legend_move: '将被调走的任务', + daas_task_rebalance_legend_locked: '不可均衡的任务', + daas_task_rebalance_cancel: '取消', + daas_task_rebalance_confirm: '确认均衡', + daas_task_rebalance_empty: '暂无可均衡的任务', + daas_task_rebalance_create_success: '均衡方案已创建,开始执行任务迁移', + daas_task_rebalance_type_initial_sync: '全量', + daas_task_rebalance_type_cdc: '增量', + daas_task_rebalance_type_full_cdc: '全量+增量', + daas_task_rebalance_reason_only_one_agent: '可用引擎不足 2 个,无法均衡', + daas_task_rebalance_reason_agent_offline: '当前引擎已离线', + daas_task_rebalance_reason_status_error: '任务非运行中,无法迁移', + daas_task_rebalance_reason_manual_agent: '已指定固定引擎,无法迁移', + daas_task_rebalance_reason_incremental: '全量同步中,无法迁移', + daas_task_rebalance_history_title: '任务均衡记录', + daas_task_rebalance_history_list_title: '历史记录', + daas_task_rebalance_history_empty: '暂无均衡记录', + daas_task_rebalance_history_empty_desc: '任务均衡后记录将显示在这里', + daas_task_rebalance_history_detail_empty: '请在左侧选择一条记录', + daas_task_rebalance_history_task_total: '{count} 个任务', + daas_task_rebalance_history_record_running: '执行中', + daas_task_rebalance_history_record_done: '已完成', + daas_task_rebalance_history_record_partial: '部分完成', + daas_task_rebalance_history_record_cancelled: '已取消', + daas_task_rebalance_history_record_failed: '失败', + daas_task_rebalance_history_stat_success: '成功', + daas_task_rebalance_history_stat_failed: '失败', + daas_task_rebalance_history_stat_skipped: '跳过', + daas_task_rebalance_history_stat_running: '运行中', + daas_task_rebalance_history_stat_pending: '等待中', + daas_task_rebalance_history_col_task: '任务名称', + daas_task_rebalance_history_col_source: '源引擎', + daas_task_rebalance_history_col_target: '目标引擎', + daas_task_rebalance_history_col_status: '状态', + daas_task_rebalance_history_col_error: '错误原因', + daas_task_rebalance_history_col_action: '操作', + daas_task_rebalance_history_cancel: '取消', + daas_task_rebalance_history_cancel_remaining: '取消剩余任务', + daas_task_rebalance_history_cancel_remaining_confirm: + '确定取消剩余 {count} 个等待中的任务吗?此操作不可撤销。', + daas_task_rebalance_history_cancel_success: '任务已取消', + daas_task_rebalance_history_cancel_remaining_success: '已取消 {count} 个任务', + daas_task_rebalance_history_progress: '执行进度', + page_title_task_rebalance: '任务均衡', webhook_alerts: 'Webhook 告警', webhook_alerts_detail: 'Webhook 告警详情', diff --git a/apps/daas/src/i18n/langs/zh-TW.js b/apps/daas/src/i18n/langs/zh-TW.js index 19fc16744..75225d001 100644 --- a/apps/daas/src/i18n/langs/zh-TW.js +++ b/apps/daas/src/i18n/langs/zh-TW.js @@ -2141,6 +2141,57 @@ export default { daas_cluster_engine_hostname: '主機名/IP', daas_cluster_connection_count: 'TCP 連接數', daas_cluser_keyword_placeholder: '搜索主機名', + daas_task_rebalance_button: '任務調度均衡', + daas_task_rebalance_title: '任務均衡方案', + daas_task_rebalance_subtitle: '拖動任務卡片到目標引擎,調整各引擎的任務分佈', + daas_task_rebalance_online: '在線', + daas_task_rebalance_offline: '離線', + daas_task_rebalance_memory: '內存使用率', + daas_task_rebalance_task_count: '任務數', + daas_task_rebalance_legend_move: '將被調走的任務', + daas_task_rebalance_legend_locked: '不可均衡的任務', + daas_task_rebalance_cancel: '取消', + daas_task_rebalance_confirm: '確認均衡', + daas_task_rebalance_empty: '暫無可均衡的任務', + daas_task_rebalance_create_success: '均衡方案已創建,開始執行任務遷移', + daas_task_rebalance_type_initial_sync: '全量', + daas_task_rebalance_type_cdc: '增量', + daas_task_rebalance_type_full_cdc: '全量+增量', + daas_task_rebalance_reason_only_one_agent: '可用引擎不足 2 個,無法均衡', + daas_task_rebalance_reason_agent_offline: '當前引擎已離線', + daas_task_rebalance_reason_status_error: '任務非運行中,無法遷移', + daas_task_rebalance_reason_manual_agent: '已指定固定引擎,無法遷移', + daas_task_rebalance_reason_incremental: '全量同步中,無法遷移', + daas_task_rebalance_history_title: '任務均衡記錄', + daas_task_rebalance_history_list_title: '歷史記錄', + daas_task_rebalance_history_empty: '暫無均衡記錄', + daas_task_rebalance_history_empty_desc: '任務均衡後記錄將顯示在這裡', + daas_task_rebalance_history_detail_empty: '請在左側選擇一條記錄', + daas_task_rebalance_history_task_total: '{count} 個任務', + daas_task_rebalance_history_record_running: '執行中', + daas_task_rebalance_history_record_done: '已完成', + daas_task_rebalance_history_record_partial: '部分完成', + daas_task_rebalance_history_record_cancelled: '已取消', + daas_task_rebalance_history_record_failed: '失敗', + daas_task_rebalance_history_stat_success: '成功', + daas_task_rebalance_history_stat_failed: '失敗', + daas_task_rebalance_history_stat_skipped: '跳過', + daas_task_rebalance_history_stat_running: '運行中', + daas_task_rebalance_history_stat_pending: '等待中', + daas_task_rebalance_history_col_task: '任務名稱', + daas_task_rebalance_history_col_source: '源引擎', + daas_task_rebalance_history_col_target: '目標引擎', + daas_task_rebalance_history_col_status: '狀態', + daas_task_rebalance_history_col_error: '錯誤原因', + daas_task_rebalance_history_col_action: '操作', + daas_task_rebalance_history_cancel: '取消', + daas_task_rebalance_history_cancel_remaining: '取消剩餘任務', + daas_task_rebalance_history_cancel_remaining_confirm: + '確定取消剩餘 {count} 個等待中的任務嗎?此操作不可撤銷。', + daas_task_rebalance_history_cancel_success: '任務已取消', + daas_task_rebalance_history_cancel_remaining_success: '已取消 {count} 個任務', + daas_task_rebalance_history_progress: '執行進度', + page_title_task_rebalance: '任務均衡', webhook_alerts: 'Webhook 告警', webhook_alerts_detail: 'Webhook 告警詳情', diff --git a/apps/daas/src/router/menu.ts b/apps/daas/src/router/menu.ts index 137caefb2..fbee1f777 100644 --- a/apps/daas/src/router/menu.ts +++ b/apps/daas/src/router/menu.ts @@ -83,6 +83,11 @@ export const MENU = [ { name: 'dataEncryption' }, { name: 'projectManagement' }, { name: 'dataImportExport' }, + { + name: 'taskRebalanceHistory', + parent: 'taskRebalance', + code: 'v2_task_rebalance', + }, ], }, { diff --git a/apps/daas/src/router/routes.ts b/apps/daas/src/router/routes.ts index 422154889..12494f2db 100644 --- a/apps/daas/src/router/routes.ts +++ b/apps/daas/src/router/routes.ts @@ -482,6 +482,29 @@ export const routes = [ }, ], }, + /* ---------- 任务均衡 ----------*/ + { + path: '/task-rebalance', + name: 'taskRebalance', + component: Layout, + redirect: { + name: 'taskRebalanceHistory', + }, + meta: { + title: 'page_title_task_rebalance', + }, + children: [ + { + path: '', + name: 'taskRebalanceHistory', + component: () => import('@/views/task-rebalance/History.vue'), + meta: { + hideTitle: true, + title: 'page_title_task_rebalance', + }, + }, + ], + }, /* ---------- 函数管理 ----------*/ { path: '/function', diff --git a/apps/daas/src/views/cluster/Cluster.vue b/apps/daas/src/views/cluster/Cluster.vue index 9927ce666..7c2174f02 100644 --- a/apps/daas/src/views/cluster/Cluster.vue +++ b/apps/daas/src/views/cluster/Cluster.vue @@ -35,11 +35,20 @@ import { Modal } from '@tap/component/src/modal' import { useI18n } from '@tap/i18n' import { downloadJson } from '@tap/shared' import Cookie from '@tap/shared/src/cookie' -import { nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue' +import { + computed, + nextTick, + onMounted, + onUnmounted, + reactive, + ref, + watch, +} from 'vue' import { useRoute, useRouter } from 'vue-router' import SetTag from '@/views/cluster/SetTag.vue' import AddServe from './AddServe.vue' import { STATUS_MAP } from './const' +import TaskRebalanceDrawer from './TaskRebalanceDrawer.vue' import UpdateLicense from './UpdateLicense.vue' const { t } = useI18n() @@ -169,6 +178,7 @@ const filterItems = ref([ ]) const bindWorkerMap = ref({}) const viewType = ref('cluster') +const showRebalanceDrawer = ref(false) const netStatDialog = reactive({ visible: false, data: [], @@ -630,6 +640,24 @@ const getStatus = (type: string) => { return STATUS_MAP[type] || '-' } +const parseUsage = (val?: string) => { + if (!val || val === '-') return 0 + const num = Number.parseFloat(val) + return Number.isNaN(num) + ? 0 + : Math.min(100, Math.max(0, Number(num.toFixed(2)))) +} + +const rebalanceAgents = computed(() => + (waterfallData.value as any[]).map((item) => ({ + agentId: item.systemInfo?.process_id, + name: item.agentName || item.systemInfo?.hostname, + online: item.status === 'running', + cpuUsage: parseUsage(item.metricValues?.CpuUsage), + memUsage: parseUsage(item.metricValues?.HeapMemoryUsage), + })), +) + const navigateToTaskList = (item: any, syncType: 'migrate' | 'sync') => { const processId = item.systemInfo?.process_id if (!processId) return @@ -1133,11 +1161,16 @@ const onUpdateLicenseSuccess = () => {
@@ -2293,6 +2326,11 @@ const onUpdateLicenseSuccess = () => { :service-id="updateLicenseDialog.serviceId" @success="onUpdateLicenseSuccess" /> + + diff --git a/apps/daas/src/views/cluster/TaskRebalanceDrawer.vue b/apps/daas/src/views/cluster/TaskRebalanceDrawer.vue new file mode 100644 index 000000000..a3be3dac6 --- /dev/null +++ b/apps/daas/src/views/cluster/TaskRebalanceDrawer.vue @@ -0,0 +1,593 @@ + + + + + + + diff --git a/apps/daas/src/views/role/Role.vue b/apps/daas/src/views/role/Role.vue index c9648acbb..282e4abe8 100644 --- a/apps/daas/src/views/role/Role.vue +++ b/apps/daas/src/views/role/Role.vue @@ -249,6 +249,7 @@ const pageSort = [ { name: 'v2_shared_cache' }, { name: 'v2_project_management' }, { name: 'v2_project_import_and_export' }, + { name: 'v2_task_rebalance' }, ], }, { diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue new file mode 100644 index 000000000..5a7d563ce --- /dev/null +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -0,0 +1,571 @@ + + + + + diff --git a/apps/daas/src/views/task-rebalance/openapi.yaml b/apps/daas/src/views/task-rebalance/openapi.yaml new file mode 100644 index 000000000..b964447c8 --- /dev/null +++ b/apps/daas/src/views/task-rebalance/openapi.yaml @@ -0,0 +1,389 @@ +openapi: 3.0.3 +info: + title: Tapdata Task Rebalance API + version: 1.0.0 + description: | + 任务均衡 (Task Rebalance) 模块对外接口。云版会直接拒绝所有调用并返回 `task.rebalance.disabled`。 + + **统一响应包装**:所有接口均返回 `ResponseMessage`,结构为: + ```json + { "reqId": "uuid", "ts": 1700000000000, "code": "ok", "message": null, "data": } + ``` + 业务异常时 `code` 为错误 key(如 `task.rebalance.disabled`),`message` 为已按请求 `Accept-Language` 解析的本地化文案,`data` 为 null。HTTP 状态码默认 200;仅 `NotLogin`/`NotAuthorized` 返回 401。 +servers: + - url: / + description: 当前部署的 TM 实例(同源调用) +security: + - userIdHeader: [] + - accessToken: [] + - basicAuth: [] +tags: + - name: TaskRebalance + description: 任务均衡 (创建 / 查询 / 取消) +paths: + /api/task/rebalance/preview: + post: + tags: [TaskRebalance] + summary: 生成均衡预览 + description: | + 基于当前在线 agent 与运行中任务,模拟一次均衡分配,返回每个任务的可调走状态与评分,但不落库、不执行。 + 前端可按 `data.tasks[].priorityScore` 降序展示,根据 `movable` / `schedulableStatus` 渲染状态。 + 当可用 agent 不足 2 个时返回 `data.reason = "task.rebalance.onlyOneAgent"` 且 `data.tasks` 为空。 + operationId: previewTaskRebalance + responses: + '200': + description: 成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseOfPreview' + '401': { $ref: '#/components/responses/Unauthorized' } + default: { $ref: '#/components/responses/BusinessError' } + /api/task/rebalance: + post: + tags: [TaskRebalance] + summary: 创建并启动均衡 + description: | + 以最新预览为依据落库 `TaskRebalance` 记录及一组 `TaskRebalanceJob` 子任务,并异步开始执行。 + 若当前已有 `RUNNING` 均衡,返回 `task.rebalance.alreadyRunning`; + 若没有可调走的任务,返回 `task.rebalance.noTask`; + 若预览本身有阻断原因(如仅一个 agent),返回该 key。 + operationId: createTaskRebalance + responses: + '200': + description: 成功创建 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseOfRebalance' + '401': { $ref: '#/components/responses/Unauthorized' } + default: { $ref: '#/components/responses/BusinessError' } + get: + tags: [TaskRebalance] + summary: 历史列表 (分页) + description: | + 通过项目通用 `Filter` JSON 进行分页/排序/过滤。例: + `?filter={"limit":20,"skip":0,"order":"createTime DESC","where":{"status":"OK"}}` + operationId: listTaskRebalance + parameters: + - in: query + name: filter + required: false + schema: + type: string + description: Filter JSON 字符串,URL encode 后传入 + responses: + '200': + description: 成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseOfPageRebalance' + '401': { $ref: '#/components/responses/Unauthorized' } + default: { $ref: '#/components/responses/BusinessError' } + /api/task/rebalance/active: + get: + tags: [TaskRebalance] + summary: 是否有正在运行的均衡 + operationId: hasActiveTaskRebalance + responses: + '200': + description: 成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseOfActive' + '401': { $ref: '#/components/responses/Unauthorized' } + default: { $ref: '#/components/responses/BusinessError' } + /api/task/rebalance/{id}: + get: + tags: [TaskRebalance] + summary: 均衡详情 (含子任务) + operationId: getTaskRebalanceDetail + parameters: + - $ref: '#/components/parameters/RebalanceId' + responses: + '200': + description: 成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseOfDetail' + '401': { $ref: '#/components/responses/Unauthorized' } + default: { $ref: '#/components/responses/BusinessError' } + /api/task/rebalance/{id}/cancel: + post: + tags: [TaskRebalance] + summary: 取消该均衡下所有 PENDING 子任务 + description: 已进入 `STOPPING / STARTING` 的子任务会继续跑完自己的周期,不会被打断。 + operationId: cancelTaskRebalance + parameters: + - $ref: '#/components/parameters/RebalanceId' + responses: + '200': + description: 成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseOfVoid' + '401': { $ref: '#/components/responses/Unauthorized' } + default: { $ref: '#/components/responses/BusinessError' } + /api/task/rebalance/{id}/cancel/{taskId}: + post: + tags: [TaskRebalance] + summary: 取消单个 PENDING 子任务 + operationId: cancelTaskRebalanceJob + parameters: + - $ref: '#/components/parameters/RebalanceId' + - in: path + name: taskId + required: true + schema: { type: string } + description: 子任务对应的业务 task id + responses: + '200': + description: 成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseOfVoid' + '401': { $ref: '#/components/responses/Unauthorized' } + default: { $ref: '#/components/responses/BusinessError' } +components: + securitySchemes: + userIdHeader: + type: apiKey + in: header + name: user_id + description: 网关/反向代理透传的用户 id 头,TM 由此查用户 + accessToken: + type: apiKey + in: query + name: access_token + description: 个人 access token,可作为查询参数 + basicAuth: + type: http + scheme: basic + description: HTTP Basic(仅运维/工具链使用) + parameters: + RebalanceId: + in: path + name: id + required: true + schema: { type: string } + description: TaskRebalance 主键 (ObjectId hex) + responses: + Unauthorized: + description: 未登录 / 登录态失效 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + examples: + notLogin: + value: + reqId: 5a2c-... + ts: 1700000000000 + code: NotLogin + message: Not login + data: null + BusinessError: + description: 业务异常 (HTTP 200,code 为 i18n key) + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + examples: + disabled: + summary: 云版禁用 + value: { reqId: "...", ts: 1700000000000, code: task.rebalance.disabled, message: "Task rebalance is disabled.", data: null } + alreadyRunning: + summary: 已有运行中均衡 + value: { reqId: "...", ts: 1700000000000, code: task.rebalance.alreadyRunning, message: "A task rebalance is already running.", data: null } + noTask: + summary: 没有可调走任务 + value: { reqId: "...", ts: 1700000000000, code: task.rebalance.noTask, message: "No task needs to be rebalanced.", data: null } + onlyOneAgent: + summary: 可用 agent 不足 2 个 + value: { reqId: "...", ts: 1700000000000, code: task.rebalance.onlyOneAgent, message: "At least two available agents are required for task rebalance.", data: null } + taskRebalancing: + summary: 调用任务 start/pause 时该任务正被均衡 + value: { reqId: "...", ts: 1700000000000, code: task.rebalance.taskRebalancing, message: "The task is being rebalanced. Please try again later.", data: null } + notAuthorized: + summary: 无 v2_task_rebalance 菜单权限 + value: { reqId: "...", ts: 1700000000000, code: NotAuthorized, message: "No permission", data: null } + schemas: + ApiResponseBase: + type: object + required: [reqId, ts, code] + properties: + reqId: { type: string, description: 请求 trace id, example: "5a2c-...-ff" } + ts: { type: integer, format: int64, description: 服务端时间戳 ms, example: 1700000000000 } + code: { type: string, description: "成功为 \"ok\",失败为 i18n key", example: ok } + message: { type: string, nullable: true, description: 业务异常时的本地化文案 } + stack: { type: string, nullable: true, description: 服务端堆栈(调试用,可忽略) } + ApiErrorResponse: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: + code: { type: string, enum: [task.rebalance.disabled, task.rebalance.taskRebalancing, task.rebalance.alreadyRunning, task.rebalance.noTask, task.rebalance.onlyOneAgent, task.rebalance.offlineTaskOnAgent, NotLogin, NotAuthorized, SystemError, IllegalArgument, IllegalState] } + data: { nullable: true } + ApiResponseOfPreview: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: { data: { $ref: '#/components/schemas/TaskRebalancePreviewVo' } } + ApiResponseOfRebalance: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: { data: { $ref: '#/components/schemas/TaskRebalanceVo' } } + ApiResponseOfPageRebalance: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: { data: { $ref: '#/components/schemas/PageOfTaskRebalanceVo' } } + ApiResponseOfActive: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: + data: + type: object + required: [active] + properties: { active: { type: boolean, example: true } } + ApiResponseOfDetail: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: { data: { $ref: '#/components/schemas/TaskRebalanceDetailVo' } } + ApiResponseOfVoid: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: { data: { nullable: true } } + PageOfTaskRebalanceVo: + type: object + required: [total, items] + properties: + total: { type: integer, format: int64, example: 12 } + items: + type: array + items: { $ref: '#/components/schemas/TaskRebalanceVo' } + + + TaskRebalancePreviewVo: + type: object + description: 预览结果,整体阻断时只填 reason,否则 tasks 包含全部统计任务(含不可调走的) + properties: + tasks: + type: array + items: { $ref: '#/components/schemas/TaskPreview' } + moveCount: { type: integer, description: 将被实际迁移的任务数 (changed=true 的计数), example: 3 } + reason: + type: string + nullable: true + description: 整体阻断原因(i18n key),有值则忽略 tasks + example: task.rebalance.onlyOneAgent + TaskPreview: + type: object + properties: + taskId: { type: string, example: 6630e9c8a2c5b80012a1b2c3 } + taskName: { type: string, example: 订单同步 } + type: { type: string, enum: [initial_sync, cdc, "initial_sync+cdc"] } + syncType: { type: string, enum: [sync, migrate, logCollector, mem_cache, connHeartbeat] } + status: { type: string, example: running, description: 来自 TaskDto.status } + sourceAgentId: { type: string, example: agent-01 } + targetAgentId: { type: string, description: 算法建议迁移到的 agent;不可调走时等于 sourceAgentId, example: agent-02 } + movable: { type: boolean } + schedulableStatus: { $ref: '#/components/schemas/SchedulableStatus' } + changed: { type: boolean, description: 本次是否被规划迁移 } + reason: { type: string, nullable: true, description: 不可调走的中文/英文说明 } + priorityScore: { type: integer, description: 值越大越优先迁移, example: 5230 } + priorityScoreItems: + type: object + description: 评分明细(前端可做 tooltip) + properties: + syncType: { type: integer } + nodeCount: { type: integer } + startTime: { type: integer } + nodeCount: { type: integer, description: DAG 节点数 } + startTime: { type: integer, format: int64, nullable: true, description: 任务启动毫秒时间戳 } + TaskRebalanceVo: + type: object + properties: + id: { type: string } + name: { type: string, example: Task rebalance 1700000000000 } + status: { $ref: '#/components/schemas/RebalanceStatus' } + createTime: { type: string, format: date-time } + lastUpdated: { type: string, format: date-time } + finishAt: { type: string, format: date-time, nullable: true } + totalCount: { type: integer } + pendingCount: { type: integer } + stoppingCount: { type: integer } + startingCount: { type: integer } + okCount: { type: integer } + failedCount: { type: integer } + cancelledCount: { type: integer } + errorMesg: { type: string, nullable: true, description: 整体中止原因(例如目标 agent 离线导致 abort) } + userId: { type: string } + createUser: { type: string } + lastUpdBy: { type: string } + customId: { type: string } + TaskRebalanceDetailVo: + type: object + required: [rebalance, jobs] + properties: + rebalance: { $ref: '#/components/schemas/TaskRebalanceVo' } + jobs: + type: array + items: { $ref: '#/components/schemas/TaskRebalanceJobVo' } + TaskRebalanceJobVo: + type: object + properties: + id: { type: string } + rebalanceId: { type: string } + taskId: { type: string } + taskName: { type: string } + status: { $ref: '#/components/schemas/JobStatus' } + errorMesg: { type: string, nullable: true } + sourceAgentId: { type: string } + targetAgentId: { type: string } + beginAt: { type: string, format: date-time, nullable: true } + finishAt: { type: string, format: date-time, nullable: true } + createTime: { type: string, format: date-time } + lastUpdated: { type: string, format: date-time } + userId: { type: string } + createUser: { type: string } + lastUpdBy: { type: string } + customId: { type: string } + RebalanceStatus: + type: string + enum: [RUNNING, OK, FAILED] + description: | + - RUNNING:调度中 + - OK:全部子任务成功 + - FAILED:存在失败或取消的子任务 + JobStatus: + type: string + enum: [PENDING, STOPPING, STARTING, OK, CANCELLED, INVALID_AGENT, STATUS_ERROR, STOP_TIMEOUT, START_TIMEOUT] + description: | + - PENDING:等待调度 + - STOPPING:已下发暂停 + - STARTING:暂停完成,已切换 agentId,下发启动 + - OK:成功 + - CANCELLED:用户取消,或被目标 agent 离线中止兜底 + - INVALID_AGENT:目标 agent 离线(触发整次均衡 abort) + - STATUS_ERROR:执行期任务状态/agent 已变化,不能迁移 + - STOP_TIMEOUT:等 STATUS_STOP 超时;任务可能滞留在源 agent + - START_TIMEOUT:等 STATUS_RUNNING 超时;agentId 已切到 target,需人工确认 + SchedulableStatus: + type: string + enum: [OK, AGENT_OFFLINE, STATUS_ERROR, MANUAL_AGENT, INCREMENTAL_NOT_STARTED] + description: | + - OK:可调走 + - AGENT_OFFLINE:当前 agent 已离线 + - STATUS_ERROR:任务非 running + - MANUAL_AGENT:accessNodeType 为手动指定 agent 或 agent 组 + - INCREMENTAL_NOT_STARTED:initial_sync+cdc 任务还未进入增量阶段 diff --git a/packages/api/src/core/index.ts b/packages/api/src/core/index.ts index e73304928..055e46f4f 100644 --- a/packages/api/src/core/index.ts +++ b/packages/api/src/core/index.ts @@ -57,3 +57,4 @@ export * from './live-data-platform' export * from './group-info' export * from './ai' export * from './data-trace' +export * from './task-rebalance' diff --git a/packages/api/src/core/task-rebalance.ts b/packages/api/src/core/task-rebalance.ts new file mode 100644 index 000000000..ffc9d534d --- /dev/null +++ b/packages/api/src/core/task-rebalance.ts @@ -0,0 +1,152 @@ +import { requestClient, type Filter, type PageFetchResult } from '../request' + +const BASE_URL = '/api/task/rebalance' + +export type RebalanceStatus = 'RUNNING' | 'OK' | 'FAILED' + +export type JobStatus = + | 'PENDING' + | 'STOPPING' + | 'STARTING' + | 'OK' + | 'CANCELLED' + | 'INVALID_AGENT' + | 'STATUS_ERROR' + | 'STOP_TIMEOUT' + | 'START_TIMEOUT' + | 'FAILED' + +export type SchedulableStatus = + | 'OK' + | 'AGENT_OFFLINE' + | 'STATUS_ERROR' + | 'MANUAL_AGENT' + | 'INCREMENTAL_NOT_STARTED' + +export interface TaskPreview { + taskId: string + taskName: string + type: 'initial_sync' | 'cdc' | 'initial_sync+cdc' + syncType: 'sync' | 'migrate' | 'logCollector' | 'mem_cache' | 'connHeartbeat' + status: string + sourceAgentId: string + targetAgentId: string + movable: boolean + schedulableStatus: SchedulableStatus + changed: boolean + reason?: string | null + priorityScore: number + priorityScoreItems?: { + syncType?: number + nodeCount?: number + startTime?: number + } + nodeCount: number + startTime?: number | null +} + +export interface TaskRebalancePreviewVo { + tasks?: TaskPreview[] + moveCount?: number + reason?: string | null +} + +export interface TaskRebalanceVo { + id: string + name: string + status: RebalanceStatus + createTime: string + lastUpdated: string + finishAt?: string | null + totalCount: number + pendingCount: number + stoppingCount: number + startingCount: number + okCount: number + failedCount: number + cancelledCount: number + errorMesg?: string | null + userId: string + createUser: string + lastUpdBy: string + customId: string +} + +export interface TaskRebalanceJobVo { + id: string + rebalanceId: string + taskId: string + taskName: string + status: JobStatus + errorMesg?: string | null + sourceAgentId: string + targetAgentId: string + beginAt?: string | null + finishAt?: string | null + createTime: string + lastUpdated: string + userId: string + createUser: string + lastUpdBy: string + customId: string +} + +export interface TaskRebalanceDetailVo { + rebalance: TaskRebalanceVo + jobs: TaskRebalanceJobVo[] +} + +export interface TaskRebalanceActiveVo { + active: boolean +} + +/** + * 生成均衡预览,不落库、不执行 + */ +export function previewTaskRebalance() { + return requestClient.post(`${BASE_URL}/preview`) +} + +/** + * 创建并启动均衡 + */ +export function createTaskRebalance(data?: TaskRebalancePreviewVo) { + return requestClient.post(BASE_URL, data) +} + +/** + * 历史列表 (分页) + */ +export function fetchTaskRebalances(filter?: Filter) { + return requestClient.get>(BASE_URL, { + params: { filter: filter ? JSON.stringify(filter) : undefined }, + }) +} + +/** + * 是否有正在运行的均衡 + */ +export function hasActiveTaskRebalance() { + return requestClient.get(`${BASE_URL}/active`) +} + +/** + * 均衡详情 (含子任务) + */ +export function getTaskRebalanceDetail(id: string) { + return requestClient.get(`${BASE_URL}/${id}`) +} + +/** + * 取消该均衡下所有 PENDING 子任务 + */ +export function cancelTaskRebalance(id: string) { + return requestClient.post(`${BASE_URL}/${id}/cancel`) +} + +/** + * 取消单个 PENDING 子任务 + */ +export function cancelTaskRebalanceJob(id: string, taskId: string) { + return requestClient.post(`${BASE_URL}/${id}/cancel/${taskId}`) +} From b4fffdcf0abde5931d261d637f94e76a86f409b4 Mon Sep 17 00:00:00 2001 From: Feynman Date: Thu, 4 Jun 2026 12:12:26 +0800 Subject: [PATCH 02/11] feat(cluster): implement independent polling for CPU and memory usage rates to enhance performance and user experience --- apps/daas/src/views/cluster/Cluster.vue | 42 +++++++ .../daas/src/views/task-rebalance/History.vue | 115 +++++++++++++++++- 2 files changed, 151 insertions(+), 6 deletions(-) diff --git a/apps/daas/src/views/cluster/Cluster.vue b/apps/daas/src/views/cluster/Cluster.vue index 7c2174f02..13e4518ec 100644 --- a/apps/daas/src/views/cluster/Cluster.vue +++ b/apps/daas/src/views/cluster/Cluster.vue @@ -274,6 +274,9 @@ const init = async () => { await getDataApi(true) await loadTags() handleFilterAgent() + // Start polling usage rates independently so CPU/Mem stay fresh + // without re-fetching the full cluster list every time. + timer.value = setInterval(refreshUsageRate, 10000) as unknown as null } const submitForm = async () => { @@ -446,6 +449,45 @@ const getUsageRate = (processId: string[]) => { }) } +/** + * Poll only the metric values (CPU / Mem) and patch them onto the existing + * waterfallData items so the rest of the page state is not disturbed. + */ +const refreshUsageRate = async () => { + const data = waterfallData.value as any[] + if (!data.length) return + const processIds = data + .map((it) => it?.systemInfo?.process_id) + .filter(Boolean) + if (!processIds.length) return + + try { + const workerResponse = await getUsageRate(processIds) + const metricValuesData: Record< + string, + { CpuUsage: string; HeapMemoryUsage: string } + > = {} + for (const w of workerResponse?.items || []) { + if (w.process_id && w.metricValues) { + metricValuesData[w.process_id] = { + CpuUsage: `${(Number(w.metricValues.CpuUsage) * 100).toFixed(2)}%`, + HeapMemoryUsage: `${(Number(w.metricValues.HeapMemoryUsage) * 100).toFixed(2)}%`, + } + } + } + for (const item of data) { + const pid = item.systemInfo?.process_id + if (item?.engine?.status === 'running' && pid && metricValuesData[pid]) { + item.metricValues = metricValuesData[pid] + } + } + // Re-assign to trigger Vue reactivity on nested metricValues changes + waterfallData.value = [...data] as never[] + } catch { + // ignore refresh errors silently + } +} + const getAllBindWorker = async () => { try { const data = await queryAllBindWorker() diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index 5a7d563ce..0cb402c7d 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -1,4 +1,5 @@ + - +
diff --git a/packages/business/src/views/shared-mining/List.vue b/packages/business/src/views/shared-mining/List.vue index 4f7745504..01437f639 100644 --- a/packages/business/src/views/shared-mining/List.vue +++ b/packages/business/src/views/shared-mining/List.vue @@ -1,4 +1,5 @@ - @@ -597,7 +592,7 @@ export default { :close-on-click-modal="false" > - + Date: Mon, 8 Jun 2026 14:44:03 +0800 Subject: [PATCH 05/11] feat(task-rebalance): add 'CANCELLED' status and update UI for task history --- .../daas/src/views/task-rebalance/History.vue | 39 +++++++++++++++---- packages/api/src/core/task-rebalance.ts | 2 +- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index bba627477..0e7d081c8 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -144,7 +144,7 @@ const CATEGORY_ORDER: Record = { const CATEGORY_LABEL: Record = { success: 'daas_task_rebalance_history_stat_success', failed: 'daas_task_rebalance_history_stat_failed', - skipped: 'daas_task_rebalance_history_stat_skipped', + skipped: 'daas_task_rebalance_history_record_cancelled', running: 'daas_task_rebalance_history_stat_running', pending: 'daas_task_rebalance_history_stat_pending', } @@ -269,6 +269,13 @@ function getRecordStatus(record: TaskRebalanceVo): RecordStatus { type: 'danger', } } + if (record.status === 'CANCELLED') { + return { + key: 'cancelled', + label: 'daas_task_rebalance_history_record_cancelled', + type: 'info', + } + } if (record.failedCount > 0) { return { key: 'partial', @@ -439,7 +446,7 @@ onUnmounted(stopDetailPolling) >
- {{ + {{ formatTime(record.createTime) }}
+
+ + + + {{ record.createUser }} +
{{ t('daas_task_rebalance_history_task_total', { @@ -600,11 +613,23 @@ onUnmounted(stopDetailPolling) min-width="160" show-overflow-tooltip > - + - - + -->
diff --git a/packages/api/src/core/task-rebalance.ts b/packages/api/src/core/task-rebalance.ts index ffc9d534d..c31797a35 100644 --- a/packages/api/src/core/task-rebalance.ts +++ b/packages/api/src/core/task-rebalance.ts @@ -2,7 +2,7 @@ import { requestClient, type Filter, type PageFetchResult } from '../request' const BASE_URL = '/api/task/rebalance' -export type RebalanceStatus = 'RUNNING' | 'OK' | 'FAILED' +export type RebalanceStatus = 'RUNNING' | 'OK' | 'FAILED' | 'CANCELLED' export type JobStatus = | 'PENDING' From bbf42020dfca35052da049a7607408ed8514376d Mon Sep 17 00:00:00 2001 From: Feynman Date: Mon, 8 Jun 2026 15:04:02 +0800 Subject: [PATCH 06/11] feat(History.vue): enhance task rebalance detail fetching with onSuccess handler to update local records --- apps/daas/src/views/task-rebalance/History.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index 0e7d081c8..32ceed2ba 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -191,7 +191,14 @@ const { if (!selectedId.value) return null return await getTaskRebalanceDetail(selectedId.value) }, - { manual: true, initialData: null }, + { + manual: true, + initialData: null, + onSuccess(data) { + const rebalance = records.value?.find((r) => r.id === data?.rebalance.id) + rebalance && Object.assign(rebalance, data?.rebalance) + }, + }, ) const rebalance = computed(() => detail.value?.rebalance || null) From 0ca34ea28158f66fc223808c5768435f941f70d2 Mon Sep 17 00:00:00 2001 From: Feynman Date: Mon, 8 Jun 2026 15:39:59 +0800 Subject: [PATCH 07/11] refactor(History.vue): remove unused action column and adjust styling for error message display --- apps/daas/src/views/task-rebalance/History.vue | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index 32ceed2ba..0697d8b91 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -619,6 +619,7 @@ onUnmounted(stopDetailPolling) prop="errorMesg" min-width="160" show-overflow-tooltip + class-name="p-0" > - From 7a7a0d5cac55f06bf6f56d3be973b1f6f2244d10 Mon Sep 17 00:00:00 2001 From: Feynman Date: Thu, 11 Jun 2026 11:03:46 +0800 Subject: [PATCH 08/11] feat: add granular task rebalance permissions and update UI states and localization labels --- apps/daas/src/i18n/langs/en.js | 13 ++ apps/daas/src/i18n/langs/zh-CN.js | 13 ++ apps/daas/src/i18n/langs/zh-TW.js | 13 ++ apps/daas/src/views/cluster/Cluster.vue | 12 +- apps/daas/src/views/role/Role.vue | 19 ++- .../daas/src/views/task-rebalance/History.vue | 114 +++++++++++++----- 6 files changed, 151 insertions(+), 33 deletions(-) diff --git a/apps/daas/src/i18n/langs/en.js b/apps/daas/src/i18n/langs/en.js index d263a3a95..a7b6c8d34 100644 --- a/apps/daas/src/i18n/langs/en.js +++ b/apps/daas/src/i18n/langs/en.js @@ -2248,6 +2248,7 @@ export default { daas_role_role_gongnengquanxian: 'Function Permission', daas_role_role_yemianquanxian: 'Page Permission', daas_role_role_gongnengmokuai: 'Function Module', + daas_role_role_no_matching_module: 'No matching modules', daas_role_role_gouxuanxiangyingmo: 'Check the corresponding module to indicate that the navigation is visible to users under the current role, and enable [View All Data] to indicate that the role can view and operate all the data under this module, and if it is not checked, it can only view and operate the data created and authorized by itself data. ', daas_feature_unavailable: 'Restricted Feature', @@ -2289,6 +2290,7 @@ export default { daas_task_rebalance_legend_locked: 'Tasks that cannot be rebalanced', daas_task_rebalance_cancel: 'Cancel', daas_task_rebalance_confirm: 'Confirm Rebalance', + daas_task_rebalance_create: 'Create Rebalance', daas_task_rebalance_empty: 'No tasks available for rebalancing', daas_task_rebalance_create_success: 'Rebalance plan created, migration started', daas_task_rebalance_type_initial_sync: 'Full', @@ -2310,11 +2312,22 @@ export default { 'Records will appear here after a rebalance', daas_task_rebalance_history_detail_empty: 'Select a record on the left', daas_task_rebalance_history_task_total: '{count} tasks', + daas_task_rebalance_history_record_creating: 'Creating', daas_task_rebalance_history_record_running: 'Running', daas_task_rebalance_history_record_done: 'Completed', daas_task_rebalance_history_record_partial: 'Partially Completed', daas_task_rebalance_history_record_cancelled: 'Cancelled', daas_task_rebalance_history_record_failed: 'Failed', + daas_task_rebalance_history_job_pending: 'Pending', + daas_task_rebalance_history_job_starting: 'Starting', + daas_task_rebalance_history_job_stopping: 'Stopping', + daas_task_rebalance_history_job_ok: 'Completed', + daas_task_rebalance_history_job_cancelled: 'Cancelled', + daas_task_rebalance_history_job_invalid_agent: 'Invalid Agent', + daas_task_rebalance_history_job_status_error: 'Status Error', + daas_task_rebalance_history_job_stop_timeout: 'Stop Timeout', + daas_task_rebalance_history_job_start_timeout: 'Start Timeout', + daas_task_rebalance_history_job_failed: 'Failed', daas_task_rebalance_history_stat_success: 'Success', daas_task_rebalance_history_stat_failed: 'Failed', daas_task_rebalance_history_stat_skipped: 'Skipped', diff --git a/apps/daas/src/i18n/langs/zh-CN.js b/apps/daas/src/i18n/langs/zh-CN.js index eeb02340a..b981e9681 100644 --- a/apps/daas/src/i18n/langs/zh-CN.js +++ b/apps/daas/src/i18n/langs/zh-CN.js @@ -2131,6 +2131,7 @@ export default { daas_role_role_gongnengmokuai: '功能模块', daas_role_role_gouxuanxiangyingmo: '勾选相应模块表示此导航对当前角色下用户可见,开启【查看全部数据】则表示角色可以查看和操作该模块下所有的数据,不勾选则只能查看和操作自己创建和被授权的数据。', + daas_role_role_no_matching_module: '没有匹配的模块', daas_feature_unavailable: '升级版本,解锁更多功能!', daas_feature_unavailable_subtitle: @@ -2168,6 +2169,7 @@ export default { daas_task_rebalance_legend_locked: '不可均衡的任务', daas_task_rebalance_cancel: '取消', daas_task_rebalance_confirm: '确认均衡', + daas_task_rebalance_create: '创建均衡', daas_task_rebalance_empty: '暂无可均衡的任务', daas_task_rebalance_create_success: '均衡方案已创建,开始执行任务迁移', daas_task_rebalance_type_initial_sync: '全量', @@ -2184,11 +2186,22 @@ export default { daas_task_rebalance_history_empty_desc: '任务均衡后记录将显示在这里', daas_task_rebalance_history_detail_empty: '请在左侧选择一条记录', daas_task_rebalance_history_task_total: '{count} 个任务', + daas_task_rebalance_history_record_creating: '创建中', daas_task_rebalance_history_record_running: '执行中', daas_task_rebalance_history_record_done: '已完成', daas_task_rebalance_history_record_partial: '部分完成', daas_task_rebalance_history_record_cancelled: '已取消', daas_task_rebalance_history_record_failed: '失败', + daas_task_rebalance_history_job_pending: '等待中', + daas_task_rebalance_history_job_starting: '启动中', + daas_task_rebalance_history_job_stopping: '停止中', + daas_task_rebalance_history_job_ok: '已完成', + daas_task_rebalance_history_job_cancelled: '已取消', + daas_task_rebalance_history_job_invalid_agent: '引擎不可用', + daas_task_rebalance_history_job_status_error: '状态异常', + daas_task_rebalance_history_job_stop_timeout: '停止超时', + daas_task_rebalance_history_job_start_timeout: '启动超时', + daas_task_rebalance_history_job_failed: '失败', daas_task_rebalance_history_stat_success: '成功', daas_task_rebalance_history_stat_failed: '失败', daas_task_rebalance_history_stat_skipped: '跳过', diff --git a/apps/daas/src/i18n/langs/zh-TW.js b/apps/daas/src/i18n/langs/zh-TW.js index 3bdbd07a3..071f038a6 100644 --- a/apps/daas/src/i18n/langs/zh-TW.js +++ b/apps/daas/src/i18n/langs/zh-TW.js @@ -2115,6 +2115,7 @@ export default { daas_role_role_gongnengquanxian: '功能權限', daas_role_role_yemianquanxian: '頁面權限', daas_role_role_gongnengmokuai: '功能模塊', + daas_role_role_no_matching_module: '沒有匹配的模塊', daas_role_role_gouxuanxiangyingmo: '勾選相應模塊表示此導航對當前角色下用戶可見,開啟【查看全部數據】則表示角色可以查看和操作該模塊下所有的數據,不勾選則只能查看和操作自己創建和被授權的數據。 ', daas_feature_unavailable: '功能暫不可用', @@ -2153,6 +2154,7 @@ export default { daas_task_rebalance_legend_locked: '不可均衡的任務', daas_task_rebalance_cancel: '取消', daas_task_rebalance_confirm: '確認均衡', + daas_task_rebalance_create: '創建均衡', daas_task_rebalance_empty: '暫無可均衡的任務', daas_task_rebalance_create_success: '均衡方案已創建,開始執行任務遷移', daas_task_rebalance_type_initial_sync: '全量', @@ -2169,11 +2171,22 @@ export default { daas_task_rebalance_history_empty_desc: '任務均衡後記錄將顯示在這裡', daas_task_rebalance_history_detail_empty: '請在左側選擇一條記錄', daas_task_rebalance_history_task_total: '{count} 個任務', + daas_task_rebalance_history_record_creating: '創建中', daas_task_rebalance_history_record_running: '執行中', daas_task_rebalance_history_record_done: '已完成', daas_task_rebalance_history_record_partial: '部分完成', daas_task_rebalance_history_record_cancelled: '已取消', daas_task_rebalance_history_record_failed: '失敗', + daas_task_rebalance_history_job_pending: '等待中', + daas_task_rebalance_history_job_starting: '啟動中', + daas_task_rebalance_history_job_stopping: '停止中', + daas_task_rebalance_history_job_ok: '已完成', + daas_task_rebalance_history_job_cancelled: '已取消', + daas_task_rebalance_history_job_invalid_agent: '引擎不可用', + daas_task_rebalance_history_job_status_error: '狀態異常', + daas_task_rebalance_history_job_stop_timeout: '停止超時', + daas_task_rebalance_history_job_start_timeout: '啟動超時', + daas_task_rebalance_history_job_failed: '失敗', daas_task_rebalance_history_stat_success: '成功', daas_task_rebalance_history_stat_failed: '失敗', daas_task_rebalance_history_stat_skipped: '跳過', diff --git a/apps/daas/src/views/cluster/Cluster.vue b/apps/daas/src/views/cluster/Cluster.vue index b597c4409..5d7060a5f 100644 --- a/apps/daas/src/views/cluster/Cluster.vue +++ b/apps/daas/src/views/cluster/Cluster.vue @@ -28,6 +28,7 @@ import { } from '@tap/api/src/core/workers' import { useRequest } from '@tap/api/src/request' import PageContainer from '@tap/business/src/components/PageContainer.vue' +import { useHas } from '@tap/business/src/composables' import { dayjs, makeDragNodeImage } from '@tap/business/src/shared' import { FilterBar } from '@tap/component/src/filter-bar' import { IconButton } from '@tap/component/src/icon-button' @@ -52,6 +53,11 @@ import TaskRebalanceDrawer from './TaskRebalanceDrawer.vue' import UpdateLicense from './UpdateLicense.vue' const { t } = useI18n() +const $has = useHas() + +const hasRebalancePermission = computed(() => { + return $has('v2_task_rebalance_Edit') +}) // Types interface TagDialog { @@ -1223,7 +1229,11 @@ const onUpdateLicenseSuccess = () => { :items="filterItems" @fetch="getDataApi()" /> - + {{ $t('daas_task_rebalance_button') }} diff --git a/apps/daas/src/views/role/Role.vue b/apps/daas/src/views/role/Role.vue index 282e4abe8..c7fd1a327 100644 --- a/apps/daas/src/views/role/Role.vue +++ b/apps/daas/src/views/role/Role.vue @@ -249,7 +249,16 @@ const pageSort = [ { name: 'v2_shared_cache' }, { name: 'v2_project_management' }, { name: 'v2_project_import_and_export' }, - { name: 'v2_task_rebalance' }, + { + name: 'v2_task_rebalance', + buttons: [ + { + label: t('daas_task_rebalance_create'), + name: 'v2_task_rebalance_Edit', + checked: false, + }, + ], + }, ], }, { @@ -737,7 +746,13 @@ onMounted(() => {
- +
diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index 0697d8b91..c4b691ca5 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -11,6 +11,7 @@ import { import { fetchWorkers } from '@tap/api/src/core/workers' import { useRequest } from '@tap/api/src/request' import PageContainer from '@tap/business/src/components/PageContainer.vue' +import { useHas } from '@tap/business/src/composables' import { dayjs } from '@tap/business/src/shared' import { Modal } from '@tap/component/src/modal' import { useI18n } from '@tap/i18n' @@ -18,6 +19,11 @@ import { computed, onUnmounted, ref, watch } from 'vue' import TaskRebalanceDrawer from '../cluster/TaskRebalanceDrawer.vue' const { t } = useI18n() +const $has = useHas() + +const hasEditPermission = computed(() => { + return $has('v2_task_rebalance_Edit') +}) type JobCategory = 'running' | 'pending' | 'failed' | 'skipped' | 'success' @@ -149,6 +155,19 @@ const CATEGORY_LABEL: Record = { pending: 'daas_task_rebalance_history_stat_pending', } +const JOB_STATUS_LABEL: Record = { + PENDING: 'daas_task_rebalance_history_job_pending', + STARTING: 'daas_task_rebalance_history_job_starting', + STOPPING: 'daas_task_rebalance_history_job_stopping', + OK: 'daas_task_rebalance_history_job_ok', + CANCELLED: 'daas_task_rebalance_history_job_cancelled', + INVALID_AGENT: 'daas_task_rebalance_history_job_invalid_agent', + STATUS_ERROR: 'daas_task_rebalance_history_job_status_error', + STOP_TIMEOUT: 'daas_task_rebalance_history_job_stop_timeout', + START_TIMEOUT: 'daas_task_rebalance_history_job_start_timeout', + FAILED: 'daas_task_rebalance_history_job_failed', +} + const CATEGORY_ICON = { running: IconLucideLoaderCircle, pending: IconLucideClock, @@ -255,47 +274,78 @@ function formatDuration(start?: string | null, end?: string | null): string { return `${hours}h ${minutes % 60}m` } +type RebalanceStatus = 'CREATING' | 'RUNNING' | 'OK' | 'FAILED' | 'CANCELLED' + interface RecordStatus { - key: 'running' | 'done' | 'partial' | 'cancelled' | 'failed' + key: 'creating' | 'running' | 'done' | 'partial' | 'cancelled' | 'failed' label: string type: 'primary' | 'success' | 'warning' | 'info' | 'danger' } -function getRecordStatus(record: TaskRebalanceVo): RecordStatus { - if (record.status === 'RUNNING') { - return { +const RECORD_STATUS_MAP = new Map([ + [ + 'CREATING', + { + key: 'creating', + label: 'daas_task_rebalance_history_record_creating', + type: 'info', + }, + ], + [ + 'RUNNING', + { key: 'running', label: 'daas_task_rebalance_history_record_running', type: 'primary', - } - } - if (record.status === 'FAILED') { - return { + }, + ], + [ + 'OK', + { + key: 'done', + label: 'daas_task_rebalance_history_record_done', + type: 'success', + }, + ], + [ + 'FAILED', + { key: 'failed', label: 'daas_task_rebalance_history_record_failed', type: 'danger', - } - } - if (record.status === 'CANCELLED') { - return { - key: 'cancelled', - label: 'daas_task_rebalance_history_record_cancelled', - type: 'info', - } - } - if (record.failedCount > 0) { - return { - key: 'partial', - label: 'daas_task_rebalance_history_record_partial', - type: 'warning', - } - } - if (record.cancelledCount > 0 && record.okCount === 0) { - return { + }, + ], + [ + 'CANCELLED', + { key: 'cancelled', label: 'daas_task_rebalance_history_record_cancelled', type: 'info', + }, + ], +]) + +function getRecordStatus(record: TaskRebalanceVo): RecordStatus { + const mapped = RECORD_STATUS_MAP.get(record.status as RebalanceStatus) + if (mapped) { + // For OK status, check for partial success or full cancellation + if (record.status === 'OK') { + if (record.failedCount > 0) { + return { + key: 'partial', + label: 'daas_task_rebalance_history_record_partial', + type: 'warning', + } + } + if (record.cancelledCount > 0 && record.okCount === 0) { + return { + key: 'cancelled', + label: 'daas_task_rebalance_history_record_cancelled', + type: 'info', + } + } } + return mapped } return { key: 'done', @@ -411,7 +461,11 @@ onUnmounted(stopDetailPolling) }} @@ -507,7 +561,7 @@ onUnmounted(stopDetailPolling) t('daas_task_rebalance_history_progress') }} - {{ t(CATEGORY_LABEL[row.category as JobCategory]) }} + {{ t(JOB_STATUS_LABEL[row.status as JobStatus] ?? CATEGORY_LABEL[row.category as JobCategory]) }} @@ -625,7 +679,7 @@ onUnmounted(stopDetailPolling)
{{ row.errorMesg || '-' }} Date: Mon, 15 Jun 2026 16:04:37 +0800 Subject: [PATCH 09/11] refactor: simplify button rendering in Cluster.vue and History.vue by removing conditional rendering for rebalance button --- apps/daas/src/views/cluster/Cluster.vue | 6 +----- apps/daas/src/views/task-rebalance/History.vue | 13 +++++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/daas/src/views/cluster/Cluster.vue b/apps/daas/src/views/cluster/Cluster.vue index 5d7060a5f..9897a4e72 100644 --- a/apps/daas/src/views/cluster/Cluster.vue +++ b/apps/daas/src/views/cluster/Cluster.vue @@ -1229,11 +1229,7 @@ const onUpdateLicenseSuccess = () => { :items="filterItems" @fetch="getDataApi()" /> - + {{ $t('daas_task_rebalance_button') }}
diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index c4b691ca5..2f5a05f37 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -461,11 +461,7 @@ onUnmounted(stopDetailPolling) }} @@ -663,7 +659,12 @@ onUnmounted(stopDetailPolling) :is="CATEGORY_ICON[row.category as JobCategory]" /> - {{ t(JOB_STATUS_LABEL[row.status as JobStatus] ?? CATEGORY_LABEL[row.category as JobCategory]) }} + {{ + t( + JOB_STATUS_LABEL[row.status as JobStatus] ?? + CATEGORY_LABEL[row.category as JobCategory], + ) + }} From c1d982690edb7135dd4089e5e58e4528ae8e1e75 Mon Sep 17 00:00:00 2001 From: Feynman Date: Mon, 15 Jun 2026 16:51:47 +0800 Subject: [PATCH 10/11] feat: add conditional rendering for rebalance button in Cluster.vue and History.vue based on user permissions --- apps/daas/src/views/cluster/Cluster.vue | 6 +++++- apps/daas/src/views/task-rebalance/History.vue | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/daas/src/views/cluster/Cluster.vue b/apps/daas/src/views/cluster/Cluster.vue index 9897a4e72..5d7060a5f 100644 --- a/apps/daas/src/views/cluster/Cluster.vue +++ b/apps/daas/src/views/cluster/Cluster.vue @@ -1229,7 +1229,11 @@ const onUpdateLicenseSuccess = () => { :items="filterItems" @fetch="getDataApi()" /> - + {{ $t('daas_task_rebalance_button') }} diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index 2f5a05f37..7528dd74e 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -461,7 +461,11 @@ onUnmounted(stopDetailPolling) }} From ba9d7600c24dfec2acf52cf5ffc0f956a35e7960 Mon Sep 17 00:00:00 2001 From: Feynman Date: Mon, 15 Jun 2026 17:59:58 +0800 Subject: [PATCH 11/11] feat(History.vue): implement task link navigation for task monitor and enhance styling for task names --- .../src/views/cluster/TaskRebalanceDrawer.vue | 4 -- .../daas/src/views/task-rebalance/History.vue | 53 +++++++++++++++++-- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/apps/daas/src/views/cluster/TaskRebalanceDrawer.vue b/apps/daas/src/views/cluster/TaskRebalanceDrawer.vue index b94fc4029..25f788671 100644 --- a/apps/daas/src/views/cluster/TaskRebalanceDrawer.vue +++ b/apps/daas/src/views/cluster/TaskRebalanceDrawer.vue @@ -11,10 +11,6 @@ import { useI18n } from '@tap/i18n' import { computed, ref, watch } from 'vue' import { useRouter } from 'vue-router' -const emit = defineEmits<{ - (e: 'success'): void -}>() - const { t } = useI18n() const router = useRouter() diff --git a/apps/daas/src/views/task-rebalance/History.vue b/apps/daas/src/views/task-rebalance/History.vue index 7528dd74e..1253decca 100644 --- a/apps/daas/src/views/task-rebalance/History.vue +++ b/apps/daas/src/views/task-rebalance/History.vue @@ -16,10 +16,27 @@ import { dayjs } from '@tap/business/src/shared' import { Modal } from '@tap/component/src/modal' import { useI18n } from '@tap/i18n' import { computed, onUnmounted, ref, watch } from 'vue' +import { useRouter } from 'vue-router' import TaskRebalanceDrawer from '../cluster/TaskRebalanceDrawer.vue' const { t } = useI18n() const $has = useHas() +const router = useRouter() + +const SYNC_TYPE_ROUTE_MAP: Record = { + sync: 'TaskMonitor', + migrate: 'MigrationMonitor', + logCollector: 'SharedMiningMonitor', + mem_cache: 'SharedCacheMonitor', + connHeartbeat: 'HeartbeatMonitor', +} + +function openTaskMonitor(taskId: string, syncType: string) { + const name = SYNC_TYPE_ROUTE_MAP[syncType] + if (!name) return + const { href } = router.resolve({ name, params: { id: taskId } }) + window.open(href, '_blank') +} const hasEditPermission = computed(() => { return $has('v2_task_rebalance_Edit') @@ -620,10 +637,24 @@ onUnmounted(stopDetailPolling) > + > + +