feat(sj_1.0.0): 新增通知告警配置

This commit is contained in:
opensnail 2024-04-17 16:37:30 +08:00
parent ef3d26dd08
commit e1401777a1
14 changed files with 653 additions and 6 deletions

View File

@ -160,6 +160,7 @@ const local: App.I18n.Schema = {
namepase: 'Namepase',
manage: 'System Manage',
manage_user: 'User Manage',
notify: 'notify',
'manage_user-detail': 'User Detail',
manage_role: 'Role Manage',
manage_menu: 'Menu Manage',
@ -496,6 +497,29 @@ const local: App.I18n.Schema = {
local: 'Local Icon'
}
}
},
notifyConfig: {
title: 'Alarm Notify List',
groupName: 'Group name',
businessName: 'Business ID',
notifyStatus: 'State',
notifyType: 'Notify type',
notifyScene: 'Notify scene',
notifyThreshold: 'Notify threshold',
description: 'Describe',
notifyAttribute: 'Notify Attribute',
form: {
businessId: 'Please select Business ID',
description: 'Please enter Describe',
notifyType: 'Please select Notification type',
notifyAttribute: 'Please enter notify attribute',
notifyScene: 'Please select Notification scene',
groupName: 'Please select Group name',
notifyThreshold: 'Please enter Notification threshold',
notifyStatus: 'Please select State'
},
addNotifyConfig: 'Add Alarm notification',
editNotifyConfig: 'Add Alarm notification'
}
},
form: {

View File

@ -160,6 +160,7 @@ const local: App.I18n.Schema = {
namepase: '命名空间',
manage: '系统管理',
manage_user: '用户管理',
notify: '告警通知',
'manage_user-detail': '用户详情',
manage_role: '角色管理',
manage_menu: '菜单管理',
@ -492,6 +493,29 @@ const local: App.I18n.Schema = {
local: '本地图标'
}
}
},
notifyConfig: {
title: '告警通知列表',
groupName: '组名称',
businessName: '业务ID',
notifyStatus: '状态',
notifyType: '通知类型',
notifyScene: '通知场景',
notifyThreshold: '通知阈值',
description: '描述',
notifyAttribute: '通知属性',
form: {
businessId: '请选择业务ID',
description: '请输入描述',
notifyType: '请选择通知类型',
notifyAttribute: '请求输入通知属性',
notifyScene: '请选择通知场景',
groupName: '请选择组名称',
notifyThreshold: '请输入通知阈值',
notifyStatus: '请选择状态'
},
addNotifyConfig: '新增告警通知',
editNotifyConfig: '编辑告警通知'
}
},
form: {

View File

@ -36,6 +36,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
"multi-menu_first_child": () => import("@/views/multi-menu/first_child/index.vue"),
"multi-menu_second_child_home": () => import("@/views/multi-menu/second_child_home/index.vue"),
namepase: () => import("@/views/namepase/index.vue"),
notify: () => import("@/views/notify/index.vue"),
pods: () => import("@/views/pods/index.vue"),
"user-center": () => import("@/views/user-center/index.vue"),
};

View File

@ -332,6 +332,15 @@ export const generatedRoutes: GeneratedRoute[] = [
order: 2
}
},
{
name: 'notify',
path: '/notify',
component: 'layout.base$view.notify',
meta: {
title: 'notify',
i18nKey: 'route.notify'
}
},
{
name: 'pods',
path: '/pods',

View File

@ -114,14 +114,14 @@ function transformElegantRouteToVueRoute(
}
}
// add redirect to child
if (children?.length && !vueRoute.redirect) {
vueRoute.redirect = {
name: children[0].name
};
}
if (children?.length) {
const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child, layouts, views));
@ -175,6 +175,7 @@ const routeMap: RouteMap = {
"multi-menu_second_child": "/multi-menu/second/child",
"multi-menu_second_child_home": "/multi-menu/second/child/home",
"namepase": "/namepase",
"notify": "/notify",
"pods": "/pods",
"user-center": "/user-center"
};

View File

@ -4,3 +4,4 @@ export * from './system';
export * from './dashboard';
export * from './namespace';
export * from './system-manage';
export * from './notify';

28
src/service/api/notify.ts Normal file
View File

@ -0,0 +1,28 @@
import { request } from '../request';
/** get notify list */
export function fetchGetNotifyConfigList(params?: Api.NotifyConfig.NotifySearchParams) {
return request<Api.NotifyConfig.NotifyConfigList>({
url: '/notify-config/list',
method: 'get',
params
});
}
/** add notify */
export function fetchAddNotify(data: Api.NotifyConfig.NotifyConfig) {
return request<boolean>({
url: '/notify-config',
method: 'post',
data
});
}
/** edit notify */
export function fetchEditNotify(data: Api.NotifyConfig.NotifyConfig) {
return request<boolean>({
url: '/notify-config',
method: 'put',
data
});
}

53
src/typings/api.d.ts vendored
View File

@ -422,4 +422,57 @@ declare namespace Api {
children?: MenuTree[];
};
}
/**
* namespace NotifyConfig
*
* backend api module: "notifyConfig"
*/
namespace NotifyConfig {
type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'page' | 'size'>;
/** notify-config */
type NotifyConfig = Common.CommonRecord<{
/** 组名称 */
groupName: string;
/** 业务ID */
businessId: string;
/** 状态 */
notifyStatus: string;
/** 通知类型 */
notifyType: string;
/** 通知属性 */
notifyAttribute: string;
/** 通知场景 */
notifyScene: string;
/** 通知阈值 */
notifyThreshold: number;
/** 描述 */
description: string;
}>;
/** notify-config search params */
type NotifySearchParams = CommonType.RecordNullable<
Pick<
Api.NotifyConfig.NotifyConfig,
| 'groupName'
| 'businessId'
| 'notifyStatus'
| 'notifyType'
| 'notifyAttribute'
| 'notifyScene'
| 'notifyThreshold'
| 'description'
> &
CommonSearchParams
>;
/** notify-config list */
type NotifyConfigList = Common.PaginatingQueryRecord<
{
/** 业务名称 */
businessName: string;
} & NotifyConfig
>;
}
}

23
src/typings/app.d.ts vendored
View File

@ -671,6 +671,29 @@ declare namespace App {
};
};
};
notifyConfig: {
title: string;
groupName: string;
businessName: string;
notifyStatus: string;
notifyType: string;
notifyScene: string;
notifyThreshold: string;
description: string;
notifyAttribute: string;
form: {
businessId: string;
description: string;
notifyType: string;
notifyAttribute: string;
notifyScene: string;
groupName: string;
notifyThreshold: string;
notifyStatus: string;
};
addNotifyConfig: string;
editNotifyConfig: string;
};
};
form: {
required: string;

View File

@ -52,6 +52,7 @@ declare module 'vue' {
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NEmpty: typeof import('naive-ui')['NEmpty']
NFlex: typeof import('naive-ui')['NFlex']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']

View File

@ -49,6 +49,7 @@ declare module "@elegant-router/types" {
"multi-menu_second_child": "/multi-menu/second/child";
"multi-menu_second_child_home": "/multi-menu/second/child/home";
"namepase": "/namepase";
"notify": "/notify";
"pods": "/pods";
"user-center": "/user-center";
};
@ -65,7 +66,7 @@ declare module "@elegant-router/types" {
/**
* custom route key
*/
*/
export type CustomRouteKey = Extract<
RouteKey,
| "root"
@ -78,7 +79,7 @@ declare module "@elegant-router/types" {
/**
* the generated route key
*/
*/
export type GeneratedRouteKey = Exclude<RouteKey, CustomRouteKey>;
/**
@ -90,12 +91,14 @@ declare module "@elegant-router/types" {
| "404"
| "500"
| "about"
| "demo-route"
| "function"
| "home"
| "login"
| "manage"
| "multi-menu"
| "namepase"
| "notify"
| "pods"
| "user-center"
>;
@ -120,6 +123,7 @@ declare module "@elegant-router/types" {
| "500"
| "login"
| "about"
| "demo-route_child"
| "function_hide-child_one"
| "function_hide-child_three"
| "function_hide-child_two"
@ -136,6 +140,7 @@ declare module "@elegant-router/types" {
| "multi-menu_first_child"
| "multi-menu_second_child_home"
| "namepase"
| "notify"
| "pods"
| "user-center"
>;
@ -212,7 +217,7 @@ declare module "@elegant-router/types" {
component: `view.${K}`;
}
: never;
/**
* the center level route
*/
@ -235,7 +240,7 @@ declare module "@elegant-router/types" {
children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom first level route
*/

178
src/views/notify/index.vue Normal file
View File

@ -0,0 +1,178 @@
<script setup lang="tsx">
import { NButton, NPopconfirm } from 'naive-ui';
import { fetchGetNotifyConfigList } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import NotifyConfigOperateDrawer from './modules/notify-config-operate-drawer.vue';
import NotifyConfigSearch from './modules/notify-config-search.vue';
const appStore = useAppStore();
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetNotifyConfigList,
apiParams: {
page: 1,
size: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
groupName: null,
notifyStatus: null,
notifyScene: null,
notifyThreshold: null,
description: null
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64
},
{
key: 'groupName',
title: $t('page.notifyConfig.groupName'),
align: 'left',
width: 120
},
{
key: 'businessName',
title: $t('page.notifyConfig.businessName'),
align: 'left',
width: 120
},
{
key: 'notifyStatus',
title: $t('page.notifyConfig.notifyStatus'),
align: 'left',
width: 120
},
{
key: 'notifyType',
title: $t('page.notifyConfig.notifyType'),
align: 'left',
width: 120
},
{
key: 'notifyScene',
title: $t('page.notifyConfig.notifyScene'),
align: 'left',
width: 120
},
{
key: 'notifyThreshold',
title: $t('page.notifyConfig.notifyThreshold'),
align: 'left',
width: 120
},
{
key: 'description',
title: $t('page.notifyConfig.description'),
align: 'left',
width: 120
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 130,
render: row => (
<div class="flex-center gap-8px">
<NButton type="primary" ghost size="small" onClick={() => edit(row.id!)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id!)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
</div>
)
}
]
});
const {
drawerVisible,
operateType,
editingData,
handleAdd,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
onBatchDeleted();
}
function handleDelete(id: string) {
// request
console.log(id);
onDeleted();
}
function edit(id: string) {
handleEdit(id);
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<NotifyConfigSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
<NCard
:title="$t('page.notifyConfig.title')"
:bordered="false"
size="small"
class="sm:flex-1-hidden card-wrapper"
header-class="view-card-header"
>
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
<NotifyConfigOperateDrawer
v-model:visible="drawerVisible"
:operate-type="operateType"
:row-data="editingData"
@submitted="getData"
/>
</NCard>
</div>
</template>
<style scoped></style>

View File

@ -0,0 +1,228 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import OperateDrawer from '@/components/common/operate-drawer.vue';
import { $t } from '@/locales';
import { fetchAddNotify, fetchEditNotify } from '@/service/api';
defineOptions({
name: 'NotifyConfigOperateDrawer'
});
interface Props {
/** the type of operation */
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.NotifyConfig.NotifyConfig | null;
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: $t('page.notifyConfig.addNotifyConfig'),
edit: $t('page.notifyConfig.editNotifyConfig')
};
return titles[props.operateType];
});
type Model = Pick<
Api.NotifyConfig.NotifyConfig,
| 'id'
| 'groupName'
| 'businessId'
| 'notifyStatus'
| 'notifyType'
| 'notifyAttribute'
| 'notifyScene'
| 'notifyThreshold'
| 'description'
>;
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
groupName: '',
businessId: '',
notifyStatus: '',
notifyType: '',
notifyScene: '',
notifyThreshold: 0,
notifyAttribute: '',
description: ''
};
}
type RuleKey = Extract<
keyof Model,
'groupName' | 'businessId' | 'notifyStatus' | 'notifyType' | 'notifyScene' | 'notifyThreshold' | 'description'
>;
const rules: Record<RuleKey, App.Global.FormRule> = {
groupName: defaultRequiredRule,
businessId: defaultRequiredRule,
notifyStatus: defaultRequiredRule,
notifyType: defaultRequiredRule,
notifyScene: defaultRequiredRule,
notifyThreshold: defaultRequiredRule,
description: defaultRequiredRule
};
function handleUpdateModelWhenEdit() {
if (props.operateType === 'add') {
Object.assign(model, createDefaultModel());
return;
}
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData);
}
}
function closeDrawer() {
visible.value = false;
}
async function handleSubmit() {
await validate();
// request
if (props.operateType === 'add') {
const {
groupName,
businessId,
notifyStatus,
notifyType,
notifyAttribute,
notifyScene,
notifyThreshold,
description
} = model;
fetchAddNotify({
groupName,
businessId,
notifyStatus,
notifyType,
notifyAttribute,
notifyScene,
notifyThreshold,
description
});
}
if (props.operateType === 'edit') {
const {
id,
groupName,
businessId,
notifyStatus,
notifyType,
notifyAttribute,
notifyScene,
notifyThreshold,
description
} = model;
fetchEditNotify({
id,
groupName,
businessId,
notifyStatus,
notifyType,
notifyAttribute,
notifyScene,
notifyThreshold,
description
});
}
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
}
watch(visible, () => {
if (visible.value) {
handleUpdateModelWhenEdit();
restoreValidation();
}
});
</script>
<template>
<OperateDrawer v-model="visible" :title="title" @handle-submit="handleSubmit">
<NForm ref="formRef" :model="model" :rules="rules">
<NGrid x-gap="12" :cols="6" item-responsive responsive="screen">
<NGi span="m:6 l:4">
<NFormItem :label="$t('page.notifyConfig.notifyScene')" path="name">
<NSelect
v-model:value="model.notifyScene"
:placeholder="$t('page.notifyConfig.page.notifyScene')"
clearable
/>
</NFormItem>
</NGi>
<NGi span="m:6 l:2">
<NFormItem :label="$t('page.notifyConfig.notifyThreshold')" path="name">
<NInputNumber
v-model:value="model.notifyThreshold"
:placeholder="$t('page.notifyConfig.form.notifyThreshold')"
/>
</NFormItem>
</NGi>
</NGrid>
<NGrid x-gap="12" :cols="6" item-responsive responsive="screen">
<NGi span="m:6 l:4">
<NFormItem :label="$t('page.notifyConfig.groupName')" path="name">
<NSelect v-model:value="model.groupName" :placeholder="$t('page.notifyConfig.form.groupName')" clearable />
</NFormItem>
</NGi>
<NGi span="m:6 l:2">
<NFormItem :label="$t('page.notifyConfig.notifyStatus')" path="name">
<NSelect
v-model:value="model.notifyStatus"
:placeholder="$t('page.notifyConfig.form.notifyStatus')"
clearable
/>
</NFormItem>
</NGi>
</NGrid>
<NGrid x-gap="12" :cols="6" item-responsive responsive="screen">
<NGi span="m:6 l:4">
<NFormItem :label="$t('page.notifyConfig.notifyType')" path="name">
<NSelect
v-model:value="model.notifyType"
:placeholder="$t('page.notifyConfig.form.notifyType')"
clearable
/>
</NFormItem>
</NGi>
<NGi span="m:6 l:2">
<NFormItem :label="$t('page.notifyConfig.notifyAttribute')" path="name">
<NSelect
v-model:value="model.notifyAttribute"
:placeholder="$t('page.notifyConfig.form.notifyAttribute')"
clearable
/>
</NFormItem>
</NGi>
</NGrid>
<NFormItem :label="$t('page.notifyConfig.description')" path="description">
<NInput v-model:value="model.description" :placeholder="$t('page.notifyConfig.form.description')" />
</NFormItem>
</NForm>
</OperateDrawer>
</template>
<style scoped></style>

View File

@ -0,0 +1,71 @@
<script setup lang="ts">
import { ref } from 'vue';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useNaiveForm } from '@/hooks/common/form';
defineOptions({
name: 'NotifyConfigSearch'
});
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
const emit = defineEmits<Emits>();
const appStore = useAppStore();
const title = ref(appStore.isMobile ? $t('common.search') : undefined);
const { formRef, validate, restoreValidation } = useNaiveForm();
const model = defineModel<Api.NotifyConfig.NotifySearchParams>('model', { required: true });
async function reset() {
await restoreValidation();
emit('reset');
}
async function search() {
await validate();
emit('search');
}
</script>
<template>
<NCard :title="title" :bordered="false" size="small" class="card-wrapper">
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80" :show-feedback="appStore.isMobile">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyConfig.groupName')" path="userName" class="pr-24px">
<NSelect v-model:value="model.groupName" :placeholder="$t('page.notifyConfig.groupName')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyConfig.notifyStatus')" path="userName" class="pr-24px">
<NSelect v-model:value="model.notifyStatus" :placeholder="$t('page.notifyConfig.notifyStatus')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyConfig.notifyScene')" path="userName" class="pr-24px">
<NSelect v-model:value="model.notifyScene" :placeholder="$t('page.notifyConfig.notifyScene')" clearable />
</NFormItemGi>
<NFormItemGi span="24 m:12" class="pr-24px">
<NSpace class="w-full" justify="end">
<NButton @click="reset">
<template #icon>
<icon-ic-round-refresh class="text-icon" />
</template>
{{ $t('common.reset') }}
</NButton>
<NButton type="primary" ghost @click="search">
<template #icon>
<icon-ic-round-search class="text-icon" />
</template>
{{ $t('common.search') }}
</NButton>
</NSpace>
</NFormItemGi>
</NGrid>
</NForm>
</NCard>
</template>
<style scoped></style>