feat: 新增 ele infra redis 监控模块
							parent
							
								
									2e95c591bf
								
							
						
					
					
						commit
						a597f80b23
					
				| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import type { InfraRedisApi } from '#/api/infra/redis';
 | 
			
		||||
 | 
			
		||||
import { onMounted, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { Page } from '@vben/common-ui';
 | 
			
		||||
 | 
			
		||||
import { ElCard } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
import { getRedisMonitorInfo } from '#/api/infra/redis';
 | 
			
		||||
import { DocAlert } from '#/components/doc-alert';
 | 
			
		||||
 | 
			
		||||
import Commands from './modules/commands.vue';
 | 
			
		||||
import Info from './modules/info.vue';
 | 
			
		||||
import Memory from './modules/memory.vue';
 | 
			
		||||
 | 
			
		||||
const redisData = ref<InfraRedisApi.RedisMonitorInfo>();
 | 
			
		||||
 | 
			
		||||
/** 统一加载 Redis 数据 */
 | 
			
		||||
const loadRedisData = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    redisData.value = await getRedisMonitorInfo();
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('加载 Redis 数据失败', error);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  loadRedisData();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <Page auto-content-height>
 | 
			
		||||
    <template #doc>
 | 
			
		||||
      <DocAlert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" />
 | 
			
		||||
      <DocAlert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <ElCard class="mt-5" header="Redis 概览">
 | 
			
		||||
      <Info :redis-data="redisData" />
 | 
			
		||||
    </ElCard>
 | 
			
		||||
 | 
			
		||||
    <div class="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
 | 
			
		||||
      <ElCard header="内存使用">
 | 
			
		||||
        <Memory :redis-data="redisData" />
 | 
			
		||||
      </ElCard>
 | 
			
		||||
 | 
			
		||||
      <ElCard header="命令统计">
 | 
			
		||||
        <Commands :redis-data="redisData" />
 | 
			
		||||
      </ElCard>
 | 
			
		||||
    </div>
 | 
			
		||||
  </Page>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,103 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import type { EchartsUIType } from '@vben/plugins/echarts';
 | 
			
		||||
 | 
			
		||||
import type { InfraRedisApi } from '#/api/infra/redis';
 | 
			
		||||
 | 
			
		||||
import { onMounted, ref, watch } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  redisData?: InfraRedisApi.RedisMonitorInfo;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const chartRef = ref<EchartsUIType>();
 | 
			
		||||
const { renderEcharts } = useEcharts(chartRef);
 | 
			
		||||
 | 
			
		||||
/** 渲染命令统计图表 */
 | 
			
		||||
const renderCommandStats = () => {
 | 
			
		||||
  if (!props.redisData?.commandStats) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 处理数据
 | 
			
		||||
  const commandStats = [] as any[];
 | 
			
		||||
  const nameList = [] as string[];
 | 
			
		||||
  props.redisData.commandStats.forEach((row) => {
 | 
			
		||||
    commandStats.push({
 | 
			
		||||
      name: row.command,
 | 
			
		||||
      value: row.calls,
 | 
			
		||||
    });
 | 
			
		||||
    nameList.push(row.command);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // 渲染图表
 | 
			
		||||
  renderEcharts({
 | 
			
		||||
    title: {
 | 
			
		||||
      text: '命令统计',
 | 
			
		||||
      left: 'center',
 | 
			
		||||
    },
 | 
			
		||||
    tooltip: {
 | 
			
		||||
      trigger: 'item',
 | 
			
		||||
      formatter: '{a} <br/>{b} : {c} ({d}%)',
 | 
			
		||||
    },
 | 
			
		||||
    legend: {
 | 
			
		||||
      type: 'scroll',
 | 
			
		||||
      orient: 'vertical',
 | 
			
		||||
      right: 30,
 | 
			
		||||
      top: 10,
 | 
			
		||||
      bottom: 20,
 | 
			
		||||
      data: nameList,
 | 
			
		||||
      textStyle: {
 | 
			
		||||
        color: '#a1a1a1',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    series: [
 | 
			
		||||
      {
 | 
			
		||||
        name: '命令',
 | 
			
		||||
        type: 'pie',
 | 
			
		||||
        radius: [20, 120],
 | 
			
		||||
        center: ['40%', '60%'],
 | 
			
		||||
        data: commandStats,
 | 
			
		||||
        roseType: 'radius',
 | 
			
		||||
        label: {
 | 
			
		||||
          show: true,
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          label: {
 | 
			
		||||
            show: true,
 | 
			
		||||
          },
 | 
			
		||||
          itemStyle: {
 | 
			
		||||
            shadowBlur: 10,
 | 
			
		||||
            shadowOffsetX: 0,
 | 
			
		||||
            shadowColor: 'rgba(0, 0, 0, 0.5)',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 监听数据变化,重新渲染图表 */
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.redisData,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    if (newVal) {
 | 
			
		||||
      renderCommandStats();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { deep: true },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (props.redisData) {
 | 
			
		||||
    renderCommandStats();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <EchartsUI ref="chartRef" height="420px" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import type { InfraRedisApi } from '#/api/infra/redis';
 | 
			
		||||
 | 
			
		||||
import { ElDescriptions, ElDescriptionsItem } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
defineProps<{
 | 
			
		||||
  redisData?: InfraRedisApi.RedisMonitorInfo;
 | 
			
		||||
}>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <ElDescriptions :column="6" border size="default" :label-width="138">
 | 
			
		||||
    <ElDescriptionsItem label="Redis 版本">
 | 
			
		||||
      {{ redisData?.info?.redis_version }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="运行模式">
 | 
			
		||||
      {{ redisData?.info?.redis_mode === 'standalone' ? '单机' : '集群' }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="端口">
 | 
			
		||||
      {{ redisData?.info?.tcp_port }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="客户端数">
 | 
			
		||||
      {{ redisData?.info?.connected_clients }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="运行时间(天)">
 | 
			
		||||
      {{ redisData?.info?.uptime_in_days }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="使用内存">
 | 
			
		||||
      {{ redisData?.info?.used_memory_human }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="使用 CPU">
 | 
			
		||||
      {{
 | 
			
		||||
        redisData?.info
 | 
			
		||||
          ? parseFloat(redisData?.info?.used_cpu_user_children).toFixed(2)
 | 
			
		||||
          : ''
 | 
			
		||||
      }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="内存配置">
 | 
			
		||||
      {{ redisData?.info?.maxmemory_human }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="AOF 是否开启">
 | 
			
		||||
      {{ redisData?.info?.aof_enabled === '0' ? '否' : '是' }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="RDB 是否成功">
 | 
			
		||||
      {{ redisData?.info?.rdb_last_bgsave_status }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="Key 数量">
 | 
			
		||||
      {{ redisData?.dbSize }}
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
    <ElDescriptionsItem label="网络入口/出口">
 | 
			
		||||
      {{ redisData?.info?.instantaneous_input_kbps }}kps /
 | 
			
		||||
      {{ redisData?.info?.instantaneous_output_kbps }}kps
 | 
			
		||||
    </ElDescriptionsItem>
 | 
			
		||||
  </ElDescriptions>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,137 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import type { EchartsUIType } from '@vben/plugins/echarts';
 | 
			
		||||
 | 
			
		||||
import type { InfraRedisApi } from '#/api/infra/redis';
 | 
			
		||||
 | 
			
		||||
import { onMounted, ref, watch } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  redisData?: InfraRedisApi.RedisMonitorInfo;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const chartRef = ref<EchartsUIType>();
 | 
			
		||||
const { renderEcharts } = useEcharts(chartRef);
 | 
			
		||||
 | 
			
		||||
/** 解析内存值,移除单位,转为数字 */
 | 
			
		||||
const parseMemoryValue = (memStr: string | undefined): number => {
 | 
			
		||||
  if (!memStr) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  try {
 | 
			
		||||
    // 从字符串中提取数字部分,例如 "1.2M" 中的 1.2
 | 
			
		||||
    const str = String(memStr); // 显式转换为字符串类型
 | 
			
		||||
    const match = str.match(/^([\d.]+)/);
 | 
			
		||||
    return match ? Number.parseFloat(match[1] as string) : 0;
 | 
			
		||||
  } catch {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 渲染内存使用图表 */
 | 
			
		||||
const renderMemoryChart = () => {
 | 
			
		||||
  if (!props.redisData?.info) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 处理数据
 | 
			
		||||
  const usedMemory = props.redisData.info.used_memory_human || '0';
 | 
			
		||||
  const memoryValue = parseMemoryValue(usedMemory);
 | 
			
		||||
 | 
			
		||||
  // 渲染图表
 | 
			
		||||
  renderEcharts({
 | 
			
		||||
    title: {
 | 
			
		||||
      text: '内存使用情况',
 | 
			
		||||
      left: 'center',
 | 
			
		||||
    },
 | 
			
		||||
    tooltip: {
 | 
			
		||||
      formatter: `{b} <br/>{a} : ${usedMemory}`,
 | 
			
		||||
    },
 | 
			
		||||
    series: [
 | 
			
		||||
      {
 | 
			
		||||
        name: '峰值',
 | 
			
		||||
        type: 'gauge',
 | 
			
		||||
        min: 0,
 | 
			
		||||
        max: 100,
 | 
			
		||||
        splitNumber: 10,
 | 
			
		||||
        color: '#F5C74E',
 | 
			
		||||
        radius: '85%',
 | 
			
		||||
        center: ['50%', '50%'],
 | 
			
		||||
        startAngle: 225,
 | 
			
		||||
        endAngle: -45,
 | 
			
		||||
        axisLine: {
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            color: [
 | 
			
		||||
              [0.2, '#7FFF00'],
 | 
			
		||||
              [0.8, '#00FFFF'],
 | 
			
		||||
              [1, '#FF0000'],
 | 
			
		||||
            ],
 | 
			
		||||
            width: 10,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        axisTick: {
 | 
			
		||||
          length: 5,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            color: '#76D9D7',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        splitLine: {
 | 
			
		||||
          length: 20,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            color: '#76D9D7',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
          color: '#76D9D7',
 | 
			
		||||
          distance: 15,
 | 
			
		||||
          fontSize: 15,
 | 
			
		||||
        },
 | 
			
		||||
        pointer: {
 | 
			
		||||
          width: 7,
 | 
			
		||||
          show: true,
 | 
			
		||||
        },
 | 
			
		||||
        detail: {
 | 
			
		||||
          show: true,
 | 
			
		||||
          offsetCenter: [0, '50%'],
 | 
			
		||||
          color: 'auto',
 | 
			
		||||
          fontSize: 30,
 | 
			
		||||
          formatter: usedMemory,
 | 
			
		||||
        },
 | 
			
		||||
        progress: {
 | 
			
		||||
          show: true,
 | 
			
		||||
        },
 | 
			
		||||
        data: [
 | 
			
		||||
          {
 | 
			
		||||
            value: memoryValue,
 | 
			
		||||
            name: '内存消耗',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 监听数据变化,重新渲染图表 */
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.redisData,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    if (newVal) {
 | 
			
		||||
      renderMemoryChart();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { deep: true },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (props.redisData) {
 | 
			
		||||
    renderMemoryChart();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <EchartsUI ref="chartRef" height="420px" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
		Loading…
	
		Reference in New Issue