diff --git a/src/store/index.js b/src/store/index.js index 7336315..328d71f 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -18,6 +18,7 @@ import { const defaultState = () => ({ init: false, + serverTime: 0, serverGroup: [], serverList: [], serverCount: { @@ -50,6 +51,9 @@ let firstSetServers = true; const store = createStore({ state: defaultState(), mutations: { + SET_SERVER_TIME(state, time) { + state.serverTime = time; + }, SET_SERVER_GROUP(state, serverGroup) { state.serverGroup = serverGroup; }, @@ -153,6 +157,9 @@ const store = createStore({ }) { msg.on('servers', (res) => { if (res) { + if (res.now) { + commit('SET_SERVER_TIME', res.now); + } const servers = res.servers?.map?.((i) => { const item = { ...i, diff --git a/src/views/components/server-detail/server-monitor.vue b/src/views/components/server-detail/server-monitor.vue index 8ba3d34..899f485 100644 --- a/src/views/components/server-detail/server-monitor.vue +++ b/src/views/components/server-detail/server-monitor.vue @@ -16,6 +16,7 @@ title="是否自动刷新" @click="switchRefresh" > + 刷新
- 刷新
+ 削峰
- 削峰 +
+
+ + 最近 + +
+
+ {{ minuteItem.label }} +
+
+
@@ -97,6 +119,7 @@ import { onMounted, onUnmounted, } from 'vue'; +import { useStore } from 'vuex'; import config from '@/config'; import request from '@/utils/request'; import validate from '@/utils/validate'; @@ -115,12 +138,46 @@ const props = defineProps({ }, }); +const store = useStore(); + +const minute = ref(1440); +const minutes = [{ + label: '30分钟', + value: 30, +}, { + label: '1小时', + value: 60, +}, { + label: '3小时', + value: 180, +}, { + label: '6小时', + value: 360, +}, { + label: '12小时', + value: 720, +}, { + label: '24小时', + value: 1440, +}]; const refreshData = ref(true); const peakShaving = ref(false); const showCates = ref({}); const monitorData = ref([]); +const accpetShowTime = computed(() => { + const now = store.state.serverTime || Date.now(); + return now - (minute.value * 60 * 1000); +}); + +const minuteActiveArrowStyle = computed(() => { + const index = minutes.findIndex((i) => i.value === minute.value); + return { + left: `calc(${index} * var(--minute-item-width))`, + }; +}); + const monitorChartData = computed(() => { /** * 处理监控数据以生成分类的平均延迟随时间变化的列表。 @@ -149,17 +206,25 @@ const monitorChartData = computed(() => { avgs: [], }; } + const showAvgDelay = []; + const showCreateTime = i.created_at.filter((o, index) => { + const status = o >= accpetShowTime.value; + if (status) { + showAvgDelay.push(i.avg_delay[index]); + } + return status; + }); const { threshold, mean, max, min, - } = peakShaving.value ? getThreshold(i.avg_delay, 2) : {}; - i.created_at.forEach((o, index) => { + } = peakShaving.value ? getThreshold(showAvgDelay, 2) : {}; + showCreateTime.forEach((o, index) => { if (dateMap[o]) { return; } - const avgDelay = i.avg_delay[index]; + const avgDelay = showAvgDelay[index]; if (peakShaving.value) { if (avgDelay === 0) { return; @@ -238,6 +303,10 @@ function switchRefresh() { refreshData.value = !refreshData.value; } +function toggleMinute(value) { + minute.value = value; +} + function toggleShowCate(id) { showCates.value[id] = !showCates.value[id]; } @@ -299,12 +368,14 @@ onUnmounted(() => { .module-head-group { display: flex; + flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; - height: 30px; .module-title { + width: max-content; + height: 30px; line-height: 30px; font-size: 16px; color: #eee; @@ -312,15 +383,16 @@ onUnmounted(() => { .right-box { display: flex; + flex-wrap: wrap; align-items: center; - gap: 10px; + gap: 12px; } .peak-shaving-group, .refresh-data-group { display: flex; align-items: center; - gap: 6px; + gap: 4px; cursor: pointer; @media screen and (max-width: 1024px) { @@ -351,6 +423,7 @@ onUnmounted(() => { .switch-dot { left: 16px; + box-shadow: 1px 1px 2px #000; } } } @@ -360,6 +433,83 @@ onUnmounted(() => { font-size: 12px; } } + + .last-update-time-group { + --minute-item-width: 50px; + --minute-item-height: 20px; + display: flex; + align-items: center; + gap: 4px; + + .last-update-time-label { + color: #ddd; + height: var(--minute-item-height); + line-height: var(--minute-item-height); + font-size: 12px; + } + + @media screen and (max-width: 660px) { + --minute-item-width: 46px; + } + + @media screen and (max-width: 600px) { + --minute-item-width: 46px; + } + + @media screen and (max-width: 400px) { + .last-update-time-label { + display: none; + } + } + + @media screen and (max-width: 330px) { + margin-left: -12px; + } + + @media screen and (max-width: 320px) { + margin-left: -18px; + } + } + .minutes { + position: relative; + display: flex; + align-items: center; + // padding: 0 10px; + height: var(--minute-item-height); + background: rgba(#fff, 0.1); + border-radius: calc(var(--minute-item-height) / 2); + + .minute-item { + position: relative; + z-index: 10; + width: var(--minute-item-width); + height: var(--minute-item-height); + line-height: var(--minute-item-height); + font-size: 12px; + text-align: center; + cursor: pointer; + color: #aaa; + transition: color 0.3s; + + &.active { + color: #fff; + text-shadow: 1px 1px 2px #000; + } + } + + .active-arrow { + position: absolute; + top: 0; + left: 0; + width: var(--minute-item-width); + height: var(--minute-item-height); + border-radius: calc(var(--minute-item-height) / 2); + background: #4caf50; + // opacity: 0.5; + transition: left 0.3s; + z-index: 1; + } + } } .monitor-cate-group {