summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-02-20 07:56:39 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-02-20 07:56:39 +0900
commitbc17a0b2cb3e789d79e277ecd64a10ae1ac2e7a5 (patch)
tree394de527e5adc8e2c543a3c1c3b59fee945dade4
parentwip (diff)
downloadsharkey-bc17a0b2cb3e789d79e277ecd64a10ae1ac2e7a5.tar.gz
sharkey-bc17a0b2cb3e789d79e277ecd64a10ae1ac2e7a5.tar.bz2
sharkey-bc17a0b2cb3e789d79e277ecd64a10ae1ac2e7a5.zip
wip
-rw-r--r--src/web/app/common/scripts/bytes-to-size.ts6
-rw-r--r--src/web/app/desktop/-tags/home-widgets/server.tag533
-rw-r--r--src/web/app/desktop/views/components/widgets/server.cpu-memory.vue127
-rw-r--r--src/web/app/desktop/views/components/widgets/server.cpu.vue68
-rw-r--r--src/web/app/desktop/views/components/widgets/server.disk.vue76
-rw-r--r--src/web/app/desktop/views/components/widgets/server.info.vue25
-rw-r--r--src/web/app/desktop/views/components/widgets/server.memory.vue76
-rw-r--r--src/web/app/desktop/views/components/widgets/server.pie.vue61
-rw-r--r--src/web/app/desktop/views/components/widgets/server.uptimes.vue46
-rw-r--r--src/web/app/desktop/views/components/widgets/server.vue127
-rw-r--r--src/web/app/filters/bytes.ts8
-rw-r--r--src/web/app/filters/index.ts1
-rw-r--r--src/web/app/init.ts3
13 files changed, 618 insertions, 539 deletions
diff --git a/src/web/app/common/scripts/bytes-to-size.ts b/src/web/app/common/scripts/bytes-to-size.ts
deleted file mode 100644
index 1d2b1e7ce3..0000000000
--- a/src/web/app/common/scripts/bytes-to-size.ts
+++ /dev/null
@@ -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];
-};
diff --git a/src/web/app/desktop/-tags/home-widgets/server.tag b/src/web/app/desktop/-tags/home-widgets/server.tag
deleted file mode 100644
index 992517163a..0000000000
--- a/src/web/app/desktop/-tags/home-widgets/server.tag
+++ /dev/null
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.cpu-memory.vue b/src/web/app/desktop/views/components/widgets/server.cpu-memory.vue
new file mode 100644
index 0000000000..00b3dc3af1
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.cpu-memory.vue
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.cpu.vue b/src/web/app/desktop/views/components/widgets/server.cpu.vue
new file mode 100644
index 0000000000..337ff62cec
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.cpu.vue
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.disk.vue b/src/web/app/desktop/views/components/widgets/server.disk.vue
new file mode 100644
index 0000000000..c21c56290e
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.disk.vue
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.info.vue b/src/web/app/desktop/views/components/widgets/server.info.vue
new file mode 100644
index 0000000000..870baf1497
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.info.vue
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.memory.vue b/src/web/app/desktop/views/components/widgets/server.memory.vue
new file mode 100644
index 0000000000..2afc627fd5
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.memory.vue
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.pie.vue b/src/web/app/desktop/views/components/widgets/server.pie.vue
new file mode 100644
index 0000000000..45ca8101b7
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.pie.vue
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.uptimes.vue b/src/web/app/desktop/views/components/widgets/server.uptimes.vue
new file mode 100644
index 0000000000..06713d83ce
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.uptimes.vue
@@ -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>
diff --git a/src/web/app/desktop/views/components/widgets/server.vue b/src/web/app/desktop/views/components/widgets/server.vue
new file mode 100644
index 0000000000..5aa01fd4e8
--- /dev/null
+++ b/src/web/app/desktop/views/components/widgets/server.vue
@@ -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>
diff --git a/src/web/app/filters/bytes.ts b/src/web/app/filters/bytes.ts
new file mode 100644
index 0000000000..3afb11e9ae
--- /dev/null
+++ b/src/web/app/filters/bytes.ts
@@ -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];
+});
diff --git a/src/web/app/filters/index.ts b/src/web/app/filters/index.ts
new file mode 100644
index 0000000000..16ff8c87a4
--- /dev/null
+++ b/src/web/app/filters/index.ts
@@ -0,0 +1 @@
+require('./bytes');
diff --git a/src/web/app/init.ts b/src/web/app/init.ts
index 8abb7f7aa5..c3eede0d36 100644
--- a/src/web/app/init.ts
+++ b/src/web/app/init.ts
@@ -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) {