refactor: charts and overview

This commit is contained in:
Torsten Dittmann
2022-11-08 14:54:56 +01:00
parent 4a23b4aa1a
commit 1ea46fdd97
14 changed files with 264 additions and 196 deletions
+17
View File
@@ -13,6 +13,7 @@
"@aw-labs/ui": "0.0.0-69",
"@popperjs/core": "^2.11.6",
"echarts": "^5.4.0",
"pretty-bytes": "^6.0.0",
"prismjs": "^1.29.0",
"tippy.js": "^6.3.7",
"web-vitals": "^3.0.3"
@@ -5942,6 +5943,17 @@
"svelte": "^3.2.0"
}
},
"node_modules/pretty-bytes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz",
"integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==",
"engines": {
"node": "^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
@@ -11977,6 +11989,11 @@
"dev": true,
"requires": {}
},
"pretty-bytes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz",
"integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg=="
},
"pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+1
View File
@@ -24,6 +24,7 @@
"@aw-labs/ui": "0.0.0-69",
"@popperjs/core": "^2.11.6",
"echarts": "^5.4.0",
"pretty-bytes": "^6.0.0",
"prismjs": "^1.29.0",
"tippy.js": "^6.3.7",
"web-vitals": "^3.0.3"
+1 -1
View File
@@ -6,7 +6,7 @@
"borderWidth": 1
},
"lineStyle": {
"width": 2
"width": 1
},
"symbolSize": 4,
"symbol": "emptyCircle",
+1 -1
View File
@@ -42,7 +42,7 @@
(await import('echarts/components')).TransformComponent,
(await import('echarts/components')).LegendComponent,
(await import('echarts/features')).LabelLayout,
(await import('echarts/renderers')).CanvasRenderer
(await import('echarts/renderers')).SVGRenderer
]);
makeChart();
});
+6 -1
View File
@@ -27,6 +27,11 @@ export const defaultConfig: EChartsOption = {
yAxis: {
type: 'value',
splitNumber: 3,
minInterval: 1
minInterval: 1,
splitLine: {
lineStyle: {
width: 1
}
}
}
};
+2 -2
View File
@@ -45,11 +45,11 @@
}
},
"axisLabel": {
"color": "#666666"
"color": "#868EA3"
},
"splitLine": {
"lineStyle": {
"color": ["#333333"]
"color": ["#4F5769"]
}
},
"splitArea": {
+4 -3
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import type { LineSeriesOption } from 'echarts/charts';
import type { EChartsOption } from 'echarts';
import Base from './base.svelte';
import { Colors } from './config';
import Base from './base.svelte';
export let series: LineSeriesOption[];
export let options: EChartsOption = null;
@@ -14,9 +14,10 @@
s.type = 'line';
s.stack = 'total';
s.lineStyle = {
shadowBlur: 1,
shadowBlur: 38,
shadowColor: Colors.Primary,
shadowOffsetY: 0
shadowOffsetY: 15,
shadowOffsetX: 0
};
s.showSymbol = false;
+8 -45
View File
@@ -1,3 +1,5 @@
import prettyBytes from 'pretty-bytes';
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
export function calculateSize(bytes: number, decimals = 1) {
@@ -20,55 +22,16 @@ export function bytesToSize(value: number, unit: string) {
return value / Math.pow(1024, index);
}
export function humanFileSize(value: number): {
export function humanFileSize(bytes: number): {
value: string;
unit: string;
} {
if (!value) return { value: 'No', unit: 'Bytes' };
const length = value.toString().length;
const unit =
length < 7
? 'kilobyte'
: length < 11
? 'megabyte'
: length < 15
? 'gigabyte'
: length < 19
? 'terabyte'
: 'petabyte';
const formatter = Intl.NumberFormat('en', {
style: 'unit',
maximumSignificantDigits: 3,
maximumFractionDigits: 2,
unit
});
while (value > 1000) {
value /= 1000;
}
const parts: {
integer: string;
unit: string;
fraction?: string;
decimal?: string;
} = formatter.formatToParts(value).reduce(
(prev, curr) => {
prev[curr.type] = curr.value;
return prev;
},
{
integer: '',
unit: '',
decimal: '',
fraction: ''
}
);
const value = prettyBytes(bytes, {
locale: 'en'
}).split(' ');
return {
value: `${parts.integer}${parts.decimal}${parts.fraction}`,
unit: parts.unit
value: value[0],
unit: value[1]
};
}
+1 -1
View File
@@ -5,6 +5,6 @@ import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async () => {
return {
header: Header,
breadcrumb: Breadcrumbs
breadcrumbs: Breadcrumbs
};
};
@@ -1,21 +1,41 @@
<script context="module" lang="ts">
const formatter = Intl.NumberFormat('en', {
notation: 'compact'
});
// TODO: metric type is wrong
export function last(set: Array<unknown>): Models.Metric | null {
return (set as Models.Metric[]).slice(-1)[0] ?? null;
}
// TODO: metric type is wrong
export function total(set: Array<unknown>): number {
return (set as Models.Metric[]).reduce((prev, curr) => prev + curr.value, 0);
}
export function format(number: number): string {
return formatter.format(number);
}
</script>
<script lang="ts">
import type { Models } from '@aw-labs/appwrite-console';
import { Container, type UsagePeriods } from '$lib/layout';
import { page } from '$app/stores';
import { project, stats } from '../store';
import { project } from '../store';
import { usage } from './store';
import { onMount } from 'svelte';
import { afterNavigate } from '$app/navigation';
import { DropList, DropListItem, Heading } from '$lib/components';
import { BarChart, LineChart } from '$lib/charts';
import { Heading } from '$lib/components';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { base } from '$app/paths';
import Realtime from './realtime.svelte';
import Bandwith from './bandwith.svelte';
import Requests from './requests.svelte';
$: projectId = $page.params.project;
$: path = `/console/project-${projectId}/overview`;
let period: UsagePeriods = '30d';
let showPeriodBandwidth = false;
let showPeriodRequests = false;
onMount(handle);
afterNavigate(handle);
@@ -28,40 +48,10 @@
}
}
const formatter = Intl.NumberFormat('en', {
notation: 'compact'
});
// TODO: metric type is wrong
function last(set: Array<unknown>): Models.Metric | null {
return (set as Models.Metric[]).slice(-1)[0] ?? null;
}
// TODO: metric type is wrong
function total(set: Array<unknown>): number {
return (set as Models.Metric[]).reduce((prev, curr) => prev + curr.value, 0);
}
function format(number: number): string {
return formatter.format(number);
}
function changePeriod(newPeriod: UsagePeriods) {
period = newPeriod;
usage.load(projectId, period);
showPeriodBandwidth = false;
showPeriodRequests = false;
}
//TODO: workaround for broken types
$: network = $usage?.network as unknown as Array<{
date: number;
value: number;
}>;
$: requests = $usage?.requests as unknown as Array<{
date: number;
value: number;
}>;
</script>
<svelte:head>
@@ -71,98 +61,14 @@
{#if $project}
<Container overlapCover>
{#if $usage}
{@const bandwith = humanFileSize(total($usage.network))}
{@const storage = humanFileSize(last($usage.storage).value)}
<section class="common-section">
<div class="grid-dashboard-1s-2m-6l">
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
<div class="u-flex u-gap-16 u-main-space-between">
<div>
<div class="heading-level-4">
{bandwith.value}
<span class="body-text-2">{bandwith.unit}</span>
</div>
<div>Bandwidth</div>
</div>
<DropList
bind:show={showPeriodBandwidth}
placement="bottom-start"
childStart
noArrow>
<button
class="transparent-button"
on:click={() => (showPeriodBandwidth = !showPeriodBandwidth)}>
<span class="text">{period}</span>
<span class="icon-cheveron-down" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
<DropListItem on:click={() => changePeriod('24h')}>
24h
</DropListItem>
<DropListItem on:click={() => changePeriod('30d')}>
30d
</DropListItem>
<DropListItem on:click={() => changePeriod('90d')}>
90d
</DropListItem>
</svelte:fragment>
</DropList>
</div>
{#if network.length}
<div style="height: 12rem;">
<BarChart
series={[
{
name: 'Bandwidth',
data: [...network.map((e) => [e.date, e.value])]
}
]} />
</div>
{/if}
<Bandwith {period} on:change={(e) => changePeriod(e.detail)} />
</div>
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
<div class="u-flex u-gap-16 u-main-space-between">
<div>
<div class="heading-level-4">
{format(total($usage.requests))}
</div>
<div>Requests</div>
</div>
<DropList
bind:show={showPeriodRequests}
placement="bottom-start"
childStart
noArrow>
<button
class="transparent-button"
on:click={() => (showPeriodRequests = !showPeriodRequests)}>
<span class="text">{period}</span>
<span class="icon-cheveron-down" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
<DropListItem on:click={() => changePeriod('24h')}>
24h
</DropListItem>
<DropListItem on:click={() => changePeriod('30d')}>
30d
</DropListItem>
<DropListItem on:click={() => changePeriod('90d')}>
90d
</DropListItem>
</svelte:fragment>
</DropList>
</div>
{#if network.length}
<div style="height: 12rem;">
<LineChart
series={[
{
name: 'Requests',
data: [...requests.map((e) => [e.date, e.value])]
}
]} />
</div>
{/if}
<Requests {period} on:change={(e) => changePeriod(e.detail)} />
</div>
<a
href={`${base}/console/project-${projectId}/databases`}
@@ -178,12 +84,14 @@
<div class="grid-item-1-start-end" />
<div class="grid-item-1-end-start">
<div class="heading-level-4">XX</div>
<div>Databases</div>
<div class="heading-level-4">
{format(last($usage.documents).value)}
</div>
<div>Documents</div>
</div>
<div class="grid-item-1-end-end">
<div class="text">Documents: {last($usage.documents).value}</div>
<div class="text">Databases: XX</div>
</div>
</div>
</a>
@@ -261,23 +169,7 @@
</a>
<div
class="card is-2-columns-medium-screen is-2-columns-large-screen is-2-rows-large-screen is-location-row-2-end-large-screen">
{#if $stats.get(projectId)}
<div class="heading-level-4">
{format($stats.get(projectId)[11][1])}
</div>
<div>Realtime Connections</div>
<div style="height: 18em">
<BarChart
series={[
{
name: 'Realtime connection',
data: $stats.get(projectId)
}
]} />
</div>
{:else}
<div>waiting for realtime connections</div>
{/if}
<Realtime />
</div>
</div>
</section>
@@ -0,0 +1,77 @@
<script lang="ts">
import { Card, DropList, DropListItem } from '$lib/components';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { total } from './+layout.svelte';
import { usage } from './store';
import type { UsagePeriods } from '$lib/layout';
import { createEventDispatcher } from 'svelte';
import { BarChart } from '$lib/charts';
export let period: UsagePeriods;
let showPeriod = false;
const dispatch = createEventDispatcher();
$: network = $usage?.network as unknown as Array<{
date: number;
value: number;
}>;
$: bandwith = humanFileSize(total($usage.network));
</script>
<div class="u-flex u-gap-16 u-main-space-between">
<div>
<div class="heading-level-4">
{bandwith.value}
<span class="body-text-2">{bandwith.unit}</span>
</div>
<div>Bandwidth</div>
</div>
<DropList bind:show={showPeriod} placement="bottom-start" childStart noArrow>
<button class="transparent-button" on:click={() => (showPeriod = !showPeriod)}>
<span class="text">{period}</span>
<span class="icon-cheveron-down" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
<DropListItem on:click={() => dispatch('change', '24h')}>24h</DropListItem>
<DropListItem on:click={() => dispatch('change', '30d')}>30d</DropListItem>
<DropListItem on:click={() => dispatch('change', '90d')}>90d</DropListItem>
</svelte:fragment>
</DropList>
</div>
{#if bandwith.value !== '0'}
<div style="height: 12rem;">
<BarChart
options={{
yAxis: {
axisLabel: {
formatter: (value) =>
value
? `${humanFileSize(+value).value} ${humanFileSize(+value).unit}`
: '0'
}
}
}}
series={[
{
name: 'Bandwidth',
data: [...network.map((e) => [e.date, e.value])],
tooltip: {
valueFormatter: (value) =>
`${humanFileSize(+value).value} ${humanFileSize(+value).unit}`
}
}
]} />
</div>
{:else}
<Card>
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
<span
class="icon-chart-square-bar text-large"
aria-hidden="true"
style="font-size: var(--icon-size-large);" />
<p class="u-bold">No data to show</p>
</div>
</Card>
{/if}
@@ -9,7 +9,7 @@ export const load: PageLoad = async ({ params, depends }) => {
return {
header: Header,
breadcrumb: Breadcrumbs,
breadcrumbs: Breadcrumbs,
platform: await sdkForConsole.projects.getPlatform(params.project, params.platform)
};
};
@@ -0,0 +1,45 @@
<script lang="ts">
import { page } from '$app/stores';
import { BarChart } from '$lib/charts';
import { Card } from '$lib/components';
import { stats } from '../store';
import { format } from './+layout.svelte';
$: projectId = $page.params.project;
$: projectStats = $stats?.get(projectId);
$: current = projectStats ? projectStats[11][1] : 0;
$: total = projectStats?.reduce((prev, next) => prev + next[1], 0) ?? 0;
</script>
<div class="heading-level-4">
{format(current)}
</div>
<div>Realtime Connections</div>
{#if total}
<div style="height: 18em">
<BarChart
series={[
{
name: 'Realtime connection',
data: $stats.get(projectId)
}
]} />
</div>
{:else}
<Card>
<div
class="u-flex u-cross-center u-flex-vertical u-main-center u-flex"
style="height: 10rem;">
<span
class="icon-chart-square-bar text-large"
aria-hidden="true"
style="font-size: var(--icon-size-large);" />
<p class="u-bold">No data to show</p>
<a
class="link"
href="https://appwrite.io/docs/realtime"
target="_blank"
rel="noopener noreferrer">Get started with Realtime</a>
</div>
</Card>
{/if}
@@ -0,0 +1,67 @@
<script lang="ts">
import { Card, DropList, DropListItem } from '$lib/components';
import { format, total } from './+layout.svelte';
import { usage } from './store';
import type { UsagePeriods } from '$lib/layout';
import { createEventDispatcher } from 'svelte';
import { LineChart } from '$lib/charts';
export let period: UsagePeriods;
let showPeriod = false;
const dispatch = createEventDispatcher();
$: requests = $usage?.requests as unknown as Array<{
date: number;
value: number;
}>;
</script>
<div class="u-flex u-gap-16 u-main-space-between">
<div>
<div class="heading-level-4">
{format(total($usage.requests))}
</div>
<div>Requests</div>
</div>
<DropList bind:show={showPeriod} placement="bottom-start" childStart noArrow>
<button class="transparent-button" on:click={() => (showPeriod = !showPeriod)}>
<span class="text">{period}</span>
<span class="icon-cheveron-down" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
<DropListItem on:click={() => dispatch('change', '24h')}>24h</DropListItem>
<DropListItem on:click={() => dispatch('change', '30d')}>30d</DropListItem>
<DropListItem on:click={() => dispatch('change', '90d')}>90d</DropListItem>
</svelte:fragment>
</DropList>
</div>
{#if total($usage.requests) !== 0}
<div style="height: 12rem;">
<LineChart
options={{
yAxis: {
axisLabel: {
formatter: format
}
}
}}
series={[
{
name: 'Requests',
data: [...requests.map((e) => [e.date, e.value])]
}
]} />
</div>
{:else}
<Card>
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
<span
class="icon-chart-square-bar text-large"
aria-hidden="true"
style="font-size: var(--icon-size-large);" />
<p class="u-bold">No data to show</p>
</div>
</Card>
{/if}