mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-12-28 06:33:08 +02:00
wip
This commit is contained in:
parent
b4a874766e
commit
bc17a0b2cb
13 changed files with 618 additions and 539 deletions
|
@ -1,6 +0,0 @@
|
|||
export default (bytes, digits = 0) => {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes == 0) return '0Byte';
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i];
|
||||
};
|
|
@ -1,533 +0,0 @@
|
|||
<mk-server-home-widget data-melt={ data.design == 2 }>
|
||||
<template v-if="data.design == 0">
|
||||
<p class="title">%fa:server%%i18n:desktop.tags.mk-server-home-widget.title%</p>
|
||||
<button @click="toggle" title="%i18n:desktop.tags.mk-server-home-widget.toggle%">%fa:sort%</button>
|
||||
</template>
|
||||
<p class="initializing" v-if="initializing">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||
<mk-server-home-widget-cpu-and-memory-usage v-if="!initializing" show={ data.view == 0 } connection={ connection }/>
|
||||
<mk-server-home-widget-cpu v-if="!initializing" show={ data.view == 1 } connection={ connection } meta={ meta }/>
|
||||
<mk-server-home-widget-memory v-if="!initializing" show={ data.view == 2 } connection={ connection }/>
|
||||
<mk-server-home-widget-disk v-if="!initializing" show={ data.view == 3 } connection={ connection }/>
|
||||
<mk-server-home-widget-uptimes v-if="!initializing" show={ data.view == 4 } connection={ connection }/>
|
||||
<mk-server-home-widget-info v-if="!initializing" show={ data.view == 5 } connection={ connection } meta={ meta }/>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
background #fff
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
&[data-melt]
|
||||
background transparent !important
|
||||
border none !important
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
padding 0 16px
|
||||
line-height 42px
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color #888
|
||||
box-shadow 0 1px rgba(0, 0, 0, 0.07)
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
> button
|
||||
position absolute
|
||||
z-index 2
|
||||
top 0
|
||||
right 0
|
||||
padding 0
|
||||
width 42px
|
||||
font-size 0.9em
|
||||
line-height 42px
|
||||
color #ccc
|
||||
|
||||
&:hover
|
||||
color #aaa
|
||||
|
||||
&:active
|
||||
color #999
|
||||
|
||||
> .initializing
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.mixin('os');
|
||||
|
||||
this.data = {
|
||||
view: 0,
|
||||
design: 0
|
||||
};
|
||||
|
||||
this.mixin('widget');
|
||||
|
||||
this.mixin('server-stream');
|
||||
this.connection = this.serverStream.getConnection();
|
||||
this.connectionId = this.serverStream.use();
|
||||
|
||||
this.initializing = true;
|
||||
|
||||
this.on('mount', () => {
|
||||
this.mios.getMeta().then(meta => {
|
||||
this.update({
|
||||
initializing: false,
|
||||
meta
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.on('unmount', () => {
|
||||
this.serverStream.dispose(this.connectionId);
|
||||
});
|
||||
|
||||
this.toggle = () => {
|
||||
this.data.view++;
|
||||
if (this.data.view == 6) this.data.view = 0;
|
||||
|
||||
// Save widget state
|
||||
this.save();
|
||||
};
|
||||
|
||||
this.func = () => {
|
||||
if (++this.data.design == 3) this.data.design = 0;
|
||||
this.save();
|
||||
};
|
||||
</script>
|
||||
</mk-server-home-widget>
|
||||
|
||||
<mk-server-home-widget-cpu-and-memory-usage>
|
||||
<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id={ cpuGradientId } x1="0" x2="0" y1="1" y2="0">
|
||||
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
|
||||
<stop offset="33%" stop-color="hsl(120, 80%, 70%)"></stop>
|
||||
<stop offset="66%" stop-color="hsl(60, 80%, 70%)"></stop>
|
||||
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
|
||||
</linearGradient>
|
||||
<mask id={ cpuMaskId } x="0" y="0" riot-width={ viewBoxX } riot-height={ viewBoxY }>
|
||||
<polygon
|
||||
riot-points={ cpuPolygonPoints }
|
||||
fill="#fff"
|
||||
fill-opacity="0.5"/>
|
||||
<polyline
|
||||
riot-points={ cpuPolylinePoints }
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
stroke-width="1"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-1" y="-1"
|
||||
riot-width={ viewBoxX + 2 } riot-height={ viewBoxY + 2 }
|
||||
style="stroke: none; fill: url(#{ cpuGradientId }); mask: url(#{ cpuMaskId })"/>
|
||||
<text x="1" y="5">CPU <tspan>{ cpuP }%</tspan></text>
|
||||
</svg>
|
||||
<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id={ memGradientId } x1="0" x2="0" y1="1" y2="0">
|
||||
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
|
||||
<stop offset="33%" stop-color="hsl(120, 80%, 70%)"></stop>
|
||||
<stop offset="66%" stop-color="hsl(60, 80%, 70%)"></stop>
|
||||
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
|
||||
</linearGradient>
|
||||
<mask id={ memMaskId } x="0" y="0" riot-width={ viewBoxX } riot-height={ viewBoxY }>
|
||||
<polygon
|
||||
riot-points={ memPolygonPoints }
|
||||
fill="#fff"
|
||||
fill-opacity="0.5"/>
|
||||
<polyline
|
||||
riot-points={ memPolylinePoints }
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
stroke-width="1"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-1" y="-1"
|
||||
riot-width={ viewBoxX + 2 } riot-height={ viewBoxY + 2 }
|
||||
style="stroke: none; fill: url(#{ memGradientId }); mask: url(#{ memMaskId })"/>
|
||||
<text x="1" y="5">MEM <tspan>{ memP }%</tspan></text>
|
||||
</svg>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> svg
|
||||
display block
|
||||
padding 10px
|
||||
width 50%
|
||||
float left
|
||||
|
||||
&:first-child
|
||||
padding-right 5px
|
||||
|
||||
&:last-child
|
||||
padding-left 5px
|
||||
|
||||
> text
|
||||
font-size 5px
|
||||
fill rgba(0, 0, 0, 0.55)
|
||||
|
||||
> tspan
|
||||
opacity 0.5
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
import uuid from 'uuid';
|
||||
|
||||
this.viewBoxX = 50;
|
||||
this.viewBoxY = 30;
|
||||
this.stats = [];
|
||||
this.connection = this.opts.connection;
|
||||
this.cpuGradientId = uuid();
|
||||
this.cpuMaskId = uuid();
|
||||
this.memGradientId = uuid();
|
||||
this.memMaskId = uuid();
|
||||
|
||||
this.on('mount', () => {
|
||||
this.connection.on('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.on('unmount', () => {
|
||||
this.connection.off('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.onStats = stats => {
|
||||
stats.mem.used = stats.mem.total - stats.mem.free;
|
||||
this.stats.push(stats);
|
||||
if (this.stats.length > 50) this.stats.shift();
|
||||
|
||||
const cpuPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - s.cpu_usage) * this.viewBoxY}`).join(' ');
|
||||
const memPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - (s.mem.used / s.mem.total)) * this.viewBoxY}`).join(' ');
|
||||
|
||||
const cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||
const memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||
|
||||
const cpuP = (stats.cpu_usage * 100).toFixed(0);
|
||||
const memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
|
||||
|
||||
this.update({
|
||||
cpuPolylinePoints,
|
||||
memPolylinePoints,
|
||||
cpuPolygonPoints,
|
||||
memPolygonPoints,
|
||||
cpuP,
|
||||
memP
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-server-home-widget-cpu-and-memory-usage>
|
||||
|
||||
<mk-server-home-widget-cpu>
|
||||
<mk-server-home-widget-pie ref="pie"/>
|
||||
<div>
|
||||
<p>%fa:microchip%CPU</p>
|
||||
<p>{ cores } Cores</p>
|
||||
<p>{ model }</p>
|
||||
</div>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> mk-server-home-widget-pie
|
||||
padding 10px
|
||||
height 100px
|
||||
float left
|
||||
|
||||
> div
|
||||
float left
|
||||
width calc(100% - 100px)
|
||||
padding 10px 10px 10px 0
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.cores = this.opts.meta.cpu.cores;
|
||||
this.model = this.opts.meta.cpu.model;
|
||||
this.connection = this.opts.connection;
|
||||
|
||||
this.on('mount', () => {
|
||||
this.connection.on('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.on('unmount', () => {
|
||||
this.connection.off('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.onStats = stats => {
|
||||
this.$refs.pie.render(stats.cpu_usage);
|
||||
};
|
||||
</script>
|
||||
</mk-server-home-widget-cpu>
|
||||
|
||||
<mk-server-home-widget-memory>
|
||||
<mk-server-home-widget-pie ref="pie"/>
|
||||
<div>
|
||||
<p>%fa:flask%Memory</p>
|
||||
<p>Total: { bytesToSize(total, 1) }</p>
|
||||
<p>Used: { bytesToSize(used, 1) }</p>
|
||||
<p>Free: { bytesToSize(free, 1) }</p>
|
||||
</div>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> mk-server-home-widget-pie
|
||||
padding 10px
|
||||
height 100px
|
||||
float left
|
||||
|
||||
> div
|
||||
float left
|
||||
width calc(100% - 100px)
|
||||
padding 10px 10px 10px 0
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
import bytesToSize from '../../../common/scripts/bytes-to-size';
|
||||
|
||||
this.connection = this.opts.connection;
|
||||
this.bytesToSize = bytesToSize;
|
||||
|
||||
this.on('mount', () => {
|
||||
this.connection.on('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.on('unmount', () => {
|
||||
this.connection.off('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.onStats = stats => {
|
||||
stats.mem.used = stats.mem.total - stats.mem.free;
|
||||
this.$refs.pie.render(stats.mem.used / stats.mem.total);
|
||||
|
||||
this.update({
|
||||
total: stats.mem.total,
|
||||
used: stats.mem.used,
|
||||
free: stats.mem.free
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-server-home-widget-memory>
|
||||
|
||||
<mk-server-home-widget-disk>
|
||||
<mk-server-home-widget-pie ref="pie"/>
|
||||
<div>
|
||||
<p>%fa:R hdd%Storage</p>
|
||||
<p>Total: { bytesToSize(total, 1) }</p>
|
||||
<p>Available: { bytesToSize(available, 1) }</p>
|
||||
<p>Used: { bytesToSize(used, 1) }</p>
|
||||
</div>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> mk-server-home-widget-pie
|
||||
padding 10px
|
||||
height 100px
|
||||
float left
|
||||
|
||||
> div
|
||||
float left
|
||||
width calc(100% - 100px)
|
||||
padding 10px 10px 10px 0
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
import bytesToSize from '../../../common/scripts/bytes-to-size';
|
||||
|
||||
this.connection = this.opts.connection;
|
||||
this.bytesToSize = bytesToSize;
|
||||
|
||||
this.on('mount', () => {
|
||||
this.connection.on('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.on('unmount', () => {
|
||||
this.connection.off('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.onStats = stats => {
|
||||
stats.disk.used = stats.disk.total - stats.disk.free;
|
||||
|
||||
this.$refs.pie.render(stats.disk.used / stats.disk.total);
|
||||
|
||||
this.update({
|
||||
total: stats.disk.total,
|
||||
used: stats.disk.used,
|
||||
available: stats.disk.available
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-server-home-widget-disk>
|
||||
|
||||
<mk-server-home-widget-uptimes>
|
||||
<p>Uptimes</p>
|
||||
<p>Process: { process ? process.toFixed(0) : '---' }s</p>
|
||||
<p>OS: { os ? os.toFixed(0) : '---' }s</p>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
padding 10px 14px
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.connection = this.opts.connection;
|
||||
|
||||
this.on('mount', () => {
|
||||
this.connection.on('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.on('unmount', () => {
|
||||
this.connection.off('stats', this.onStats);
|
||||
});
|
||||
|
||||
this.onStats = stats => {
|
||||
this.update({
|
||||
process: stats.process_uptime,
|
||||
os: stats.os_uptime
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-server-home-widget-uptimes>
|
||||
|
||||
<mk-server-home-widget-info>
|
||||
<p>Maintainer: <b>{ meta.maintainer }</b></p>
|
||||
<p>Machine: { meta.machine }</p>
|
||||
<p>Node: { meta.node }</p>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
padding 10px 14px
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.meta = this.opts.meta;
|
||||
</script>
|
||||
</mk-server-home-widget-info>
|
||||
|
||||
<mk-server-home-widget-pie>
|
||||
<svg viewBox="0 0 1 1" preserveAspectRatio="none">
|
||||
<circle
|
||||
riot-r={ r }
|
||||
cx="50%" cy="50%"
|
||||
fill="none"
|
||||
stroke-width="0.1"
|
||||
stroke="rgba(0, 0, 0, 0.05)"/>
|
||||
<circle
|
||||
riot-r={ r }
|
||||
cx="50%" cy="50%"
|
||||
riot-stroke-dasharray={ Math.PI * (r * 2) }
|
||||
riot-stroke-dashoffset={ strokeDashoffset }
|
||||
fill="none"
|
||||
stroke-width="0.1"
|
||||
riot-stroke={ color }/>
|
||||
<text x="50%" y="50%" dy="0.05" text-anchor="middle">{ (p * 100).toFixed(0) }%</text>
|
||||
</svg>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> svg
|
||||
display block
|
||||
height 100%
|
||||
|
||||
> circle
|
||||
transform-origin center
|
||||
transform rotate(-90deg)
|
||||
transition stroke-dashoffset 0.5s ease
|
||||
|
||||
> text
|
||||
font-size 0.15px
|
||||
fill rgba(0, 0, 0, 0.6)
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.r = 0.4;
|
||||
|
||||
this.render = p => {
|
||||
const color = `hsl(${180 - (p * 180)}, 80%, 70%)`;
|
||||
const strokeDashoffset = (1 - p) * (Math.PI * (this.r * 2));
|
||||
|
||||
this.update({
|
||||
p,
|
||||
color,
|
||||
strokeDashoffset
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-server-home-widget-pie>
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<div class="cpu-memory">
|
||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none">
|
||||
<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"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-1" y="-1"
|
||||
:width="viewBoxX + 2" :height="viewBoxY + 2"
|
||||
: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 }`" preserveAspectRatio="none">
|
||||
<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"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-1" y="-1"
|
||||
:width="viewBoxX + 2" :height="viewBoxY + 2"
|
||||
: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">
|
||||
import Vue from 'vue';
|
||||
import uuid from 'uuid';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['connection'],
|
||||
data() {
|
||||
return {
|
||||
viewBoxX: 50,
|
||||
viewBoxY: 30,
|
||||
stats: [],
|
||||
cpuGradientId: uuid(),
|
||||
cpuMaskId: uuid(),
|
||||
memGradientId: uuid(),
|
||||
memMaskId: uuid(),
|
||||
cpuPolylinePoints: '',
|
||||
memPolylinePoints: '',
|
||||
cpuPolygonPoints: '',
|
||||
memPolygonPoints: '',
|
||||
cpuP: '',
|
||||
memP: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.connection.on('stats', this.onStats);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.connection.off('stats', this.onStats);
|
||||
},
|
||||
methods: {
|
||||
onStats(stats) {
|
||||
stats.mem.used = stats.mem.total - stats.mem.free;
|
||||
this.stats.push(stats);
|
||||
if (this.stats.length > 50) this.stats.shift();
|
||||
|
||||
this.cpuPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - s.cpu_usage) * this.viewBoxY}`).join(' ');
|
||||
this.memPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - (s.mem.used / s.mem.total)) * this.viewBoxY}`).join(' ');
|
||||
|
||||
this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||
this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||
|
||||
this.cpuP = (stats.cpu_usage * 100).toFixed(0);
|
||||
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.cpu-memory
|
||||
> svg
|
||||
display block
|
||||
padding 10px
|
||||
width 50%
|
||||
float left
|
||||
|
||||
&:first-child
|
||||
padding-right 5px
|
||||
|
||||
&:last-child
|
||||
padding-left 5px
|
||||
|
||||
> text
|
||||
font-size 5px
|
||||
fill rgba(0, 0, 0, 0.55)
|
||||
|
||||
> tspan
|
||||
opacity 0.5
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
</style>
|
68
src/web/app/desktop/views/components/widgets/server.cpu.vue
Normal file
68
src/web/app/desktop/views/components/widgets/server.cpu.vue
Normal file
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div class="cpu">
|
||||
<x-pie class="pie" :value="usage"/>
|
||||
<div>
|
||||
<p>%fa:microchip%CPU</p>
|
||||
<p>{{ cores }} Cores</p>
|
||||
<p>{{ model }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XPie from './server.pie.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
'x-pie': XPie
|
||||
},
|
||||
props: ['connection', 'meta'],
|
||||
data() {
|
||||
return {
|
||||
usage: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.connection.on('stats', this.onStats);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.connection.off('stats', this.onStats);
|
||||
},
|
||||
methods: {
|
||||
onStats(stats) {
|
||||
this.usage = stats.cpu_usage;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.cpu
|
||||
> .pie
|
||||
padding 10px
|
||||
height 100px
|
||||
float left
|
||||
|
||||
> div
|
||||
float left
|
||||
width calc(100% - 100px)
|
||||
padding 10px 10px 10px 0
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
</style>
|
76
src/web/app/desktop/views/components/widgets/server.disk.vue
Normal file
76
src/web/app/desktop/views/components/widgets/server.disk.vue
Normal file
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div class="disk">
|
||||
<x-pie class="pie" :value="usage"/>
|
||||
<div>
|
||||
<p>%fa:R hdd%Storage</p>
|
||||
<p>Total: {{ total | bytes(1) }}</p>
|
||||
<p>Available: {{ available | bytes(1) }}</p>
|
||||
<p>Used: {{ used | bytes(1) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XPie from './server.pie.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
'x-pie': XPie
|
||||
},
|
||||
props: ['connection'],
|
||||
data() {
|
||||
return {
|
||||
usage: 0,
|
||||
total: 0,
|
||||
used: 0,
|
||||
available: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.connection.on('stats', this.onStats);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.connection.off('stats', this.onStats);
|
||||
},
|
||||
methods: {
|
||||
onStats(stats) {
|
||||
stats.disk.used = stats.disk.total - stats.disk.free;
|
||||
this.usage = stats.disk.used / stats.disk.total;
|
||||
this.total = stats.disk.total;
|
||||
this.used = stats.disk.used;
|
||||
this.available = stats.disk.available;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.disk
|
||||
> .pie
|
||||
padding 10px
|
||||
height 100px
|
||||
float left
|
||||
|
||||
> div
|
||||
float left
|
||||
width calc(100% - 100px)
|
||||
padding 10px 10px 10px 0
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
</style>
|
25
src/web/app/desktop/views/components/widgets/server.info.vue
Normal file
25
src/web/app/desktop/views/components/widgets/server.info.vue
Normal file
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="info">
|
||||
<p>Maintainer: <b>{{ meta.maintainer }}</b></p>
|
||||
<p>Machine: {{ meta.machine }}</p>
|
||||
<p>Node: {{ meta.node }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['meta']
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="info" scoped>
|
||||
.uptimes
|
||||
padding 10px 14px
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
</style>
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div class="memory">
|
||||
<x-pie class="pie" :value="usage"/>
|
||||
<div>
|
||||
<p>%fa:flask%Memory</p>
|
||||
<p>Total: {{ total | bytes(1) }}</p>
|
||||
<p>Used: {{ used | bytes(1) }}</p>
|
||||
<p>Free: {{ free | bytes(1) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XPie from './server.pie.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
'x-pie': XPie
|
||||
},
|
||||
props: ['connection'],
|
||||
data() {
|
||||
return {
|
||||
usage: 0,
|
||||
total: 0,
|
||||
used: 0,
|
||||
free: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.connection.on('stats', this.onStats);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.connection.off('stats', this.onStats);
|
||||
},
|
||||
methods: {
|
||||
onStats(stats) {
|
||||
stats.mem.used = stats.mem.total - stats.mem.free;
|
||||
this.usage = stats.mem.used / stats.mem.total;
|
||||
this.total = stats.mem.total;
|
||||
this.used = stats.mem.used;
|
||||
this.free = stats.mem.free;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.memory
|
||||
> .pie
|
||||
padding 10px
|
||||
height 100px
|
||||
float left
|
||||
|
||||
> div
|
||||
float left
|
||||
width calc(100% - 100px)
|
||||
padding 10px 10px 10px 0
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
</style>
|
61
src/web/app/desktop/views/components/widgets/server.pie.vue
Normal file
61
src/web/app/desktop/views/components/widgets/server.pie.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<svg 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">{{ (p * 100).toFixed(0) }}%</text>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
r: 0.4
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
color(): string {
|
||||
return `hsl(${180 - (this.value * 180)}, 80%, 70%)`;
|
||||
},
|
||||
strokeDashoffset(): number {
|
||||
return (1 - this.value) * (Math.PI * (this.r * 2));
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
svg
|
||||
display block
|
||||
height 100%
|
||||
|
||||
> circle
|
||||
transform-origin center
|
||||
transform rotate(-90deg)
|
||||
transition stroke-dashoffset 0.5s ease
|
||||
|
||||
> text
|
||||
font-size 0.15px
|
||||
fill rgba(0, 0, 0, 0.6)
|
||||
|
||||
</style>
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="uptimes">
|
||||
<p>Uptimes</p>
|
||||
<p>Process: {{ process ? process.toFixed(0) : '---' }}s</p>
|
||||
<p>OS: {{ os ? os.toFixed(0) : '---' }}s</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['connection'],
|
||||
data() {
|
||||
return {
|
||||
process: 0,
|
||||
os: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.connection.on('stats', this.onStats);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.connection.off('stats', this.onStats);
|
||||
},
|
||||
methods: {
|
||||
onStats(stats) {
|
||||
this.process = stats.process_uptime;
|
||||
this.os = stats.os_uptime;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.uptimes
|
||||
padding 10px 14px
|
||||
|
||||
> p
|
||||
margin 0
|
||||
font-size 12px
|
||||
color #505050
|
||||
|
||||
&:first-child
|
||||
font-weight bold
|
||||
</style>
|
127
src/web/app/desktop/views/components/widgets/server.vue
Normal file
127
src/web/app/desktop/views/components/widgets/server.vue
Normal file
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<div class="mkw-server" :data-melt="props.design == 2">
|
||||
<template v-if="props.design == 0">
|
||||
<p class="title">%fa:server%%i18n:desktop.tags.mk-server-home-widget.title%</p>
|
||||
<button @click="toggle" title="%i18n:desktop.tags.mk-server-home-widget.toggle%">%fa:sort%</button>
|
||||
</template>
|
||||
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||
<template v-if="!fetching">
|
||||
<x-cpu-memory v-show="props.view == 0" :connection="connection"/>
|
||||
<x-cpu v-show="props.view == 1" :connection="connection" :meta="meta"/>
|
||||
<x-memory v-show="props.view == 2" :connection="connection"/>
|
||||
<x-disk v-show="props.view == 3" :connection="connection"/>
|
||||
<x-uptimes v-show="props.view == 4" :connection="connection"/>
|
||||
<x-info v-show="props.view == 5" :connection="connection" :meta="meta"/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import define from '../../../../common/define-widget';
|
||||
import XCpuMemory from './server.cpu-memory.vue';
|
||||
import XCpu from './server.cpu.vue';
|
||||
import XMemory from './server.memory.vue';
|
||||
import XDisk from './server.disk.vue';
|
||||
import XUptimes from './server.uptimes.vue';
|
||||
import XInfo from './server.info.vue';
|
||||
|
||||
export default define({
|
||||
name: 'server',
|
||||
props: {
|
||||
design: 0,
|
||||
view: 0
|
||||
}
|
||||
}).extend({
|
||||
components: {
|
||||
'x-cpu-and-memory': XCpuMemory,
|
||||
'x-cpu': XCpu,
|
||||
'x-memory': XMemory,
|
||||
'x-disk': XDisk,
|
||||
'x-uptimes': XUptimes,
|
||||
'x-info': XInfo
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
meta: null,
|
||||
connection: null,
|
||||
connectionId: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
this.fetching = false;
|
||||
});
|
||||
|
||||
this.connection = (this as any).os.streams.serverStream.getConnection();
|
||||
this.connectionId = (this as any).os.streams.serverStream.use();
|
||||
},
|
||||
beforeDestroy() {
|
||||
(this as any).os.streams.serverStream.dispose(this.connectionId);
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
if (this.props.design == 5) {
|
||||
this.props.design = 0;
|
||||
} else {
|
||||
this.props.design++;
|
||||
}
|
||||
},
|
||||
func() {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mkw-server
|
||||
background #fff
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
&[data-melt]
|
||||
background transparent !important
|
||||
border none !important
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
padding 0 16px
|
||||
line-height 42px
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color #888
|
||||
box-shadow 0 1px rgba(0, 0, 0, 0.07)
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
> button
|
||||
position absolute
|
||||
z-index 2
|
||||
top 0
|
||||
right 0
|
||||
padding 0
|
||||
width 42px
|
||||
font-size 0.9em
|
||||
line-height 42px
|
||||
color #ccc
|
||||
|
||||
&:hover
|
||||
color #aaa
|
||||
|
||||
&:active
|
||||
color #999
|
||||
|
||||
> .fetching
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
</style>
|
8
src/web/app/filters/bytes.ts
Normal file
8
src/web/app/filters/bytes.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
Vue.filter('bytes', (v, digits = 0) => {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (v == 0) return '0Byte';
|
||||
const i = Math.floor(Math.log(v) / Math.log(1024));
|
||||
return (v / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i];
|
||||
});
|
1
src/web/app/filters/index.ts
Normal file
1
src/web/app/filters/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
require('./bytes');
|
|
@ -20,6 +20,9 @@ require('./common/views/directives');
|
|||
// Register global components
|
||||
require('./common/views/components');
|
||||
|
||||
// Register global filters
|
||||
require('./filters');
|
||||
|
||||
Vue.mixin({
|
||||
destroyed(this: any) {
|
||||
if (this.$el.parentNode) {
|
||||
|
|
Loading…
Reference in a new issue