diff --git a/apps/daas/src/utils/settings.js b/apps/daas/src/utils/settings.js index 830ca1f69..d66829a30 100644 --- a/apps/daas/src/utils/settings.js +++ b/apps/daas/src/utils/settings.js @@ -1,5 +1,15 @@ let settings = {} +const normalizeSetting = (setting) => { + return { + ...setting, + value: + setting.isArray && typeof setting.value === 'string' + ? setting.value.split(',') + : setting.value, + } +} + export const getSettings = () => { return Object.values(settings) } @@ -8,8 +18,7 @@ export const setSettings = (data = []) => { settings = {} data.forEach((setting) => { - setting.value = setting.isArray ? setting.value.split(',') : setting.value - settings[setting.key] = setting + settings[setting.key] = normalizeSetting(setting) }) } diff --git a/apps/daas/src/views/setting/Setting.vue b/apps/daas/src/views/setting/Setting.vue index 11533f289..5fb8d30fd 100644 --- a/apps/daas/src/views/setting/Setting.vue +++ b/apps/daas/src/views/setting/Setting.vue @@ -14,6 +14,7 @@ import { AdminOutlined } from '@tap/component/src/icon' import { TextFileReader } from '@tap/form/src/components/text-file-reader' import { getCurrentLanguage } from '@tap/i18n/src/shared/util' import Cookie from '@tap/shared/src/cookie' +import { setSettings } from '@tap/shared/src/settings' import Time from '@tap/shared/src/time' import { find, uniq } from 'lodash-es' import i18n from '@/i18n' @@ -299,12 +300,15 @@ export default { // 保存 save() { const settingData = [] - this.formData.items.filter((item) => { + this.formData.items.forEach((item) => { item.items.forEach((childItem) => { settingData.push(childItem) }) }) - saveSettings(settingData).then(() => { + saveSettings(settingData).then(async () => { + const settings = await fetchSettings() + setSettings(settings) + localStorage.setItem('TAPDATA_SETTINGS', JSON.stringify(settings)) this.$message.success(this.$t('public_message_save_ok')) if (this.appearanceForm && Object.keys(this.appearanceForm).length) { diff --git a/apps/daas/src/views/user/List.vue b/apps/daas/src/views/user/List.vue index 881c0f0e3..49f99ef77 100644 --- a/apps/daas/src/views/user/List.vue +++ b/apps/daas/src/views/user/List.vue @@ -385,15 +385,10 @@ export default { fetchRoles(filter).then((data) => { const items = data?.items || [] this.roleList = items - const options = [] - items.forEach((db) => { - if (db.name !== 'admin') { - options.push({ - label: db.name, - value: db.id, - }) - } - }) + const options = items.map((db) => ({ + label: db.name, + value: db.id, + })) this.createFormConfig.items[3].options = options }) }, diff --git a/packages/api/src/core/alarm.ts b/packages/api/src/core/alarm.ts index 36a4903e1..d03711d6f 100644 --- a/packages/api/src/core/alarm.ts +++ b/packages/api/src/core/alarm.ts @@ -105,3 +105,12 @@ export function updateTaskAlarm(params: { }) { return requestClient.post(`${BASE_URL}/updateTaskAlarm`, params) } + +export function batchUpdateTaskAlarm(params: { + taskIds: string[] + alarmRules?: AlarmRuleVO[] | null + alarmSettings?: AlarmSettingVO[] | null + emailReceivers?: string[] | null +}) { + return requestClient.post('/api/task/alarm/batch-update', params) +} diff --git a/packages/business/src/locale/lang/en.js b/packages/business/src/locale/lang/en.js index b1c632747..1cf91eff5 100644 --- a/packages/business/src/locale/lang/en.js +++ b/packages/business/src/locale/lang/en.js @@ -258,6 +258,17 @@ export default { packages_business_dataFlow_bulkScheuled: 'Batch Start', packages_business_dataFlow_bulkStopping: 'Batch Stop', packages_business_dataFlow_taskBulkOperation: 'Batch Operation', + packages_business_task_batch_alarm_email_action: 'Batch Set Alert Receivers', + packages_business_task_batch_alarm_email_title: 'Batch Set Alert Receivers', + packages_business_task_batch_alarm_email_label: 'Email Receivers', + packages_business_task_batch_alarm_email_placeholder: + 'Select email receivers', + packages_business_task_batch_alarm_email_overwrite_tip: + "After saving, the selected tasks' existing alert receiver settings will be overwritten.", + packages_business_task_batch_alarm_email_empty: + 'No email receivers available. Save directly to clear alert receivers from the selected tasks.', + packages_business_task_batch_alarm_email_no_edit_permission_tip: + '{count} tasks do not have edit permission and will be skipped.', packages_business_dataFlow_addTag: 'Add Tag', packages_business_dataVerify_dataVerify: 'Data Verify', packages_business_dataFlow_selectAll: 'Select All', @@ -752,8 +763,10 @@ export default { 'When the task encounters an error', packages_business_setting_alarmnotification_dangrenwustop: 'When the Agent service stops', - packages_business_setting_alarmnotification_dangyinqinglixian: 'When the engine stops', - packages_business_setting_alarmnotification_dangyinqinghuifu: 'When the engine starts', + packages_business_setting_alarmnotification_dangyinqinglixian: + 'When the engine stops', + packages_business_setting_alarmnotification_dangyinqinghuifu: + 'When the engine starts', packages_business_setting_alarmnotification_api_server_worker_delay_p50_warn: 'When APIServer worker P50 per minute exceeding threshold', packages_business_setting_alarmnotification_api_server_worker_delay_p95_warn: @@ -1770,6 +1783,8 @@ export default { 'Under active development. Some features may still be incomplete.', packages_business_column_setting: 'Display Settings', packages_business_column_reset: 'Reset', - packages_business_api_server_connection_pool_deficiency_warn: 'When any API Server connection pool resource is tight and triggers the alarm threshold', - packages_business_api_server_connection_pool_idle_warn: 'When the number of connections in the connection pool of any API Server falls below the alarm threshold', + packages_business_api_server_connection_pool_deficiency_warn: + 'When any API Server connection pool resource is tight and triggers the alarm threshold', + packages_business_api_server_connection_pool_idle_warn: + 'When the number of connections in the connection pool of any API Server falls below the alarm threshold', } diff --git a/packages/business/src/locale/lang/zh-CN.js b/packages/business/src/locale/lang/zh-CN.js index a99d17589..7bd94fa33 100644 --- a/packages/business/src/locale/lang/zh-CN.js +++ b/packages/business/src/locale/lang/zh-CN.js @@ -232,6 +232,16 @@ export default { packages_business_dataFlow_bulkScheuled: '批量启动', packages_business_dataFlow_bulkStopping: '批量停止', packages_business_dataFlow_taskBulkOperation: '任务操作', + packages_business_task_batch_alarm_email_action: '批量设置告警接收人', + packages_business_task_batch_alarm_email_title: '批量设置告警接收人', + packages_business_task_batch_alarm_email_label: '邮件接收人', + packages_business_task_batch_alarm_email_placeholder: '请选择邮件接收人', + packages_business_task_batch_alarm_email_overwrite_tip: + '保存后,将覆盖所选任务原有的告警接收人设置', + packages_business_task_batch_alarm_email_empty: + '暂无可选邮件接收人,可直接保存以清空所选任务的告警接收人', + packages_business_task_batch_alarm_email_no_edit_permission_tip: + '以下 {count} 个任务没有编辑权限,保存时将自动跳过', packages_business_dataFlow_addTag: '添加标签', packages_business_dataVerify_dataVerify: '数据校验', packages_business_dataFlow_selectAll: '全选', diff --git a/packages/business/src/locale/lang/zh-TW.js b/packages/business/src/locale/lang/zh-TW.js index 068e909f6..f65c24de8 100644 --- a/packages/business/src/locale/lang/zh-TW.js +++ b/packages/business/src/locale/lang/zh-TW.js @@ -231,6 +231,16 @@ export default { packages_business_dataFlow_bulkScheuled: '批量啟動', packages_business_dataFlow_bulkStopping: '批量停止', packages_business_dataFlow_taskBulkOperation: '任務操作', + packages_business_task_batch_alarm_email_action: '批量設置告警接收人', + packages_business_task_batch_alarm_email_title: '批量設置告警接收人', + packages_business_task_batch_alarm_email_label: '郵件接收人', + packages_business_task_batch_alarm_email_placeholder: '請選擇郵件接收人', + packages_business_task_batch_alarm_email_overwrite_tip: + '保存後,將覆蓋所選任務原有的告警接收人設置', + packages_business_task_batch_alarm_email_empty: + '暫無可選郵件接收人,可直接保存以清空所選任務的告警接收人', + packages_business_task_batch_alarm_email_no_edit_permission_tip: + '以下 {count} 個任務沒有編輯權限,保存時將自動跳過', packages_business_dataFlow_addTag: '添加標籤', packages_business_dataVerify_dataVerify: '數據校驗', packages_business_dataFlow_selectAll: '全選', diff --git a/packages/business/src/views/task/BatchAlarmEmailDialog.vue b/packages/business/src/views/task/BatchAlarmEmailDialog.vue new file mode 100644 index 000000000..b7ea01b8b --- /dev/null +++ b/packages/business/src/views/task/BatchAlarmEmailDialog.vue @@ -0,0 +1,242 @@ + + + + + + + + + + + + + {{ + $t( + 'packages_business_task_batch_alarm_email_no_edit_permission_tip', + { + count: noEditTasks.length, + }, + ) + }} + + + + + {{ task.name || task.id }} + + + + + + + + + + + + + + + + + + + {{ $t('packages_business_task_batch_alarm_email_overwrite_tip') }} + + + + {{ $t('packages_business_task_batch_alarm_email_empty') }} + + + + + + {{ $t('public_button_cancel') }} + + {{ $t('public_button_save') }} + + + + + + diff --git a/packages/business/src/views/task/List.vue b/packages/business/src/views/task/List.vue index b54108beb..551879cb4 100644 --- a/packages/business/src/views/task/List.vue +++ b/packages/business/src/views/task/List.vue @@ -35,6 +35,7 @@ import UpgradeFee from '../../components/UpgradeFee.vue' import Upload from '../../components/UploadDialog.vue' import syncTaskAgent from '../../mixins/syncTaskAgent' import { makeStatusAndDisabled, MILESTONE_TYPE, STATUS_MAP } from '../../shared' +import BatchAlarmEmailDialog from './BatchAlarmEmailDialog.vue' import EditInfoDialog from './EditInfoDialog.vue' import SkipError from './SkipError.vue' import TaskName from './TaskName.vue' @@ -57,6 +58,7 @@ export default { SyncStatus, EditInfoDialog, TaskName, + BatchAlarmEmailDialog, }, mixins: [syncTaskAgent], @@ -272,6 +274,7 @@ export default { taskIncrementDelay: true, taskIncrementDelayThreshold: true, heartbeatTaskRunning: true, + emailReceivers: true, } const where = { syncType, @@ -393,6 +396,26 @@ export default { return time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '-' }, + formatEmailReceivers(receivers) { + return Array.isArray(receivers) + ? receivers.map((receiver) => receiver.trim()).filter(Boolean) + : [] + }, + + getEmailLocalPart(email = '') { + return email.split('@')[0] || email + }, + + getEmailDomain(email = '') { + const domain = email.split('@')[1] + + return domain ? `@${domain}` : '' + }, + + getEmailInitial(email = '') { + return email.trim().charAt(0).toUpperCase() || '@' + }, + getFilterItems() { const items = [ { @@ -867,6 +890,18 @@ export default { exportTasks(ids) }, + batchUpdateAlarmEmailReceivers(ids, item = {}) { + const tasks = item?.id + ? [item] + : this.multipleSelection.filter(({ id }) => ids.includes(id)) + this.$refs.batchAlarmEmailDialog.open(tasks) + }, + + handleBatchAlarmEmailSuccess() { + this.table.fetch() + this.table.clearSelection() + }, + handleCommand(command, node) { const commandFilter = ['start', 'stop', 'del', 'initialize'] let ids = [] @@ -1093,6 +1128,7 @@ export default { class="data-flow-list" :enable-custom-columns="syncType" :locked-columns="['name', 'operation']" + :default-hidden-columns="['emailReceivers']" :classify="{ authority: 'SYNC_category_management', types: ['dataflow'], @@ -1206,20 +1242,44 @@ export default { {{ $t('packages_business_dataFlow_bulkScheuled') }} + > + + + + {{ $t('packages_business_dataFlow_bulkScheuled') }} {{ $t('packages_business_dataFlow_bulkStopping') }} + > + + + + {{ $t('packages_business_dataFlow_bulkStopping') }} {{ $t('packages_business_dataFlow_batchRest') }} + > + + + + {{ $t('packages_business_dataFlow_batchRest') }} - {{ $t('packages_business_dataFlow_batchDelete') }} + + + + + {{ $t('packages_business_dataFlow_batchDelete') }} + + + + + + {{ $t('packages_business_task_batch_alarm_email_action') }} @@ -1370,6 +1430,101 @@ export default { {{ formatTime(row.last_updated) }} + + + + + + + {{ $t('packages_dag_email_receivers') }} + + + + + + + + + + + {{ $t('packages_dag_email_receivers') }} + + + {{ formatEmailReceivers(row.emailReceivers).length }} + + + + + {{ getEmailInitial(receiver) }} + + + {{ getEmailLocalPart(receiver) }} + {{ + getEmailDomain(receiver) + }} + + + + + + + {{ + getEmailInitial(formatEmailReceivers(row.emailReceivers)[0]) + }} + + + {{ + getEmailLocalPart( + formatEmailReceivers(row.emailReceivers)[0], + ) + }} + {{ + getEmailDomain(formatEmailReceivers(row.emailReceivers)[0]) + }} + + + + + + {{ formatEmailReceivers(row.emailReceivers).length }} + + + + + + + {{ getEmailInitial(formatEmailReceivers(row.emailReceivers)[0]) }} + + + {{ + getEmailLocalPart(formatEmailReceivers(row.emailReceivers)[0]) + }} + {{ + getEmailDomain(formatEmailReceivers(row.emailReceivers)[0]) + }} + + + - + + {{ $t('public_button_start') }} - - - {{ $t('public_button_force_stop') }} - - - {{ $t('public_button_stop') }} - - + + {{ $t('public_button_force_stop') }} + + + {{ $t('public_button_stop') }} + + @@ -1676,5 +1835,151 @@ export default { :deep(.task-status-cell .cell) { overflow: visible; } + + .email-receiver-header, + .email-receiver-card, + .email-receiver-count { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; + } + + .email-receiver-header-icon { + flex: 0 0 auto; + color: var(--icon-n2); + } + + .email-receiver-card { + width: fit-content; + max-width: 100%; + height: 28px; + padding: 0 8px 0 4px; + border: 1px solid transparent; + border-radius: 14px; + background: rgba(129, 139, 152, 0.08); + } + + .email-receiver-card.is-multiple { + cursor: pointer; + + &:hover { + border-color: var(--color-primary); + background: rgba(44, 101, 255, 0.08); + } + } + + .email-receiver-avatar { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--color-primary); + color: #fff; + font-size: 11px; + font-weight: 600; + line-height: 1; + } + + .email-receiver-primary { + min-width: 0; + overflow: hidden; + color: var(--text-normal); + text-overflow: ellipsis; + white-space: nowrap; + } + + .email-receiver-domain { + color: var(--text-light); + } + + .email-receiver-count { + flex: 0 0 auto; + height: 20px; + padding: 0 6px; + border-radius: 10px; + background: #fff; + color: var(--color-primary); + font-size: 12px; + font-weight: 600; + } +} + +:global(.email-receiver-popover-content) { + max-width: 320px; + padding: 2px; +} + +:global(.email-receiver-popover-header), +:global(.email-receiver-popover-title), +:global(.email-receiver-popover-item) { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +:global(.email-receiver-popover-header) { + justify-content: space-between; + margin-bottom: 6px; + padding: 0 2px 6px; + border-bottom: 1px solid var(--border-light); +} + +:global(.email-receiver-popover-title) { + color: var(--text-normal); + font-weight: 600; +} + +:global(.email-receiver-popover-icon) { + color: var(--icon-n2); +} + +:global(.email-receiver-popover-count) { + min-width: 20px; + height: 20px; + border-radius: 10px; + background: rgba(44, 101, 255, 0.1); + color: var(--color-primary); + font-size: 12px; + font-weight: 600; + line-height: 20px; + text-align: center; +} + +:global(.email-receiver-avatar) { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--color-primary); + color: #fff; + font-size: 11px; + font-weight: 600; + line-height: 1; +} + +:global(.email-receiver-popover-item) { + padding: 5px 2px; +} + +:global(.email-receiver-domain) { + color: var(--text-light); +} + +:global(.email-receiver-popover-email) { + min-width: 0; + overflow: hidden; + color: var(--text-normal); + line-height: 20px; + text-overflow: ellipsis; + white-space: nowrap; + word-break: break-all; } diff --git a/packages/shared/src/settings.ts b/packages/shared/src/settings.ts index 8e496dd48..39a991ebb 100644 --- a/packages/shared/src/settings.ts +++ b/packages/shared/src/settings.ts @@ -1,5 +1,15 @@ let settings: Record = {} +const normalizeSetting = (setting: Record) => { + return { + ...setting, + value: + setting.isArray && typeof setting.value === 'string' + ? setting.value.split(',') + : setting.value, + } +} + export const getSettings = () => { return Object.values(settings) } @@ -8,8 +18,7 @@ export const setSettings = (data: Record[] = []) => { settings = {} data.forEach((setting) => { - setting.value = setting.isArray ? setting.value.split(',') : setting.value - settings[setting.key] = setting + settings[setting.key] = normalizeSetting(setting) }) }