mirror of
https://activitypub.software/TransFem-org/Sharkey.git
synced 2024-12-15 17:35:44 +01: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
|
// Register global components
|
||||||
require('./common/views/components');
|
require('./common/views/components');
|
||||||
|
|
||||||
|
// Register global filters
|
||||||
|
require('./filters');
|
||||||
|
|
||||||
Vue.mixin({
|
Vue.mixin({
|
||||||
destroyed(this: any) {
|
destroyed(this: any) {
|
||||||
if (this.$el.parentNode) {
|
if (this.$el.parentNode) {
|
||||||
|
|
Loading…
Reference in a new issue