first commit
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
<!--用户选择框-->
|
||||
<template>
|
||||
<div>
|
||||
<BasicModal v-bind="$attrs" @register="register" title="数据对比" width="50%" destroyOnClose :showOkBtn="false">
|
||||
<a-row :gutter="6" v-if="dataVersionList" style="margin-left: 2px">
|
||||
<span style="margin-top: 5px; margin-right: 3px; margin-left: 4px">版本对比:</span>
|
||||
<a-select placeholder="版本号" @change="handleChange1" v-model:value="params.dataId1">
|
||||
<a-select-option v-for="(log, logindex) in dataVersionList" :key="log.value" :value="log.value">
|
||||
{{ log.text }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-select placeholder="版本号" @change="handleChange2" style="padding-left: 10px" v-model:value="params.dataId2">
|
||||
<a-select-option v-for="(log, logindex) in dataVersionList" :key="log.value" :value="log.value">
|
||||
{{ log.text }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
v-bind="getBindValue"
|
||||
:rowClassName="setDataCss"
|
||||
:striped="false"
|
||||
:showIndexColumn="false"
|
||||
:pagination="false"
|
||||
:canResize="false"
|
||||
:bordered="true"
|
||||
:dataSource="dataSource"
|
||||
:searchInfo="searchInfo"
|
||||
v-if="isUpdate"
|
||||
>
|
||||
<template #dataVersionTitle1="{ record }"> <Icon icon="icon-park-outline:grinning-face" /> 版本:{{ dataVersion1Num }} </template>
|
||||
<template #dataVersionTitle2="{ record }"> <Icon icon="icon-park-outline:grinning-face" /> 版本:{{ dataVersion2Num }} </template>
|
||||
<template #avatarslot="{ record }">
|
||||
<div class="anty-img-wrap" v-if="record.dataVersion1 != record.dataVersion2">
|
||||
<Icon icon="mdi:arrow-right-bold" style="color: red"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, unref, ref, reactive, watch } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { queryCompareList, queryDataVerList } from './datalog.api';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { selectProps } from '/@/components/Form/src/jeecg/props/props';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataLogCompareModal',
|
||||
components: {
|
||||
//此处需要异步加载BasicTable
|
||||
BasicModal,
|
||||
BasicTable: createAsyncComponent(() => import('/@/components/Table/src/BasicTable.vue'), { loading: true }),
|
||||
},
|
||||
props: {
|
||||
...selectProps,
|
||||
},
|
||||
emits: ['register', 'btnOk'],
|
||||
setup(props, { emit, refs }) {
|
||||
const { createMessage } = useMessage();
|
||||
const attrs = useAttrs();
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs));
|
||||
const dataSource = ref([]);
|
||||
const dataVersion1Num = ref('');
|
||||
const dataVersion2Num = ref('');
|
||||
const isUpdate = ref(true);
|
||||
const searchInfo = {};
|
||||
const dataId1 = ref('');
|
||||
const dataId2 = ref('');
|
||||
const dataId = ref('');
|
||||
const dataTable1 = ref('');
|
||||
const dataID3 = ref('');
|
||||
const dataTable = ref('');
|
||||
const confirmLoading = ref(false);
|
||||
const dataVersionList = ref([]);
|
||||
let params = reactive({ dataId1: '', dataId2: '' });
|
||||
let dataLog = reactive({});
|
||||
const [register, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
let checkedRows = data.selectedRows;
|
||||
dataTable.value = checkedRows[0].dataTable;
|
||||
dataId.value = checkedRows[0].dataId;
|
||||
dataId1.value = checkedRows[0].id;
|
||||
dataId2.value = checkedRows[1].id;
|
||||
params.dataId1 = dataId1.value;
|
||||
params.dataId2 = dataId2.value;
|
||||
await initDataVersionList();
|
||||
await initTableData();
|
||||
}
|
||||
});
|
||||
|
||||
//定义表格列
|
||||
const columns = [
|
||||
{
|
||||
title: '字段名',
|
||||
dataIndex: 'code',
|
||||
width: 20,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
dataIndex: 'dataVersion1',
|
||||
align: 'left',
|
||||
width: 60,
|
||||
slots: { title: 'dataVersionTitle1' },
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'imgshow',
|
||||
align: 'center',
|
||||
slots: { customRender: 'avatarslot' },
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
dataIndex: 'dataVersion2',
|
||||
width: 60,
|
||||
filters: [],
|
||||
filterMultiple: false,
|
||||
slots: { title: 'dataVersionTitle2' },
|
||||
},
|
||||
];
|
||||
async function initTableData() {
|
||||
console.info('params', params);
|
||||
queryCompareList(unref(params)).then((res) => {
|
||||
console.info('test', res);
|
||||
dataVersion1Num.value = res[0].dataVersion;
|
||||
dataVersion2Num.value = res[1].dataVersion;
|
||||
let json1 = JSON.parse(res[0].dataContent);
|
||||
let json2 = JSON.parse(res[1].dataContent);
|
||||
let data = [];
|
||||
for (var item1 in json1) {
|
||||
for (var item2 in json2) {
|
||||
if (item1 == item2) {
|
||||
data.push({
|
||||
code: item1,
|
||||
imgshow: '',
|
||||
dataVersion1: json1[item1],
|
||||
dataVersion2: json2[item2],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
dataSource.value = data;
|
||||
});
|
||||
}
|
||||
function handleChange1(value) {
|
||||
if (params.dataId2 == value) {
|
||||
createMessage.warning('相同版本号不能比较');
|
||||
return;
|
||||
}
|
||||
params.dataId1 = value;
|
||||
initTableData();
|
||||
}
|
||||
function handleChange2(value) {
|
||||
if (params.dataId1 == value) {
|
||||
createMessage.warning('相同版本号不能比较');
|
||||
return;
|
||||
}
|
||||
params.dataId2 = value;
|
||||
initTableData();
|
||||
}
|
||||
function setDataCss(record) {
|
||||
let className = 'trcolor';
|
||||
const dataVersion1 = record.dataVersion1;
|
||||
const dataVersion2 = record.dataVersion2;
|
||||
if (dataVersion1 != dataVersion2) {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
async function initDataVersionList() {
|
||||
queryDataVerList({ dataTable: dataTable.value, dataId: dataId.value }).then((res) => {
|
||||
dataVersionList.value = res.map((value, key, arr) => {
|
||||
let item = {};
|
||||
item['text'] = value['dataVersion'];
|
||||
item['value'] = value['id'];
|
||||
return item;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
//config,
|
||||
searchInfo,
|
||||
dataSource,
|
||||
setDataCss,
|
||||
isUpdate,
|
||||
dataVersionList,
|
||||
dataVersion1Num,
|
||||
dataVersion2Num,
|
||||
queryCompareList,
|
||||
initDataVersionList,
|
||||
register,
|
||||
handleChange1,
|
||||
handleChange2,
|
||||
params,
|
||||
getBindValue,
|
||||
columns,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.anty-img-wrap {
|
||||
height: 25px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.anty-img-wrap > img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.marginCss {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" title="数据对比窗口" :minHeight="300" width="800px" @ok="handleSubmit">
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form @submit="handleSubmit" :form="form" class="form">
|
||||
<a-row class="form-row" :gutter="24">
|
||||
<a-col :md="12" :sm="8">
|
||||
<a-form-item label="数据库表名" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }" name="dataTable">
|
||||
<a-input placeholder="请输入数据库表名" v-model:value="dataTable" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="12" :sm="8">
|
||||
<a-form-item label="数据ID" :label-col="{ span: 5 }" :wrapper-col="{ span: 15 }">
|
||||
<a-input placeholder="请输入数据ID" v-model:value="dataId" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row class="form-row" :gutter="24">
|
||||
<a-col :md="12" :sm="8">
|
||||
<a-form-item label="版本号1" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }">
|
||||
<a-select placeholder="请选择版本号" @change="handleChange1" v-model:value="dataVersion1">
|
||||
<a-select-option v-for="(log, logindex) in dataVersionList" :key="logindex.toString()" :value="log.id">
|
||||
{{ log.dataVersion }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="8">
|
||||
<a-form-item label="版本号2" :label-col="{ span: 5 }" :wrapper-col="{ span: 15 }">
|
||||
<a-select placeholder="请选择版本号" @change="handleChange2" v-model:value="dataVersion2">
|
||||
<a-select-option v-for="(log, logindex) in dataVersionList" :key="logindex.toString()" :value="log.id">
|
||||
{{ log.dataVersion }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</BasicModal>
|
||||
<DataLogCompareModal @register="registerDataLogCompareModal"></DataLogCompareModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
|
||||
import { queryDataVerList } from './datalog.api';
|
||||
import { reactive, ref, unref } from 'vue';
|
||||
import DataLogCompareModal from './DataLogCompareModal.vue';
|
||||
const dataId1 = ref('');
|
||||
const dataId2 = ref('');
|
||||
const dataId = ref('');
|
||||
const dataTable1 = ref('');
|
||||
const dataID3 = ref('');
|
||||
const dataTable = ref('');
|
||||
const confirmLoading = ref(false);
|
||||
const isUpdate = ref(true);
|
||||
const dataVersionList = ref([]);
|
||||
let dataLog = reactive({});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
let checkedRows = data.selectedRows;
|
||||
dataTable.value = checkedRows[0].dataTable;
|
||||
dataId.value = checkedRows[0].dataId;
|
||||
dataId1.value = checkedRows[0].id;
|
||||
dataId2.value = checkedRows[1].id;
|
||||
initDataVersionList();
|
||||
}
|
||||
});
|
||||
|
||||
const [registerDataLogCompareModal, { openModal }] = useModal();
|
||||
|
||||
function handleChange1(value) {
|
||||
dataId1.value = value;
|
||||
}
|
||||
|
||||
function handleChange2(value) {
|
||||
dataId2.value = value;
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
let result = { dataId1: dataId1.value, dataId2: dataId2.value };
|
||||
openModal(true, {
|
||||
result,
|
||||
isUpdate: true,
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function initDataVersionList() {
|
||||
queryDataVerList({ dataTable: dataTable.value, dataId: dataId.value }).then((res) => {
|
||||
dataVersionList.value = res.map((value, key, arr) => {
|
||||
arr['label'] = value;
|
||||
return arr;
|
||||
});
|
||||
console.info(dataVersionList.value);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.detail-iframe {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 600px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/dataLog/list',
|
||||
queryDataVerList = '/sys/dataLog/queryDataVerList',
|
||||
queryCompareList = '/sys/dataLog/queryCompareList',
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据日志列表
|
||||
* @param params
|
||||
*/
|
||||
export const getDataLogList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询数据日志列表
|
||||
* @param params
|
||||
*/
|
||||
export const queryDataVerList = (params) => {
|
||||
return defHttp.get({ url: Api.queryDataVerList, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询对比数据
|
||||
* @param params
|
||||
*/
|
||||
export const queryCompareList = (params) => {
|
||||
return defHttp.get({ url: Api.queryCompareList, params });
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '表名',
|
||||
dataIndex: 'dataTable',
|
||||
width: 150,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '数据ID',
|
||||
dataIndex: 'dataId',
|
||||
width: 350,
|
||||
},
|
||||
{
|
||||
title: '版本号',
|
||||
dataIndex: 'dataVersion',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '数据内容',
|
||||
dataIndex: 'dataContent',
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
dataIndex: 'createBy',
|
||||
sorter: true,
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'dataTable',
|
||||
label: '表名',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'dataId',
|
||||
label: '数据ID',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleCompare" style="margin-right: 5px">数据比较</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<DataLogCompareModal @register="registerModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-datalog" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import DataLogCompareModal from './DataLogCompareModal.vue';
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
import { getDataLogList } from './datalog.api';
|
||||
import { columns, searchFormSchema } from './datalog.data';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
const { createMessage } = useMessage();
|
||||
const checkedRows = ref<Array<object | number>>([]);
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
designScope: 'datalog-template',
|
||||
tableProps: {
|
||||
title: '数据日志列表',
|
||||
api: getDataLogList,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
labelWidth: 120,
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
actionColumn: false,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
|
||||
|
||||
function handleCompare() {
|
||||
let obj = selectedRows.value;
|
||||
console.info('sfsfsf', obj);
|
||||
if (!obj || obj.length != 2) {
|
||||
createMessage.warning('请选择两条数据!');
|
||||
return false;
|
||||
}
|
||||
if (obj[0].dataId != obj[1].dataId) {
|
||||
createMessage.warning('请选择相同的数据库表和数据ID进行比较!');
|
||||
return false;
|
||||
}
|
||||
openModal(true, {
|
||||
selectedRows,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="40%">
|
||||
<BasicForm @register="registerForm">
|
||||
<template #pwd="{ model, field }">
|
||||
<a-row :gutter="8">
|
||||
<a-col :sm="15" :md="16" :lg="17" :xl="19">
|
||||
<a-input-password v-model:value="model[field]" placeholder="请输入密码" />
|
||||
</a-col>
|
||||
<a-col :sm="9" :md="7" :lg="7" :xl="5">
|
||||
<a-button type="primary" style="width: 100%" @click="handleTest">测试</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from './datasource.data';
|
||||
import { saveOrUpdateDataSource, getDataSourceById, testConnection } from './datasource.api';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
// Emits声明
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
//表单配置
|
||||
const [registerForm, { getFieldsValue, resetFields, validateFields, setFieldsValue, validate }] = useForm({
|
||||
// labelWidth: 150,
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//获取详情
|
||||
data.record = await getDataSourceById({ id: data.record.id });
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增数据源' : '编辑数据源'));
|
||||
|
||||
async function handleTest() {
|
||||
let keys = ['dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword'];
|
||||
// 获取以上字段的值,并清除校验状态
|
||||
let fieldsValues = getFieldsValue(keys);
|
||||
let setFields = {};
|
||||
keys.forEach((key) => (setFields[key] = { value: fieldsValues[key], errors: null }));
|
||||
await validateFields(keys).then((values) => {
|
||||
let loading = createMessage.loading('连接中....', 0);
|
||||
testConnection(values)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
createMessage.success('连接成功');
|
||||
}
|
||||
})
|
||||
.catch((error) => {})
|
||||
.finally(() => loading());
|
||||
});
|
||||
}
|
||||
|
||||
//表单提交事件
|
||||
async function handleSubmit(v) {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateDataSource(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/dataSource/list',
|
||||
save = '/sys/dataSource/add',
|
||||
edit = '/sys/dataSource/edit',
|
||||
get = '/sys/dataSource/queryById',
|
||||
delete = '/sys/dataSource/delete',
|
||||
testConnection = '/online/cgreport/api/testConnection',
|
||||
deleteBatch = '/sys/dataSource/deleteBatch',
|
||||
exportXlsUrl = 'sys/dataSource/exportXls',
|
||||
importExcelUrl = 'sys/dataSource/importExcel',
|
||||
}
|
||||
/**
|
||||
* 导出api
|
||||
*/
|
||||
export const getExportUrl = Api.exportXlsUrl;
|
||||
/**
|
||||
* 导入api
|
||||
*/
|
||||
export const getImportUrl = Api.importExcelUrl;
|
||||
|
||||
/**
|
||||
* 查询数据源列表
|
||||
* @param params
|
||||
*/
|
||||
export const getDataSourceList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更新数据源
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateDataSource = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询数据源详情
|
||||
* @param params
|
||||
*/
|
||||
export const getDataSourceById = (params) => {
|
||||
return defHttp.get({ url: Api.get, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除数据源
|
||||
* @param params
|
||||
*/
|
||||
export const deleteDataSource = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
* @param params
|
||||
*/
|
||||
export const testConnection = (params) => {
|
||||
return defHttp.post({ url: Api.testConnection, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除数据源
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteDataSource = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,185 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
const dbDriverMap = {
|
||||
// MySQL 数据库
|
||||
'1': { dbDriver: 'com.mysql.jdbc.Driver' },
|
||||
//MySQL5.7+ 数据库
|
||||
'4': { dbDriver: 'com.mysql.cj.jdbc.Driver' },
|
||||
// Oracle
|
||||
'2': { dbDriver: 'oracle.jdbc.OracleDriver' },
|
||||
// SQLServer 数据库
|
||||
'3': { dbDriver: 'com.microsoft.sqlserver.jdbc.SQLServerDriver' },
|
||||
// marialDB 数据库
|
||||
'5': { dbDriver: 'org.mariadb.jdbc.Driver' },
|
||||
// postgresql 数据库
|
||||
'6': { dbDriver: 'org.postgresql.Driver' },
|
||||
// 达梦 数据库
|
||||
'7': { dbDriver: 'dm.jdbc.driver.DmDriver' },
|
||||
// 人大金仓 数据库
|
||||
'8': { dbDriver: 'com.kingbase8.Driver' },
|
||||
// 神通 数据库
|
||||
'9': { dbDriver: 'com.oscar.Driver' },
|
||||
// SQLite 数据库
|
||||
'10': { dbDriver: 'org.sqlite.JDBC' },
|
||||
// DB2 数据库
|
||||
'11': { dbDriver: 'com.ibm.db2.jcc.DB2Driver' },
|
||||
// Hsqldb 数据库
|
||||
'12': { dbDriver: 'org.hsqldb.jdbc.JDBCDriver' },
|
||||
// Derby 数据库
|
||||
'13': { dbDriver: 'org.apache.derby.jdbc.ClientDriver' },
|
||||
// H2 数据库
|
||||
'14': { dbDriver: 'org.h2.Driver' },
|
||||
// 其他数据库
|
||||
'15': { dbDriver: '' },
|
||||
};
|
||||
const dbUrlMap = {
|
||||
// MySQL 数据库
|
||||
'1': { dbUrl: 'jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false' },
|
||||
//MySQL5.7+ 数据库
|
||||
'4': {
|
||||
dbUrl:
|
||||
'jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai',
|
||||
},
|
||||
// Oracle
|
||||
'2': { dbUrl: 'jdbc:oracle:thin:@127.0.0.1:1521:ORCL' },
|
||||
// SQLServer 数据库
|
||||
'3': { dbUrl: 'jdbc:sqlserver://127.0.0.1:1433;SelectMethod=cursor;DatabaseName=jeecgboot' },
|
||||
// Mariadb 数据库
|
||||
'5': { dbUrl: 'jdbc:mariadb://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useSSL=false' },
|
||||
// Postgresql 数据库
|
||||
'6': { dbUrl: 'jdbc:postgresql://127.0.0.1:5432/jeecg-boot' },
|
||||
// 达梦 数据库
|
||||
'7': { dbUrl: 'jdbc:dm://127.0.0.1:5236/?jeecg-boot&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8' },
|
||||
// 人大金仓 数据库
|
||||
'8': { dbUrl: 'jdbc:kingbase8://127.0.0.1:54321/jeecg-boot' },
|
||||
// 神通 数据库
|
||||
'9': { dbUrl: 'jdbc:oscar://192.168.1.125:2003/jeecg-boot' },
|
||||
// SQLite 数据库
|
||||
'10': { dbUrl: 'jdbc:sqlite://opt/test.db' },
|
||||
// DB2 数据库
|
||||
'11': { dbUrl: 'jdbc:db2://127.0.0.1:50000/jeecg-boot' },
|
||||
// Hsqldb 数据库
|
||||
'12': { dbUrl: 'jdbc:hsqldb:hsql://127.0.0.1/jeecg-boot' },
|
||||
// Derby 数据库
|
||||
'13': { dbUrl: 'jdbc:derby://127.0.0.1:1527/jeecg-boot' },
|
||||
// H2 数据库
|
||||
'14': { dbUrl: 'jdbc:h2:tcp://127.0.0.1:8082/jeecg-boot' },
|
||||
// 其他数据库
|
||||
'15': { dbUrl: '' },
|
||||
};
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '数据源名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '数据库类型',
|
||||
dataIndex: 'dbType_dictText',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '驱动类',
|
||||
dataIndex: 'dbDriver',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '数据源地址',
|
||||
dataIndex: 'dbUrl',
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'dbUsername',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
label: '数据源名称',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'dbType',
|
||||
label: '数据库类型',
|
||||
component: 'JDictSelectTag',
|
||||
colProps: { span: 8 },
|
||||
componentProps: () => {
|
||||
return {
|
||||
dictCode: 'database_type',
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'id',
|
||||
label: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
label: '数据源编码',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
dynamicDisabled: ({ values }) => {
|
||||
return !!values.id;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: '数据源名称',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'dbType',
|
||||
label: '数据库类型',
|
||||
component: 'JDictSelectTag',
|
||||
required: true,
|
||||
componentProps: ({ formModel }) => {
|
||||
return {
|
||||
dictCode: 'database_type',
|
||||
onChange: (e: any) => {
|
||||
formModel = Object.assign(formModel, dbDriverMap[e], dbUrlMap[e]);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'dbDriver',
|
||||
label: '驱动类',
|
||||
required: true,
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
field: 'dbUrl',
|
||||
label: '数据源地址',
|
||||
required: true,
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
field: 'dbUsername',
|
||||
label: '用户名',
|
||||
required: true,
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
field: 'dbPassword',
|
||||
label: '密码',
|
||||
required: true,
|
||||
component: 'InputPassword',
|
||||
slot: 'pwd',
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
label: '备注',
|
||||
component: 'InputTextArea',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleAdd" style="margin-right: 5px">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<DataSourceModal @register="registerModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-datasource" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { getDataSourceList, deleteDataSource, batchDeleteDataSource, getExportUrl, getImportUrl } from './datasource.api';
|
||||
import { columns, searchFormSchema } from './datasource.data';
|
||||
import DataSourceModal from './DataSourceModal.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
const { createMessage } = useMessage();
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, onImportXls, onExportXls } = useListPage({
|
||||
designScope: 'quartz-template',
|
||||
tableProps: {
|
||||
title: '任务列表',
|
||||
api: getDataSourceList,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
labelWidth: 120,
|
||||
schemas: searchFormSchema,
|
||||
fieldMapToTime: [['fieldTime', ['beginDate', 'endDate'], 'YYYY-MM-DD HH:mm:ss']],
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: '数据源列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConifg: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteDataSource({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteDataSource({ ids: selectedRowKeys.value }, reload);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<Skeleton v-if="spinning" active />
|
||||
<div v-else>
|
||||
<a-row>
|
||||
<template v-if="diskInfo && diskInfo.length > 0">
|
||||
<a-col :span="6" v-for="(item, index) in diskInfo" :key="'diskInfo' + index">
|
||||
<gauge :data="item"></gauge>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { queryDiskInfo } from './disk.api';
|
||||
import gauge from './gauge.vue';
|
||||
|
||||
const diskInfo = ref([]);
|
||||
const spinning = ref(true);
|
||||
|
||||
function loadRedisInfo() {
|
||||
queryDiskInfo()
|
||||
.then((res) => {
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
// 当前算法算的是磁盘的已使用空间
|
||||
res[i].restPPT = 100 - parseInt(String((res[i].rest / res[i].max) * 100));
|
||||
}
|
||||
diskInfo.value = res;
|
||||
})
|
||||
.finally(() => (spinning.value = false));
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadRedisInfo();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
queryDiskInfo = '/sys/actuator/redis/queryDiskInfo',
|
||||
}
|
||||
|
||||
/**
|
||||
* 详细信息
|
||||
*/
|
||||
export const queryDiskInfo = () => {
|
||||
return defHttp.get({ url: Api.queryDiskInfo }, { successMessageMode: 'none' });
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<div ref="chartRef" style="width: 100%; height: 400px"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, Ref } from 'vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useECharts } from '/@/hooks/web/useECharts';
|
||||
import { GaugeChart } from 'echarts/charts';
|
||||
|
||||
const props = defineProps({ data: {} });
|
||||
const dataSource = ref([]);
|
||||
const chartRef = ref<HTMLDivElement | null>(null);
|
||||
const { setOptions, echarts } = useECharts(chartRef as Ref<HTMLDivElement>);
|
||||
const loading = ref(false);
|
||||
const { createMessage } = useMessage();
|
||||
const option = reactive({
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
progress: {
|
||||
show: true,
|
||||
width: 18,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
width: 18,
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
},
|
||||
splitLine: {
|
||||
length: 15,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
distance: 25,
|
||||
color: '#999',
|
||||
fontSize: 15,
|
||||
},
|
||||
anchor: {
|
||||
show: true,
|
||||
showAbove: true,
|
||||
size: 25,
|
||||
itemStyle: {
|
||||
borderWidth: 10,
|
||||
},
|
||||
},
|
||||
title: {},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
fontSize: 50,
|
||||
formatter: '{value}%',
|
||||
offsetCenter: [0, '80%'],
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 70,
|
||||
name: '本地磁盘',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function initCharts() {
|
||||
option.series[0].data[0].name = props.data.name;
|
||||
option.series[0].data[0].value = props.data.restPPT;
|
||||
setOptions(option);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.info(props.data);
|
||||
echarts.use(GaugeChart);
|
||||
initCharts();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<BasicTable :ellipsis="true" @register="registerTable" :searchInfo="searchInfo" :columns="logColumns" :expand-column-width="16">
|
||||
<template #tableTitle>
|
||||
<a-tabs defaultActiveKey="4" @change="tabChange" size="small">
|
||||
<a-tab-pane tab="异常日志" key="4"></a-tab-pane>
|
||||
<a-tab-pane tab="登录日志" key="1"></a-tab-pane>
|
||||
<a-tab-pane tab="操作日志" key="2"></a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div v-if="searchInfo.logType == 2">
|
||||
<div style="margin-bottom: 5px">
|
||||
<a-badge status="success" style="vertical-align: middle" />
|
||||
<span style="vertical-align: middle">请求方法:{{ record.method }}</span></div
|
||||
>
|
||||
<div>
|
||||
<a-badge status="processing" style="vertical-align: middle" />
|
||||
<span style="vertical-align: middle">请求参数:{{ record.requestParam }}</span></div
|
||||
>
|
||||
</div>
|
||||
<div v-if="searchInfo.logType == 4">
|
||||
<div style="margin-bottom: 5px">
|
||||
<a-badge status="success" style="vertical-align: middle" />
|
||||
<span class="error-box" style="vertical-align: middle">异常堆栈:{{ record.requestParam }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-log" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { getLogList } from './log.api';
|
||||
import {
|
||||
columns,
|
||||
searchFormSchema,
|
||||
operationLogColumn,
|
||||
operationSearchFormSchema,
|
||||
exceptionColumns
|
||||
} from './log.data';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
const { createMessage } = useMessage();
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
|
||||
const logColumns = ref<any>(exceptionColumns);
|
||||
const searchSchema = ref<any>(searchFormSchema);
|
||||
const searchInfo = { logType: '4' };
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
designScope: 'user-list',
|
||||
tableProps: {
|
||||
title: '日志列表',
|
||||
api: getLogList,
|
||||
expandRowByClick: true,
|
||||
showActionColumn: false,
|
||||
rowSelection: {
|
||||
columnWidth: 20,
|
||||
},
|
||||
formConfig: {
|
||||
schemas: searchSchema,
|
||||
fieldMapToTime: [['fieldTime', ['createTime_begin', 'createTime_end'], 'YYYY-MM-DD']],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }] = tableContext;
|
||||
|
||||
// 日志类型
|
||||
function tabChange(key) {
|
||||
searchInfo.logType = key;
|
||||
//update-begin---author:wangshuai ---date:20220506 for:[VUEN-943]vue3日志管理列表翻译不对------------
|
||||
if (key == '2') {
|
||||
logColumns.value = operationLogColumn;
|
||||
searchSchema.value = operationSearchFormSchema;
|
||||
}else if(key == '4'){
|
||||
searchSchema.value = searchFormSchema;
|
||||
logColumns.value = exceptionColumns;
|
||||
} else {
|
||||
searchSchema.value = searchFormSchema;
|
||||
logColumns.value = columns;
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20220506 for:[VUEN-943]vue3日志管理列表翻译不对--------------
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择事件
|
||||
*/
|
||||
function onSelectChange(selectedRowKeys: (string | number)[]) {
|
||||
checkedKeys.value = selectedRowKeys;
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.error-box {
|
||||
white-space: break-spaces;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/log/list',
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日志列表
|
||||
* @param params
|
||||
*/
|
||||
export const getLogList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '日志内容',
|
||||
dataIndex: 'logContent',
|
||||
width: 100,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '操作人ID',
|
||||
dataIndex: 'userid',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'username',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'ip',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '耗时(毫秒)',
|
||||
dataIndex: 'costTime',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '客户端类型',
|
||||
dataIndex: 'clientType_dictText',
|
||||
width: 60,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 操作日志需要操作类型
|
||||
*/
|
||||
export const operationLogColumn: BasicColumn[] = [
|
||||
...columns,
|
||||
{
|
||||
title: '操作类型',
|
||||
dataIndex: 'operateType_dictText',
|
||||
width: 40,
|
||||
},
|
||||
];
|
||||
|
||||
export const exceptionColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '异常标题',
|
||||
dataIndex: 'logContent',
|
||||
width: 100,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '请求地址',
|
||||
dataIndex: 'requestUrl',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '请求参数',
|
||||
dataIndex: 'method',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'username',
|
||||
width: 60,
|
||||
customRender: ({ record }) => {
|
||||
let pname = record.username;
|
||||
let pid = record.userid;
|
||||
if(!pname && !pid){
|
||||
return "";
|
||||
}
|
||||
return pname + " (账号: "+ pid + " )";
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'ip',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '客户端类型',
|
||||
dataIndex: 'clientType_dictText',
|
||||
width: 60,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'keyWord',
|
||||
label: '搜索日志',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'fieldTime',
|
||||
component: 'RangePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
valueType: 'Date',
|
||||
},
|
||||
colProps: {
|
||||
span: 6,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const operationSearchFormSchema: FormSchema[] = [
|
||||
...searchFormSchema,
|
||||
{
|
||||
field: 'operateType',
|
||||
label: '操作类型',
|
||||
component: 'JDictSelectTag',
|
||||
colProps: { span: 4 },
|
||||
componentProps: {
|
||||
dictCode: 'operate_type',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" title="查看详情" :minHeight="600" :showCancelBtn="false" :showOkBtn="false" :height="88" :destroyOnClose="true">
|
||||
<a-card class="daily-article">
|
||||
<a-card-meta :title="content.titile" :description="'发布人:' + content.sender + ' 发布时间: ' + content.sendTime"> </a-card-meta>
|
||||
<a-divider />
|
||||
<div v-html="content.msgContent" class="article-content"></div>
|
||||
<div>
|
||||
<a-button v-if="hasHref" @click="jumpToHandlePage">前往办理<ArrowRightOutlined /></a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { ArrowRightOutlined } from '@ant-design/icons-vue';
|
||||
import { useRouter } from 'vue-router'
|
||||
import xss from 'xss'
|
||||
import { options } from './XssWhiteList'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
import { ref, unref } from 'vue';
|
||||
const isUpdate = ref(true);
|
||||
const content = ref({});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//data.record.msgContent = '<p>2323</p><input onmouseover=alert(1)>xss test';
|
||||
//update-begin-author:taoyan date:2022-7-14 for: VUEN-1702 【禁止问题】sql注入漏洞
|
||||
if(data.record.msgContent){
|
||||
//update-begin---author:wangshuai---date:2023-11-15---for:【QQYUN-7049】3.6.0版本 通知公告中发布的富文本消息,在我的消息中查看没有样式---
|
||||
data.record.msgContent = xss(data.record.msgContent,options);
|
||||
//update-end---author:wangshuai---date:2023-11-15---for:【QQYUN-7049】3.6.0版本 通知公告中发布的富文本消息,在我的消息中查看没有样式---
|
||||
}
|
||||
//update-end-author:taoyan date:2022-7-14 for: VUEN-1702 【禁止问题】sql注入漏洞
|
||||
content.value = data.record;
|
||||
showHrefButton();
|
||||
}
|
||||
});
|
||||
|
||||
const hasHref = ref(false)
|
||||
//查看消息详情可以跳转
|
||||
function showHrefButton(){
|
||||
if(content.value.busId){
|
||||
hasHref.value = true;
|
||||
}
|
||||
}
|
||||
//跳转至办理页面
|
||||
function jumpToHandlePage(){
|
||||
let temp:any = content.value
|
||||
if(temp.busId){
|
||||
//这个busId是 任务ID
|
||||
let jsonStr = temp.msgAbstract;
|
||||
let query = {};
|
||||
try {
|
||||
if(jsonStr){
|
||||
let temp = JSON.parse(jsonStr)
|
||||
if(temp){
|
||||
Object.keys(temp).map(k=>{
|
||||
query[k] = temp[k]
|
||||
});
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
console.log('参数解析异常', e)
|
||||
}
|
||||
|
||||
console.log('query', query, jsonStr)
|
||||
console.log('busId', temp.busId)
|
||||
|
||||
if(Object.keys(query).length>0){
|
||||
// taskId taskDefKey procInsId
|
||||
router.push({ path: '/task/handle/' + temp.busId, query: query })
|
||||
}else{
|
||||
router.push({ path: '/task/handle/' + temp.busId })
|
||||
}
|
||||
}
|
||||
closeModal();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.detail-iframe {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 600px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<component :is="currentModal" :formData="formData" v-model:visible="modalVisible"></component>
|
||||
</template>
|
||||
<script setup lang="ts" name="dynamic-notice">
|
||||
import { ref, shallowRef, ComponentOptions, nextTick, defineAsyncComponent } from 'vue';
|
||||
const props = defineProps({
|
||||
path: { type: String, default: '' },
|
||||
formData: { type: Object, default: {} },
|
||||
});
|
||||
const modalVisible = ref<Boolean>(false);
|
||||
const currentModal = shallowRef<Nullable<ComponentOptions>>(null);
|
||||
const formData = ref<any>(props.formData);
|
||||
|
||||
const componentType = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 跟换组件和传值事件
|
||||
*/
|
||||
function detail() {
|
||||
setTimeout(() => {
|
||||
if (props.path) {
|
||||
nextTick(() => {
|
||||
currentModal.value = componentType[props.path];
|
||||
formData.value = props.formData;
|
||||
modalVisible.value = true;
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
detail,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
//xss攻击白名单列表
|
||||
export const options = {
|
||||
whiteList: {
|
||||
h1: ['style'],
|
||||
h2: ['style'],
|
||||
h3: ['style'],
|
||||
h4: ['style'],
|
||||
h5: ['style'],
|
||||
h6: ['style'],
|
||||
hr: ['style'],
|
||||
span: ['style'],
|
||||
strong: ['style'],
|
||||
b: ['style'],
|
||||
i: ['style'],
|
||||
br: [],
|
||||
p: ['style'],
|
||||
pre: ['style'],
|
||||
code: ['style'],
|
||||
a: ['style', 'target', 'href', 'title', 'rel'],
|
||||
img: ['style', 'src', 'title','width','height'],
|
||||
div: ['style'],
|
||||
table: ['style', 'width', 'border', 'height'],
|
||||
tr: ['style'],
|
||||
td: ['style', 'width', 'colspan'],
|
||||
th: ['style', 'width', 'colspan'],
|
||||
tbody: ['style'],
|
||||
ul: ['style'],
|
||||
li: ['style'],
|
||||
ol: ['style'],
|
||||
dl: ['style'],
|
||||
dt: ['style'],
|
||||
em: ['style'],
|
||||
cite: ['style'],
|
||||
section: ['style'],
|
||||
header: ['style'],
|
||||
footer: ['style'],
|
||||
blockquote: ['style'],
|
||||
audio: ['autoplay', 'controls', 'loop', 'preload', 'src'],
|
||||
video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width'],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :searchInfo="searchInfo">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" @click="handlerReadAllMsg">全部标注已读</a-button>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<DetailModal @register="register" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-mynews" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import DetailModal from './DetailModal.vue';
|
||||
import { getMyNewsList, editCementSend, syncNotic, readAllMsg, getOne } from './mynews.api';
|
||||
import { columns, searchFormSchema } from './mynews.data';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
const glob = useGlobSetting();
|
||||
const { createMessage } = useMessage();
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
const content = ref({});
|
||||
const searchInfo = { logType: '1' };
|
||||
const [register, { openModal: openDetail }] = useModal();
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { getLogList } from '/@/views/monitor/log/log.api';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { useMessageHref } from '/@/views/system/message/components/useSysMessage';
|
||||
const appStore = useAppStore();
|
||||
|
||||
const {goPage} = useMessageHref()
|
||||
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
designScope: 'mynews-list',
|
||||
tableProps: {
|
||||
title: '我的消息',
|
||||
api: getMyNewsList,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
//update-begin---author:wangshuai---date:2024-06-11---for:【TV360X-545】我的消息列表不能通过时间范围查询---
|
||||
fieldMapToTime: [['sendTime', ['sendTimeBegin', 'sendTimeEnd'], 'YYYY-MM-DD']],
|
||||
//update-end---author:wangshuai---date:2024-06-11---for:【TV360X-545】我的消息列表不能通过时间范围查询---
|
||||
},
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload }] = tableContext;
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '查看',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
let anntId = record.anntId;
|
||||
editCementSend({ anntId: anntId }).then((res) => {
|
||||
reload();
|
||||
syncNotic({ anntId: anntId });
|
||||
});
|
||||
const openModalFun = ()=>{
|
||||
openDetail(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
goPage(record, openModalFun);
|
||||
|
||||
}
|
||||
// 日志类型
|
||||
function callback(key) {
|
||||
searchInfo.logType = key;
|
||||
reload();
|
||||
}
|
||||
|
||||
//全部标记已读
|
||||
function handlerReadAllMsg() {
|
||||
readAllMsg({}, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择事件
|
||||
*/
|
||||
function onSelectChange(selectedRowKeys: (string | number)[]) {
|
||||
checkedKeys.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
//update-begin-author:taoyan date:2022-8-23 for: 消息跳转,打开详情表单
|
||||
onMounted(()=>{
|
||||
initHrefModal();
|
||||
});
|
||||
function initHrefModal(){
|
||||
let params = appStore.getMessageHrefParams;
|
||||
if(params){
|
||||
let anntId = params.id;
|
||||
if(anntId){
|
||||
editCementSend({ anntId: anntId }).then(() => {
|
||||
reload();
|
||||
syncNotic({ anntId: anntId });
|
||||
});
|
||||
}
|
||||
let detailId = params.detailId;
|
||||
if(detailId){
|
||||
getOne(detailId).then(data=>{
|
||||
console.log('getOne', data)
|
||||
openDetail(true, {
|
||||
record: data,
|
||||
isUpdate: true,
|
||||
});
|
||||
appStore.setMessageHrefParams('')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-23 for: 消息跳转,打开详情表单
|
||||
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/sysAnnouncementSend/getMyAnnouncementSend',
|
||||
editCementSend = '/sys/sysAnnouncementSend/editByAnntIdAndUserId',
|
||||
readAllMsg = '/sys/sysAnnouncementSend/readAll',
|
||||
syncNotic = '/sys/annountCement/syncNotic',
|
||||
getOne = '/sys/sysAnnouncementSend/getOne',
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询消息列表
|
||||
* @param params
|
||||
*/
|
||||
export const getMyNewsList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户系统消息阅读状态
|
||||
* @param params
|
||||
*/
|
||||
export const editCementSend = (params) => {
|
||||
return defHttp.put({ url: Api.editCementSend, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 一键已读
|
||||
* @param params
|
||||
*/
|
||||
export const readAllMsg = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认操作',
|
||||
content: '是否全部标注已读?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.put({ url: Api.readAllMsg, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 同步消息
|
||||
* @param params
|
||||
*/
|
||||
export const syncNotic = (params) => {
|
||||
return defHttp.get({ url: Api.syncNotic, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据消息发送记录ID获取消息内容
|
||||
* @param sendId
|
||||
*/
|
||||
export const getOne = (sendId) => {
|
||||
return defHttp.get({ url: Api.getOne, params:{sendId} });
|
||||
};
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'titile',
|
||||
width: 100,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '消息类型',
|
||||
dataIndex: 'msgCategory',
|
||||
width: 80,
|
||||
customRender: ({ text }) => {
|
||||
return render.renderDictNative(
|
||||
text,
|
||||
[
|
||||
{ label: '通知公告', value: '1', color: 'blue' },
|
||||
{ label: '系统消息', value: '2' },
|
||||
],
|
||||
true
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '发布人',
|
||||
dataIndex: 'sender',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '发布时间',
|
||||
dataIndex: 'sendTime',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '优先级',
|
||||
dataIndex: 'priority',
|
||||
width: 80,
|
||||
customRender: ({ text }) => {
|
||||
const color = text == 'L' ? 'blue' : text == 'M' ? 'yellow' : 'red';
|
||||
return render.renderTag(render.renderDict(text, 'priority'), color);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '阅读状态',
|
||||
dataIndex: 'readFlag',
|
||||
width: 80,
|
||||
customRender: ({ text }) => {
|
||||
return render.renderDictNative(
|
||||
text,
|
||||
[
|
||||
{ label: '未读', value: '0', color: 'red' },
|
||||
{ label: '已读', value: '1' },
|
||||
],
|
||||
true
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'titile',
|
||||
label: '标题',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
field: 'sender',
|
||||
label: '发布人',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
field: 'sendTime',
|
||||
label: '发布时间',
|
||||
component: 'RangeDate',
|
||||
componentProps: {
|
||||
valueType: 'Date',
|
||||
},
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="40%">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from './quartz.data';
|
||||
import { saveOrUpdateQuartz, getQuartzById } from './quartz.api';
|
||||
import { isJsonObjectString } from '/@/utils/is';
|
||||
// Emits声明
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
// labelWidth: 150,
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
// update-begin--author:liaozhiyang---date:20231017---for:【issues/790】弹窗内文本框不居中问题
|
||||
labelWidth: 100,
|
||||
// update-end--author:liaozhiyang---date:20231017---for:【issues/790】弹窗内文本框不居中问题
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//获取详情
|
||||
//data.record = await getQuartzById({id: data.record.id});
|
||||
try {
|
||||
data.record.paramterType = isJsonObjectString(data?.record?.parameter) ? 'json' : 'string';
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增任务' : '编辑任务'));
|
||||
//表单提交事件
|
||||
async function handleSubmit(v) {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateQuartz(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd" style="margin-right: 5px">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<QuartzModal @register="registerModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-quartz" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { getQuartzList, deleteQuartz, batchDeleteQuartz, executeImmediately, resumeJob, pauseJob, getExportUrl, getImportUrl } from './quartz.api';
|
||||
import { columns, searchFormSchema } from './quartz.data';
|
||||
import QuartzModal from './QuartzModal.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
designScope: 'quartz-template',
|
||||
tableProps: {
|
||||
title: '任务列表',
|
||||
api: getQuartzList,
|
||||
columns: columns,
|
||||
actionColumn: {
|
||||
width: 180,
|
||||
},
|
||||
formConfig: {
|
||||
labelWidth: 120,
|
||||
schemas: searchFormSchema,
|
||||
fieldMapToTime: [['fieldTime', ['beginDate', 'endDate'], 'YYYY-MM-DD HH:mm:ss']],
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: '定时任务列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
|
||||
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '启动',
|
||||
popConfirm: {
|
||||
title: '是否启动选中任务?',
|
||||
confirm: handlerResume.bind(null, record),
|
||||
},
|
||||
ifShow: (_action) => {
|
||||
return record.status == -1;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '停止',
|
||||
popConfirm: {
|
||||
title: '是否暂停选中任务?',
|
||||
confirm: handlerPause.bind(null, record),
|
||||
},
|
||||
ifShow: (_action) => {
|
||||
return record.status == 0;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '立即执行',
|
||||
popConfirm: {
|
||||
title: '是否立即执行任务?',
|
||||
confirm: handlerExecute.bind(null, record),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteQuartz({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即执行
|
||||
*/
|
||||
async function handlerExecute(record) {
|
||||
await executeImmediately({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停
|
||||
*/
|
||||
async function handlerPause(record) {
|
||||
await pauseJob({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动
|
||||
*/
|
||||
async function handlerResume(record) {
|
||||
await resumeJob({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteQuartz({ ids: selectedRowKeys.value }, () => {
|
||||
// -update-begin--author:liaozhiyang---date:20240702---for:【TV360X-1662】菜单管理、定时任务批量删除清空选中
|
||||
reload();
|
||||
selectedRows.value = [];
|
||||
selectedRowKeys.value = [];
|
||||
// -update-end--author:liaozhiyang---date:20240702---for:【TV360X-1662】菜单管理、定时任务批量删除清空选中
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,107 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/quartzJob/list',
|
||||
save = '/sys/quartzJob/add',
|
||||
edit = '/sys/quartzJob/edit',
|
||||
get = '/sys/quartzJob/queryById',
|
||||
pause = '/sys/quartzJob/pause',
|
||||
resume = '/sys/quartzJob/resume',
|
||||
delete = '/sys/quartzJob/delete',
|
||||
exportXlsUrl = '/sys/quartzJob/exportXls',
|
||||
importExcelUrl = '/sys/quartzJob/importExcel',
|
||||
execute = '/sys/quartzJob/execute',
|
||||
deleteBatch = '/sys/quartzJob/deleteBatch',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出api
|
||||
*/
|
||||
export const getExportUrl = Api.exportXlsUrl;
|
||||
/**
|
||||
* 导入api
|
||||
*/
|
||||
export const getImportUrl = Api.importExcelUrl;
|
||||
/**
|
||||
* 查询任务列表
|
||||
* @param params
|
||||
*/
|
||||
export const getQuartzList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更新任务
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateQuartz = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询任务详情
|
||||
* @param params
|
||||
*/
|
||||
export const getQuartzById = (params) => {
|
||||
return defHttp.get({ url: Api.get, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
* @param params
|
||||
*/
|
||||
export const deleteQuartz = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 启动
|
||||
* @param params
|
||||
*/
|
||||
export const resumeJob = (params, handleSuccess) => {
|
||||
return defHttp.get({ url: Api.resume, params }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 暂停
|
||||
* @param params
|
||||
*/
|
||||
export const pauseJob = (params, handleSuccess) => {
|
||||
return defHttp.get({ url: Api.pause, params }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 立即执行
|
||||
* @param params
|
||||
*/
|
||||
export const executeImmediately = (params, handleSuccess) => {
|
||||
return defHttp.get({ url: Api.execute, params }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除任务
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteQuartz = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
import { JCronValidator } from '/@/components/Form';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '任务类名',
|
||||
dataIndex: 'jobClassName',
|
||||
width: 200,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: 'Cron表达式',
|
||||
dataIndex: 'cronExpression',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '参数',
|
||||
dataIndex: 'parameter',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
const color = text == '0' ? 'green' : text == '-1' ? 'red' : 'gray';
|
||||
return render.renderTag(render.renderDict(text, 'quartz_status'), color);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'jobClassName',
|
||||
label: '任务类名',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '任务状态',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'quartz_status',
|
||||
stringToNumber: true,
|
||||
},
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'id',
|
||||
label: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'jobClassName',
|
||||
label: '任务类名',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'cronExpression',
|
||||
label: 'Cron表达式',
|
||||
component: 'JEasyCron',
|
||||
defaultValue: '* * * * * ? *',
|
||||
rules: [{ required: true, message: '请输入Cron表达式' }, { validator: JCronValidator }],
|
||||
},
|
||||
{
|
||||
field: 'paramterType',
|
||||
label: '参数类型',
|
||||
component: 'Select',
|
||||
defaultValue: 'string',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '字符串', value: 'string' },
|
||||
{ label: 'JSON对象', value: 'json' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'parameter',
|
||||
label: '参数',
|
||||
component: 'InputTextArea',
|
||||
ifShow: ({ values }) => {
|
||||
return values.paramterType == 'string';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'parameter',
|
||||
label: '参数',
|
||||
component: 'JAddInput',
|
||||
helpMessage: '键值对形式填写',
|
||||
ifShow: ({ values }) => {
|
||||
return values.paramterType == 'json';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'quartz_status',
|
||||
type: 'radioButton',
|
||||
stringToNumber: true,
|
||||
dropdownStyle: {
|
||||
maxHeight: '6vh',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
label: '描述',
|
||||
component: 'InputTextArea',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<a-card>
|
||||
<!-- Redis 信息实时监控 -->
|
||||
<a-row :gutter="8">
|
||||
<a-col :sm="24" :xl="12">
|
||||
<div ref="chartRef" style="width: 100%; height: 300px"></div>
|
||||
</a-col>
|
||||
<a-col :sm="24" :xl="12">
|
||||
<div ref="chartRef2" style="width: 100%; height: 300px"></div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<BasicTable @register="registerTable" :api="getInfo"></BasicTable>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-redis" setup>
|
||||
import { onMounted, ref, reactive, Ref, onUnmounted } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { getInfo, getRedisInfo, getMetricsHistory } from './redis.api';
|
||||
import dayjs from 'dayjs';
|
||||
import { columns } from './redis.data';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useECharts } from '/@/hooks/web/useECharts';
|
||||
|
||||
const dataSource = ref([]);
|
||||
const chartRef = ref<HTMLDivElement | null>(null);
|
||||
const chartRef2 = ref<HTMLDivElement | null>(null);
|
||||
const { setOptions, echarts } = useECharts(chartRef as Ref<HTMLDivElement>);
|
||||
const { setOptions: setOptions2, echarts: echarts2 } = useECharts(chartRef2 as Ref<HTMLDivElement>);
|
||||
const loading = ref(false);
|
||||
let timer = null;
|
||||
const { createMessage } = useMessage();
|
||||
const key = reactive({
|
||||
title: {
|
||||
text: 'Redis Key 实时数量(个)',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [],
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color: '#ff6987',
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#dc143c',
|
||||
width: 10,
|
||||
type: 'solid',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const memory = reactive({
|
||||
title: {
|
||||
text: 'Redis 内存实时占用情况(KB)',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [],
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color: '#74bcff',
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#1890ff',
|
||||
width: 10,
|
||||
type: 'solid',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const [registerTable, { reload }] = useTable({
|
||||
columns,
|
||||
showIndexColumn: false,
|
||||
pagination: false,
|
||||
bordered: true,
|
||||
});
|
||||
|
||||
// 获取一组数据中最大和最小的值
|
||||
function getMaxAndMin(dataSource, field) {
|
||||
let maxValue = null,
|
||||
minValue = null;
|
||||
dataSource.forEach((item) => {
|
||||
let value = Number.parseInt(item[field]);
|
||||
// max
|
||||
if (maxValue == null) {
|
||||
maxValue = value;
|
||||
} else if (value > maxValue) {
|
||||
maxValue = value;
|
||||
}
|
||||
// min
|
||||
if (minValue == null) {
|
||||
minValue = value;
|
||||
} else if (value < minValue) {
|
||||
minValue = value;
|
||||
}
|
||||
});
|
||||
return [maxValue, minValue];
|
||||
}
|
||||
|
||||
function loadRedisInfo() {
|
||||
getInfo().then((res) => {
|
||||
dataSource.value = res.result;
|
||||
});
|
||||
}
|
||||
|
||||
function initCharts() {
|
||||
setOptions(memory);
|
||||
setOptions2(key);
|
||||
}
|
||||
|
||||
/** 开启定时器 */
|
||||
function openTimer() {
|
||||
loadHistoryData();
|
||||
closeTimer();
|
||||
timer = setInterval(() => {
|
||||
loadData();
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
/** 关闭定时器 */
|
||||
function closeTimer() {
|
||||
if (timer) clearInterval(timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载历史监控数据
|
||||
*/
|
||||
function loadHistoryData() {
|
||||
getMetricsHistory().then((res) => {
|
||||
let dbSizes = res.dbSize;
|
||||
let memories = res.memory;
|
||||
dbSizes.forEach((dbSize) => {
|
||||
key.xAxis.data.push(dayjs(dbSize.create_time).format('hh:mm:ss'));
|
||||
key.series[0].data.push(dbSize.dbSize);
|
||||
});
|
||||
memories.forEach((memoryData) => {
|
||||
memory.xAxis.data.push(dayjs(memoryData.create_time).format('hh:mm:ss'));
|
||||
memory.series[0].data.push(memoryData.used_memory / 1000);
|
||||
});
|
||||
setOptions(memory, false);
|
||||
setOptions2(key, false);
|
||||
});
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
getRedisInfo()
|
||||
.then((res) => {
|
||||
let time = dayjs().format('hh:mm:ss');
|
||||
let [{ dbSize: currentSize }, memoryInfo] = res;
|
||||
let currentMemory = memoryInfo.used_memory / 1000;
|
||||
// push 数据
|
||||
key.xAxis.data.push(time);
|
||||
key.series[0].data.push(currentSize);
|
||||
memory.xAxis.data.push(time);
|
||||
memory.series[0].data.push(currentMemory);
|
||||
// 最大长度为80
|
||||
if (key.series[0].data.length > 80) {
|
||||
key.xAxis.data.splice(0, 1);
|
||||
key.series[0].data.splice(0, 1);
|
||||
memory.xAxis.data.splice(0, 1);
|
||||
memory.series[0].data.splice(0, 1);
|
||||
}
|
||||
setOptions(memory, false);
|
||||
setOptions2(key, false);
|
||||
|
||||
// 计算 Key 最大最小值
|
||||
//let keyPole = getMaxAndMin(key.dataSource, 'y');
|
||||
//key.max = Math.floor(keyPole[0]) + 10;
|
||||
//key.min = Math.floor(keyPole[1]) - 10;
|
||||
//if (key.min < 0) this.key.min = 0;
|
||||
|
||||
// 计算 Memory 最大最小值
|
||||
//let memoryPole = getMaxAndMin(memory.dataSource, 'y');
|
||||
//memory.max = Math.floor(memoryPole[0]) + 100;
|
||||
//memory.min = Math.floor(memoryPole[1]) - 100;
|
||||
//if (memory.min < 0) memory.min = 0;
|
||||
})
|
||||
.catch((e) => {
|
||||
//closeTimer()
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initCharts();
|
||||
openTimer();
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:220230719---for:【issues-615】系统监控中的REDIS监控页面打开,再关闭后,没有关闭计时器
|
||||
onUnmounted(() => {
|
||||
closeTimer();
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:220230719---for:【issues-615】系统监控中的REDIS监控页面打开,再关闭后,没有关闭计时器
|
||||
</script>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
keysSize = '/sys/actuator/redis/keysSize',
|
||||
memoryInfo = '/sys/actuator/redis/memoryInfo',
|
||||
info = '/sys/actuator/redis/info',
|
||||
metricsHistory = '/sys/actuator/redis/metrics/history',
|
||||
}
|
||||
|
||||
/**
|
||||
* key个数
|
||||
*/
|
||||
export const getKeysSize = () => {
|
||||
return defHttp.get({ url: Api.keysSize }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 内存信息
|
||||
*/
|
||||
export const getMemoryInfo = () => {
|
||||
return defHttp.get({ url: Api.memoryInfo }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 详细信息
|
||||
*/
|
||||
export const getInfo = () => {
|
||||
return defHttp.get({ url: Api.info });
|
||||
};
|
||||
|
||||
/**
|
||||
* 历史监控记录
|
||||
*/
|
||||
export const getMetricsHistory = () => {
|
||||
return defHttp.get({ url: Api.metricsHistory });
|
||||
};
|
||||
|
||||
export const getRedisInfo = () => {
|
||||
return Promise.all([getKeysSize(), getMemoryInfo()]);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: 'Key',
|
||||
dataIndex: 'key',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
dataIndex: 'description',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'Value',
|
||||
dataIndex: 'value',
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,424 @@
|
||||
<template>
|
||||
<BasicDrawer v-bind="$attrs" @register="registerDrawer" :title="getTitle" width="30%" @ok="handleSubmit" destroyOnClose showFooter>
|
||||
<a-form ref="formRef" :label-col="labelCol" :wrapper-col="wrapperCol" :model="router" :rules="validatorRules">
|
||||
<a-form-item label="路由ID" name="routerId">
|
||||
<a-input v-model:value="router.routerId" placeholder="路由唯一ID" />
|
||||
</a-form-item>
|
||||
<a-form-item label="路由名称" name="name">
|
||||
<a-input v-model:value="router.name" placeholder="路由名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="路由URI" name="uri">
|
||||
<a-input v-model:value="router.uri" placeholder="路由URL" />
|
||||
</a-form-item>
|
||||
<a-form-item label="路由状态" name="status">
|
||||
<a-switch default-checked :checked-value="1" :un-checked-value="0" v-model:checked="router.status" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="predicates" label="路由条件">
|
||||
<div v-for="(item, index) in router.predicates">
|
||||
<!--当name在noKeyRouter时不需要指定key-->
|
||||
<template v-if="noKeyRouter.includes(item.name)">
|
||||
<a-divider
|
||||
>{{ item.name }}
|
||||
<DeleteOutlined size="22" @click="removePredicate(router, index)" />
|
||||
</a-divider>
|
||||
<div>
|
||||
<template v-for="(tag, tagIndx) in item.args">
|
||||
<a-input
|
||||
ref="inputRef2"
|
||||
v-if="tagIndx == currentTagIndex && index == currentNameIndex"
|
||||
type="text"
|
||||
size="small"
|
||||
:style="{ width: '190px' }"
|
||||
v-model:value="state.inputValue"
|
||||
@change="handleInputChange"
|
||||
@blur="handleInputEditConfirm(item, tag, tagIndx)"
|
||||
@keyup.enter="handleInputEditConfirm(item, tag, tagIndx)"
|
||||
/>
|
||||
<a-tag
|
||||
v-else
|
||||
:key="tag"
|
||||
style="margin-bottom: 2px"
|
||||
:closable="true"
|
||||
@close="() => removeTag(item, tag)"
|
||||
@click="editTag(item, tag, tagIndx, index)"
|
||||
>
|
||||
{{ tag }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<a-input
|
||||
ref="inputRef"
|
||||
v-if="state.inputVisible && index == currentNameIndex"
|
||||
type="text"
|
||||
size="small"
|
||||
:style="{ width: '100px' }"
|
||||
v-model:value="state.inputValue"
|
||||
@change="handleInputChange"
|
||||
@blur="handleInputConfirm(item)"
|
||||
@keyup.enter="handleInputConfirm(item)"
|
||||
/>
|
||||
<a-tag v-else style="background: #fff; borderstyle: dashed; margin-bottom: 2px" @click="showInput(item, index)">
|
||||
<PlusOutlined size="22" />
|
||||
新建{{ item.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<!--当name不在noKeyRouter时需要指定key-->
|
||||
<template v-if="!noKeyRouter.includes(item.name)">
|
||||
<a-divider
|
||||
>{{ item.name }}
|
||||
<DeleteOutlined size="22" @click="removePredicate(router, index)" />
|
||||
</a-divider>
|
||||
<div>
|
||||
<template v-for="(value, key) in item.args">
|
||||
<a-row>
|
||||
<a-col :span="5" style="margin-top: 8px">
|
||||
<span v-if="key == 'header'">Header名称</span>
|
||||
<span v-if="key == 'regexp'">参数值</span>
|
||||
<span v-if="key == 'param'">参数名</span>
|
||||
<span v-if="key == 'name'">参数名</span>
|
||||
</a-col>
|
||||
<a-col :span="18">
|
||||
<a-input
|
||||
:defaultValue="value"
|
||||
placeholder="参数值"
|
||||
style="width: 70%; margin-right: 8px; margin-top: 3px"
|
||||
@change="(e) => valueChange(e, item.args, key)"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<p class="btn" style="padding-top: 10px">
|
||||
<a-dropdown trigger="click">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item :key="item.name" v-for="item in tagArray" @click="predicatesHandleMenuClick(item)">{{ item.name }}</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button type="dashed" style="margin-left: 8px; width: 100%">
|
||||
添加路由条件
|
||||
<DownOutlined :size="22" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</p>
|
||||
</a-form-item>
|
||||
<a-form-item name="predicates" label="过滤器">
|
||||
<div v-for="(item, index) in router.filters">
|
||||
<a-divider
|
||||
>{{ item.name }}
|
||||
<DeleteOutlined size="22" @click="removeFilter(router, index)" />
|
||||
</a-divider>
|
||||
<div v-for="(tag, index) in item.args" :key="tag.key">
|
||||
<!-- update-begin---author:wangshuai ---date: 20230829 for:vue3.0后自定义表单重复组件要用a-form-item-rest,否则会警告提醒------------ -->
|
||||
<a-form-item-rest>
|
||||
<a-input v-model:value="tag.key" placeholder="参数键" style="width: 45%; margin-right: 8px" />
|
||||
<a-input v-model:value="tag.value" placeholder="参数值" style="width: 40%; margin-right: 8px; margin-top: 3px" />
|
||||
</a-form-item-rest>
|
||||
<!-- update-end---author:wangshuai ---date: 20230829 for:vue3.0后自定义表单重复组件要用a-form-item-rest,否则会警告提醒------------ -->
|
||||
<CloseOutlined :size="22" @click="removeFilterParams(item, index)" />
|
||||
</div>
|
||||
<a-button type="dashed" style="margin-left: 28%; width: 37%; margin-top: 5px" size="small" @click="addFilterParams(item)">
|
||||
<DownOutlined :size="22" />
|
||||
添加参数
|
||||
</a-button>
|
||||
</div>
|
||||
<p class="btn" style="padding-top: 10px">
|
||||
<a-dropdown trigger="click">
|
||||
<template #overlay>
|
||||
<a-menu @click="filterHandleMenuClick">
|
||||
<a-menu-item :key="item.key" :name="item.name" v-for="item in filterArray">{{ item.name }}</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button type="dashed" style="margin-left: 8px; width: 100%">
|
||||
添加过滤器
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</p>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref, useAttrs, reactive, nextTick } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
import { saveOrUpdateRoute } from './route.api';
|
||||
import { DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { DownOutlined } from '@ant-design/icons-vue';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const labelCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 5 },
|
||||
});
|
||||
const wrapperCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 16 },
|
||||
});
|
||||
const attrs = useAttrs();
|
||||
const isUpdate = ref(true);
|
||||
const inputRef = ref();
|
||||
const inputRef2 = ref();
|
||||
let state = reactive({
|
||||
inputVisible: false,
|
||||
inputValue: '',
|
||||
});
|
||||
const currentNameIndex = ref(0);
|
||||
const currentTagIndex = ref(-1);
|
||||
const validatorRules = {
|
||||
routerId: [{ required: true, message: 'routerId不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '路由名称不能为空', trigger: 'blur' }],
|
||||
uri: [{ required: true, message: 'uri不能为空', trigger: 'blur' }],
|
||||
};
|
||||
const noKeyRouter = ['Path', 'Host', 'Method', 'After', 'Before', 'Between', 'RemoteAddr'];
|
||||
const filterArray = [/*{ key: 0, name: '熔断器' },*/ { key: 1, name: '限流过滤器' }];
|
||||
const tagArray = ref([
|
||||
{
|
||||
name: 'Path',
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: 'Header',
|
||||
args: {
|
||||
header: '',
|
||||
regexp: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Query',
|
||||
args: {
|
||||
param: '',
|
||||
regexp: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Method',
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: 'Host',
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: 'Cookie',
|
||||
args: {
|
||||
name: '',
|
||||
regexp: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'After',
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: 'Before',
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: 'Between',
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: 'RemoteAddr',
|
||||
args: [],
|
||||
},
|
||||
]);
|
||||
const formRef = ref();
|
||||
let router = reactive({});
|
||||
|
||||
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
setDrawerProps({ confirmLoading: false });
|
||||
initRouter();
|
||||
if (unref(isUpdate)) {
|
||||
router = Object.assign(router, data.record);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增路由' : '编辑路由'));
|
||||
|
||||
//删除路由条件配置项
|
||||
function removeTag(item, removedTag) {
|
||||
let tags = item.args.filter((tag) => tag !== removedTag);
|
||||
item.args = tags;
|
||||
}
|
||||
|
||||
//初始化参数
|
||||
function initRouter() {
|
||||
router = Object.assign(router, {
|
||||
id: '',
|
||||
routerId: '',
|
||||
name: '',
|
||||
uri: '',
|
||||
status: 1,
|
||||
predicates: [],
|
||||
filters: [],
|
||||
});
|
||||
}
|
||||
|
||||
//添加路由选项
|
||||
function predicatesHandleMenuClick(e) {
|
||||
router.predicates.push({
|
||||
args: e.args,
|
||||
name: e.name,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 值修改事件
|
||||
* @param e
|
||||
* @param item
|
||||
* @param key
|
||||
*/
|
||||
function valueChange(e, item, key) {
|
||||
item[key] = e.target.value;
|
||||
}
|
||||
|
||||
function editTag(item, tag, tagIndex, index) {
|
||||
currentNameIndex.value = index;
|
||||
currentTagIndex.value = tagIndex;
|
||||
state.inputValue = tag;
|
||||
nextTick(() => {
|
||||
inputRef2.value[0].focus();
|
||||
});
|
||||
}
|
||||
|
||||
//显示输入框
|
||||
function showInput(item, index) {
|
||||
state.inputValue = '';
|
||||
state.inputVisible = true;
|
||||
currentNameIndex.value = index;
|
||||
nextTick(() => {
|
||||
inputRef.value[0].focus();
|
||||
});
|
||||
}
|
||||
|
||||
//路由选项输入框改变事件
|
||||
function handleInputChange(e) {
|
||||
console.info('change', e);
|
||||
console.info('change', e.target.value);
|
||||
//state.value = e.target.value;
|
||||
//state.tag=true;
|
||||
}
|
||||
|
||||
//删除路由条件
|
||||
function removePredicate(item, index) {
|
||||
item.predicates.splice(index, 1);
|
||||
}
|
||||
|
||||
//删除过滤器参数
|
||||
function removeFilterParams(item, index) {
|
||||
item.args.splice(index, 1);
|
||||
}
|
||||
|
||||
//删除过滤器
|
||||
function removeFilter(item, index) {
|
||||
item.filters.splice(index, 1);
|
||||
}
|
||||
|
||||
//添加过滤器参数
|
||||
function addFilterParams(item) {
|
||||
item.args.push({
|
||||
key: 'key' + item.args.length + 1,
|
||||
value: '',
|
||||
});
|
||||
}
|
||||
|
||||
//过滤器添加事件
|
||||
function filterHandleMenuClick(e) {
|
||||
if (e.key == 0) {
|
||||
router.filters.push({
|
||||
args: [
|
||||
{
|
||||
key: 'name',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
key: 'fallbackUri',
|
||||
value: 'forward:/fallback',
|
||||
},
|
||||
],
|
||||
name: 'Hystrix',
|
||||
title: filterArray[0].name,
|
||||
});
|
||||
}
|
||||
console.info('test', router);
|
||||
if (e.key == 1) {
|
||||
router.filters.push({
|
||||
args: [
|
||||
{
|
||||
key: 'key-resolver',
|
||||
value: '#{@ipKeyResolver}',
|
||||
},
|
||||
{
|
||||
key: 'redis-rate-limiter.replenishRate',
|
||||
value: 20,
|
||||
},
|
||||
{
|
||||
key: 'redis-rate-limiter.burstCapacity',
|
||||
value: 20,
|
||||
},
|
||||
],
|
||||
name: 'RequestRateLimiter',
|
||||
title: filterArray[0].name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//输入框确认
|
||||
function handleInputConfirm(item) {
|
||||
let tags = item.args;
|
||||
const inputValue = state.inputValue;
|
||||
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||
item.args = [...tags, state.inputValue];
|
||||
}
|
||||
state.inputVisible = false;
|
||||
state.inputValue = '';
|
||||
currentTagIndex.value = -1;
|
||||
currentNameIndex.value = -1;
|
||||
}
|
||||
|
||||
//输入框确认
|
||||
function handleInputEditConfirm(item, tag, index) {
|
||||
const inputValue = state.inputValue;
|
||||
if (inputValue) {
|
||||
item.args[index] = state.inputValue;
|
||||
}
|
||||
currentTagIndex.value = -1;
|
||||
currentNameIndex.value = -1;
|
||||
}
|
||||
|
||||
//关闭弹窗
|
||||
function handleCancel() {}
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
async function handleSubmit() {
|
||||
await formRef.value.validate().then(() => {
|
||||
try {
|
||||
setDrawerProps({ confirmLoading: true });
|
||||
//重新构造表单提交对象,切记不可修改router对象,数组修改为字符串容易造成界面混乱
|
||||
let params = Object.assign({}, router, {
|
||||
predicates: JSON.stringify(router.predicates),
|
||||
filters: JSON.stringify(router.filters),
|
||||
});
|
||||
//提交表单
|
||||
saveOrUpdateRoute({ router: params }).then(() => {
|
||||
closeDrawer();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
});
|
||||
} finally {
|
||||
setDrawerProps({ confirmLoading: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" title="路由回收站" :showOkBtn="false" width="1000px" destroyOnClose>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #status="{ record, text }">
|
||||
<a-tag color="pink" v-if="text == 0">禁用</a-tag>
|
||||
<a-tag color="#87d068" v-if="text == 1">正常</a-tag>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { columns } from '../route.data';
|
||||
import { deleteRouteList, putRecycleBin, deleteRecycleBin } from '../route.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
const [registerModal] = useModalInner(() => {
|
||||
checkedKeys.value = [];
|
||||
});
|
||||
//注册table数据
|
||||
const [registerTable, { reload }] = useTable({
|
||||
rowKey: 'id',
|
||||
api: deleteRouteList,
|
||||
columns: columns,
|
||||
striped: true,
|
||||
useSearchForm: false,
|
||||
bordered: true,
|
||||
showIndexColumn: false,
|
||||
pagination: false,
|
||||
tableSetting: { fullScreen: true },
|
||||
canResize: false,
|
||||
actionColumn: {
|
||||
width: 150,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 还原事件
|
||||
*/
|
||||
async function handleRevert(record) {
|
||||
await putRecycleBin({ ids: record.id }, reload);
|
||||
emit('success');
|
||||
}
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteRecycleBin({ ids: record.id }, reload);
|
||||
}
|
||||
|
||||
//获取操作栏事件
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '取回',
|
||||
icon: 'ant-design:redo-outlined',
|
||||
popConfirm: {
|
||||
title: '是否确认取回',
|
||||
confirm: handleRevert.bind(null, record),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '彻底删除',
|
||||
icon: 'ant-design:scissor-outlined',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<BasicTable @register="registerTable" :indexColumnProps="indexColumnProps">
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd" style="margin-right: 5px">新增</a-button>
|
||||
<a-button type="primary" @click="openRecycleModal(true)" preIcon="ant-design:hdd-outlined"> 回收站</a-button>
|
||||
</template>
|
||||
<template #status="{ record, text }">
|
||||
<a-tag color="pink" v-if="text == 0">禁用</a-tag>
|
||||
<a-tag color="#87d068" v-if="text == 1">正常</a-tag>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<RouteModal @register="registerDrawer" @success="reload" />
|
||||
<!--回收站弹窗-->
|
||||
<RouteRecycleBinModal @register="registerRecycleModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-route" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { getRouteList, deleteRoute, copyRoute } from './route.api';
|
||||
import { columns } from './route.data';
|
||||
import RouteModal from './RouteModal.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import RouteRecycleBinModal from './components/RouteRecycleBinModal.vue';
|
||||
const { createMessage } = useMessage();
|
||||
const [registerDrawer, { openDrawer }] = useDrawer();
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
|
||||
//回收站model
|
||||
const [registerRecycleModal, { openModal: openRecycleModal }] = useModal();
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
designScope: 'router-template',
|
||||
tableProps: {
|
||||
title: '路由列表',
|
||||
api: getRouteList,
|
||||
useSearchForm: false,
|
||||
columns: columns,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
/**
|
||||
* 序号列配置
|
||||
*/
|
||||
const indexColumnProps = {
|
||||
dataIndex: 'index',
|
||||
width: '15px',
|
||||
};
|
||||
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '复制',
|
||||
popConfirm: {
|
||||
title: '是否确认复制',
|
||||
confirm: handleCopy.bind(null, record),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择事件
|
||||
*/
|
||||
function onSelectChange(selectedRowKeys: (string | number)[]) {
|
||||
checkedKeys.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openDrawer(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openDrawer(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 复制
|
||||
*/
|
||||
async function handleCopy(record) {
|
||||
await copyRoute({ id: record.id }, reload);
|
||||
createMessage.success('复制成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteRoute({ id: record.id }, reload);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,73 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/gatewayRoute/list',
|
||||
deleteList = '/sys/gatewayRoute/deleteList',
|
||||
save = '/sys/gatewayRoute/add',
|
||||
edit = '/sys/gatewayRoute/updateAll',
|
||||
delete = '/sys/gatewayRoute/delete',
|
||||
|
||||
copyRoute = '/sys/gatewayRoute/copyRoute',
|
||||
batchPutRecycleBin = '/sys/gatewayRoute/putRecycleBin',
|
||||
batchDeleteRecycleBin = '/sys/gatewayRoute/deleteRecycleBin',
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询路由列表
|
||||
* @param params
|
||||
*/
|
||||
export const getRouteList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
/**
|
||||
* 查询逻辑删除的路由列表
|
||||
* @param params
|
||||
*/
|
||||
export const deleteRouteList = (params) => {
|
||||
return defHttp.get({ url: Api.deleteList, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更新路由
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateRoute = (params) => {
|
||||
return defHttp.post({ url: Api.edit, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除路由
|
||||
* @param params
|
||||
*/
|
||||
export const deleteRoute = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 回收站还原
|
||||
* @param params
|
||||
*/
|
||||
export const putRecycleBin = (params, handleSuccess) => {
|
||||
return defHttp.put({ url: Api.batchPutRecycleBin, params }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 回收站删除
|
||||
* @param params
|
||||
*/
|
||||
export const deleteRecycleBin = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: `${Api.batchDeleteRecycleBin}?ids=${params.ids}` }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 复制
|
||||
*/
|
||||
export const copyRoute = (params, handleSuccess) => {
|
||||
return defHttp.get({ url: Api.copyRoute, params }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '路由ID',
|
||||
dataIndex: 'routerId',
|
||||
width: 200,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '路由名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '路由URI',
|
||||
dataIndex: 'uri',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
slots: { customRender: 'status' },
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
label: '路由ID',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: '路由名称',
|
||||
component: 'InputNumber',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'uri',
|
||||
label: '路由URI',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
field: 'predicates',
|
||||
label: '路由条件',
|
||||
slot: 'predicates',
|
||||
component: 'Input',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<a-tabs v-model:activeKey="activeKey" @change="tabChange">
|
||||
<a-tab-pane key="1" tab="服务器信息"></a-tab-pane>
|
||||
<a-tab-pane key="2" tab="JVM信息" force-render></a-tab-pane>
|
||||
<!-- <a-tab-pane key="3" tab="Tomcat信息"></a-tab-pane> -->
|
||||
<a-tab-pane key="6" tab="Undertow信息"></a-tab-pane>
|
||||
<a-tab-pane key="4" tab="磁盘监控">
|
||||
<DiskInfo v-if="activeKey == 4" style="height: 100%"></DiskInfo>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab="内存信息" />
|
||||
</a-tabs>
|
||||
<!-- update-begin---author:wangshuai ---date: 20230829 for:性能监控切换到磁盘监控再切回来报错列为空,不能用if判断------------>
|
||||
<BasicTable @register="registerTable" :searchInfo="searchInfo" :dataSource="dataSource" v-show="activeKey != 4">
|
||||
<!-- update-end---author:wangshuai ---date: 20230829 for:性能监控切换到磁盘监控再切回来报错列为空,不能用if判断------------>
|
||||
<template #tableTitle>
|
||||
<div slot="message"
|
||||
>上次更新时间:{{ lastUpdateTime }}
|
||||
<a-divider type="vertical" />
|
||||
<a @click="handleUpdate">立即更新</a></div
|
||||
>
|
||||
</template>
|
||||
<template #param="{ record, text }">
|
||||
<a-tag :color="textInfo[record.param].color">{{ text }}</a-tag>
|
||||
</template>
|
||||
<template #text="{ record }">
|
||||
{{ textInfo[record.param].text }}
|
||||
</template>
|
||||
<template #value="{ record, text }"> {{ text }} {{ textInfo[record.param].unit }} </template>
|
||||
</BasicTable>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-server" setup>
|
||||
import { onMounted, ref, unref } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import DiskInfo from '../disk/DiskInfo.vue';
|
||||
import { getServerInfo, getTextInfo, getMoreInfo } from './server.api';
|
||||
import { columns } from './server.data';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const dataSource = ref([]);
|
||||
const activeKey = ref('1');
|
||||
const moreInfo = ref({});
|
||||
const lastUpdateTime = ref({});
|
||||
let textInfo = ref({});
|
||||
const { createMessage } = useMessage();
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
|
||||
const searchInfo = { logType: '1' };
|
||||
const [registerTable, { reload }] = useTable({
|
||||
columns,
|
||||
showIndexColumn: false,
|
||||
bordered: true,
|
||||
pagination: false,
|
||||
canResize: false,
|
||||
tableSetting: { fullScreen: true },
|
||||
rowKey: 'id',
|
||||
});
|
||||
|
||||
//tab切换
|
||||
function tabChange(key) {
|
||||
if (key != 4) {
|
||||
getInfoList(key);
|
||||
}
|
||||
}
|
||||
|
||||
//加载信息
|
||||
function getInfoList(infoType) {
|
||||
lastUpdateTime.value = dayjs().format('YYYY年MM月DD日 HH时mm分ss秒');
|
||||
getServerInfo(infoType).then((res) => {
|
||||
textInfo.value = getTextInfo(infoType);
|
||||
moreInfo.value = getMoreInfo(infoType);
|
||||
let info = [];
|
||||
if (infoType === '5') {
|
||||
for (let param in res[0].result) {
|
||||
let data = res[0].result[param];
|
||||
let val = convert(data, unref(textInfo)[param].valueType);
|
||||
info.push({ id: param, param, text: 'false value', value: val });
|
||||
}
|
||||
} else {
|
||||
res.forEach((value, id) => {
|
||||
let more = unref(moreInfo)[value.name];
|
||||
if (!(more instanceof Array)) {
|
||||
more = [''];
|
||||
}
|
||||
more.forEach((item, idx) => {
|
||||
let param = value.name + item;
|
||||
let val = convert(value.measurements[idx].value, unref(textInfo)[param].valueType);
|
||||
info.push({ id: param + id, param, text: 'false value', value: val });
|
||||
});
|
||||
});
|
||||
}
|
||||
dataSource.value = info;
|
||||
});
|
||||
}
|
||||
|
||||
function handleUpdate() {
|
||||
getInfoList(activeKey.value);
|
||||
}
|
||||
|
||||
//单位转换
|
||||
function convert(value, type) {
|
||||
if (type === 'Number') {
|
||||
return Number(value * 100).toFixed(2);
|
||||
} else if (type === 'Date') {
|
||||
return dayjs(value * 1000).format('YYYY-MM-DD HH:mm:ss');
|
||||
} else if (type === 'RAM') {
|
||||
return Number(value / 1048576).toFixed(3);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getInfoList(activeKey.value);
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,392 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
cpuCount = '/actuator/metrics/system.cpu.count',
|
||||
cpuUsage = '/actuator/metrics/system.cpu.usage',
|
||||
processStartTime = '/actuator/metrics/process.start.time',
|
||||
processUptime = '/actuator/metrics/process.uptime',
|
||||
processCpuUsage = '/actuator/metrics/process.cpu.usage',
|
||||
|
||||
jvmMemoryMax = '/actuator/metrics/jvm.memory.max',
|
||||
jvmMemoryCommitted = '/actuator/metrics/jvm.memory.committed',
|
||||
jvmMemoryUsed = '/actuator/metrics/jvm.memory.used',
|
||||
jvmBufferMemoryUsed = '/actuator/metrics/jvm.buffer.memory.used',
|
||||
jvmBufferCount = '/actuator/metrics/jvm.buffer.count',
|
||||
jvmThreadsDaemon = '/actuator/metrics/jvm.threads.daemon',
|
||||
jvmThreadsLive = '/actuator/metrics/jvm.threads.live',
|
||||
jvmThreadsPeak = '/actuator/metrics/jvm.threads.peak',
|
||||
jvmClassesLoaded = '/actuator/metrics/jvm.classes.loaded',
|
||||
jvmClassesUnloaded = '/actuator/metrics/jvm.classes.unloaded',
|
||||
jvmGcMemoryAllocated = '/actuator/metrics/jvm.gc.memory.allocated',
|
||||
jvmGcMemoryPromoted = '/actuator/metrics/jvm.gc.memory.promoted',
|
||||
jvmGcMaxDataSize = '/actuator/metrics/jvm.gc.max.data.size',
|
||||
jvmGcLiveDataSize = '/actuator/metrics/jvm.gc.live.data.size',
|
||||
jvmGcPause = '/actuator/metrics/jvm.gc.pause',
|
||||
|
||||
tomcatSessionsCreated = '/actuator/metrics/tomcat.sessions.created',
|
||||
tomcatSessionsExpired = '/actuator/metrics/tomcat.sessions.expired',
|
||||
tomcatSessionsActiveCurrent = '/actuator/metrics/tomcat.sessions.active.current',
|
||||
tomcatSessionsActiveMax = '/actuator/metrics/tomcat.sessions.active.max',
|
||||
tomcatSessionsRejected = '/actuator/metrics/tomcat.sessions.rejected',
|
||||
|
||||
memoryInfo = '/sys/actuator/memory/info',
|
||||
// undertow 监控
|
||||
undertowSessionsCreated = '/actuator/metrics/undertow.sessions.created',
|
||||
undertowSessionsExpired = '/actuator/metrics/undertow.sessions.expired',
|
||||
undertowSessionsActiveCurrent = '/actuator/metrics/undertow.sessions.active.current',
|
||||
undertowSessionsActiveMax = '/actuator/metrics/undertow.sessions.active.max',
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询cpu数量
|
||||
*/
|
||||
export const getCpuCount = () => {
|
||||
return defHttp.get({ url: Api.cpuCount }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询系统 CPU 使用率
|
||||
*/
|
||||
export const getCpuUsage = () => {
|
||||
return defHttp.get({ url: Api.cpuUsage }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询应用启动时间点
|
||||
*/
|
||||
export const getProcessStartTime = () => {
|
||||
return defHttp.get({ url: Api.processStartTime }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询应用已运行时间
|
||||
*/
|
||||
export const getProcessUptime = () => {
|
||||
return defHttp.get({ url: Api.processUptime }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询当前应用 CPU 使用率
|
||||
*/
|
||||
export const getProcessCpuUsage = () => {
|
||||
return defHttp.get({ url: Api.processCpuUsage }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询JVM 最大内存
|
||||
*/
|
||||
export const getJvmMemoryMax = () => {
|
||||
return defHttp.get({ url: Api.jvmMemoryMax }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* JVM 可用内存
|
||||
*/
|
||||
export const getJvmMemoryCommitted = () => {
|
||||
return defHttp.get({ url: Api.jvmMemoryCommitted }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* JVM 已用内存
|
||||
*/
|
||||
export const getJvmMemoryUsed = () => {
|
||||
return defHttp.get({ url: Api.jvmMemoryUsed }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* JVM 缓冲区已用内存
|
||||
*/
|
||||
export const getJvmBufferMemoryUsed = () => {
|
||||
return defHttp.get({ url: Api.jvmBufferMemoryUsed }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*JVM 当前缓冲区数量
|
||||
*/
|
||||
export const getJvmBufferCount = () => {
|
||||
return defHttp.get({ url: Api.jvmBufferCount }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
**JVM 守护线程数量
|
||||
*/
|
||||
export const getJvmThreadsDaemon = () => {
|
||||
return defHttp.get({ url: Api.jvmThreadsDaemon }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*JVM 当前活跃线程数量
|
||||
*/
|
||||
export const getJvmThreadsLive = () => {
|
||||
return defHttp.get({ url: Api.jvmThreadsLive }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*JVM 峰值线程数量
|
||||
*/
|
||||
export const getJvmThreadsPeak = () => {
|
||||
return defHttp.get({ url: Api.jvmThreadsPeak }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*JVM 已加载 Class 数量
|
||||
*/
|
||||
export const getJvmClassesLoaded = () => {
|
||||
return defHttp.get({ url: Api.jvmClassesLoaded }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*JVM 未加载 Class 数量
|
||||
*/
|
||||
export const getJvmClassesUnloaded = () => {
|
||||
return defHttp.get({ url: Api.jvmClassesUnloaded }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
**GC 时, 年轻代分配的内存空间
|
||||
*/
|
||||
export const getJvmGcMemoryAllocated = () => {
|
||||
return defHttp.get({ url: Api.jvmGcMemoryAllocated }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*GC 时, 老年代分配的内存空间
|
||||
*/
|
||||
export const getJvmGcMemoryPromoted = () => {
|
||||
return defHttp.get({ url: Api.jvmGcMemoryPromoted }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*GC 时, 老年代的最大内存空间
|
||||
*/
|
||||
export const getJvmGcMaxDataSize = () => {
|
||||
return defHttp.get({ url: Api.jvmGcMaxDataSize }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*FullGC 时, 老年代的内存空间
|
||||
*/
|
||||
export const getJvmGcLiveDataSize = () => {
|
||||
return defHttp.get({ url: Api.jvmGcLiveDataSize }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*系统启动以来GC 次数
|
||||
*/
|
||||
export const getJvmGcPause = () => {
|
||||
return defHttp.get({ url: Api.jvmGcPause }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*tomcat 已创建 session 数
|
||||
*/
|
||||
export const getTomcatSessionsCreated = () => {
|
||||
return defHttp.get({ url: Api.tomcatSessionsCreated }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*tomcat 已过期 session 数
|
||||
*/
|
||||
export const getTomcatSessionsExpired = () => {
|
||||
return defHttp.get({ url: Api.tomcatSessionsExpired }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*tomcat 当前活跃 session 数
|
||||
*/
|
||||
export const getTomcatSessionsActiveCurrent = () => {
|
||||
return defHttp.get({ url: Api.tomcatSessionsActiveCurrent }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*tomcat 活跃 session 数峰值
|
||||
*/
|
||||
export const getTomcatSessionsActiveMax = () => {
|
||||
return defHttp.get({ url: Api.tomcatSessionsActiveMax }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*超过session 最大配置后,拒绝的 session 个数
|
||||
*/
|
||||
export const getTomcatSessionsRejected = () => {
|
||||
return defHttp.get({ url: Api.tomcatSessionsRejected }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*undertow 已创建 session 数
|
||||
*/
|
||||
export const getUndertowSessionsCreated = () => {
|
||||
return defHttp.get({ url: Api.undertowSessionsCreated }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*undertow 已过期 session 数
|
||||
*/
|
||||
export const getUndertowSessionsExpired = () => {
|
||||
return defHttp.get({ url: Api.undertowSessionsExpired }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*undertow 当前活跃 session 数
|
||||
*/
|
||||
export const getUndertowSessionsActiveCurrent = () => {
|
||||
return defHttp.get({ url: Api.undertowSessionsActiveCurrent }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
*undertow 活跃 session 数峰值
|
||||
*/
|
||||
export const getUndertowSessionsActiveMax = () => {
|
||||
return defHttp.get({ url: Api.undertowSessionsActiveMax }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 内存信息
|
||||
*/
|
||||
export const getMemoryInfo = () => {
|
||||
return defHttp.get({ url: Api.memoryInfo }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
export const getMoreInfo = (infoType) => {
|
||||
if (infoType == '1') {
|
||||
return {};
|
||||
}
|
||||
if (infoType == '2') {
|
||||
return { 'jvm.gc.pause': ['.count', '.totalTime'] };
|
||||
}
|
||||
if (infoType == '3') {
|
||||
return {
|
||||
'tomcat.global.request': ['.count', '.totalTime'],
|
||||
'tomcat.servlet.request': ['.count', '.totalTime'],
|
||||
};
|
||||
}
|
||||
if (infoType == '5') {
|
||||
return {};
|
||||
}
|
||||
if (infoType == '6') {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const getTextInfo = (infoType) => {
|
||||
if (infoType == '1') {
|
||||
return {
|
||||
'system.cpu.count': { color: 'green', text: 'CPU 数量', unit: '核' },
|
||||
'system.cpu.usage': { color: 'green', text: '系统 CPU 使用率', unit: '%', valueType: 'Number' },
|
||||
'process.start.time': { color: 'purple', text: '应用启动时间点', unit: '', valueType: 'Date' },
|
||||
'process.uptime': { color: 'purple', text: '应用已运行时间', unit: '秒' },
|
||||
'process.cpu.usage': { color: 'purple', text: '当前应用 CPU 使用率', unit: '%', valueType: 'Number' },
|
||||
};
|
||||
}
|
||||
if (infoType == '2') {
|
||||
return {
|
||||
'jvm.memory.max': { color: 'purple', text: 'JVM 最大内存', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.memory.committed': { color: 'purple', text: 'JVM 可用内存', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.memory.used': { color: 'purple', text: 'JVM 已用内存', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.buffer.memory.used': { color: 'cyan', text: 'JVM 缓冲区已用内存', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.buffer.count': { color: 'cyan', text: '当前缓冲区数量', unit: '个' },
|
||||
'jvm.threads.daemon': { color: 'green', text: 'JVM 守护线程数量', unit: '个' },
|
||||
'jvm.threads.live': { color: 'green', text: 'JVM 当前活跃线程数量', unit: '个' },
|
||||
'jvm.threads.peak': { color: 'green', text: 'JVM 峰值线程数量', unit: '个' },
|
||||
'jvm.classes.loaded': { color: 'orange', text: 'JVM 已加载 Class 数量', unit: '个' },
|
||||
'jvm.classes.unloaded': { color: 'orange', text: 'JVM 未加载 Class 数量', unit: '个' },
|
||||
'jvm.gc.memory.allocated': { color: 'pink', text: 'GC 时, 年轻代分配的内存空间', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.gc.memory.promoted': { color: 'pink', text: 'GC 时, 老年代分配的内存空间', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.gc.max.data.size': { color: 'pink', text: 'GC 时, 老年代的最大内存空间', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.gc.live.data.size': { color: 'pink', text: 'FullGC 时, 老年代的内存空间', unit: 'MB', valueType: 'RAM' },
|
||||
'jvm.gc.pause.count': { color: 'blue', text: '系统启动以来GC 次数', unit: '次' },
|
||||
'jvm.gc.pause.totalTime': { color: 'blue', text: '系统启动以来GC 总耗时', unit: '秒' },
|
||||
};
|
||||
}
|
||||
if (infoType == '3') {
|
||||
return {
|
||||
'tomcat.sessions.created': { color: 'green', text: 'tomcat 已创建 session 数', unit: '个' },
|
||||
'tomcat.sessions.expired': { color: 'green', text: 'tomcat 已过期 session 数', unit: '个' },
|
||||
'tomcat.sessions.active.current': { color: 'green', text: 'tomcat 当前活跃 session 数', unit: '个' },
|
||||
'tomcat.sessions.active.max': { color: 'green', text: 'tomcat 活跃 session 数峰值', unit: '个' },
|
||||
'tomcat.sessions.rejected': { color: 'green', text: '超过session 最大配置后,拒绝的 session 个数', unit: '个' },
|
||||
'tomcat.global.sent': { color: 'purple', text: '发送的字节数', unit: 'bytes' },
|
||||
'tomcat.global.request.max': { color: 'purple', text: 'request 请求最长耗时', unit: '秒' },
|
||||
'tomcat.global.request.count': { color: 'purple', text: '全局 request 请求次数', unit: '次' },
|
||||
'tomcat.global.request.totalTime': { color: 'purple', text: '全局 request 请求总耗时', unit: '秒' },
|
||||
'tomcat.servlet.request.max': { color: 'cyan', text: 'servlet 请求最长耗时', unit: '秒' },
|
||||
'tomcat.servlet.request.count': { color: 'cyan', text: 'servlet 总请求次数', unit: '次' },
|
||||
'tomcat.servlet.request.totalTime': { color: 'cyan', text: 'servlet 请求总耗时', unit: '秒' },
|
||||
'tomcat.threads.current': { color: 'pink', text: 'tomcat 当前线程数(包括守护线程)', unit: '个' },
|
||||
'tomcat.threads.config.max': { color: 'pink', text: 'tomcat 配置的线程最大数', unit: '个' },
|
||||
};
|
||||
}
|
||||
if (infoType == '5') {
|
||||
return {
|
||||
'memory.physical.total': { color: 'green', text: '总物理内存', unit: 'MB', valueType: 'RAM' },
|
||||
'memory.physical.used': { color: 'green', text: '已使用物理内存', unit: 'MB', valueType: 'RAM' },
|
||||
'memory.physical.free': { color: 'green', text: '可用物理内存', unit: 'MB', valueType: 'RAM' },
|
||||
'memory.physical.usage': { color: 'green', text: '物理内存使用率', unit: '%', valueType: 'Number' },
|
||||
'memory.runtime.total': { color: 'purple', text: 'JVM总内存', unit: 'MB', valueType: 'RAM' },
|
||||
'memory.runtime.used': { color: 'purple', text: 'JVM已使用内存', unit: 'MB', valueType: 'RAM' },
|
||||
'memory.runtime.max': { color: 'purple', text: 'JVM最大内存', unit: 'MB', valueType: 'RAM' },
|
||||
'memory.runtime.free': { color: 'purple', text: 'JVM可用内存', unit: 'MB', valueType: 'RAM' },
|
||||
'memory.runtime.usage': { color: 'purple', text: 'JVM内存使用率', unit: '%', valueType: 'Number' },
|
||||
};
|
||||
}
|
||||
if (infoType == '6') {
|
||||
// undertow 监控
|
||||
return {
|
||||
'undertow.sessions.created': { color: 'green', text: 'undertow 已创建 session 数', unit: '个' },
|
||||
'undertow.sessions.expired': { color: 'green', text: 'undertow 已过期 session 数', unit: '个' },
|
||||
'undertow.sessions.active.current': { color: 'green', text: 'undertow 当前活跃 session 数', unit: '个' },
|
||||
'undertow.sessions.active.max': { color: 'green', text: 'undertow 活跃 session 数峰值', unit: '个' },
|
||||
'undertow.sessions.rejected': { color: 'green', text: '超过session 最大配置后,拒绝的 session 个数', unit: '个' },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询cpu数量
|
||||
* @param params
|
||||
*/
|
||||
export const getServerInfo = (infoType) => {
|
||||
if (infoType == '1') {
|
||||
return Promise.all([getCpuCount(), getCpuUsage(), getProcessStartTime(), getProcessUptime(), getProcessCpuUsage()]);
|
||||
}
|
||||
if (infoType == '2') {
|
||||
return Promise.all([
|
||||
getJvmMemoryMax(),
|
||||
getJvmMemoryCommitted(),
|
||||
getJvmMemoryUsed(),
|
||||
getJvmBufferCount(),
|
||||
getJvmBufferMemoryUsed(),
|
||||
getJvmThreadsDaemon(),
|
||||
getJvmThreadsLive(),
|
||||
getJvmThreadsPeak(),
|
||||
getJvmClassesLoaded(),
|
||||
getJvmClassesUnloaded(),
|
||||
getJvmGcLiveDataSize(),
|
||||
getJvmGcMaxDataSize(),
|
||||
getJvmGcMemoryAllocated(),
|
||||
getJvmGcMemoryPromoted(),
|
||||
getJvmGcPause(),
|
||||
]);
|
||||
}
|
||||
if (infoType == '3') {
|
||||
return Promise.all([
|
||||
getTomcatSessionsActiveCurrent(),
|
||||
getTomcatSessionsActiveMax(),
|
||||
getTomcatSessionsCreated(),
|
||||
getTomcatSessionsExpired(),
|
||||
getTomcatSessionsRejected(),
|
||||
]);
|
||||
}
|
||||
if (infoType == '5') {
|
||||
return Promise.all([getMemoryInfo()]);
|
||||
}
|
||||
// undertow监控
|
||||
if (infoType == '6') {
|
||||
return Promise.all([
|
||||
getUndertowSessionsActiveCurrent(),
|
||||
getUndertowSessionsActiveMax(),
|
||||
getUndertowSessionsCreated(),
|
||||
getUndertowSessionsExpired(),
|
||||
]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '参数',
|
||||
dataIndex: 'param',
|
||||
width: 80,
|
||||
align: 'left',
|
||||
slots: { customRender: 'param' },
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'text',
|
||||
slots: { customRender: 'text' },
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '当前值',
|
||||
dataIndex: 'value',
|
||||
slots: { customRender: 'value' },
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<BasicTable @register="registerTable" :dataSource="dataSource" @change="handlerTableChange">
|
||||
<template #tableTitle>
|
||||
<div slot="message">
|
||||
共追踪到 {{ dataSource.length }} 条近期HTTP请求记录
|
||||
<a-divider type="vertical" />
|
||||
<a @click="loadDate">立即刷新</a>
|
||||
</div>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<a-radio-group class="http-status-choose" size="small" v-model:value="query" @change="loadDate">
|
||||
<a-radio-button value="all">全部</a-radio-button>
|
||||
<a-radio-button value="success">成功</a-radio-button>
|
||||
<a-radio-button value="error">错误</a-radio-button>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="monitor-trace" setup>
|
||||
import { onMounted, ref, reactive } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { getActuatorList } from './trace.api';
|
||||
import { columns } from './trace.data';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const dataSource = ref([]);
|
||||
const { createMessage } = useMessage();
|
||||
const query = ref('all');
|
||||
const order = ref('');
|
||||
|
||||
const [registerTable, { reload }] = useTable({
|
||||
columns,
|
||||
showIndexColumn: false,
|
||||
bordered: true,
|
||||
rowKey: 'id',
|
||||
});
|
||||
|
||||
function loadDate() {
|
||||
getActuatorList(query.value,order.value).then((res) => {
|
||||
let filterData = [];
|
||||
for (let d of res.traces) {
|
||||
if (d.request.method !== 'OPTIONS' && d.request.uri.indexOf('httptrace') === -1) {
|
||||
filterData.push(d);
|
||||
}
|
||||
}
|
||||
dataSource.value = filterData;
|
||||
});
|
||||
}
|
||||
|
||||
const handlerTableChange = (args, arg1, sort, action) => {
|
||||
if ('sort' == action.action && sort.field) {
|
||||
order.value = sort.field;
|
||||
if (sort.order) {
|
||||
order.value += sort.order == 'ascend' ? '/asc' : '/desc';
|
||||
} else {
|
||||
order.value = '';
|
||||
}
|
||||
}
|
||||
loadDate();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadDate();
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
:deep(.jeecg-basic-table-header__toolbar) {
|
||||
width: 150px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
actuatorList = '/actuator/jeecghttptrace/',
|
||||
}
|
||||
|
||||
/**
|
||||
* 追踪信息
|
||||
*/
|
||||
export const getActuatorList = (query: String, order: String) => {
|
||||
return defHttp.get({ url: Api.actuatorList + query + '/' + order }, { isTransformResponse: false });
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import dayjs from 'dayjs';
|
||||
import _get from 'lodash.get';
|
||||
import { h } from 'vue';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '请求时间',
|
||||
dataIndex: 'timestamp',
|
||||
width: 50,
|
||||
customRender({ text }) {
|
||||
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求方法',
|
||||
dataIndex: 'request.method',
|
||||
width: 20,
|
||||
customRender({ record, column }) {
|
||||
let value = _get(record, column.dataIndex!);
|
||||
let color = '';
|
||||
if (value === 'GET') {
|
||||
color = '#87d068';
|
||||
}
|
||||
if (value === 'POST') {
|
||||
color = '#2db7f5';
|
||||
}
|
||||
if (value === 'PUT') {
|
||||
color = '#ffba5a';
|
||||
}
|
||||
if (value === 'DELETE') {
|
||||
color = '#ff5500';
|
||||
}
|
||||
return h(Tag, { color }, () => value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求URL',
|
||||
dataIndex: 'request.uri',
|
||||
width: 200,
|
||||
customRender({ record, column }) {
|
||||
return _get(record, column.dataIndex!);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '响应状态',
|
||||
dataIndex: 'response.status',
|
||||
width: 50,
|
||||
customRender({ record, column }) {
|
||||
let value = _get(record, column.dataIndex!);
|
||||
let color = '';
|
||||
if (value < 200) {
|
||||
color = 'pink';
|
||||
} else if (value < 201) {
|
||||
color = 'green';
|
||||
} else if (value < 399) {
|
||||
color = 'cyan';
|
||||
} else if (value < 403) {
|
||||
color = 'orange';
|
||||
} else if (value < 501) {
|
||||
color = 'red';
|
||||
}
|
||||
return h(Tag, { color }, () => value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求耗时',
|
||||
dataIndex: 'timeTaken',
|
||||
width: 50,
|
||||
customRender({ record, column }) {
|
||||
let value = _get(record, column.dataIndex!);
|
||||
let color = 'red';
|
||||
if (value < 500) {
|
||||
color = 'green';
|
||||
} else if (value < 1000) {
|
||||
color = 'cyan';
|
||||
} else if (value < 1500) {
|
||||
color = 'orange';
|
||||
}
|
||||
return h(Tag, { color }, () => `${value} ms`);
|
||||
},
|
||||
sorter: true,
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user