mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #1629 from appwrite/database-reads-writes
Add: database read, writes to usage
This commit is contained in:
@@ -13,7 +13,6 @@
|
||||
{formatted}
|
||||
series={series.map((s) => {
|
||||
s.type = 'bar';
|
||||
s.stack = 'total';
|
||||
s.barMaxWidth = 6;
|
||||
s.itemStyle = {
|
||||
borderRadius: [10, 10, 0, 0]
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { default as BarChart } from './bar.svelte';
|
||||
export { default as LineChart } from './line.svelte';
|
||||
export { default as Legend, type LegendData } from './legend.svelte';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<script context="module" lang="ts">
|
||||
export type LegendData = {
|
||||
name: string;
|
||||
value: string | number | boolean;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Colors } from '$lib/charts/config';
|
||||
import { Status } from '$lib/components';
|
||||
|
||||
export let legendData: LegendData[] = [];
|
||||
|
||||
let colors = Object.values(Colors);
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-cross-center u-gap-16">
|
||||
{#each legendData as { name, value }, index}
|
||||
<Status status="none" statusIconStyle="background-color: {colors[index % colors.length]}">
|
||||
{name} ({value})
|
||||
</Status>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -6,17 +6,35 @@
|
||||
| 'completed'
|
||||
| 'processing'
|
||||
| 'ready'
|
||||
| 'building';
|
||||
| 'building'
|
||||
| 'none';
|
||||
|
||||
export let statusIconStyle: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="status"
|
||||
class="status u-cross-center"
|
||||
class:is-pending={status === 'pending'}
|
||||
class:is-failed={status === 'failed'}
|
||||
class:is-complete={status === 'completed' || status === 'ready'}
|
||||
class:is-processing={status === 'processing' || status === 'building'}>
|
||||
{#if status}
|
||||
<span class="status-icon" />
|
||||
<span class="status-icon" style={statusIconStyle} />
|
||||
{/if}
|
||||
<span class="text" data-private><slot /></span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.status-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.status {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.text {
|
||||
line-height: 140%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,6 +13,7 @@ export { default as WizardStep } from './wizardStep.svelte';
|
||||
export { default as Breadcrumbs } from './breadcrumbs.svelte';
|
||||
export { default as Unauthenticated } from './unauthenticated.svelte';
|
||||
export { default as Usage, type UsagePeriods } from './usage.svelte';
|
||||
export { default as UsageMultiple } from './usageMultiple.svelte';
|
||||
export { default as Activity } from './activity.svelte';
|
||||
export { default as Progress } from './progress.svelte';
|
||||
export { default as GridHeader } from './gridHeader.svelte';
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
metrics: Models.Metric[],
|
||||
endingTotal: number
|
||||
): Array<[string, number]> {
|
||||
return metrics.reduceRight(
|
||||
return (metrics ?? []).reduceRight(
|
||||
(acc, curr) => {
|
||||
acc.data.unshift([curr.date, acc.total]);
|
||||
acc.total -= curr.value;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import { Container } from '$lib/layout';
|
||||
import { BarChart, Legend, type LegendData } from '$lib/charts';
|
||||
import { accumulateFromEndingTotal } from '$lib/layout/usage.svelte';
|
||||
import { Card, Heading, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
|
||||
import { page } from '$app/stores';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
import { formatNumberWithCommas } from '$lib/helpers/numbers';
|
||||
|
||||
export let title: string;
|
||||
export let total: number[];
|
||||
export let path: string = null;
|
||||
export let count: Models.Metric[][];
|
||||
export let legendData: LegendData[];
|
||||
export let showHeader: boolean = true;
|
||||
export let overlapContainerCover = false;
|
||||
</script>
|
||||
|
||||
<Container overlapCover={overlapContainerCover}>
|
||||
<div class="u-flex u-main-space-between common-section">
|
||||
{#if showHeader}
|
||||
<Heading tag="h2" size="5">{title}</Heading>
|
||||
{/if}
|
||||
|
||||
{#if path}
|
||||
<SecondaryTabs>
|
||||
<SecondaryTabsItem href={`${path}/24h`} disabled={$page.params.period === '24h'}>
|
||||
24h
|
||||
</SecondaryTabsItem>
|
||||
<SecondaryTabsItem
|
||||
href={`${path}/30d`}
|
||||
disabled={!$page.params.period || $page.params.period === '30d'}>
|
||||
30d
|
||||
</SecondaryTabsItem>
|
||||
<SecondaryTabsItem href={`${path}/90d`} disabled={$page.params.period === '90d'}>
|
||||
90d
|
||||
</SecondaryTabsItem>
|
||||
</SecondaryTabs>
|
||||
{/if}
|
||||
</div>
|
||||
<Card>
|
||||
{#if count}
|
||||
{@const totalCount = total.reduce((a, b) => a + b, 0)}
|
||||
|
||||
<Heading tag="h6" size="6">{formatNumberWithCommas(totalCount)}</Heading>
|
||||
<p>Total {title.toLocaleLowerCase()}</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
|
||||
<div class="multiple-chart-container u-flex-vertical u-gap-16">
|
||||
<BarChart
|
||||
formatted={$page.params.period === '24h' ? 'hours' : 'days'}
|
||||
series={count.map((c, index) => ({
|
||||
name: legendData[index].name,
|
||||
data: accumulateFromEndingTotal(c, total[index])
|
||||
}))} />
|
||||
|
||||
{#if legendData}
|
||||
<Legend {legendData} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Card>
|
||||
</Container>
|
||||
|
||||
<style lang="scss">
|
||||
.multiple-chart-container {
|
||||
height: 12rem;
|
||||
}
|
||||
|
||||
:global(.multiple-chart-container .echart) {
|
||||
margin-top: -1em;
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
</style>
|
||||
@@ -179,9 +179,13 @@ export type Aggregation = {
|
||||
export type OrganizationUsage = {
|
||||
bandwidth: Array<Models.Metric>;
|
||||
executions: Array<Models.Metric>;
|
||||
databasesReads: Array<Models.Metric>;
|
||||
databasesWrites: Array<Models.Metric>;
|
||||
executionsTotal: number;
|
||||
filesStorageTotal: number;
|
||||
buildsStorageTotal: number;
|
||||
databasesReadsTotal: number;
|
||||
databasesWritesTotal: number;
|
||||
deploymentsStorageTotal: number;
|
||||
executionsMBSecondsTotal: number;
|
||||
buildsMBSecondsTotal: number;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { bytesToSize, humanFileSize, mbSecondsToGBHours } from '$lib/helpers/sizeConvertion';
|
||||
import { BarChart } from '$lib/charts';
|
||||
import { BarChart, Legend } from '$lib/charts';
|
||||
import ProjectBreakdown from './ProjectBreakdown.svelte';
|
||||
import { formatNum } from '$lib/helpers/string';
|
||||
import { accumulateFromEndingTotal, total } from '$lib/layout/usage.svelte';
|
||||
@@ -29,6 +29,11 @@
|
||||
const plan = data?.plan ?? undefined;
|
||||
|
||||
$: project = (data.organizationUsage as OrganizationUsage).projects;
|
||||
|
||||
$: legendData = [
|
||||
{ name: 'Reads', value: data.organizationUsage.databasesReadsTotal },
|
||||
{ name: 'Writes', value: data.organizationUsage.databasesWritesTotal }
|
||||
];
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -133,7 +138,7 @@
|
||||
<span
|
||||
class="icon-chart-square-bar text-large"
|
||||
aria-hidden="true"
|
||||
style="font-size: 32px;" />
|
||||
style:font-size="32px" />
|
||||
<p class="u-bold">No data to show</p>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -193,6 +198,67 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Database reads and writes</Heading>
|
||||
|
||||
<p class="text">
|
||||
The total number of database reads and writes across all projects in your organization.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if data.organizationUsage.databasesReads || data.organizationUsage.databasesWrites}
|
||||
<div style:margin-top="-1.5em" style:margin-bottom="-1em">
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: formatNum
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Reads',
|
||||
data: [
|
||||
...(data.organizationUsage.databasesReads ?? []).map((e) => [
|
||||
e.date,
|
||||
e.value
|
||||
])
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Writes',
|
||||
data: [
|
||||
...(data.organizationUsage.databasesWrites ?? []).map((e) => [
|
||||
e.date,
|
||||
e.value
|
||||
])
|
||||
]
|
||||
}
|
||||
]} />
|
||||
</div>
|
||||
|
||||
<Legend {legendData} />
|
||||
|
||||
{#if project?.length > 0}
|
||||
<ProjectBreakdown
|
||||
{data}
|
||||
projects={project}
|
||||
databaseOperationMetric={['databasesReads', 'databasesWrites']} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<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: 32px;" />
|
||||
<p class="u-bold">No data to show</p>
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Executions</Heading>
|
||||
|
||||
@@ -318,6 +384,7 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">GB hours</Heading>
|
||||
|
||||
@@ -378,6 +445,7 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Phone OTP</Heading>
|
||||
<p class="text">
|
||||
@@ -430,6 +498,7 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<TotalMembers members={data?.organizationMembers} />
|
||||
|
||||
<p class="text common-section u-color-text-gray">
|
||||
|
||||
@@ -29,7 +29,11 @@ export const load: PageLoad = async ({ params, parent }) => {
|
||||
executionsMBSecondsTotal: null,
|
||||
buildsMBSecondsTotal: null,
|
||||
authPhoneTotal: null,
|
||||
authPhoneEstimate: null
|
||||
authPhoneEstimate: null,
|
||||
databasesReads: null,
|
||||
databasesWrites: null,
|
||||
databasesReadsTotal: null,
|
||||
databasesWritesTotal: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+98
-23
@@ -15,14 +15,26 @@
|
||||
import type { OrganizationUsage } from '$lib/sdk/billing';
|
||||
import { base } from '$app/paths';
|
||||
import { canSeeProjects } from '$lib/stores/roles';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
type Metric =
|
||||
| 'users'
|
||||
| 'storage'
|
||||
| 'bandwidth'
|
||||
| 'executions'
|
||||
| 'authPhoneTotal'
|
||||
| 'databasesReads'
|
||||
| 'databasesWrites';
|
||||
|
||||
type Metric = 'users' | 'storage' | 'bandwidth' | 'executions' | 'authPhoneTotal';
|
||||
type Estimate = 'authPhoneEstimate';
|
||||
|
||||
type DatabaseOperationMetric = Extract<Metric, 'databasesReads' | 'databasesWrites'>;
|
||||
|
||||
export let data: PageData;
|
||||
export let projects: OrganizationUsage['projects'];
|
||||
export let metric: Metric;
|
||||
export let metric: Metric | undefined = undefined;
|
||||
export let estimate: Estimate | undefined = undefined;
|
||||
export let databaseOperationMetric: DatabaseOperationMetric[] | undefined = undefined;
|
||||
|
||||
function getMetricTitle(metric: Metric): string {
|
||||
switch (metric) {
|
||||
@@ -38,25 +50,48 @@
|
||||
}
|
||||
|
||||
function groupByProject(
|
||||
metric: Metric,
|
||||
estimate?: Estimate
|
||||
): Array<{ projectId: string; usage: number; estimate?: number }> {
|
||||
metric: Metric | undefined,
|
||||
estimate?: Estimate,
|
||||
databaseOps?: DatabaseOperationMetric[]
|
||||
): Array<{
|
||||
projectId: string;
|
||||
databasesReads?: number;
|
||||
databasesWrites?: number;
|
||||
usage?: number;
|
||||
estimate?: number;
|
||||
}> {
|
||||
const data = [];
|
||||
for (const project of projects) {
|
||||
const usage = project[metric];
|
||||
if (!usage) {
|
||||
continue;
|
||||
if (metric) {
|
||||
const usage = project[metric];
|
||||
if (!usage) continue;
|
||||
|
||||
data.push({
|
||||
projectId: project.projectId,
|
||||
usage: usage ?? 0,
|
||||
estimate: estimate ? project[estimate] : undefined
|
||||
});
|
||||
} else if (databaseOps) {
|
||||
const reads = project['databasesReads'] ?? 0;
|
||||
const writes = project['databasesWrites'] ?? 0;
|
||||
|
||||
if (reads || writes) {
|
||||
data.push({
|
||||
projectId: project.projectId,
|
||||
databasesReads: reads,
|
||||
databasesWrites: writes
|
||||
});
|
||||
}
|
||||
}
|
||||
data.push({
|
||||
projectId: project.projectId,
|
||||
usage: usage ?? 0,
|
||||
estimate: estimate ? project[estimate] : undefined
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function format(value: number): string {
|
||||
if (databaseOperationMetric) {
|
||||
return abbreviateNumber(value);
|
||||
}
|
||||
|
||||
switch (metric) {
|
||||
case 'authPhoneTotal':
|
||||
return formatNumberWithCommas(value);
|
||||
@@ -68,6 +103,12 @@
|
||||
return humanFileSize(value).value + humanFileSize(value).unit;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (metric === undefined && databaseOperationMetric === undefined) {
|
||||
throw new Error(`metric or database operations must be defined`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Collapsible>
|
||||
@@ -76,7 +117,13 @@
|
||||
<TableScroll dense noStyles noMargin style="table-layout: auto">
|
||||
<TableHeader>
|
||||
<TableCellHead>Project</TableCellHead>
|
||||
<TableCellHead>{getMetricTitle(metric)}</TableCellHead>
|
||||
{#if databaseOperationMetric}
|
||||
<TableCellHead>Reads</TableCellHead>
|
||||
<TableCellHead>Writes</TableCellHead>
|
||||
{:else}
|
||||
<TableCellHead>{getMetricTitle(metric)}</TableCellHead>
|
||||
{/if}
|
||||
|
||||
{#if estimate}
|
||||
<TableCellHead>Estimated cost</TableCellHead>
|
||||
{/if}
|
||||
@@ -85,17 +132,33 @@
|
||||
{/if}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each groupByProject(metric, estimate).sort((a, b) => b.usage - a.usage) as project}
|
||||
{#each groupByProject(metric, estimate, databaseOperationMetric).sort((a, b) => {
|
||||
const aValue = a.usage ?? a.databasesReads ?? 0;
|
||||
const bValue = b.usage ?? b.databasesReads ?? 0;
|
||||
return bValue - aValue;
|
||||
}) as project}
|
||||
{#if !$canSeeProjects}
|
||||
<TableRow>
|
||||
<TableCell title="Project">
|
||||
{data.projectNames[project.projectId]?.name ?? 'Unknown'}
|
||||
</TableCell>
|
||||
<TableCell title={getMetricTitle(metric)}
|
||||
>{format(project.usage)}</TableCell>
|
||||
{#if databaseOperationMetric}
|
||||
<TableCell title="Reads">
|
||||
{format(project.databasesReads ?? 0)}
|
||||
</TableCell>
|
||||
<TableCell title="Writes">
|
||||
{format(project.databasesWrites ?? 0)}
|
||||
</TableCell>
|
||||
{:else}
|
||||
<TableCell>
|
||||
{format(project.usage)}
|
||||
</TableCell>
|
||||
{/if}
|
||||
|
||||
{#if project.estimate}
|
||||
<TableCell title="Estimated cost"
|
||||
>{formatCurrency(project.estimate)}</TableCell>
|
||||
<TableCell title="Estimated cost">
|
||||
{formatCurrency(project.estimate)}
|
||||
</TableCell>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{:else}
|
||||
@@ -103,11 +166,23 @@
|
||||
<TableCell title="Project">
|
||||
{data.projectNames[project.projectId]?.name ?? 'Unknown'}
|
||||
</TableCell>
|
||||
<TableCell title={getMetricTitle(metric)}
|
||||
>{format(project.usage)}</TableCell>
|
||||
{#if databaseOperationMetric}
|
||||
<TableCell title="Reads">
|
||||
{format(project.databasesReads ?? 0)}
|
||||
</TableCell>
|
||||
<TableCell title="Writes">
|
||||
{format(project.databasesWrites ?? 0)}
|
||||
</TableCell>
|
||||
{:else}
|
||||
<TableCell>
|
||||
{format(project.usage)}
|
||||
</TableCell>
|
||||
{/if}
|
||||
|
||||
{#if project.estimate}
|
||||
<TableCell title="Estimated cost"
|
||||
>{formatCurrency(project.estimate)}</TableCell>
|
||||
<TableCell title="Estimated cost">
|
||||
{formatCurrency(project.estimate)}
|
||||
</TableCell>
|
||||
{/if}
|
||||
<TableCell right={true}>
|
||||
<span
|
||||
|
||||
+29
-10
@@ -1,21 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Usage } from '$lib/layout';
|
||||
import { Usage, UsageMultiple } from '$lib/layout';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
$: total = data.collectionsTotal;
|
||||
$: count = data.collections;
|
||||
|
||||
$: reads = data.databaseReads;
|
||||
$: readsTotal = data.databaseReadsTotal;
|
||||
|
||||
$: writes = data.databaseWrites;
|
||||
$: writesTotal = data.databaseWritesTotal;
|
||||
</script>
|
||||
|
||||
<Usage
|
||||
title="Databases"
|
||||
path={`${base}/project-${$page.params.project}/databases/database-${$page.params.database}/usage`}
|
||||
{total}
|
||||
{count}
|
||||
countMetadata={{
|
||||
legend: 'Collections',
|
||||
title: 'Total collections'
|
||||
}} />
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Usage
|
||||
title="Usage"
|
||||
path={`${base}/project-${$page.params.project}/databases/database-${$page.params.database}/usage`}
|
||||
{total}
|
||||
{count}
|
||||
countMetadata={{
|
||||
legend: 'Collections',
|
||||
title: 'Total collections'
|
||||
}} />
|
||||
|
||||
<UsageMultiple
|
||||
title="Reads and writes"
|
||||
showHeader={false}
|
||||
overlapContainerCover
|
||||
total={[readsTotal, writesTotal]}
|
||||
count={[reads, writes]}
|
||||
legendData={[
|
||||
{ name: 'Reads', value: readsTotal },
|
||||
{ name: 'Writes', value: writesTotal }
|
||||
]} />
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Usage } from '$lib/layout';
|
||||
import { Usage, UsageMultiple } from '$lib/layout';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
$: total = data.databasesTotal;
|
||||
$: count = data.databases;
|
||||
|
||||
$: reads = data.databasesReads;
|
||||
$: readsTotal = data.databasesReadsTotal;
|
||||
|
||||
$: writes = data.databasesWrites;
|
||||
$: writesTotal = data.databasesWritesTotal;
|
||||
</script>
|
||||
|
||||
<Usage
|
||||
title="Databases"
|
||||
path={`${base}/project-${$page.params.project}/databases/usage`}
|
||||
{total}
|
||||
{count}
|
||||
countMetadata={{
|
||||
legend: 'Databases',
|
||||
title: 'Total databases'
|
||||
}} />
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Usage
|
||||
title="Usage"
|
||||
path={`${base}/project-${$page.params.project}/databases/usage`}
|
||||
{total}
|
||||
{count}
|
||||
countMetadata={{
|
||||
legend: 'Databases',
|
||||
title: 'Total databases'
|
||||
}} />
|
||||
|
||||
<UsageMultiple
|
||||
title="Reads and writes"
|
||||
showHeader={false}
|
||||
overlapContainerCover
|
||||
total={[readsTotal, writesTotal]}
|
||||
count={[reads, writes]}
|
||||
legendData={[
|
||||
{ name: 'Reads', value: readsTotal },
|
||||
{ name: 'Writes', value: writesTotal }
|
||||
]} />
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { bytesToSize, humanFileSize, mbSecondsToGBHours } from '$lib/helpers/sizeConvertion';
|
||||
import { BarChart } from '$lib/charts';
|
||||
import { BarChart, Legend } from '$lib/charts';
|
||||
import { formatNum } from '$lib/helpers/string';
|
||||
import { total } from '$lib/layout/usage.svelte';
|
||||
import { BillingPlan } from '$lib/constants.js';
|
||||
@@ -37,6 +37,14 @@
|
||||
data.usage.deploymentsStorageTotal +
|
||||
data.usage.buildsStorageTotal;
|
||||
|
||||
$: dbReads = data.usage.databasesReads;
|
||||
$: dbWrites = data.usage.databasesWrites;
|
||||
|
||||
$: legendData = [
|
||||
{ name: 'Reads', value: data.usage.databasesReadsTotal },
|
||||
{ name: 'Writes', value: data.usage.databasesWritesTotal }
|
||||
];
|
||||
|
||||
const tier = data?.currentInvoice?.plan ?? $organization?.billingPlan;
|
||||
const plan = tierToPlan(tier).name;
|
||||
|
||||
@@ -196,6 +204,48 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Database reads and writes</Heading>
|
||||
|
||||
<p class="text">Total database reads and writes in your project.</p>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
{#if dbReads || dbWrites}
|
||||
<div style:margin-top="-1.5em" style:margin-bottom="-1em">
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: formatNum
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Reads',
|
||||
data: [...dbReads.map((e) => [e.date, e.value])]
|
||||
},
|
||||
{
|
||||
name: 'Writes',
|
||||
data: [...dbWrites.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
</div>
|
||||
|
||||
<Legend {legendData} />
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<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: 32px;" />
|
||||
<p class="u-bold">No data to show</p>
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Executions</Heading>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user