Frontend: Removed the server metrics widget
This commit is contained in:
parent
1366adcebf
commit
b2a55f1d6d
|
@ -61,10 +61,6 @@ export default function (app: App) {
|
|||
"MkwSlideshow",
|
||||
defineAsyncComponent(() => import("./slideshow.vue"))
|
||||
);
|
||||
app.component(
|
||||
"MkwServerMetric",
|
||||
defineAsyncComponent(() => import("./server-metric/index.vue"))
|
||||
);
|
||||
app.component(
|
||||
"MkwOnlineUsers",
|
||||
defineAsyncComponent(() => import("./online-users.vue"))
|
||||
|
@ -108,7 +104,6 @@ export const widgets = [
|
|||
"federation",
|
||||
"postForm",
|
||||
"slideshow",
|
||||
"serverMetric",
|
||||
"serverInfo",
|
||||
"onlineUsers",
|
||||
"jobQueue",
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
<template>
|
||||
<div class="lcfyofjk">
|
||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
||||
<defs>
|
||||
<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
|
||||
<stop offset="0%" stop-color="hsl(189, 43%, 73%)"></stop>
|
||||
<stop offset="100%" stop-color="hsl(343, 76%, 68%)"></stop>
|
||||
</linearGradient>
|
||||
<mask
|
||||
:id="cpuMaskId"
|
||||
x="0"
|
||||
y="0"
|
||||
:width="viewBoxX"
|
||||
:height="viewBoxY"
|
||||
>
|
||||
<polygon
|
||||
:points="cpuPolygonPoints"
|
||||
fill="#fff"
|
||||
fill-opacity="0.5"
|
||||
/>
|
||||
<polyline
|
||||
:points="cpuPolylinePoints"
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<circle :cx="cpuHeadX" :cy="cpuHeadY" r="1.5" fill="#fff" />
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-2"
|
||||
y="-2"
|
||||
:width="viewBoxX + 4"
|
||||
:height="viewBoxY + 4"
|
||||
:style="`stroke: none; fill: url(#${cpuGradientId}); mask: url(#${cpuMaskId})`"
|
||||
/>
|
||||
<text x="1" y="5">
|
||||
CPU
|
||||
<tspan>{{ cpuP }}%</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
||||
<defs>
|
||||
<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
|
||||
<stop offset="0%" stop-color="hsl(189, 43%, 73%)"></stop>
|
||||
<stop offset="100%" stop-color="hsl(343, 76%, 68%)"></stop>
|
||||
</linearGradient>
|
||||
<mask
|
||||
:id="memMaskId"
|
||||
x="0"
|
||||
y="0"
|
||||
:width="viewBoxX"
|
||||
:height="viewBoxY"
|
||||
>
|
||||
<polygon
|
||||
:points="memPolygonPoints"
|
||||
fill="#fff"
|
||||
fill-opacity="0.5"
|
||||
/>
|
||||
<polyline
|
||||
:points="memPolylinePoints"
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<circle :cx="memHeadX" :cy="memHeadY" r="1.5" fill="#fff" />
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-2"
|
||||
y="-2"
|
||||
:width="viewBoxX + 4"
|
||||
:height="viewBoxY + 4"
|
||||
:style="`stroke: none; fill: url(#${memGradientId}); mask: url(#${memMaskId})`"
|
||||
/>
|
||||
<text x="1" y="5">
|
||||
MEM
|
||||
<tspan>{{ memP }}%</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount } from "vue";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
const props = defineProps<{
|
||||
connection: any;
|
||||
meta: any;
|
||||
}>();
|
||||
|
||||
let viewBoxX: number = $ref(50);
|
||||
let viewBoxY: number = $ref(30);
|
||||
let stats: any[] = $ref([]);
|
||||
const cpuGradientId = uuid();
|
||||
const cpuMaskId = uuid();
|
||||
const memGradientId = uuid();
|
||||
const memMaskId = uuid();
|
||||
let cpuPolylinePoints: string = $ref("");
|
||||
let memPolylinePoints: string = $ref("");
|
||||
let cpuPolygonPoints: string = $ref("");
|
||||
let memPolygonPoints: string = $ref("");
|
||||
let cpuHeadX: any = $ref(null);
|
||||
let cpuHeadY: any = $ref(null);
|
||||
let memHeadX: any = $ref(null);
|
||||
let memHeadY: any = $ref(null);
|
||||
let cpuP: string = $ref("");
|
||||
let memP: string = $ref("");
|
||||
|
||||
onMounted(() => {
|
||||
props.connection.on("stats", onStats);
|
||||
props.connection.on("statsLog", onStatsLog);
|
||||
props.connection.send("requestLog", {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
props.connection.off("stats", onStats);
|
||||
props.connection.off("statsLog", onStatsLog);
|
||||
});
|
||||
|
||||
function onStats(connStats) {
|
||||
stats.push(connStats);
|
||||
if (stats.length > 50) stats.shift();
|
||||
|
||||
let cpuPolylinePointsStats = stats.map((s, i) => [
|
||||
viewBoxX - (stats.length - 1 - i),
|
||||
(1 - s.cpu) * viewBoxY,
|
||||
]);
|
||||
let memPolylinePointsStats = stats.map((s, i) => [
|
||||
viewBoxX - (stats.length - 1 - i),
|
||||
(1 - s.mem.active / props.meta.mem.total) * viewBoxY,
|
||||
]);
|
||||
cpuPolylinePoints = cpuPolylinePointsStats
|
||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
||||
.join(" ");
|
||||
memPolylinePoints = memPolylinePointsStats
|
||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
||||
.join(" ");
|
||||
|
||||
cpuPolygonPoints = `${
|
||||
viewBoxX - (stats.length - 1)
|
||||
},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
||||
memPolygonPoints = `${
|
||||
viewBoxX - (stats.length - 1)
|
||||
},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
||||
|
||||
cpuHeadX = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][0];
|
||||
cpuHeadY = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][1];
|
||||
memHeadX = memPolylinePointsStats[memPolylinePointsStats.length - 1][0];
|
||||
memHeadY = memPolylinePointsStats[memPolylinePointsStats.length - 1][1];
|
||||
|
||||
cpuP = (connStats.cpu * 100).toFixed(0);
|
||||
memP = ((connStats.mem.active / props.meta.mem.total) * 100).toFixed(0);
|
||||
}
|
||||
|
||||
function onStatsLog(statsLog) {
|
||||
for (const revStats of [...statsLog].reverse()) {
|
||||
onStats(revStats);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.lcfyofjk {
|
||||
display: flex;
|
||||
|
||||
> svg {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
width: 50%;
|
||||
|
||||
&:first-child {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
> text {
|
||||
font-size: 5px;
|
||||
fill: currentColor;
|
||||
|
||||
> tspan {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<div class="vrvdvrys">
|
||||
<XPie class="pie" :value="usage" />
|
||||
<div>
|
||||
<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
|
||||
<p>{{ meta.cpu.cores }} Logical cores</p>
|
||||
<p>{{ meta.cpu.model }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount } from "vue";
|
||||
import XPie from "./pie.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
connection: any;
|
||||
meta: any;
|
||||
}>();
|
||||
|
||||
let usage: number = $ref(0);
|
||||
|
||||
function onStats(stats) {
|
||||
usage = stats.cpu;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
props.connection.on("stats", onStats);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
props.connection.off("stats", onStats);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vrvdvrys {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
|
||||
> .pie {
|
||||
height: 82px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
|
||||
&:first-child {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
|
||||
> i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,57 +0,0 @@
|
|||
<template>
|
||||
<div class="zbwaqsat">
|
||||
<XPie class="pie" :value="usage" />
|
||||
<div>
|
||||
<p><i class="ph-hard-drives ph-bold ph-lg"></i>Disk</p>
|
||||
<p>Total: {{ bytes(total, 1) }}</p>
|
||||
<p>Free: {{ bytes(available, 1) }}</p>
|
||||
<p>Used: {{ bytes(used, 1) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {} from "vue";
|
||||
import XPie from "./pie.vue";
|
||||
import bytes from "@/filters/bytes";
|
||||
|
||||
const props = defineProps<{
|
||||
meta: any; // TODO
|
||||
}>();
|
||||
|
||||
const usage = $computed(() => props.meta.fs.used / props.meta.fs.total);
|
||||
const total = $computed(() => props.meta.fs.total);
|
||||
const used = $computed(() => props.meta.fs.used);
|
||||
const available = $computed(() => props.meta.fs.total - props.meta.fs.used);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zbwaqsat {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
|
||||
> .pie {
|
||||
height: 82px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
|
||||
&:first-child {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
|
||||
> i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,135 +0,0 @@
|
|||
<template>
|
||||
<MkContainer
|
||||
:show-header="widgetProps.showHeader"
|
||||
:naked="widgetProps.transparent"
|
||||
>
|
||||
<template #header
|
||||
><i class="ph-hard-drives ph-bold ph-lg"></i
|
||||
>{{ i18n.ts._widgets.serverMetric }}</template
|
||||
>
|
||||
<template #func
|
||||
><button class="_button" @click="toggleView()">
|
||||
<i class="ph-sort-ascending ph-bold ph-lg"></i></button
|
||||
></template>
|
||||
|
||||
<div v-if="meta" class="mkw-serverMetric">
|
||||
<XCpuMemory
|
||||
v-if="widgetProps.view === 0"
|
||||
:connection="connection"
|
||||
:meta="meta"
|
||||
/>
|
||||
<XNet
|
||||
v-else-if="widgetProps.view === 1"
|
||||
:connection="connection"
|
||||
:meta="meta"
|
||||
/>
|
||||
<XCpu
|
||||
v-else-if="widgetProps.view === 2"
|
||||
:connection="connection"
|
||||
:meta="meta"
|
||||
/>
|
||||
<XMemory
|
||||
v-else-if="widgetProps.view === 3"
|
||||
:connection="connection"
|
||||
:meta="meta"
|
||||
/>
|
||||
<XDisk
|
||||
v-else-if="widgetProps.view === 4"
|
||||
:connection="connection"
|
||||
:meta="meta"
|
||||
/>
|
||||
<XMeili
|
||||
v-else-if="
|
||||
instance.features.searchFilters && widgetProps.view === 5
|
||||
"
|
||||
:connection="connection"
|
||||
:meta="meta"
|
||||
/>
|
||||
</div>
|
||||
</MkContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import {
|
||||
useWidgetPropsManager,
|
||||
Widget,
|
||||
WidgetComponentEmits,
|
||||
WidgetComponentExpose,
|
||||
WidgetComponentProps,
|
||||
} from "../widget";
|
||||
import XCpuMemory from "./cpu-mem.vue";
|
||||
import XNet from "./net.vue";
|
||||
import XCpu from "./cpu.vue";
|
||||
import XMemory from "./mem.vue";
|
||||
import XDisk from "./disk.vue";
|
||||
import XMeili from "./meilisearch.vue";
|
||||
import MkContainer from "@/components/MkContainer.vue";
|
||||
import { GetFormResultType } from "@/scripts/form";
|
||||
import * as os from "@/os";
|
||||
import { stream } from "@/stream";
|
||||
import { i18n } from "@/i18n";
|
||||
import { instance } from "@/instance";
|
||||
|
||||
const name = "serverMetric";
|
||||
|
||||
const widgetPropsDef = {
|
||||
showHeader: {
|
||||
type: "boolean" as const,
|
||||
default: true,
|
||||
},
|
||||
transparent: {
|
||||
type: "boolean" as const,
|
||||
default: false,
|
||||
},
|
||||
view: {
|
||||
type: "number" as const,
|
||||
default: 0,
|
||||
hidden: true,
|
||||
},
|
||||
};
|
||||
|
||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||
|
||||
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||
const props = defineProps<{ widget?: Widget<WidgetProps> }>();
|
||||
const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
|
||||
|
||||
const { widgetProps, configure, save } = useWidgetPropsManager(
|
||||
name,
|
||||
widgetPropsDef,
|
||||
props,
|
||||
emit
|
||||
);
|
||||
|
||||
const meta = ref(null);
|
||||
|
||||
os.api("server-info", {}).then((res) => {
|
||||
meta.value = res;
|
||||
});
|
||||
|
||||
const toggleView = () => {
|
||||
if (
|
||||
(widgetProps.view === 5 && instance.features.searchFilters) ||
|
||||
(widgetProps.view === 4 && !instance.features.searchFilters)
|
||||
) {
|
||||
widgetProps.view = 0;
|
||||
} else {
|
||||
widgetProps.view++;
|
||||
}
|
||||
save();
|
||||
};
|
||||
|
||||
const connection = stream.useChannel("serverStats");
|
||||
onUnmounted(() => {
|
||||
connection.dispose();
|
||||
});
|
||||
|
||||
defineExpose<WidgetComponentExpose>({
|
||||
name,
|
||||
configure,
|
||||
id: props.widget ? props.widget.id : null,
|
||||
});
|
||||
</script>
|
|
@ -1,85 +0,0 @@
|
|||
<template>
|
||||
<div class="verusivbr">
|
||||
<XPie
|
||||
v-tooltip="i18n.ts.meiliIndexCount"
|
||||
class="pie"
|
||||
:value="progress"
|
||||
:reverse="true"
|
||||
/>
|
||||
<div>
|
||||
<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
|
||||
<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>
|
||||
<p>{{ i18n.ts._widgets.meiliSize }}: {{ bytes(totalSize, 1) }}</p>
|
||||
<p>{{ i18n.ts._widgets.meiliIndexCount }}: {{ indexCount }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted } from "vue";
|
||||
import bytes from "@/filters/bytes";
|
||||
import XPie from "./pie.vue";
|
||||
import { i18n } from "@/i18n";
|
||||
import * as os from "@/os";
|
||||
|
||||
const props = defineProps<{
|
||||
connection: any;
|
||||
meta: any;
|
||||
}>();
|
||||
|
||||
let progress: number = $ref(0);
|
||||
let serverStats = $ref(null);
|
||||
let totalSize: number = $ref(0);
|
||||
let indexCount: number = $ref(0);
|
||||
let available: string = $ref("unavailable");
|
||||
|
||||
function onStats(stats) {
|
||||
totalSize = stats.meilisearch.size;
|
||||
indexCount = stats.meilisearch.indexed_count;
|
||||
available = stats.meilisearch.health;
|
||||
progress = indexCount / serverStats.notesCount;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
os.api("stats", {}).then((res) => {
|
||||
serverStats = res;
|
||||
});
|
||||
props.connection.on("stats", onStats);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
props.connection.off("stats", onStats);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.verusivbr {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
|
||||
> .pie {
|
||||
height: 82px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
|
||||
&:first-child {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
|
||||
> i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,73 +0,0 @@
|
|||
<template>
|
||||
<div class="zlxnikvl">
|
||||
<XPie class="pie" :value="usage" />
|
||||
<div>
|
||||
<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
|
||||
<p>Total: {{ bytes(total, 1) }}</p>
|
||||
<p>Used: {{ bytes(used, 1) }}</p>
|
||||
<p>Free: {{ bytes(free, 1) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount } from "vue";
|
||||
import XPie from "./pie.vue";
|
||||
import bytes from "@/filters/bytes";
|
||||
|
||||
const props = defineProps<{
|
||||
connection: any;
|
||||
meta: any;
|
||||
}>();
|
||||
|
||||
let usage: number = $ref(0);
|
||||
let total: number = $ref(0);
|
||||
let used: number = $ref(0);
|
||||
let free: number = $ref(0);
|
||||
|
||||
function onStats(stats) {
|
||||
usage = stats.mem.active / props.meta.mem.total;
|
||||
total = props.meta.mem.total;
|
||||
used = stats.mem.active;
|
||||
free = total - used;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
props.connection.on("stats", onStats);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
props.connection.off("stats", onStats);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zlxnikvl {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
|
||||
> .pie {
|
||||
height: 82px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
|
||||
&:first-child {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
|
||||
> i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,153 +0,0 @@
|
|||
<template>
|
||||
<div class="oxxrhrto">
|
||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
||||
<polygon
|
||||
:points="inPolygonPoints"
|
||||
fill="#f6c177"
|
||||
fill-opacity="0.5"
|
||||
/>
|
||||
<polyline
|
||||
:points="inPolylinePoints"
|
||||
fill="none"
|
||||
stroke="#f6c177"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<circle :cx="inHeadX" :cy="inHeadY" r="1.5" fill="#f6c177" />
|
||||
<text x="1" y="5">
|
||||
NET rx
|
||||
<tspan>{{ bytes(inRecent) }}</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
||||
<polygon
|
||||
:points="outPolygonPoints"
|
||||
fill="#31748f"
|
||||
fill-opacity="0.5"
|
||||
/>
|
||||
<polyline
|
||||
:points="outPolylinePoints"
|
||||
fill="none"
|
||||
stroke="#31748f"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<circle :cx="outHeadX" :cy="outHeadY" r="1.5" fill="#31748f" />
|
||||
<text x="1" y="5">
|
||||
NET tx
|
||||
<tspan>{{ bytes(outRecent) }}</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount } from "vue";
|
||||
import bytes from "@/filters/bytes";
|
||||
|
||||
const props = defineProps<{
|
||||
connection: any;
|
||||
meta: any;
|
||||
}>();
|
||||
|
||||
let viewBoxX: number = $ref(50);
|
||||
let viewBoxY: number = $ref(30);
|
||||
let stats: any[] = $ref([]);
|
||||
let inPolylinePoints: string = $ref("");
|
||||
let outPolylinePoints: string = $ref("");
|
||||
let inPolygonPoints: string = $ref("");
|
||||
let outPolygonPoints: string = $ref("");
|
||||
let inHeadX: any = $ref(null);
|
||||
let inHeadY: any = $ref(null);
|
||||
let outHeadX: any = $ref(null);
|
||||
let outHeadY: any = $ref(null);
|
||||
let inRecent: number = $ref(0);
|
||||
let outRecent: number = $ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
props.connection.on("stats", onStats);
|
||||
props.connection.on("statsLog", onStatsLog);
|
||||
props.connection.send("requestLog", {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
props.connection.off("stats", onStats);
|
||||
props.connection.off("statsLog", onStatsLog);
|
||||
});
|
||||
|
||||
function onStats(connStats) {
|
||||
stats.push(connStats);
|
||||
if (stats.length > 50) stats.shift();
|
||||
|
||||
const inPeak = Math.max(1024 * 64, Math.max(...stats.map((s) => s.net.rx)));
|
||||
const outPeak = Math.max(
|
||||
1024 * 64,
|
||||
Math.max(...stats.map((s) => s.net.tx))
|
||||
);
|
||||
|
||||
let inPolylinePointsStats = stats.map((s, i) => [
|
||||
viewBoxX - (stats.length - 1 - i),
|
||||
(1 - s.net.rx / inPeak) * viewBoxY,
|
||||
]);
|
||||
let outPolylinePointsStats = stats.map((s, i) => [
|
||||
viewBoxX - (stats.length - 1 - i),
|
||||
(1 - s.net.tx / outPeak) * viewBoxY,
|
||||
]);
|
||||
inPolylinePoints = inPolylinePointsStats
|
||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
||||
.join(" ");
|
||||
outPolylinePoints = outPolylinePointsStats
|
||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
||||
.join(" ");
|
||||
|
||||
inPolygonPoints = `${
|
||||
viewBoxX - (stats.length - 1)
|
||||
},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
||||
outPolygonPoints = `${
|
||||
viewBoxX - (stats.length - 1)
|
||||
},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
||||
|
||||
inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0];
|
||||
inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1];
|
||||
outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0];
|
||||
outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1];
|
||||
|
||||
inRecent = connStats.net.rx;
|
||||
outRecent = connStats.net.tx;
|
||||
}
|
||||
|
||||
function onStatsLog(statsLog) {
|
||||
for (const revStats of [...statsLog].reverse()) {
|
||||
onStats(revStats);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.oxxrhrto {
|
||||
display: flex;
|
||||
|
||||
> svg {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
width: 50%;
|
||||
|
||||
&:first-child {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
> text {
|
||||
font-size: 5px;
|
||||
fill: currentColor;
|
||||
|
||||
> tspan {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,64 +0,0 @@
|
|||
<template>
|
||||
<svg class="hsalcinq" viewBox="0 0 1 1" preserveAspectRatio="none">
|
||||
<circle
|
||||
:r="r"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
fill="none"
|
||||
stroke-width="0.1"
|
||||
stroke="rgba(0, 0, 0, 0.05)"
|
||||
/>
|
||||
<circle
|
||||
:r="r"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
:stroke-dasharray="Math.PI * (r * 2)"
|
||||
:stroke-dashoffset="strokeDashoffset"
|
||||
fill="none"
|
||||
stroke-width="0.1"
|
||||
:stroke="color"
|
||||
/>
|
||||
<text x="50%" y="50%" dy="0.05" text-anchor="middle">
|
||||
{{ (value * 100).toFixed(0) }}%
|
||||
</text>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {} from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
value: number;
|
||||
reverse?: boolean;
|
||||
}>();
|
||||
|
||||
const r = 0.45;
|
||||
|
||||
const color = $computed(
|
||||
() =>
|
||||
`hsl(${
|
||||
props.reverse ? props.value * 180 : 180 - props.value * 180
|
||||
}, 80%, 70%)`
|
||||
);
|
||||
const strokeDashoffset = $computed(
|
||||
() => (1 - props.value) * (Math.PI * (r * 2))
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hsalcinq {
|
||||
display: block;
|
||||
height: 100%;
|
||||
|
||||
> circle {
|
||||
transform-origin: center;
|
||||
transform: rotate(-90deg);
|
||||
transition: stroke-dashoffset 0.5s ease;
|
||||
}
|
||||
|
||||
> text {
|
||||
font-size: 0.15px;
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue