mirror of
https://github.com/snowykami/server-status-web.git
synced 2025-06-06 15:15:25 +00:00
✨ 优化储存进度条样式
This commit is contained in:
parent
3bff292a6d
commit
cbd742a85b
@ -8,7 +8,7 @@ const year = new Date().getFullYear()
|
|||||||
<h1 style="text-align: center">Server Status</h1>
|
<h1 style="text-align: center">Server Status</h1>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
<footer>
|
<footer>
|
||||||
<a href="https://github.com/snowykami/server-status-server">Server Status Dashboard</a><br>
|
<a href="https://github.com/snowykami/server-status-server">Server status dashboard</a><br>
|
||||||
© Copyright 2024-{{year}} <a href="https://sfkm.me" target="_blank">Snowykami</a> All Rights Reserved
|
© Copyright 2024-{{year}} <a href="https://sfkm.me" target="_blank">Snowykami</a> All Rights Reserved
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -72,7 +72,6 @@ export function formatDate(timestamp: number, timeOnly: boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getBaseColor(percent: number, disable: boolean = false) {
|
export function getBaseColor(percent: number, disable: boolean = false) {
|
||||||
// 获取基础颜色
|
|
||||||
// 0~60: green, 60~80: yellow, 80~90: orange, 90~100: red
|
// 0~60: green, 60~80: yellow, 80~90: orange, 90~100: red
|
||||||
if (disable) {
|
if (disable) {
|
||||||
return '#9ca3af'
|
return '#9ca3af'
|
||||||
@ -93,8 +92,6 @@ export function getBlankColor(percent: number, disable: boolean = false) {
|
|||||||
if (disable) {
|
if (disable) {
|
||||||
return '#e5e7eb'
|
return '#e5e7eb'
|
||||||
}
|
}
|
||||||
|
|
||||||
//相比base更浅的颜色
|
|
||||||
if (percent < 60) {
|
if (percent < 60) {
|
||||||
return '#bbf7d0'
|
return '#bbf7d0'
|
||||||
} else if (percent < 80) {
|
} else if (percent < 80) {
|
||||||
@ -116,3 +113,11 @@ export function formatUptime(uptime: number): string {
|
|||||||
const s = Math.floor(seconds % 60).toString().padStart(2, '0');
|
const s = Math.floor(seconds % 60).toString().padStart(2, '0');
|
||||||
return `${d}:${h}:${m}:${s}`;
|
return `${d}:${h}:${m}:${s}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDuration(duration: number): string {
|
||||||
|
const d = Math.floor(duration / 86400);
|
||||||
|
const h = Math.floor((duration % 86400) / 3600);
|
||||||
|
const m = Math.floor((duration % 3600) / 60);
|
||||||
|
const s = duration % 60;
|
||||||
|
return d > 0 ? `${d}d` : h > 0 ? `${h}h` : m > 0 ? `${m}m` : `${s}s`;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { format2Size, getBaseColor, getBlankColor } from '../api/utils.ts';
|
import {format2Size, getBaseColor, getBlankColor} from '../api/utils.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mountpoint: string;
|
mountpoint: string;
|
||||||
@ -14,8 +14,8 @@ const colorBlank = getBlankColor(props.used / props.total * 100);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="disk" :style="{ backgroundColor: colorBlank }">
|
|
||||||
<div class="disk-used" :style="{ width: props.used / props.total * 100 + '%', backgroundColor: colorUsed }"></div>
|
<div class="disk">
|
||||||
<div class="hover-text">
|
<div class="hover-text">
|
||||||
<div class="left-text">
|
<div class="left-text">
|
||||||
<span class="disk-text">{{ props.mountpoint }}</span>
|
<span class="disk-text">{{ props.mountpoint }}</span>
|
||||||
@ -24,59 +24,67 @@ const colorBlank = getBlankColor(props.used / props.total * 100);
|
|||||||
<span class="disk-text">{{ format2Size(props.used, props.total) }} [{{ props.fstype }}]</span>
|
<span class="disk-text">{{ format2Size(props.used, props.total) }} [{{ props.fstype }}]</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="progress-bar" style="display: flex; align-items: center">
|
||||||
|
<div class="disk-total" :style="{ backgroundColor: colorBlank }" style="margin-right: 0.5rem;">
|
||||||
|
<div class="disk-used"
|
||||||
|
:style="{ width: props.used / props.total * 100 + '%', backgroundColor: colorUsed }"></div>
|
||||||
|
</div>
|
||||||
|
<div style="color: var(--text-color-2)">
|
||||||
|
<div class="percentage" style="text-align: right">{{ (props.used / props.total * 100).toFixed(1) }}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
:host {
|
|
||||||
--text-size: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disk {
|
.disk {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.75rem;
|
||||||
height: 2rem;
|
}
|
||||||
width: 100%;
|
|
||||||
|
.disk-total {
|
||||||
|
height: 0.618rem;
|
||||||
|
width: 90%;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
overflow: hidden; /* Ensure the used part doesn't overflow */
|
overflow: hidden; /* Ensure the used part doesn't overflow */
|
||||||
}
|
}
|
||||||
|
|
||||||
.disk-used {
|
.disk-used {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
clip-path: inset(0 0 0 0 round var(--border-radius)); /* Apply border-radius using clip-path */
|
clip-path: inset(0 0 0 0 round 1rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-text {
|
.hover-text {
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-text {
|
.left-text {
|
||||||
margin-left: 1rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-text {
|
.right-text {
|
||||||
margin-right: 1rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disk-text {
|
.disk-text {
|
||||||
font-size: var(--text-size);
|
font-size: 16px;
|
||||||
|
color: var(--text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.percentage{
|
||||||
|
font-size: 14px;
|
||||||
color: var(--text-color-2);
|
color: var(--text-color-2);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -4,7 +4,7 @@ import {computed, onMounted, ref, watch} from "vue";
|
|||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import {
|
import {
|
||||||
format2Size,
|
format2Size,
|
||||||
formatDate,
|
formatDate, formatDuration,
|
||||||
formatSizeByUnit,
|
formatSizeByUnit,
|
||||||
formatUptime,
|
formatUptime,
|
||||||
getBaseColor,
|
getBaseColor,
|
||||||
@ -63,6 +63,7 @@ const hoverBorderColor = computed(() => {
|
|||||||
return statusColor.value
|
return statusColor.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const fontFam = 'Josefin Sans'
|
||||||
|
|
||||||
function onMountedFunc() {
|
function onMountedFunc() {
|
||||||
const cpuChart = echarts.init(cpuChartRef.value);
|
const cpuChart = echarts.init(cpuChartRef.value);
|
||||||
@ -73,15 +74,16 @@ function onMountedFunc() {
|
|||||||
// style
|
// style
|
||||||
const titleStyle = {
|
const titleStyle = {
|
||||||
color: 'rgba(0, 0, 0, 0.8)',
|
color: 'rgba(0, 0, 0, 0.8)',
|
||||||
fontSize: 15,
|
fontSize: 18,
|
||||||
}
|
}
|
||||||
const radius = ['65%', '90%']
|
const radius = ['65%', '80%']
|
||||||
const netColor = ['#a2d8f4', '#0194e3'] // Tx Rx
|
const netColor = ['#a2d8f4', '#0194e3'] // Tx Rx
|
||||||
const pieLabelPosition = 'center'
|
const pieLabelPosition = 'center'
|
||||||
const emphasis = {
|
const emphasis = {
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
fontFamily: fontFam,
|
||||||
position: ['50%', '20%'] // 设置标签位置为圆环外部
|
position: ['50%', '20%'] // 设置标签位置为圆环外部
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -96,7 +98,7 @@ function onMountedFunc() {
|
|||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
const timeDiff = (Date.now()) / 1000 - status.value.meta.observed_at
|
const timeDiff = (Date.now()) / 1000 - status.value.meta.observed_at
|
||||||
deltaTime.value = timeDiff.toFixed(1)
|
deltaTime.value = formatDuration(timeDiff)
|
||||||
// 判断该时间与上一个时间不同才push
|
// 判断该时间与上一个时间不同才push
|
||||||
if (netStats.length === 0 || netStats[netStats.length - 1][0] !== status.value.meta.observed_at) {
|
if (netStats.length === 0 || netStats[netStats.length - 1][0] !== status.value.meta.observed_at) {
|
||||||
netStats.push([status.value.meta.observed_at, status.value.hardware.net.up, status.value.hardware.net.down]) // 时间 上行 下行
|
netStats.push([status.value.meta.observed_at, status.value.hardware.net.up, status.value.hardware.net.down]) // 时间 上行 下行
|
||||||
@ -119,6 +121,9 @@ function onMountedFunc() {
|
|||||||
top: 'center',
|
top: 'center',
|
||||||
textStyle: titleStyle,
|
textStyle: titleStyle,
|
||||||
},
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontFamily: fontFam
|
||||||
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
@ -154,6 +159,9 @@ function onMountedFunc() {
|
|||||||
top: 'center',
|
top: 'center',
|
||||||
textStyle: titleStyle
|
textStyle: titleStyle
|
||||||
},
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontFamily: fontFam
|
||||||
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
@ -187,6 +195,9 @@ function onMountedFunc() {
|
|||||||
top: 'center',
|
top: 'center',
|
||||||
textStyle: titleStyle,
|
textStyle: titleStyle,
|
||||||
},
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontFamily: fontFam
|
||||||
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
@ -215,6 +226,12 @@ function onMountedFunc() {
|
|||||||
netChart.setOption(
|
netChart.setOption(
|
||||||
{
|
{
|
||||||
color: netColor,
|
color: netColor,
|
||||||
|
title: {
|
||||||
|
textStyle: titleStyle
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontFamily: fontFam
|
||||||
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
@ -228,7 +245,7 @@ function onMountedFunc() {
|
|||||||
} else {
|
} else {
|
||||||
return formatDate(params.value, true);
|
return formatDate(params.value, true);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatter: function (params: any) {
|
formatter: function (params: any) {
|
||||||
@ -237,12 +254,10 @@ function onMountedFunc() {
|
|||||||
result += item.marker + (item.seriesName == 'Tx' ? '↑' : '↓') + ': ' + formatSizeByUnit(item.value * 8, null, 'bps') + '<br/>';
|
result += item.marker + (item.seriesName == 'Tx' ? '↑' : '↓') + ': ' + formatSizeByUnit(item.value * 8, null, 'bps') + '<br/>';
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
toolbox: {
|
toolbox: {
|
||||||
feature: {
|
feature: {}
|
||||||
saveAsImage: {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: '25%',
|
top: '25%',
|
||||||
@ -259,7 +274,7 @@ function onMountedFunc() {
|
|||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: function (value: number) {
|
formatter: function (value: number) {
|
||||||
return formatDate(value, true)
|
return formatDate(value, true)
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -269,7 +284,7 @@ function onMountedFunc() {
|
|||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: function (value: number) {
|
formatter: function (value: number) {
|
||||||
return formatSizeByUnit(value * 8, null, 'b')
|
return formatSizeByUnit(value * 8, null, 'b')
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -280,7 +295,7 @@ function onMountedFunc() {
|
|||||||
stack: 'Total',
|
stack: 'Total',
|
||||||
areaStyle: {},
|
areaStyle: {},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
focus: 'series'
|
focus: 'series',
|
||||||
},
|
},
|
||||||
data: netStats.map(item => item[1]),
|
data: netStats.map(item => item[1]),
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
@ -318,9 +333,9 @@ function onMountedFunc() {
|
|||||||
// link.download = `screenshot-${status.value.meta.id}-${formatDate(Date.now(), false)}.svg`;
|
// link.download = `screenshot-${status.value.meta.id}-${formatDate(Date.now(), false)}.svg`;
|
||||||
|
|
||||||
function downloadScreenshot() {
|
function downloadScreenshot() {
|
||||||
const hostElement = document.querySelector(".host#"+status.value.meta.id);
|
const hostElement = document.querySelector(".host#" + status.value.meta.id);
|
||||||
if (hostElement) {
|
if (hostElement) {
|
||||||
html2canvas(<HTMLElement>hostElement, { scale: 2 }).then((canvas) => {
|
html2canvas(<HTMLElement>hostElement, {scale: 2}).then((canvas) => {
|
||||||
const dataURL = canvas.toDataURL("image/png");
|
const dataURL = canvas.toDataURL("image/png");
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = dataURL;
|
link.href = dataURL;
|
||||||
@ -335,19 +350,21 @@ onMounted(
|
|||||||
onMountedFunc()
|
onMountedFunc()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="host" :style="[gradientStyle, { '--hover-border-color': hoverBorderColor }]" :id="status.meta.id">
|
<div class="host" :style="[gradientStyle, { '--hover-border-color': hoverBorderColor }]" :id="status.meta.id">
|
||||||
<!-- 主机名-->
|
<!-- 主机名-->
|
||||||
<div class="host-name">{{ status.meta.name }}</div>
|
<div class="host-name">{{ status.meta.name }}</div>
|
||||||
<div class="meta-1" style="display: flex; justify-content: space-between">
|
<div class="meta-1" style="display: flex; justify-content: space-between">
|
||||||
<div class="meta1-left" style="display: flex; justify-content: flex-start; align-items: center">
|
<div class="meta1-left" style="display: flex; justify-content: flex-start; align-items: center">
|
||||||
<OutlineAnime class="outline-anime" :color="statusColor" :spreadColor="statusColor2" :is-online="isOnline"/>
|
<OutlineAnime class="outline-anime" :color="statusColor" :spreadColor="statusColor2" :is-online="isOnline"/>
|
||||||
<div class="uptime" style="margin-right: 5px"
|
<div class="uptime time-tag" style="margin-right: 5px"
|
||||||
:style="{backgroundColor: statusColor2, borderColor: statusColor}">{{ uptime }}
|
:style="{backgroundColor: statusColor2, borderColor: statusColor}">{{ uptime }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="offline-time time-tag" v-if="!isOnline">
|
||||||
|
Offline for {{ deltaTime }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta1-right" style="display: flex; justify-content: flex-end; align-items: center">
|
<div class="meta1-right" style="display: flex; justify-content: flex-end; align-items: center">
|
||||||
<img @click="downloadScreenshot" src="/svg/screenshots.svg" alt="download" style="width: 20px; height: 20px">
|
<img @click="downloadScreenshot" src="/svg/screenshots.svg" alt="download" style="width: 20px; height: 20px">
|
||||||
@ -356,7 +373,7 @@ onMounted(
|
|||||||
<div class="meta-2">
|
<div class="meta-2">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<img class="icon" :src="os.icon" alt="system">
|
<img class="icon" :src="os.icon" alt="system">
|
||||||
<span class="meta2-text">{{ os.name }} {{status.meta.os.release}} · {{status.meta.os.machine}}</span>
|
<span class="meta2-text">{{ os.name }} {{ status.meta.os.release }} · {{ status.meta.os.machine }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<img class="icon" src="/svg/timezone.svg" alt="location">
|
<img class="icon" src="/svg/timezone.svg" alt="location">
|
||||||
@ -366,9 +383,9 @@ onMounted(
|
|||||||
<img class="icon" src="/svg/label.svg" alt="labels">
|
<img class="icon" src="/svg/label.svg" alt="labels">
|
||||||
<span><span class="label meta2-text" v-for="label in status.meta.labels" :key="label">{{ label }}</span></span>
|
<span><span class="label meta2-text" v-for="label in status.meta.labels" :key="label">{{ label }}</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="section-name">
|
<div class="section-name">
|
||||||
Hardware
|
Hardware
|
||||||
</div>
|
</div>
|
||||||
@ -390,7 +407,7 @@ onMounted(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<div class="net">
|
<div class="net">
|
||||||
<div class="section-name">
|
<div class="section-name">
|
||||||
Network
|
Network
|
||||||
@ -398,7 +415,7 @@ onMounted(
|
|||||||
<div class="net-chart" ref="netChartRef"></div>
|
<div class="net-chart" ref="netChartRef"></div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<div class="disks">
|
<div class="disks">
|
||||||
<div class="section-name">
|
<div class="section-name">
|
||||||
Storage
|
Storage
|
||||||
@ -484,7 +501,7 @@ onMounted(
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uptime {
|
.time-tag {
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
--text-color-1: #000;
|
--text-color-1: #000;
|
||||||
--text-color-2: #383838;
|
--text-color-2: #5f5f5f;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ onUnmounted(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.overview {
|
.overview {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/index.ts","./src/api/node.ts","./src/api/utils.ts","./src/router/index.ts","./src/app.vue","./src/components/disk.vue","./src/components/helloworld.vue","./src/components/host.vue","./src/components/hostdisks.vue","./src/components/nav.vue","./src/components/outlineanime.vue","./src/views/home.vue","./src/views/test.vue"],"version":"5.6.2"}
|
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/index.ts","./src/api/node.ts","./src/api/utils.ts","./src/router/index.ts","./src/app.vue","./src/components/disk.vue","./src/components/helloworld.vue","./src/components/host.vue","./src/components/nav.vue","./src/components/outlineanime.vue","./src/views/home.vue","./src/views/test.vue"],"version":"5.6.2"}
|
Loading…
x
Reference in New Issue
Block a user