rename: client -> frontend

This commit is contained in:
syuilo
2022-12-27 14:36:33 +09:00
parent db6fff6f26
commit 9384f5399d
592 changed files with 111 additions and 111 deletions

View File

@@ -0,0 +1,167 @@
<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(180, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></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(180, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></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: 4.5px;
fill: currentColor;
> tspan {
opacity: 0.5;
}
}
}
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<div class="vrvdvrys">
<XPie class="pie" :value="usage"/>
<div>
<p><i class="ti ti-cpu"></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>

View File

@@ -0,0 +1,57 @@
<template>
<div class="zbwaqsat">
<XPie class="pie" :value="usage"/>
<div>
<p><i class="ti ti-database"></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>

View File

@@ -0,0 +1,87 @@
<template>
<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent">
<template #header><i class="ti ti-server"></i>{{ i18n.ts._widgets.serverMetric }}</template>
<template #func><button class="_button" @click="toggleView()"><i class="ti ti-selector"></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"/>
</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 MkContainer from '@/components/MkContainer.vue';
import { GetFormResultType } from '@/scripts/form';
import * as os from '@/os';
import { stream } from '@/stream';
import { i18n } from '@/i18n';
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 === 4) {
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>

View File

@@ -0,0 +1,73 @@
<template>
<div class="zlxnikvl">
<XPie class="pie" :value="usage"/>
<div>
<p><i class="fas fa-memory"></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>

View File

@@ -0,0 +1,140 @@
<template>
<div class="oxxrhrto">
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<polygon
:points="inPolygonPoints"
fill="#94a029"
fill-opacity="0.5"
/>
<polyline
:points="inPolylinePoints"
fill="none"
stroke="#94a029"
stroke-width="1"
/>
<circle
:cx="inHeadX"
:cy="inHeadY"
r="1.5"
fill="#94a029"
/>
<text x="1" y="5">NET rx <tspan>{{ bytes(inRecent) }}</tspan></text>
</svg>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<polygon
:points="outPolygonPoints"
fill="#ff9156"
fill-opacity="0.5"
/>
<polyline
:points="outPolylinePoints"
fill="none"
stroke="#ff9156"
stroke-width="1"
/>
<circle
:cx="outHeadX"
:cy="outHeadY"
r="1.5"
fill="#ff9156"
/>
<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: 4.5px;
fill: currentColor;
> tspan {
opacity: 0.5;
}
}
}
}
</style>

View File

@@ -0,0 +1,52 @@
<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;
}>();
const r = 0.45;
const color = $computed(() => `hsl(${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>