add nova
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<LoadingCard :loading="loading" class="px-6 py-4">
|
||||
<h3 class="h-6 flex mb-3 text-sm font-bold">
|
||||
{{ title }}
|
||||
|
||||
<span class="ml-auto font-semibold text-gray-400 text-xs"
|
||||
>({{ formattedTotal }} {{ __('total') }})</span
|
||||
>
|
||||
</h3>
|
||||
|
||||
<HelpTextTooltip :text="helpText" :width="helpWidth" />
|
||||
|
||||
<div class="flex min-h-[90px]">
|
||||
<div
|
||||
class="flex-1 overflow-hidden overflow-y-auto"
|
||||
:class="{
|
||||
'max-h-[90px]': legendsHeight === 'fixed',
|
||||
}"
|
||||
>
|
||||
<ul>
|
||||
<li
|
||||
v-for="item in formattedItems"
|
||||
:key="item.color"
|
||||
class="text-xs leading-normal"
|
||||
>
|
||||
<span
|
||||
class="inline-block rounded-full w-2 h-2 mr-2"
|
||||
:style="{
|
||||
backgroundColor: item.color,
|
||||
}"
|
||||
/>{{ item.label }} ({{ item.value }} - {{ item.percentage }}%)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="chart"
|
||||
class="flex-none rounded-b-lg ct-chart mr-4 w-[90px] h-[90px]"
|
||||
:class="{ invisible: this.currentTotal <= 0 }"
|
||||
/>
|
||||
</div>
|
||||
</LoadingCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
import map from 'lodash/map'
|
||||
import sumBy from 'lodash/sumBy'
|
||||
import Chartist from 'chartist'
|
||||
import 'chartist/dist/chartist.min.css'
|
||||
|
||||
const colorForIndex = index =>
|
||||
[
|
||||
'#F5573B',
|
||||
'#F99037',
|
||||
'#F2CB22',
|
||||
'#8FC15D',
|
||||
'#098F56',
|
||||
'#47C1BF',
|
||||
'#1693EB',
|
||||
'#6474D7',
|
||||
'#9C6ADE',
|
||||
'#E471DE',
|
||||
][index]
|
||||
|
||||
export default {
|
||||
name: 'BasePartitionMetric',
|
||||
|
||||
props: {
|
||||
loading: Boolean,
|
||||
title: String,
|
||||
helpText: {},
|
||||
helpWidth: {},
|
||||
chartData: Array,
|
||||
legendsHeight: { type: String, default: 'fixed' },
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
chartist: null,
|
||||
resizeObserver: null,
|
||||
}),
|
||||
|
||||
watch: {
|
||||
chartData: function (newData, oldData) {
|
||||
this.renderChart()
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
const debouncer = debounce(callback => callback(), Nova.config('debounce'))
|
||||
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
debouncer(() => {
|
||||
this.renderChart()
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.chartist = new Chartist.Pie(
|
||||
this.$refs.chart,
|
||||
this.formattedChartData,
|
||||
{
|
||||
donut: true,
|
||||
donutWidth: 10,
|
||||
donutSolid: true,
|
||||
startAngle: 270,
|
||||
showLabel: false,
|
||||
}
|
||||
)
|
||||
|
||||
this.chartist.on('draw', context => {
|
||||
if (context.type === 'slice') {
|
||||
context.element.attr({
|
||||
style: `fill: ${context.meta.color} !important`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.resizeObserver.observe(this.$refs.chart)
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.resizeObserver.unobserve(this.$refs.chart)
|
||||
},
|
||||
|
||||
methods: {
|
||||
renderChart() {
|
||||
this.chartist.update(this.formattedChartData)
|
||||
},
|
||||
|
||||
getItemColor(item, index) {
|
||||
return typeof item.color === 'string' ? item.color : colorForIndex(index)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
chartClasses() {
|
||||
return []
|
||||
},
|
||||
|
||||
formattedChartData() {
|
||||
return { labels: this.formattedLabels, series: this.formattedData }
|
||||
},
|
||||
|
||||
formattedItems() {
|
||||
return map(this.chartData, (item, index) => {
|
||||
return {
|
||||
label: item.label,
|
||||
value: Nova.formatNumber(item.value),
|
||||
color: this.getItemColor(item, index),
|
||||
percentage: Nova.formatNumber(String(item.percentage)),
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
formattedLabels() {
|
||||
return map(this.chartData, item => item.label)
|
||||
},
|
||||
|
||||
formattedData() {
|
||||
return map(this.chartData, (item, index) => {
|
||||
return {
|
||||
value: item.value,
|
||||
meta: { color: this.getItemColor(item, index) },
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
formattedTotal() {
|
||||
let total = this.currentTotal.toFixed(2)
|
||||
let roundedTotal = Math.round(total)
|
||||
|
||||
if (roundedTotal.toFixed(2) == total) {
|
||||
return Nova.formatNumber(new String(roundedTotal))
|
||||
}
|
||||
|
||||
return Nova.formatNumber(new String(total))
|
||||
},
|
||||
|
||||
currentTotal() {
|
||||
return sumBy(this.chartData, 'value')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<LoadingCard :loading="loading" class="flex flex-col px-6 py-4">
|
||||
<div class="h-6 flex items-center mb-4">
|
||||
<h3 class="flex-1 mr-3 leading-tight text-sm font-bold">{{ title }}</h3>
|
||||
|
||||
<HelpTextTooltip :text="helpText" :width="helpWidth" />
|
||||
|
||||
<div class="flex-none text-right">
|
||||
<span class="text-gray-500 font-medium inline-block">
|
||||
{{ formattedValue }}
|
||||
<span v-if="suffix" class="text-sm">{{ formattedSuffix }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="flex items-center text-4xl mb-4">{{ percentage }}%</p>
|
||||
|
||||
<div class="flex h-full justify-center items-center flex-grow-1 mb-4">
|
||||
<ProgressBar
|
||||
:title="formattedValue"
|
||||
:color="bgClass"
|
||||
:value="percentage"
|
||||
/>
|
||||
</div>
|
||||
</LoadingCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { singularOrPlural } from '@/util'
|
||||
|
||||
export default {
|
||||
name: 'BaseProgressMetric',
|
||||
|
||||
props: {
|
||||
loading: { default: true },
|
||||
title: {},
|
||||
helpText: {},
|
||||
helpWidth: {},
|
||||
maxWidth: {},
|
||||
target: {},
|
||||
value: {},
|
||||
percentage: {},
|
||||
format: {
|
||||
type: String,
|
||||
default: '(0[.]00a)',
|
||||
},
|
||||
avoid: { type: Boolean, default: false },
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
suffixInflection: { type: Boolean, default: true },
|
||||
},
|
||||
|
||||
computed: {
|
||||
isNullValue() {
|
||||
return this.value == null
|
||||
},
|
||||
|
||||
formattedValue() {
|
||||
if (!this.isNullValue) {
|
||||
const value = Nova.formatNumber(new String(this.value), this.format)
|
||||
|
||||
return `${this.prefix}${value}`
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
formattedSuffix() {
|
||||
if (this.suffixInflection === false) {
|
||||
return this.suffix
|
||||
}
|
||||
|
||||
return singularOrPlural(this.value, this.suffix)
|
||||
},
|
||||
|
||||
bgClass() {
|
||||
if (this.avoid) {
|
||||
return this.percentage > 60 ? 'bg-yellow-500' : 'bg-green-300'
|
||||
}
|
||||
|
||||
return this.percentage > 60 ? 'bg-green-500' : 'bg-yellow-300'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
231
nova/resources/js/components/Metrics/Base/BaseTrendMetric.vue
Normal file
231
nova/resources/js/components/Metrics/Base/BaseTrendMetric.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<LoadingCard :loading="loading" class="px-6 py-4">
|
||||
<div class="h-6 flex items-center mb-4">
|
||||
<h3 class="mr-3 leading-tight text-sm font-bold">{{ title }}</h3>
|
||||
|
||||
<HelpTextTooltip :text="helpText" :width="helpWidth" />
|
||||
|
||||
<SelectControl
|
||||
v-if="ranges.length > 0"
|
||||
class="ml-auto w-[6rem] shrink-0"
|
||||
size="xxs"
|
||||
:options="ranges"
|
||||
:selected="selectedRangeKey"
|
||||
@change="handleChange"
|
||||
:aria-label="__('Select Ranges')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="flex items-center text-4xl mb-4">
|
||||
{{ formattedValue }}
|
||||
<span v-if="suffix" class="ml-2 text-sm font-bold">{{
|
||||
formattedSuffix
|
||||
}}</span>
|
||||
</p>
|
||||
|
||||
<div
|
||||
ref="chart"
|
||||
class="absolute inset-0 rounded-b-lg ct-chart"
|
||||
style="top: 60%"
|
||||
/>
|
||||
</LoadingCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
import Chartist from 'chartist'
|
||||
import 'chartist/dist/chartist.min.css'
|
||||
import { singularOrPlural } from '@/util'
|
||||
import ChartistTooltip from 'chartist-plugin-tooltips-updated'
|
||||
import 'chartist-plugin-tooltips-updated/dist/chartist-plugin-tooltip.css'
|
||||
|
||||
export default {
|
||||
name: 'BaseTrendMetric',
|
||||
|
||||
emits: ['selected'],
|
||||
|
||||
props: {
|
||||
loading: Boolean,
|
||||
title: {},
|
||||
helpText: {},
|
||||
helpWidth: {},
|
||||
value: {},
|
||||
chartData: {},
|
||||
maxWidth: {},
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
suffixInflection: { type: Boolean, default: true },
|
||||
ranges: { type: Array, default: () => [] },
|
||||
selectedRangeKey: [String, Number],
|
||||
format: {
|
||||
type: String,
|
||||
default: '0[.]00a',
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
chartist: null,
|
||||
resizeObserver: null,
|
||||
}),
|
||||
|
||||
watch: {
|
||||
selectedRangeKey: function (newRange, oldRange) {
|
||||
this.renderChart()
|
||||
},
|
||||
|
||||
chartData: function (newData, oldData) {
|
||||
this.renderChart()
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
const debouncer = debounce(callback => callback(), Nova.config('debounce'))
|
||||
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
debouncer(() => {
|
||||
this.renderChart()
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const low = Math.min(...this.chartData)
|
||||
const high = Math.max(...this.chartData)
|
||||
|
||||
// Use zero as the graph base if the lowest value is greater than or equal to zero.
|
||||
// This avoids the awkward situation where the chart doesn't appear filled in.
|
||||
const areaBase = low >= 0 ? 0 : low
|
||||
|
||||
this.chartist = new Chartist.Line(this.$refs.chart, this.chartData, {
|
||||
lineSmooth: Chartist.Interpolation.none(),
|
||||
fullWidth: true,
|
||||
showPoint: true,
|
||||
showLine: true,
|
||||
showArea: true,
|
||||
chartPadding: {
|
||||
top: 10,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
},
|
||||
low,
|
||||
high,
|
||||
areaBase,
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
showLabel: true,
|
||||
offset: 0,
|
||||
},
|
||||
axisY: {
|
||||
showGrid: false,
|
||||
showLabel: true,
|
||||
offset: 0,
|
||||
},
|
||||
plugins: [
|
||||
ChartistTooltip({
|
||||
pointClass: 'ct-point',
|
||||
anchorToPoint: false,
|
||||
}),
|
||||
ChartistTooltip({
|
||||
pointClass: 'ct-point__left',
|
||||
anchorToPoint: false,
|
||||
tooltipOffset: {
|
||||
x: 50,
|
||||
y: -20,
|
||||
},
|
||||
}),
|
||||
ChartistTooltip({
|
||||
pointClass: 'ct-point__right',
|
||||
anchorToPoint: false,
|
||||
tooltipOffset: {
|
||||
x: -50,
|
||||
y: -20,
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
this.chartist.on('draw', data => {
|
||||
if (data.type === 'point') {
|
||||
data.element.attr({
|
||||
'ct:value': this.transformTooltipText(data.value.y),
|
||||
})
|
||||
|
||||
data.element.addClass(
|
||||
this.transformTooltipClass(data.axisX.ticks.length, data.index) ?? ''
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
this.resizeObserver.observe(this.$refs.chart)
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.resizeObserver.unobserve(this.$refs.chart)
|
||||
},
|
||||
|
||||
methods: {
|
||||
renderChart() {
|
||||
this.chartist.update(this.chartData)
|
||||
},
|
||||
|
||||
handleChange(event) {
|
||||
const value = event?.target?.value || event
|
||||
|
||||
this.$emit('selected', value)
|
||||
},
|
||||
|
||||
transformTooltipText(value) {
|
||||
let formattedValue = Nova.formatNumber(new String(value), this.format)
|
||||
|
||||
if (this.prefix) {
|
||||
return `${this.prefix}${formattedValue}`
|
||||
}
|
||||
|
||||
if (this.suffix) {
|
||||
const suffix = this.suffixInflection
|
||||
? singularOrPlural(value, this.suffix)
|
||||
: this.suffix
|
||||
|
||||
return `${formattedValue} ${suffix}`
|
||||
}
|
||||
|
||||
return `${formattedValue}`
|
||||
},
|
||||
|
||||
transformTooltipClass(total, index) {
|
||||
if (index < 2) {
|
||||
return 'ct-point__left'
|
||||
} else if (index > total - 3) {
|
||||
return 'ct-point__right'
|
||||
}
|
||||
|
||||
return 'ct-point'
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
isNullValue() {
|
||||
return this.value == null
|
||||
},
|
||||
|
||||
formattedValue() {
|
||||
if (!this.isNullValue) {
|
||||
const value = Nova.formatNumber(new String(this.value), this.format)
|
||||
|
||||
return `${this.prefix}${value}`
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
formattedSuffix() {
|
||||
if (this.suffixInflection === false) {
|
||||
return this.suffix
|
||||
}
|
||||
|
||||
return singularOrPlural(this.value, this.suffix)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
236
nova/resources/js/components/Metrics/Base/BaseValueMetric.vue
Normal file
236
nova/resources/js/components/Metrics/Base/BaseValueMetric.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<LoadingCard :loading="loading" class="px-6 py-4">
|
||||
<div class="h-6 flex items-center mb-4">
|
||||
<h3 class="mr-3 leading-tight text-sm font-bold">{{ title }}</h3>
|
||||
|
||||
<HelpTextTooltip :text="helpText" :width="helpWidth" />
|
||||
|
||||
<SelectControl
|
||||
v-if="ranges.length > 0"
|
||||
class="ml-auto w-[6rem] shrink-0"
|
||||
size="xxs"
|
||||
:options="ranges"
|
||||
:selected="selectedRangeKey"
|
||||
@change="handleChange"
|
||||
:aria-label="__('Select Ranges')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-4 space-x-4">
|
||||
<div
|
||||
v-if="icon"
|
||||
class="rounded-lg bg-primary-500 text-white h-14 w-14 flex items-center justify-center"
|
||||
>
|
||||
<Icon :type="icon" width="24" height="24" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<component
|
||||
:is="copyable ? 'CopyButton' : 'p'"
|
||||
@click="handleCopyClick"
|
||||
class="flex items-center text-4xl"
|
||||
:rounded="false"
|
||||
>
|
||||
<span v-tooltip="`${tooltipFormattedValue}`">
|
||||
{{ formattedValue }}
|
||||
</span>
|
||||
<span v-if="suffix" class="ml-2 text-sm font-bold">
|
||||
{{ formattedSuffix }}
|
||||
</span>
|
||||
</component>
|
||||
|
||||
<div v-tooltip="`${tooltipFormattedPreviousValue}`">
|
||||
<p class="flex items-center font-bold text-sm">
|
||||
<svg
|
||||
v-if="increaseOrDecreaseLabel === 'Decrease'"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-red-500 stroke-current mr-2"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-if="increaseOrDecreaseLabel === 'Increase'"
|
||||
class="text-green-500 stroke-current mr-2"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span v-if="!(increaseOrDecrease === 0)">
|
||||
<span v-if="growthPercentage !== 0">
|
||||
{{ growthPercentage }}%
|
||||
{{ __(increaseOrDecreaseLabel) }}
|
||||
</span>
|
||||
|
||||
<span v-else>{{ __('No Increase') }}</span>
|
||||
</span>
|
||||
|
||||
<span class="text-gray-400 font-semibold" v-else>
|
||||
<span v-if="previous === '0' && value !== '0'">
|
||||
{{ __('No Prior Data') }}
|
||||
</span>
|
||||
|
||||
<span v-if="value === '0' && previous !== '0' && !zeroResult">
|
||||
{{ __('No Current Data') }}
|
||||
</span>
|
||||
|
||||
<span v-if="value == '0' && previous == '0' && !zeroResult">
|
||||
{{ __('No Data') }}
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LoadingCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { increaseOrDecrease, singularOrPlural } from '@/util'
|
||||
import { CopiesToClipboard } from '@/mixins'
|
||||
|
||||
export default {
|
||||
name: 'BaseValueMetric',
|
||||
|
||||
mixins: [CopiesToClipboard],
|
||||
|
||||
emits: ['selected'],
|
||||
|
||||
props: {
|
||||
loading: { default: true },
|
||||
copyable: { default: false },
|
||||
title: {},
|
||||
helpText: {},
|
||||
helpWidth: {},
|
||||
icon: { type: String },
|
||||
maxWidth: {},
|
||||
previous: {},
|
||||
value: {},
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
suffixInflection: { default: true },
|
||||
selectedRangeKey: [String, Number],
|
||||
ranges: { type: Array, default: () => [] },
|
||||
format: { type: String, default: '(0[.]00a)' },
|
||||
tooltipFormat: { type: String, default: '(0[.]00)' },
|
||||
zeroResult: { default: false },
|
||||
},
|
||||
|
||||
data: () => ({ copied: false }),
|
||||
|
||||
methods: {
|
||||
handleChange(event) {
|
||||
let value = event?.target?.value || event
|
||||
|
||||
this.$emit('selected', value)
|
||||
},
|
||||
|
||||
handleCopyClick() {
|
||||
if (this.copyable) {
|
||||
this.copied = true
|
||||
this.copyValueToClipboard(this.tooltipFormattedValue)
|
||||
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
}, 2000)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
growthPercentage() {
|
||||
return Math.abs(this.increaseOrDecrease)
|
||||
},
|
||||
|
||||
increaseOrDecrease() {
|
||||
if (this.previous === 0 || this.previous == null || this.value === 0)
|
||||
return 0
|
||||
|
||||
return increaseOrDecrease(this.value, this.previous).toFixed(2)
|
||||
},
|
||||
|
||||
increaseOrDecreaseLabel() {
|
||||
switch (Math.sign(this.increaseOrDecrease)) {
|
||||
case 1:
|
||||
return 'Increase'
|
||||
case 0:
|
||||
return 'Constant'
|
||||
case -1:
|
||||
return 'Decrease'
|
||||
}
|
||||
},
|
||||
|
||||
sign() {
|
||||
switch (Math.sign(this.increaseOrDecrease)) {
|
||||
case 1:
|
||||
return '+'
|
||||
case 0:
|
||||
return ''
|
||||
case -1:
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
|
||||
isNullValue() {
|
||||
return this.value == null
|
||||
},
|
||||
|
||||
isNullPreviousValue() {
|
||||
return this.previous == null
|
||||
},
|
||||
|
||||
formattedValue() {
|
||||
if (!this.isNullValue) {
|
||||
return (
|
||||
this.prefix + Nova.formatNumber(new String(this.value), this.format)
|
||||
)
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
tooltipFormattedValue() {
|
||||
if (!this.isNullValue) {
|
||||
return this.value
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
tooltipFormattedPreviousValue() {
|
||||
if (!this.isNullPreviousValue) {
|
||||
return this.previous
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
formattedSuffix() {
|
||||
if (this.suffixInflection === false) {
|
||||
return this.suffix
|
||||
}
|
||||
|
||||
return singularOrPlural(this.value, this.suffix)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
119
nova/resources/js/components/Metrics/MetricTableRow.vue
Normal file
119
nova/resources/js/components/Metrics/MetricTableRow.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<tr class="group">
|
||||
<td
|
||||
v-if="row.icon"
|
||||
class="pl-6 w-8 pr-2 w-max"
|
||||
:class="{
|
||||
[row.iconClass]: true,
|
||||
[rowClasses]: true,
|
||||
'text-gray-400 dark:text-gray-600': !row.iconClass,
|
||||
}"
|
||||
>
|
||||
<Heroicon :type="row.icon" />
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="px-2 w-auto"
|
||||
:class="{
|
||||
[rowClasses]: true,
|
||||
'pl-6': !row.icon,
|
||||
'pr-6': !row.editUrl || !row.viewUrl,
|
||||
}"
|
||||
>
|
||||
<h2 class="text-base text-gray-500 truncate">
|
||||
{{ row.title }}
|
||||
</h2>
|
||||
<p class="text-gray-400 text-xs truncate">{{ row.subtitle }}</p>
|
||||
</td>
|
||||
|
||||
<td
|
||||
v-if="row.actions.length > 0"
|
||||
class="text-right pr-4 w-max"
|
||||
:class="rowClasses"
|
||||
>
|
||||
<div class="flex justify-end items-center text-gray-400">
|
||||
<Dropdown>
|
||||
<Button
|
||||
icon="ellipsis-horizontal"
|
||||
variant="action"
|
||||
:aria-label="__('Resource Row Dropdown')"
|
||||
/>
|
||||
|
||||
<template #menu>
|
||||
<DropdownMenu width="auto" class="px-1">
|
||||
<ScrollWrap
|
||||
:height="250"
|
||||
class="divide-y divide-gray-100 dark:divide-gray-800 divide-solid"
|
||||
>
|
||||
<div class="py-1">
|
||||
<DropdownMenuItem
|
||||
v-bind="actionAttributes(action)"
|
||||
v-for="action in row.actions"
|
||||
>
|
||||
{{ action.name }}
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</ScrollWrap>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import isNull from 'lodash/isNull'
|
||||
import omitBy from 'lodash/omitBy'
|
||||
import { Button, Icon } from 'laravel-nova-ui'
|
||||
import Heroicon from '@/components/Icons/Icon'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
Icon,
|
||||
Heroicon,
|
||||
},
|
||||
|
||||
props: {
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
actionAttributes(item) {
|
||||
let method = item.method || 'GET'
|
||||
|
||||
if (item.external && item.method == 'GET') {
|
||||
return {
|
||||
as: 'external',
|
||||
href: item.path,
|
||||
name: item.name,
|
||||
title: item.name,
|
||||
target: item.target || null,
|
||||
external: true,
|
||||
}
|
||||
}
|
||||
|
||||
return omitBy(
|
||||
{
|
||||
as: method === 'GET' ? 'link' : 'form-button',
|
||||
href: item.path,
|
||||
method: method !== 'GET' ? method : null,
|
||||
data: item.data || null,
|
||||
headers: item.headers || null,
|
||||
},
|
||||
isNull
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
rowClasses() {
|
||||
return ['py-2']
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
112
nova/resources/js/components/Metrics/PartitionMetric.vue
Normal file
112
nova/resources/js/components/Metrics/PartitionMetric.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<BasePartitionMetric
|
||||
:title="card.name"
|
||||
:help-text="card.helpText"
|
||||
:help-width="card.helpWidth"
|
||||
:chart-data="chartData"
|
||||
:loading="loading"
|
||||
:legends-height="card.height"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MetricBehavior } from '@/mixins'
|
||||
import { minimum } from '@/util'
|
||||
|
||||
export default {
|
||||
mixins: [MetricBehavior],
|
||||
|
||||
props: {
|
||||
card: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
resourceName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
resourceId: {
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
|
||||
lens: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
loading: true,
|
||||
chartData: [],
|
||||
}),
|
||||
|
||||
watch: {
|
||||
resourceId() {
|
||||
this.fetch()
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.fetch()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$on('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$off('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.loading = true
|
||||
|
||||
minimum(Nova.request().get(this.metricEndpoint, this.metricPayload)).then(
|
||||
({
|
||||
data: {
|
||||
value: { value },
|
||||
},
|
||||
}) => {
|
||||
this.chartData = value
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
metricEndpoint() {
|
||||
const lens = this.lens !== '' ? `/lens/${this.lens}` : ''
|
||||
if (this.resourceName && this.resourceId) {
|
||||
return `/nova-api/${this.resourceName}${lens}/${this.resourceId}/metrics/${this.card.uriKey}`
|
||||
} else if (this.resourceName) {
|
||||
return `/nova-api/${this.resourceName}${lens}/metrics/${this.card.uriKey}`
|
||||
} else {
|
||||
return `/nova-api/metrics/${this.card.uriKey}`
|
||||
}
|
||||
},
|
||||
|
||||
metricPayload() {
|
||||
const payload = { params: {} }
|
||||
|
||||
if (
|
||||
!Nova.missingResource(this.resourceName) &&
|
||||
this.card &&
|
||||
this.card.refreshWhenFiltersChange === true
|
||||
) {
|
||||
payload.params.filter =
|
||||
this.$store.getters[`${this.resourceName}/currentEncodedFilters`]
|
||||
}
|
||||
|
||||
return payload
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
154
nova/resources/js/components/Metrics/ProgressMetric.vue
Normal file
154
nova/resources/js/components/Metrics/ProgressMetric.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<BaseProgressMetric
|
||||
:title="card.name"
|
||||
:help-text="card.helpText"
|
||||
:help-width="card.helpWidth"
|
||||
:target="target"
|
||||
:value="value"
|
||||
:percentage="percentage"
|
||||
:prefix="prefix"
|
||||
:suffix="suffix"
|
||||
:suffix-inflection="suffixInflection"
|
||||
:format="format"
|
||||
:avoid="avoid"
|
||||
:loading="loading"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { minimum } from '@/util'
|
||||
import { InteractsWithDates, MetricBehavior } from '@/mixins'
|
||||
|
||||
export default {
|
||||
name: 'ProgressMetric',
|
||||
|
||||
mixins: [InteractsWithDates, MetricBehavior],
|
||||
|
||||
props: {
|
||||
card: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
resourceName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
resourceId: {
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
|
||||
lens: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
loading: true,
|
||||
format: '(0[.]00a)',
|
||||
avoid: false,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
suffixInflection: true,
|
||||
value: 0,
|
||||
target: 0,
|
||||
percentage: 0,
|
||||
zeroResult: false,
|
||||
}),
|
||||
|
||||
watch: {
|
||||
resourceId() {
|
||||
this.fetch()
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.hasRanges) {
|
||||
this.selectedRangeKey =
|
||||
this.card.selectedRangeKey || this.card.ranges[0].value
|
||||
}
|
||||
|
||||
this.fetch()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$on('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$off('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.loading = true
|
||||
|
||||
minimum(Nova.request().get(this.metricEndpoint, this.metricPayload)).then(
|
||||
({
|
||||
data: {
|
||||
value: {
|
||||
value,
|
||||
target,
|
||||
percentage,
|
||||
prefix,
|
||||
suffix,
|
||||
suffixInflection,
|
||||
format,
|
||||
avoid,
|
||||
},
|
||||
},
|
||||
}) => {
|
||||
this.value = value
|
||||
this.target = target
|
||||
this.percentage = percentage
|
||||
this.format = format || this.format
|
||||
this.avoid = avoid
|
||||
this.prefix = prefix || this.prefix
|
||||
this.suffix = suffix || this.suffix
|
||||
this.suffixInflection = suffixInflection
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
metricPayload() {
|
||||
const payload = {
|
||||
params: {
|
||||
timezone: this.userTimezone,
|
||||
},
|
||||
}
|
||||
|
||||
if (
|
||||
!Nova.missingResource(this.resourceName) &&
|
||||
this.card &&
|
||||
this.card.refreshWhenFiltersChange === true
|
||||
) {
|
||||
payload.params.filter =
|
||||
this.$store.getters[`${this.resourceName}/currentEncodedFilters`]
|
||||
}
|
||||
|
||||
return payload
|
||||
},
|
||||
|
||||
metricEndpoint() {
|
||||
const lens = this.lens !== '' ? `/lens/${this.lens}` : ''
|
||||
if (this.resourceName && this.resourceId) {
|
||||
return `/nova-api/${this.resourceName}${lens}/${this.resourceId}/metrics/${this.card.uriKey}`
|
||||
} else if (this.resourceName) {
|
||||
return `/nova-api/${this.resourceName}${lens}/metrics/${this.card.uriKey}`
|
||||
} else {
|
||||
return `/nova-api/metrics/${this.card.uriKey}`
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
133
nova/resources/js/components/Metrics/TableMetric.vue
Normal file
133
nova/resources/js/components/Metrics/TableMetric.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<LoadingCard :loading="loading" class="pt-4">
|
||||
<div class="h-6 flex items-center px-6 mb-4">
|
||||
<h3 class="mr-3 leading-tight text-sm font-bold">{{ card.name }}</h3>
|
||||
<HelpTextTooltip :text="card.helpText" :width="card.helpWidth" />
|
||||
</div>
|
||||
|
||||
<div class="mb-5 pb-4">
|
||||
<div
|
||||
v-if="value.length > 0"
|
||||
class="overflow-hidden overflow-x-auto relative"
|
||||
>
|
||||
<table class="w-full table-default table-fixed">
|
||||
<tbody
|
||||
class="border-t border-b border-gray-100 dark:border-gray-700 divide-y divide-gray-100 dark:divide-gray-700"
|
||||
>
|
||||
<MetricTableRow v-for="row in value" :row="row" />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center justify-between px-6 gap-2">
|
||||
<p class="font-normal text-center py-4">
|
||||
{{ card.emptyText }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</LoadingCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { minimum } from '@/util'
|
||||
import { InteractsWithDates, MetricBehavior } from '@/mixins'
|
||||
|
||||
export default {
|
||||
name: 'TableCard',
|
||||
|
||||
mixins: [InteractsWithDates, MetricBehavior],
|
||||
|
||||
props: {
|
||||
card: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
resourceName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
resourceId: {
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
|
||||
lens: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
loading: true,
|
||||
value: [],
|
||||
}),
|
||||
|
||||
watch: {
|
||||
resourceId() {
|
||||
this.fetch()
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.fetch()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$on('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$off('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.loading = true
|
||||
|
||||
minimum(Nova.request().get(this.metricEndpoint, this.metricPayload)).then(
|
||||
({ data: { value } }) => {
|
||||
this.value = value
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
metricPayload() {
|
||||
const payload = {
|
||||
params: {
|
||||
timezone: this.userTimezone,
|
||||
},
|
||||
}
|
||||
|
||||
if (
|
||||
!Nova.missingResource(this.resourceName) &&
|
||||
this.card &&
|
||||
this.card.refreshWhenFiltersChange === true
|
||||
) {
|
||||
payload.params.filter =
|
||||
this.$store.getters[`${this.resourceName}/currentEncodedFilters`]
|
||||
}
|
||||
|
||||
return payload
|
||||
},
|
||||
|
||||
metricEndpoint() {
|
||||
const lens = this.lens !== '' ? `/lens/${this.lens}` : ''
|
||||
if (this.resourceName && this.resourceId) {
|
||||
return `/nova-api/${this.resourceName}${lens}/${this.resourceId}/metrics/${this.card.uriKey}`
|
||||
} else if (this.resourceName) {
|
||||
return `/nova-api/${this.resourceName}${lens}/metrics/${this.card.uriKey}`
|
||||
} else {
|
||||
return `/nova-api/metrics/${this.card.uriKey}`
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
176
nova/resources/js/components/Metrics/TrendMetric.vue
Normal file
176
nova/resources/js/components/Metrics/TrendMetric.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<BaseTrendMetric
|
||||
@selected="handleRangeSelected"
|
||||
:title="card.name"
|
||||
:help-text="card.helpText"
|
||||
:help-width="card.helpWidth"
|
||||
:value="value"
|
||||
:chart-data="data"
|
||||
:ranges="card.ranges"
|
||||
:format="format"
|
||||
:prefix="prefix"
|
||||
:suffix="suffix"
|
||||
:suffix-inflection="suffixInflection"
|
||||
:selected-range-key="selectedRangeKey"
|
||||
:loading="loading"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import map from 'lodash/map'
|
||||
import { InteractsWithDates, MetricBehavior } from '@/mixins'
|
||||
import { minimum } from '@/util'
|
||||
|
||||
export default {
|
||||
name: 'TrendMetric',
|
||||
|
||||
mixins: [InteractsWithDates, MetricBehavior],
|
||||
|
||||
props: {
|
||||
card: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
resourceName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
resourceId: {
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
|
||||
lens: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
loading: true,
|
||||
value: '',
|
||||
data: [],
|
||||
format: '(0[.]00a)',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
suffixInflection: true,
|
||||
selectedRangeKey: null,
|
||||
}),
|
||||
|
||||
watch: {
|
||||
resourceId() {
|
||||
this.fetch()
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.hasRanges) {
|
||||
this.selectedRangeKey =
|
||||
this.card.selectedRangeKey || this.card.ranges[0].value
|
||||
}
|
||||
|
||||
this.fetch()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$on('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$off('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleRangeSelected(key) {
|
||||
this.selectedRangeKey = key
|
||||
this.fetch()
|
||||
},
|
||||
|
||||
fetch() {
|
||||
this.loading = true
|
||||
|
||||
minimum(Nova.request().get(this.metricEndpoint, this.metricPayload)).then(
|
||||
({
|
||||
data: {
|
||||
value: {
|
||||
labels,
|
||||
trend,
|
||||
value,
|
||||
prefix,
|
||||
suffix,
|
||||
suffixInflection,
|
||||
format,
|
||||
},
|
||||
},
|
||||
}) => {
|
||||
this.value = value
|
||||
this.labels = Object.keys(trend)
|
||||
this.data = {
|
||||
labels: Object.keys(trend),
|
||||
series: [
|
||||
map(trend, (value, label) => {
|
||||
return {
|
||||
meta: label,
|
||||
value: value,
|
||||
}
|
||||
}),
|
||||
],
|
||||
}
|
||||
this.format = format || this.format
|
||||
this.prefix = prefix || this.prefix
|
||||
this.suffix = suffix || this.suffix
|
||||
this.suffixInflection = suffixInflection
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasRanges() {
|
||||
return this.card.ranges.length > 0
|
||||
},
|
||||
|
||||
metricPayload() {
|
||||
const payload = {
|
||||
params: {
|
||||
timezone: this.userTimezone,
|
||||
twelveHourTime: this.usesTwelveHourTime,
|
||||
},
|
||||
}
|
||||
|
||||
if (
|
||||
!Nova.missingResource(this.resourceName) &&
|
||||
this.card &&
|
||||
this.card.refreshWhenFiltersChange === true
|
||||
) {
|
||||
payload.params.filter =
|
||||
this.$store.getters[`${this.resourceName}/currentEncodedFilters`]
|
||||
}
|
||||
|
||||
if (this.hasRanges) {
|
||||
payload.params.range = this.selectedRangeKey
|
||||
}
|
||||
|
||||
return payload
|
||||
},
|
||||
|
||||
metricEndpoint() {
|
||||
const lens = this.lens !== '' ? `/lens/${this.lens}` : ''
|
||||
if (this.resourceName && this.resourceId) {
|
||||
return `/nova-api/${this.resourceName}${lens}/${this.resourceId}/metrics/${this.card.uriKey}`
|
||||
} else if (this.resourceName) {
|
||||
return `/nova-api/${this.resourceName}${lens}/metrics/${this.card.uriKey}`
|
||||
} else {
|
||||
return `/nova-api/metrics/${this.card.uriKey}`
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
175
nova/resources/js/components/Metrics/ValueMetric.vue
Normal file
175
nova/resources/js/components/Metrics/ValueMetric.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<BaseValueMetric
|
||||
@selected="handleRangeSelected"
|
||||
:title="card.name"
|
||||
:copyable="copyable"
|
||||
:help-text="card.helpText"
|
||||
:help-width="card.helpWidth"
|
||||
:icon="card.icon"
|
||||
:previous="previous"
|
||||
:value="value"
|
||||
:ranges="card.ranges"
|
||||
:format="format"
|
||||
:tooltip-format="tooltipFormat"
|
||||
:prefix="prefix"
|
||||
:suffix="suffix"
|
||||
:suffix-inflection="suffixInflection"
|
||||
:selected-range-key="selectedRangeKey"
|
||||
:loading="loading"
|
||||
:zero-result="zeroResult"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { minimum } from '@/util'
|
||||
import { InteractsWithDates, MetricBehavior } from '@/mixins'
|
||||
|
||||
export default {
|
||||
name: 'ValueMetric',
|
||||
|
||||
mixins: [InteractsWithDates, MetricBehavior],
|
||||
|
||||
props: {
|
||||
card: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
resourceName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
resourceId: {
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
|
||||
lens: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
loading: true,
|
||||
copyable: false,
|
||||
format: '(0[.]00a)',
|
||||
tooltipFormat: '(0[.]00)',
|
||||
value: 0,
|
||||
previous: 0,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
suffixInflection: true,
|
||||
selectedRangeKey: null,
|
||||
zeroResult: false,
|
||||
}),
|
||||
|
||||
watch: {
|
||||
resourceId() {
|
||||
this.fetch()
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.hasRanges) {
|
||||
this.selectedRangeKey =
|
||||
this.card.selectedRangeKey || this.card.ranges[0].value
|
||||
}
|
||||
|
||||
this.fetch()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$on('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.card && this.card.refreshWhenFiltersChange === true) {
|
||||
Nova.$off('filter-changed', this.fetch)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleRangeSelected(key) {
|
||||
this.selectedRangeKey = key
|
||||
this.fetch()
|
||||
},
|
||||
|
||||
fetch() {
|
||||
this.loading = true
|
||||
|
||||
minimum(Nova.request().get(this.metricEndpoint, this.metricPayload)).then(
|
||||
({
|
||||
data: {
|
||||
value: {
|
||||
copyable,
|
||||
value,
|
||||
previous,
|
||||
prefix,
|
||||
suffix,
|
||||
suffixInflection,
|
||||
format,
|
||||
tooltipFormat,
|
||||
zeroResult,
|
||||
},
|
||||
},
|
||||
}) => {
|
||||
this.copyable = copyable
|
||||
this.value = value
|
||||
this.format = format || this.format
|
||||
this.tooltipFormat = tooltipFormat || this.tooltipFormat
|
||||
this.prefix = prefix || this.prefix
|
||||
this.suffix = suffix || this.suffix
|
||||
this.suffixInflection = suffixInflection
|
||||
this.zeroResult = zeroResult || this.zeroResult
|
||||
this.previous = previous
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasRanges() {
|
||||
return this.card.ranges.length > 0
|
||||
},
|
||||
|
||||
metricPayload() {
|
||||
const payload = {
|
||||
params: {
|
||||
timezone: this.userTimezone,
|
||||
},
|
||||
}
|
||||
|
||||
if (
|
||||
!Nova.missingResource(this.resourceName) &&
|
||||
this.card &&
|
||||
this.card.refreshWhenFiltersChange === true
|
||||
) {
|
||||
payload.params.filter =
|
||||
this.$store.getters[`${this.resourceName}/currentEncodedFilters`]
|
||||
}
|
||||
|
||||
if (this.hasRanges) {
|
||||
payload.params.range = this.selectedRangeKey
|
||||
}
|
||||
|
||||
return payload
|
||||
},
|
||||
|
||||
metricEndpoint() {
|
||||
const lens = this.lens !== '' ? `/lens/${this.lens}` : ''
|
||||
if (this.resourceName && this.resourceId) {
|
||||
return `/nova-api/${this.resourceName}${lens}/${this.resourceId}/metrics/${this.card.uriKey}`
|
||||
} else if (this.resourceName) {
|
||||
return `/nova-api/${this.resourceName}${lens}/metrics/${this.card.uriKey}`
|
||||
} else {
|
||||
return `/nova-api/metrics/${this.card.uriKey}`
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user