|
@@ -1,8 +1,90 @@
|
|
|
<template>
|
|
|
- <div class="page">
|
|
|
+ <div class="page redis-monitor">
|
|
|
+ <!-- Redis状态概览 -->
|
|
|
+ <el-row :gutter="15" class="dashboard-row">
|
|
|
+ <el-col :span="6" class="marg-b-15">
|
|
|
+ <el-card shadow="hover" class="status-card memory-card">
|
|
|
+ <div class="status-value">{{ sysInfo.memory ? memorySizeFormat(sysInfo.memory.used_memory) : '加载中...' }}</div>
|
|
|
+ <div class="status-label">内存使用</div>
|
|
|
+ <el-progress
|
|
|
+ v-if="sysInfo.memory"
|
|
|
+ :percentage="parseFloat(((sysInfo.memory.used_memory / sysInfo.memory.maxmemory) * 100).toFixed(1))"
|
|
|
+ :color="memoryColorGetter"
|
|
|
+ :stroke-width="8"
|
|
|
+ class="memory-progress"
|
|
|
+ />
|
|
|
+ <div class="status-icon"><i class="el-icon-cpu"></i></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6" class="marg-b-15">
|
|
|
+ <el-card shadow="hover" class="status-card keys-card">
|
|
|
+ <div class="status-value">{{ sysInfo.stats ? sysInfo.stats.total_commands_processed : '加载中...' }}</div>
|
|
|
+ <div class="status-label">已处理命令</div>
|
|
|
+ <div class="status-trend">
|
|
|
+ <div class="trend-value">+{{ commandsProcessedTrend }}</div>
|
|
|
+ <div class="trend-chart">
|
|
|
+ <div ref="commandsTrendRef" class="mini-chart"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="status-icon"><i class="el-icon-s-operation"></i></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6" class="marg-b-15">
|
|
|
+ <el-card shadow="hover" class="status-card clients-card">
|
|
|
+ <div class="status-value">{{ sysInfo.clients ? sysInfo.clients.connected_clients : '加载中...' }}</div>
|
|
|
+ <div class="status-label">客户端连接数</div>
|
|
|
+ <div class="clients-info" v-if="sysInfo.clients">
|
|
|
+ <span>阻塞: {{ sysInfo.clients.blocked_clients }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="status-icon"><i class="el-icon-user"></i></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6" class="marg-b-15">
|
|
|
+ <el-card shadow="hover" class="status-card uptime-card">
|
|
|
+ <div class="status-value">{{ sysInfo.server ? timeFormat(sysInfo.server.uptime_in_seconds) : '加载中...' }}</div>
|
|
|
+ <div class="status-label">服务运行时间</div>
|
|
|
+ <div class="uptime-info" v-if="sysInfo.server">
|
|
|
+ <span>版本: {{ sysInfo.server.redis_version }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="status-icon"><i class="el-icon-time"></i></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 内存使用图表 & 命令处理图表 -->
|
|
|
<el-row :gutter="15">
|
|
|
<el-col :span="12" class="marg-b-15">
|
|
|
- <el-card shadow="nover" class="box-card-height" style="height:auto">
|
|
|
+ <el-card shadow="hover" class="chart-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>内存使用监控</span>
|
|
|
+ <div class="card-header-right">
|
|
|
+ <el-tag size="small" type="info">实时更新</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div ref="memoryChartRef" class="chart-container"></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="marg-b-15">
|
|
|
+ <el-card shadow="hover" class="chart-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>命令处理监控</span>
|
|
|
+ <div class="card-header-right">
|
|
|
+ <el-tag size="small" type="info">实时更新</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div ref="commandsChartRef" class="chart-container"></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 系统信息卡片 -->
|
|
|
+ <el-row :gutter="15">
|
|
|
+ <el-col :span="12" class="marg-b-15">
|
|
|
+ <el-card shadow="hover" class="data-card">
|
|
|
<template #header>
|
|
|
<div class="card-header">
|
|
|
<span>客户端信息</span>
|
|
@@ -323,9 +405,11 @@
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts">
|
|
|
-import { toRefs, reactive, defineComponent } from 'vue';
|
|
|
+import { toRefs, reactive, defineComponent, ref, onMounted, onBeforeUnmount, computed } from 'vue';
|
|
|
import 'echarts-wordcloud';
|
|
|
import { getSSEOrigin } from '/@/utils/origin'
|
|
|
+import * as echarts from 'echarts';
|
|
|
+
|
|
|
let interval: any = null;
|
|
|
let es: any = null;
|
|
|
export default defineComponent({
|
|
@@ -335,8 +419,414 @@ export default defineComponent({
|
|
|
const state: any = reactive({
|
|
|
myCharts: [],
|
|
|
sysInfo: {},
|
|
|
+ memoryChartData: {
|
|
|
+ times: [] as string[],
|
|
|
+ values: [] as number[]
|
|
|
+ },
|
|
|
+ commandsChartData: {
|
|
|
+ times: [] as string[],
|
|
|
+ values: [] as number[],
|
|
|
+ lastValue: 0,
|
|
|
+ trend: [] as number[]
|
|
|
+ },
|
|
|
+ topCommands: [] as { name: string, value: number }[]
|
|
|
+ });
|
|
|
+
|
|
|
+ const memoryChartRef = ref<HTMLElement | null>(null);
|
|
|
+ const commandsChartRef = ref<HTMLElement | null>(null);
|
|
|
+ const commandsTrendRef = ref<HTMLElement | null>(null);
|
|
|
+ const commandsPieChartRef = ref<HTMLElement | null>(null);
|
|
|
+
|
|
|
+ let memoryChart: echarts.ECharts | null = null;
|
|
|
+ let commandsChart: echarts.ECharts | null = null;
|
|
|
+ let commandsTrendChart: echarts.ECharts | null = null;
|
|
|
+ let commandsPieChart: echarts.ECharts | null = null;
|
|
|
+
|
|
|
+ // 引入memorySizeFormat和timeFormat方法到setup作用域
|
|
|
+ function memorySizeFormat(size: any) {
|
|
|
+ size = parseFloat(size);
|
|
|
+ let rank = 0;
|
|
|
+ let rankchar = 'Bytes';
|
|
|
+ while (size > 1024 && rankchar != 'TB') {
|
|
|
+ size = size / 1024;
|
|
|
+ rank++;
|
|
|
+ if (rank == 1) {
|
|
|
+ rankchar = 'KB';
|
|
|
+ } else if (rank == 2) {
|
|
|
+ rankchar = 'MB';
|
|
|
+ } else if (rank == 3) {
|
|
|
+ rankchar = 'GB';
|
|
|
+ } else if (rank == 4) {
|
|
|
+ rankchar = 'TB';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return size.toFixed(2) + ' ' + rankchar;
|
|
|
+ }
|
|
|
+
|
|
|
+ function lengthToFixed2(size: any) {
|
|
|
+ size = parseFloat(size);
|
|
|
+ return size.toFixed(2);
|
|
|
+ }
|
|
|
+
|
|
|
+ function timeFormat(second: any) {
|
|
|
+ if (!second) return '-'
|
|
|
+ second = parseFloat(second);
|
|
|
+ let rank = 0;
|
|
|
+ let rankchar = '秒';
|
|
|
+ while ((second > 60 && rankchar != '小时' && rankchar != '天') || (second > 24 && rankchar == '小时')) {
|
|
|
+ if (rankchar == '小时') {
|
|
|
+ second = second / 24;
|
|
|
+ } else {
|
|
|
+ second = second / 60;
|
|
|
+ }
|
|
|
+ rank++;
|
|
|
+ if (rank == 1) {
|
|
|
+ rankchar = '分';
|
|
|
+ } else if (rank == 2) {
|
|
|
+ rankchar = '小时';
|
|
|
+ } else if (rank == 3) {
|
|
|
+ rankchar = '天';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return second.toFixed(2) + ' ' + rankchar;
|
|
|
+ }
|
|
|
+
|
|
|
+ const commandsProcessedTrend = computed(() => {
|
|
|
+ const trend = state.commandsChartData.trend;
|
|
|
+ if (trend.length === 0) return 0;
|
|
|
+ return trend[trend.length - 1];
|
|
|
+ });
|
|
|
+
|
|
|
+ const memoryColorGetter = computed(() => {
|
|
|
+ if (!state.sysInfo.memory) return '#409EFF';
|
|
|
+ const percentage = (state.sysInfo.memory.used_memory / state.sysInfo.memory.maxmemory) * 100;
|
|
|
+ if (percentage < 50) return '#67C23A';
|
|
|
+ if (percentage < 80) return '#E6A23C';
|
|
|
+ return '#F56C6C';
|
|
|
+ });
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ initCharts();
|
|
|
+
|
|
|
+ // 每5秒更新一次图表数据
|
|
|
+ interval = setInterval(() => {
|
|
|
+ updateMemoryChart();
|
|
|
+ updateCommandsChart();
|
|
|
+ updateCommandsPieChart();
|
|
|
+ }, 5000);
|
|
|
});
|
|
|
|
|
|
+ onBeforeUnmount(() => {
|
|
|
+ if (interval) {
|
|
|
+ clearInterval(interval);
|
|
|
+ interval = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (es) {
|
|
|
+ es.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ destroyCharts();
|
|
|
+ });
|
|
|
+
|
|
|
+ function destroyCharts() {
|
|
|
+ if (memoryChart) {
|
|
|
+ memoryChart.dispose();
|
|
|
+ memoryChart = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (commandsChart) {
|
|
|
+ commandsChart.dispose();
|
|
|
+ commandsChart = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (commandsTrendChart) {
|
|
|
+ commandsTrendChart.dispose();
|
|
|
+ commandsTrendChart = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (commandsPieChart) {
|
|
|
+ commandsPieChart.dispose();
|
|
|
+ commandsPieChart = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function initCharts() {
|
|
|
+ if (memoryChartRef.value) {
|
|
|
+ memoryChart = echarts.init(memoryChartRef.value);
|
|
|
+ const option = {
|
|
|
+ grid: {
|
|
|
+ top: 20,
|
|
|
+ right: 20,
|
|
|
+ bottom: 30,
|
|
|
+ left: 50
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ formatter: function(params: any) {
|
|
|
+ const time = params[0].axisValue;
|
|
|
+ const value = memorySizeFormat(params[0].data);
|
|
|
+ return `${time}<br />内存使用: ${value}`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: [],
|
|
|
+ axisLabel: {
|
|
|
+ rotate: 45
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLabel: {
|
|
|
+ formatter: function(value: number) {
|
|
|
+ return memorySizeFormat(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: [],
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ areaStyle: {
|
|
|
+ opacity: 0.3
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 3
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#409EFF'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ memoryChart.setOption(option);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (commandsChartRef.value) {
|
|
|
+ commandsChart = echarts.init(commandsChartRef.value);
|
|
|
+ const option = {
|
|
|
+ grid: {
|
|
|
+ top: 20,
|
|
|
+ right: 20,
|
|
|
+ bottom: 30,
|
|
|
+ left: 50
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis'
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: [],
|
|
|
+ axisLabel: {
|
|
|
+ rotate: 45
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value'
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: [],
|
|
|
+ type: 'bar',
|
|
|
+ itemStyle: {
|
|
|
+ color: '#67C23A'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ commandsChart.setOption(option);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (commandsTrendRef.value) {
|
|
|
+ commandsTrendChart = echarts.init(commandsTrendRef.value);
|
|
|
+ const option = {
|
|
|
+ grid: {
|
|
|
+ top: 0,
|
|
|
+ right: 0,
|
|
|
+ bottom: 0,
|
|
|
+ left: 0
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ show: false,
|
|
|
+ data: [1, 2, 3, 4, 5]
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: [0, 0, 0, 0, 0],
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'none',
|
|
|
+ lineStyle: {
|
|
|
+ width: 2,
|
|
|
+ color: '#67C23A'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ commandsTrendChart.setOption(option);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (commandsPieChartRef.value) {
|
|
|
+ commandsPieChart = echarts.init(commandsPieChartRef.value);
|
|
|
+ updateCommandsPieChart();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function getCurrentTime() {
|
|
|
+ const now = new Date();
|
|
|
+ const hours = now.getHours().toString().padStart(2, '0');
|
|
|
+ const minutes = now.getMinutes().toString().padStart(2, '0');
|
|
|
+ const seconds = now.getSeconds().toString().padStart(2, '0');
|
|
|
+ return `${hours}:${minutes}:${seconds}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateMemoryChart() {
|
|
|
+ if (!memoryChart || !state.sysInfo.memory) return;
|
|
|
+
|
|
|
+ const currentTime = getCurrentTime();
|
|
|
+ state.memoryChartData.times.push(currentTime);
|
|
|
+ state.memoryChartData.values.push(state.sysInfo.memory.used_memory);
|
|
|
+
|
|
|
+ // 保持最多显示20个数据点
|
|
|
+ if (state.memoryChartData.times.length > 20) {
|
|
|
+ state.memoryChartData.times.shift();
|
|
|
+ state.memoryChartData.values.shift();
|
|
|
+ }
|
|
|
+
|
|
|
+ memoryChart.setOption({
|
|
|
+ xAxis: {
|
|
|
+ data: state.memoryChartData.times
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: state.memoryChartData.values
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateCommandsChart() {
|
|
|
+ if (!commandsChart || !state.sysInfo.stats) return;
|
|
|
+
|
|
|
+ const currentTime = getCurrentTime();
|
|
|
+ const currentValue = state.sysInfo.stats.total_commands_processed;
|
|
|
+
|
|
|
+ // 计算增量
|
|
|
+ let increment = 0;
|
|
|
+ if (state.commandsChartData.lastValue > 0) {
|
|
|
+ increment = currentValue - state.commandsChartData.lastValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ state.commandsChartData.lastValue = currentValue;
|
|
|
+ state.commandsChartData.times.push(currentTime);
|
|
|
+ state.commandsChartData.values.push(increment);
|
|
|
+
|
|
|
+ // 添加到趋势数据
|
|
|
+ state.commandsChartData.trend.push(increment);
|
|
|
+ if (state.commandsChartData.trend.length > 5) {
|
|
|
+ state.commandsChartData.trend.shift();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保持最多显示20个数据点
|
|
|
+ if (state.commandsChartData.times.length > 20) {
|
|
|
+ state.commandsChartData.times.shift();
|
|
|
+ state.commandsChartData.values.shift();
|
|
|
+ }
|
|
|
+
|
|
|
+ commandsChart.setOption({
|
|
|
+ xAxis: {
|
|
|
+ data: state.commandsChartData.times
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: state.commandsChartData.values
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新小趋势图
|
|
|
+ if (commandsTrendChart) {
|
|
|
+ commandsTrendChart.setOption({
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: state.commandsChartData.trend
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateCommandsPieChart() {
|
|
|
+ if (!commandsPieChart || !state.sysInfo.stats) return;
|
|
|
+
|
|
|
+ // 构建命令统计数据
|
|
|
+ if (state.sysInfo.stats.commandstats) {
|
|
|
+ const commandsData = [];
|
|
|
+ for (const cmd in state.sysInfo.stats.commandstats) {
|
|
|
+ if (Object.prototype.hasOwnProperty.call(state.sysInfo.stats.commandstats, cmd)) {
|
|
|
+ const cmdName = cmd.replace('cmdstat_', '').toUpperCase();
|
|
|
+ const calls = state.sysInfo.stats.commandstats[cmd].calls;
|
|
|
+ commandsData.push({
|
|
|
+ name: cmdName,
|
|
|
+ value: calls
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按调用次数排序并取前10个
|
|
|
+ commandsData.sort((a, b) => b.value - a.value);
|
|
|
+ state.topCommands = commandsData.slice(0, 10);
|
|
|
+
|
|
|
+ commandsPieChart.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ right: 10,
|
|
|
+ top: 'center',
|
|
|
+ data: state.topCommands.map((item: { name: string }) => item.name)
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '命令调用次数',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['40%', '70%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ itemStyle: {
|
|
|
+ borderRadius: 10,
|
|
|
+ borderColor: '#fff',
|
|
|
+ borderWidth: 2
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: false,
|
|
|
+ position: 'center'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 'bold'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ labelLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ data: state.topCommands
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
function startWs() {
|
|
|
es = new EventSource(getSSEOrigin("/subscribe/redisinfo"));
|
|
|
|
|
@@ -359,7 +849,8 @@ export default defineComponent({
|
|
|
|
|
|
function memoryInfoMsg(event: { data: any; }) {
|
|
|
const data = JSON.parse(event.data);
|
|
|
- state.sysInfo.memory = data
|
|
|
+ state.sysInfo.memory = data;
|
|
|
+ updateMemoryChart();
|
|
|
}
|
|
|
|
|
|
function serverInfoMsg(event: { data: any; }) {
|
|
@@ -375,7 +866,9 @@ export default defineComponent({
|
|
|
|
|
|
function statsInfoMsg(event: { data: any; }) {
|
|
|
const data = JSON.parse(event.data);
|
|
|
- state.sysInfo.stats = data
|
|
|
+ state.sysInfo.stats = data;
|
|
|
+ updateCommandsChart();
|
|
|
+ updateCommandsPieChart();
|
|
|
}
|
|
|
|
|
|
function clientsInfoMsg(event: { data: any; }) {
|
|
@@ -385,6 +878,15 @@ export default defineComponent({
|
|
|
|
|
|
return {
|
|
|
...toRefs(state),
|
|
|
+ memoryChartRef,
|
|
|
+ commandsChartRef,
|
|
|
+ commandsTrendRef,
|
|
|
+ commandsPieChartRef,
|
|
|
+ commandsProcessedTrend,
|
|
|
+ memoryColorGetter,
|
|
|
+ memorySizeFormat,
|
|
|
+ lengthToFixed2,
|
|
|
+ timeFormat
|
|
|
};
|
|
|
},
|
|
|
unmounted() {
|
|
@@ -400,98 +902,184 @@ export default defineComponent({
|
|
|
return {};
|
|
|
},
|
|
|
methods: {
|
|
|
- memorySizeFormat(size: any) {
|
|
|
- size = parseFloat(size);
|
|
|
- let rank = 0;
|
|
|
- let rankchar = 'Bytes';
|
|
|
- while (size > 1024 && rankchar != 'TB') {
|
|
|
- size = size / 1024;
|
|
|
- rank++;
|
|
|
- if (rank == 1) {
|
|
|
- rankchar = 'KB';
|
|
|
- } else if (rank == 2) {
|
|
|
- rankchar = 'MB';
|
|
|
- } else if (rank == 3) {
|
|
|
- rankchar = 'GB';
|
|
|
- } else if (rank == 4) {
|
|
|
- rankchar = 'TB';
|
|
|
- }
|
|
|
- }
|
|
|
- return size.toFixed(2) + ' ' + rankchar;
|
|
|
- },
|
|
|
- lengthToFixed2(size: any) {
|
|
|
- size = parseFloat(size);
|
|
|
- return size.toFixed(2);
|
|
|
- },
|
|
|
- timeFormat(second: any) {
|
|
|
- if (!second) return '-'
|
|
|
- second = parseFloat(second);
|
|
|
- let rank = 0;
|
|
|
- let rankchar = '秒';
|
|
|
- while ((second > 60 && rankchar != '小时' && rankchar != '天') || (second > 24 && rankchar == '小时')) {
|
|
|
- if (rankchar == '小时') {
|
|
|
- second = second / 24;
|
|
|
- } else {
|
|
|
- second = second / 60;
|
|
|
- }
|
|
|
- rank++;
|
|
|
- if (rank == 1) {
|
|
|
- rankchar = '分';
|
|
|
- } else if (rank == 2) {
|
|
|
- rankchar = '小时';
|
|
|
- } else if (rank == 3) {
|
|
|
- rankchar = '天';
|
|
|
- }
|
|
|
- }
|
|
|
- return second.toFixed(2) + ' ' + rankchar;
|
|
|
- },
|
|
|
},
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
-.el-card {
|
|
|
- height: 300px;
|
|
|
- overflow-y: auto;
|
|
|
-}
|
|
|
+.redis-monitor {
|
|
|
+ .dashboard-row {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
|
|
|
-.marg-b-15 {
|
|
|
- margin-bottom: 15px;
|
|
|
-}
|
|
|
+ .status-card {
|
|
|
+ height: 120px;
|
|
|
+ position: relative;
|
|
|
+ overflow: visible;
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
|
-.cell {
|
|
|
- box-sizing: border-box;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: normal;
|
|
|
- word-break: break-all;
|
|
|
- line-height: 36px;
|
|
|
- padding-left: 10px;
|
|
|
- padding-right: 10px;
|
|
|
-}
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-5px);
|
|
|
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
|
|
|
-.cell-card {
|
|
|
- box-sizing: border-box;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: normal;
|
|
|
- word-break: break-all;
|
|
|
- line-height: 1.2;
|
|
|
- margin: 10px 0;
|
|
|
-}
|
|
|
+ .status-value {
|
|
|
+ font-size: 26px;
|
|
|
+ font-weight: bold;
|
|
|
+ line-height: 1.2;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ }
|
|
|
|
|
|
-.box-card {
|
|
|
- min-height: 380px;
|
|
|
-}
|
|
|
+ .status-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
|
|
|
-.box-card-meter {
|
|
|
- height: 230px;
|
|
|
+ .status-icon {
|
|
|
+ position: absolute;
|
|
|
+ right: 15px;
|
|
|
+ top: 15px;
|
|
|
+ font-size: 24px;
|
|
|
+ opacity: 0.2;
|
|
|
+ }
|
|
|
|
|
|
- min-height: 180px;
|
|
|
-}
|
|
|
+ .status-trend {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 10px;
|
|
|
+
|
|
|
+ .trend-value {
|
|
|
+ color: #67C23A;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .trend-chart {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .clients-info, .uptime-info {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ margin-top: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .memory-progress {
|
|
|
+ margin-top: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.memory-card { border-top: 3px solid #409EFF; }
|
|
|
+ &.keys-card { border-top: 3px solid #67C23A; }
|
|
|
+ &.clients-card { border-top: 3px solid #E6A23C; }
|
|
|
+ &.uptime-card { border-top: 3px solid #F56C6C; }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-card, .data-card {
|
|
|
+ margin-bottom: 15px;
|
|
|
+
|
|
|
+ .card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-container {
|
|
|
+ height: 300px;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .commands-chart-container {
|
|
|
+ height: 350px;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-card {
|
|
|
+ .db-card {
|
|
|
+ margin-bottom: 15px;
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-3px);
|
|
|
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .db-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ color: #409EFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ .db-info {
|
|
|
+ .db-item {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .db-label {
|
|
|
+ color: #606266;
|
|
|
+ }
|
|
|
+
|
|
|
+ .db-value {
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .mini-chart {
|
|
|
+ height: 30px;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .marg-b-15 {
|
|
|
+ margin-bottom: 15px;
|
|
|
+ }
|
|
|
|
|
|
-.el-form-item {
|
|
|
- margin-bottom: 5px;
|
|
|
+ .cell {
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: normal;
|
|
|
+ word-break: break-all;
|
|
|
+ line-height: 36px;
|
|
|
+ padding-left: 10px;
|
|
|
+ padding-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cell-card {
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: normal;
|
|
|
+ word-break: break-all;
|
|
|
+ line-height: 1.2;
|
|
|
+ margin: 10px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+
|
|
|
+ td {
|
|
|
+ padding: 8px;
|
|
|
+ &:first-child {
|
|
|
+ width: 40%;
|
|
|
+ color: #606266;
|
|
|
+ }
|
|
|
+ &:last-child {
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ tr:nth-child(even) {
|
|
|
+ background-color: #f9f9f9;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|