|
1 | 1 | <script lang="ts">
|
2 | 2 | import { page } from '$app/state';
|
| 3 | + import EChart from '$lib/chart/EChart.svelte'; |
| 4 | + import GraphErrors from '$lib/GraphErrors.svelte'; |
| 5 | + import type { EChartsOption } from 'echarts'; |
| 6 | +
|
3 | 7 | import { UtilizationResourceType } from '$houdini';
|
4 | 8 | import Card from '$lib/Card.svelte';
|
5 | 9 | import { euroValueFormatter } from '$lib/chart/cost_transformer';
|
6 |
| - import EChart from '$lib/chart/EChart.svelte'; |
7 | 10 | import SummaryCard from '$lib/components/SummaryCard.svelte';
|
8 |
| - import GraphErrors from '$lib/GraphErrors.svelte'; |
9 | 11 | import CpuIcon from '$lib/icons/CpuIcon.svelte';
|
10 | 12 | import MemoryIcon from '$lib/icons/MemoryIcon.svelte';
|
11 | 13 | import { cpuUtilization, memoryUtilization, yearlyOverageCost } from '$lib/utils/resources';
|
| 14 | + import { Heading } from '@nais/ds-svelte-community'; |
12 | 15 | import { WalletIcon } from '@nais/ds-svelte-community/icons';
|
13 |
| - import type { EChartsOption } from 'echarts'; |
14 | 16 | import prettyBytes from 'pretty-bytes';
|
15 |
| -
|
16 | 17 | import type { PageProps } from './$houdini';
|
17 | 18 |
|
18 | 19 | let { data }: PageProps = $props();
|
|
29 | 30 | limit?: number,
|
30 | 31 | color: string = '#000000'
|
31 | 32 | ): EChartsOption {
|
32 |
| - const dates = data?.map((d) => d.timestamp) || []; |
| 33 | + const safeData = data ?? []; |
| 34 | + const dates = safeData.map((d) => |
| 35 | + d.timestamp.toLocaleString('en-GB', { |
| 36 | + year: 'numeric', |
| 37 | + month: 'short', |
| 38 | + day: 'numeric', |
| 39 | + hour: '2-digit', |
| 40 | + minute: '2-digit' |
| 41 | + }) |
| 42 | + ); |
| 43 | +
|
| 44 | + const usageValues = safeData.map((d) => d.value); |
| 45 | + const referenceValues = Array(safeData.length).fill(null); |
| 46 | + const limitValues = limit !== undefined ? Array(safeData.length).fill(limit) : referenceValues; |
| 47 | + const requestValues = Array(safeData.length).fill(request); |
| 48 | +
|
33 | 49 | return {
|
34 | 50 | tooltip: {
|
35 | 51 | trigger: 'axis',
|
|
42 | 58 | xAxis: {
|
43 | 59 | type: 'category',
|
44 | 60 | boundaryGap: false,
|
45 |
| - data: dates.map((date) => { |
46 |
| - return date.toLocaleDateString('en-GB', { |
47 |
| - year: 'numeric', |
48 |
| - month: 'short', |
49 |
| - day: 'numeric', |
50 |
| - hour: '2-digit', |
51 |
| - minute: '2-digit' |
52 |
| - }); |
53 |
| - }) |
| 61 | + data: dates |
54 | 62 | },
|
55 | 63 | series: [
|
56 | 64 | {
|
57 | 65 | name: 'Usage',
|
58 | 66 | type: 'line',
|
59 |
| - data: data?.map((d) => d.value) || [], |
60 |
| - color |
61 |
| - }, |
62 |
| - { |
63 |
| - name: 'Limit', |
64 |
| - type: 'line', |
65 |
| - data: data?.map(() => limit) || [], |
| 67 | + data: usageValues, |
66 | 68 | showSymbol: false,
|
67 |
| - color: '#C30000' |
| 69 | + color, |
| 70 | + areaStyle: { |
| 71 | + opacity: 0.2 |
| 72 | + } |
68 | 73 | },
|
| 74 | + ...(limit !== undefined |
| 75 | + ? [ |
| 76 | + { |
| 77 | + name: 'Limit', |
| 78 | + type: 'line', |
| 79 | + data: limitValues, |
| 80 | + showSymbol: false, |
| 81 | + color: limitColor, |
| 82 | + markLine: { |
| 83 | + symbol: ['none', 'none'], |
| 84 | + data: [ |
| 85 | + { |
| 86 | + yAxis: limit, |
| 87 | + label: { |
| 88 | + formatter: 'Limit', |
| 89 | + position: 'end', |
| 90 | + color: limitColor, |
| 91 | + padding: [2, 5], |
| 92 | + borderRadius: 3 |
| 93 | + } |
| 94 | + } |
| 95 | + ], |
| 96 | + lineStyle: { |
| 97 | + type: 'solid', |
| 98 | + color: limitColor |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + ] |
| 103 | + : []), |
69 | 104 | {
|
70 | 105 | name: 'Requested',
|
71 | 106 | type: 'line',
|
72 |
| - data: data?.map(() => request) || [], |
| 107 | + data: requestValues, |
73 | 108 | showSymbol: false,
|
74 |
| - color: '#00C300' |
| 109 | + color: requestColor, |
| 110 | + markLine: { |
| 111 | + symbol: ['none', 'none'], |
| 112 | + data: [ |
| 113 | + { |
| 114 | + yAxis: request, |
| 115 | + label: { |
| 116 | + formatter: 'Requested', |
| 117 | + position: 'end', |
| 118 | + color: requestColor, |
| 119 | + padding: [2, 5], |
| 120 | + borderRadius: 3 |
| 121 | + } |
| 122 | + } |
| 123 | + ], |
| 124 | + lineStyle: { |
| 125 | + type: 'solid', |
| 126 | + color: requestColor |
| 127 | + } |
| 128 | + } |
75 | 129 | }
|
76 | 130 | ],
|
77 |
| -
|
78 | 131 | yAxis: {
|
79 | 132 | type: 'value',
|
80 | 133 | name: 'Usage of requested resources',
|
|
85 | 138 | }
|
86 | 139 | } as EChartsOption;
|
87 | 140 | }
|
| 141 | +
|
| 142 | + const limitColor = '#DE2E2E'; |
| 143 | + const usageMemColor = '#8269A2'; |
| 144 | + const usageCPUColor = '#FF9100'; |
| 145 | + const requestColor = '#3386E0'; |
88 | 146 | </script>
|
89 | 147 |
|
90 | 148 | <GraphErrors errors={$ResourceUtilizationForApp.errors} />
|
91 | 149 |
|
92 | 150 | {#if $ResourceUtilizationForApp.data}
|
93 | 151 | {@const utilization = $ResourceUtilizationForApp.data.team.environment.application.utilization}
|
94 |
| - |
95 | 152 | <div class="grid">
|
96 | 153 | <Card columns={3} borderColor="#83bff6">
|
97 | 154 | <SummaryCard
|
|
171 | 228 | )}
|
172 | 229 | </SummaryCard>
|
173 | 230 | </Card>
|
174 |
| - <Card columns={12} borderColor="var(--a-gray-100)"> |
175 |
| - <span class="graphHeader"> |
176 |
| - <h3 style="margin-bottom: 0">Memory usage</h3> |
177 |
| - <span class="intervalPicker"> |
178 |
| - {#each ['1h', '6h', '1d', '7d', '30d'] as interval (interval)} |
179 |
| - <a |
180 |
| - class:active={(page.url.searchParams.get('interval') || '7d') == interval} |
181 |
| - href="?interval={interval}">{interval}</a |
182 |
| - > |
183 |
| - {/each} |
184 |
| - </span> |
185 |
| - </span> |
186 |
| - <EChart |
187 |
| - options={options( |
188 |
| - utilization.memory_series.map((d) => { |
189 |
| - return { timestamp: d.timestamp, value: d.value / 1024 / 1024 / 1024 }; |
190 |
| - }), |
191 |
| - utilization.requested_memory / 1024 / 1024 / 1024, |
192 |
| - utilization.limit_memory ? utilization.limit_memory / 1024 / 1024 / 1024 : undefined, |
193 |
| - 'rgb(145, 220, 117)' |
194 |
| - )} |
195 |
| - style="height: 400px" |
196 |
| - /> |
197 |
| - |
198 |
| - <span class="graphHeader"> |
199 |
| - <h3 style="margin-bottom: 0">CPU usage</h3> |
200 |
| - </span> |
201 |
| - <EChart |
202 |
| - options={options( |
203 |
| - utilization.cpu_series, |
204 |
| - utilization.requested_cpu, |
205 |
| - utilization.limit_cpu ? utilization.limit_cpu : undefined, |
206 |
| - 'rgb(131, 191, 246)' |
207 |
| - )} |
208 |
| - style="height: 400px" |
209 |
| - /> |
210 |
| - </Card> |
211 | 231 | </div>
|
| 232 | + <span class="graphHeader"> |
| 233 | + <Heading level="2" size="medium">Memory usage</Heading> |
| 234 | + <span class="intervalPicker"> |
| 235 | + {#each ['1h', '6h', '1d', '7d', '30d'] as interval (interval)} |
| 236 | + <a |
| 237 | + class:active={(page.url.searchParams.get('interval') || '7d') == interval} |
| 238 | + href="?interval={interval}">{interval}</a |
| 239 | + > |
| 240 | + {/each} |
| 241 | + </span> |
| 242 | + </span> |
| 243 | + <EChart |
| 244 | + options={options( |
| 245 | + utilization.memory_series.map((d) => { |
| 246 | + return { timestamp: d.timestamp, value: d.value / 1024 / 1024 / 1024 }; |
| 247 | + }), |
| 248 | + utilization.requested_memory / 1024 / 1024 / 1024, |
| 249 | + utilization.limit_memory ? utilization.limit_memory / 1024 / 1024 / 1024 : undefined, |
| 250 | + usageMemColor |
| 251 | + )} |
| 252 | + style="height: 400px" |
| 253 | + /> |
| 254 | + |
| 255 | + <span class="graphHeader"> |
| 256 | + <Heading level="2" size="medium">CPU usage</Heading> |
| 257 | + </span> |
| 258 | + <EChart |
| 259 | + options={options( |
| 260 | + utilization.cpu_series, |
| 261 | + utilization.requested_cpu, |
| 262 | + utilization.limit_cpu ? utilization.limit_cpu : undefined, |
| 263 | + usageCPUColor |
| 264 | + )} |
| 265 | + style="height: 400px" |
| 266 | + /> |
212 | 267 | {/if}
|
213 | 268 |
|
214 | 269 | <style>
|
215 | 270 | .grid {
|
216 | 271 | margin-top: 1rem;
|
| 272 | + margin-bottom: var(--a-spacing-6); |
217 | 273 | display: grid;
|
218 | 274 | grid-template-columns: repeat(12, 1fr);
|
219 | 275 | column-gap: 1rem;
|
220 | 276 | row-gap: 1rem;
|
221 | 277 | }
|
222 |
| -
|
223 | 278 | .graphHeader {
|
224 | 279 | display: flex;
|
225 | 280 | justify-content: space-between;
|
|
0 commit comments