diff options
author | Ivan Topolnjak <ivantopo@gmail.com> | 2019-02-08 02:43:44 +0100 |
---|---|---|
committer | Ivan Topolnjak <ivantopo@gmail.com> | 2019-02-08 02:43:44 +0100 |
commit | 4ce838b1af6257625b27ea38d55947912cba00c9 (patch) | |
tree | 0641f5c44a108ded898b1d5ad72e093de6db6d65 /kamon-status/src | |
parent | c985537bfe1b352911aa3ba6247112dfac917171 (diff) | |
download | Kamon-4ce838b1af6257625b27ea38d55947912cba00c9.tar.gz Kamon-4ce838b1af6257625b27ea38d55947912cba00c9.tar.bz2 Kamon-4ce838b1af6257625b27ea38d55947912cba00c9.zip |
group metric series together in the same item, expand to see individual series
Diffstat (limited to 'kamon-status/src')
-rw-r--r-- | kamon-status/src/api/StatusApi.ts | 2 | ||||
-rw-r--r-- | kamon-status/src/components/Card.vue | 19 | ||||
-rw-r--r-- | kamon-status/src/components/EnvironmentCard.vue | 58 | ||||
-rw-r--r-- | kamon-status/src/components/InstrumentationModuleStatusCard.vue | 2 | ||||
-rw-r--r-- | kamon-status/src/components/MetricList.vue | 83 | ||||
-rw-r--r-- | kamon-status/src/components/MetricListItem.vue | 108 | ||||
-rw-r--r-- | kamon-status/src/components/ModuleList.vue | 40 | ||||
-rw-r--r-- | kamon-status/src/components/ModuleStatusCard.vue | 4 | ||||
-rw-r--r-- | kamon-status/src/components/OverviewCard.vue | 42 | ||||
-rw-r--r-- | kamon-status/src/components/StatusCard.vue | 9 | ||||
-rw-r--r-- | kamon-status/src/components/StatusSection.vue | 19 | ||||
-rw-r--r-- | kamon-status/src/styles/main.scss | 12 | ||||
-rw-r--r-- | kamon-status/src/views/Overview.vue | 6 |
13 files changed, 288 insertions, 116 deletions
diff --git a/kamon-status/src/api/StatusApi.ts b/kamon-status/src/api/StatusApi.ts index a077a48b..231c5eca 100644 --- a/kamon-status/src/api/StatusApi.ts +++ b/kamon-status/src/api/StatusApi.ts @@ -34,6 +34,8 @@ export interface Module { export interface Metric { name: string type: string + unitDimension: string + unitMagnitude: string tags: { [key: string ]: string } search: string } diff --git a/kamon-status/src/components/Card.vue b/kamon-status/src/components/Card.vue index d77596da..d3300336 100644 --- a/kamon-status/src/components/Card.vue +++ b/kamon-status/src/components/Card.vue @@ -1,19 +1,20 @@ <template> - <div class="outer"> + <div class="card-wrapper"> <slot/> </div> </template> +<script lang="ts"> +import { Component, Vue } from 'vue-property-decorator' -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped lang="scss"> -.outer { +@Component +export default class Card extends Vue {} +</script> + + +<style lang="scss"> +.card-wrapper { background-color: white; box-shadow: 0 2px 9px 1px rgba(0, 0, 0, 0.1); } - -hr { - margin: 0px; - border-color: #f3f3f3; -} </style> diff --git a/kamon-status/src/components/EnvironmentCard.vue b/kamon-status/src/components/EnvironmentCard.vue index 97e79d28..98dc3f9f 100644 --- a/kamon-status/src/components/EnvironmentCard.vue +++ b/kamon-status/src/components/EnvironmentCard.vue @@ -1,47 +1,47 @@ <template> - <div class="row"> - <div class="col-12"> - <card> - <div class="row py-2 no-gutters"> - <div class="col-auto py-2 px-3"> - <div class="text-uppercase text-label pb-1">Service</div> - <h6>{{ service }}</h6> - </div> - <div class="col-auto py-2 px-3"> - <div class="text-uppercase text-label pb-1">Host</div> - <h6>{{ host }}</h6> - </div> - <div class="col-auto py-2 px-3"> - <div class="text-uppercase text-label pb-1">instance</div> - <h6>{{instance}}</h6> + <status-section title="Environment"> + <card> + <div class="row py-2 no-gutters"> + <div class="col-auto py-2 px-3"> + <div class="text-uppercase text-label pb-1">Service</div> + <h6>{{ service }}</h6> + </div> + <div class="col-auto py-2 px-3"> + <div class="text-uppercase text-label pb-1">Host</div> + <h6>{{ host }}</h6> + </div> + <div class="col-auto py-2 px-3"> + <div class="text-uppercase text-label pb-1">instance</div> + <h6>{{instance}}</h6> + </div> + <div class="col-12 col-md-3 py-2 px-3"> + <div class="text-uppercase text-label pb-1">tags</div> + <div class="tag-container" v-if="Object.keys(environmentTags).length > 0"> + <span class="tag" v-for="tag in Object.keys(environmentTags)" :key="tag"> + {{ tag }}=<span class="tag-value">{{ environmentTags[tag] }}</span> + </span> </div> - <div class="col-12 col-md-3 py-2 px-3"> - <div class="text-uppercase text-label pb-1">tags</div> - <div class="tag-container" v-if="Object.keys(environmentTags).length > 0"> - <span class="tag" v-for="tag in Object.keys(environmentTags)" :key="tag"> - {{ tag }}=<span class="tag-value">{{ environmentTags[tag] }}</span> - </span> - </div> - <div v-else> - <h6>None</h6> - </div> + <div v-else> + <h6>None</h6> </div> </div> - </card> - </div> - </div> + </div> + </card> + </status-section> </template> <script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator' import {Environment} from '../api/StatusApi' import Card from './Card.vue' +import StatusSection from '../components/StatusSection.vue' import {Option, none} from 'ts-option' @Component({ components: { - card: Card + 'card': Card, + 'status-section': StatusSection } }) export default class EnvironmentCard extends Vue { diff --git a/kamon-status/src/components/InstrumentationModuleStatusCard.vue b/kamon-status/src/components/InstrumentationModuleStatusCard.vue index 6fb1206f..ed74143a 100644 --- a/kamon-status/src/components/InstrumentationModuleStatusCard.vue +++ b/kamon-status/src/components/InstrumentationModuleStatusCard.vue @@ -1,6 +1,6 @@ <template> <status-card :indicator-text="runStatus.message" :indicator-icon="runStatus.icon" :indicator-background-color="runStatus.color"> - <div slot="default" class="py-3"> + <div slot="default" class="py-3 pl-4"> <h5 class="mb-0">{{ module.name }}</h5> <div class="text-label"> {{ module.description }} diff --git a/kamon-status/src/components/MetricList.vue b/kamon-status/src/components/MetricList.vue index 60f7499a..f0f4637b 100644 --- a/kamon-status/src/components/MetricList.vue +++ b/kamon-status/src/components/MetricList.vue @@ -2,27 +2,19 @@ <div class="row no-gutters"> <div class="col-12"> <div class="search-box mb-3"> - <input class="w-100 px-3 py-2" v-model="filterPattern" type="text" placeholder="filter"> + <span class="search-icon"><i class="fas fa-search fa-fw fa-flip-horizontal"></i></span> + <input class="w-100" v-model="filterPattern" type="text"> <span class="search-stats">{{ searchStats }}</span> </div> </div> - <div class="col-12"> - <card v-if="matchedMetrics.length > 0"> - <div class="row no-gutters" v-for="(metric, index) in matchedMetrics" :key="metric.search"> - <div class="col-12 px-3 pt-2 pb-3"> - <div class="text-uppercase text-label">{{ metric.type }}</div> - <h5>{{ metric.name }}</h5> - <div class="tag-container"> - <span class="tag" v-for="tag in Object.keys(metric.tags)" :key="tag"> - {{ tag }}=<span class="tag-value">{{ metric.tags[tag] }}</span> - </span> - </div> - - </div> - <hr v-if="index < (matchedMetrics.length - 1)" class="w-100"> + <div class="col-12" v-if="matchedMetrics.length > 0"> + <div class="row no-gutters" v-for="(group, index) in groups" :key="group.name"> + <div class="col-12"> + <metric-list-item :group="group"/> </div> - </card> + <hr v-if="index < (groups.length - 1)" class="w-100"> + </div> </div> </div> </template> @@ -31,30 +23,55 @@ import { Component, Prop, Vue } from 'vue-property-decorator' import {Metric} from '../api/StatusApi' import Card from './Card.vue' - +import MetricListItem, {MetricGroup} from './MetricListItem.vue' +import StatusCard from './StatusCard.vue' +import _ from 'underscore' @Component({ components: { - card: Card + 'card': Card, + 'status-card': StatusCard, + 'metric-list-item': MetricListItem } }) export default class MetricList extends Vue { - @Prop() private metrics!: Metric[] + @Prop( { default: [] }) private metrics!: Metric[] private filterPattern: string = '' get totalMetrics(): number { return this.metrics.length } + get groups(): MetricGroup[] { + const gropedByName = _.groupBy(this.matchedMetrics, m => m.name) + const metricGroups: MetricGroup[] = [] + + Object.keys(gropedByName).forEach(metricName => { + const metrics = gropedByName[metricName] + + // All metrics with the same name must have the same unit (constrained in Kamon) so + // we can safely assume the first unit is the same for all. + metricGroups.push({ + name: metricName, + type: metrics[0].type, + unitDimension: metrics[0].unitDimension, + unitMagnitude: metrics[0].unitMagnitude, + metrics + }) + }) + + return _.sortBy(metricGroups, mg => mg.metrics.length).reverse() + } + get filterRegex(): RegExp { return new RegExp(this.filterPattern) } get searchStats(): string { if (this.filterPattern.length > 0) { - return this.matchedMetrics.length + ' matched' + return 'showing ' + this.matchedMetrics.length + ' out of ' + this.totalMetrics + ' series' } else { - return this.totalMetrics + ' metrics' + return this.totalMetrics + ' series' } } @@ -68,14 +85,19 @@ export default class MetricList extends Vue { } </script> -<style scoped lang="scss"> +<style lang="scss"> .search-box { input { color: #676767; - height: 2.5rem; + caret-color: #676767; + height: 3rem; border: none; - border-radius: 0.3rem; + border-radius: 0.4rem; background-color: #efefef; + padding-left: 3.5rem; + font-size: 1.1rem; + box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.1); + &:focus { outline: none; @@ -86,11 +108,22 @@ export default class MetricList extends Vue { color: #929292; } + .search-icon { + color: #c0c0c0; + line-height: 3rem; + font-size: 1.4rem; + position: absolute; + left: 1rem; + } + .search-stats { + color: #a2a2a2; + font-size: 1.1rem; position: absolute; - line-height: 2.5rem; + line-height: 3rem; right: 0; padding-right: 1rem; } } + </style> diff --git a/kamon-status/src/components/MetricListItem.vue b/kamon-status/src/components/MetricListItem.vue new file mode 100644 index 00000000..1414bbaf --- /dev/null +++ b/kamon-status/src/components/MetricListItem.vue @@ -0,0 +1,108 @@ +<template> + <status-card indicator-background-color="#7ade94" class="metric-list-item c-pointer my-1" :class="{ 'my-4': expanded }"> + + <div slot="status-indicator" @click="onCardClick"> + <div class="metric-count"> + {{ group.metrics.length }} + </div> + <div>SERIES</div> + </div> + + <div slot="default" @click="onCardClick"> + <div class="row no-gutters"> + <div class="col"> + <div class="py-3 pl-4"> + <h5>{{ group.name }}</h5> + <div class="text-label"> + <span>{{group.type}}</span> + <span v-if="group.unitDimension !== 'none'"> - {{ group.unitDimension }}</span> + <span v-if="group.unitMagnitude !== 'none'"> - {{ group.unitMagnitude }}</span> + </div> + </div> + </div> + <div class="col-auto expansion-icon px-5"> + <i class="fas fa-fw" :class="expansionIcon"></i> + </div> + <div class="col-12 series-container" v-if="expanded"> + <div v-for="(metric, index) in group.metrics" :key="index"> + <div class="p-3"> + <h6>Incarnation #{{ index + 1 }}</h6> + <div class="tag-container"> + <span class="tag" v-for="tag in Object.keys(metric.tags)" :key="tag"> + {{ tag }}=<span class="tag-value">{{ metric.tags[tag] }}</span> + </span> + <span v-if="Object.keys(metric.tags).length === 0" class="pl-2">Base Metric - No Tags</span> + </div> + </div> + <hr v-if="index < (group.metrics.length - 1)" class="w-100 incarnation-hr"> + </div> + </div> + </div> + </div> + + + </status-card> +</template> + +<script lang="ts"> +import { Component, Prop, Vue } from 'vue-property-decorator' +import {Metric} from '../api/StatusApi' +import Card from './Card.vue' +import StatusCard from './StatusCard.vue' +import _ from 'underscore' + +export interface MetricGroup { + name: string + type: string + unitDimension: string + unitMagnitude: string + metrics: Metric[] +} + +@Component({ + components: { + 'status-card': StatusCard + } +}) +export default class MetricListItem extends Vue { + @Prop( { default: [] }) private group!: MetricGroup + private expanded: boolean = false + + get expansionIcon(): string { + return this.expanded ? 'fa-angle-up' : 'fa-angle-down' + } + + private onCardClick() { + this.expanded = !this.expanded + } +} +</script> + +<style lang="scss"> + +.metric-count { + font-size: 2rem; + font-weight: 700; + line-height: 4rem; +} + +.expansion-icon { + line-height: 6rem; + color: #d0d0d0; + font-size: 2rem; +} + +.series-container { + background-color: #f7f7f7; +} + +.incarnation-hr { + border-color: #e2e2e2; +} + +.metric-list-item { + .tag { + background-color: #e6e6e6; + } +} +</style> diff --git a/kamon-status/src/components/ModuleList.vue b/kamon-status/src/components/ModuleList.vue index eddc05d0..6cef9e2d 100644 --- a/kamon-status/src/components/ModuleList.vue +++ b/kamon-status/src/components/ModuleList.vue @@ -1,23 +1,25 @@ <template> - <div class="row"> - <div class="col-12 pt-4 pb-2"> - <h3>Reporters</h3> - </div> - <div class="col-12 py-1" v-for="reporter in reporterModules" :key="reporter.name"> - <module-status-card :module="reporter" /> - </div> - <div v-if="!hasApmModule" class="col-12 py-1 apm-suggestion"> - <a href="https://kamon.io/" target="_blank"> - <module-status-card :is-suggestion="true" :module="apmModuleSuggestion" /> - </a> - </div> + <div class="w-100"> + <status-section title="Reporters"> + <div class="row"> + <div class="col-12 py-1" v-for="reporter in reporterModules" :key="reporter.name"> + <module-status-card :module="reporter" /> + </div> + <div v-if="!hasApmModule" class="col-12 py-1 apm-suggestion"> + <a href="https://kamon.io/" target="_blank"> + <module-status-card :is-suggestion="true" :module="apmModuleSuggestion" /> + </a> + </div> + </div> + </status-section> - <div class="col-12 pt-4 pb-2" v-if="plainModules.length > 0"> - <h2>Modules</h2> - </div> - <div class="col-12 py-1" v-for="module in plainModules" :key="module.name"> - <module-status-card :module="module"/> - </div> + <status-section title="Modules" v-if="plainModules.length > 0"> + <div class="row"> + <div class="col-12 py-1" v-for="module in plainModules" :key="module.name"> + <module-status-card :module="module"/> + </div> + </div> + </status-section> </div> </template> @@ -25,10 +27,12 @@ import { Component, Prop, Vue } from 'vue-property-decorator' import {Module, ModuleKind} from '../api/StatusApi' import ModuleStatusCard from './ModuleStatusCard.vue' +import StatusSection from './StatusSection.vue' @Component({ components: { + 'status-section': StatusSection, 'module-status-card': ModuleStatusCard } }) diff --git a/kamon-status/src/components/ModuleStatusCard.vue b/kamon-status/src/components/ModuleStatusCard.vue index 825fce40..d095d7b3 100644 --- a/kamon-status/src/components/ModuleStatusCard.vue +++ b/kamon-status/src/components/ModuleStatusCard.vue @@ -1,6 +1,6 @@ <template> <status-card :indicator-text="runStatus.message" :indicator-icon="runStatus.icon" :indicator-background-color="runStatus.color"> - <div slot="default" class="py-3"> + <div slot="default" class="py-3 pl-4"> <h5 class="mb-0 mr-3 d-inline-block">{{ module.name }}</h5> <div class="tag-container d-inline-block" v-if="!isSuggestion"> <span class="tag">{{ module.kind }}</span> @@ -36,7 +36,7 @@ export default class ModuleStatusCard extends Vue { get runStatus(): { message: string, color: string, icon: string } { if (this.isSuggestion) { return { message: 'suggested', color: '#5fd7cc', icon: 'fa-plug' } - } else if(!this.module.enabled) { + } else if (!this.module.enabled) { return { message: 'disabled', color: '#ff9898', icon: 'fa-stop-circle' } } else { return this.module.started ? diff --git a/kamon-status/src/components/OverviewCard.vue b/kamon-status/src/components/OverviewCard.vue index a1805596..8939d93d 100644 --- a/kamon-status/src/components/OverviewCard.vue +++ b/kamon-status/src/components/OverviewCard.vue @@ -1,24 +1,22 @@ <template> - <div class="row"> - <div class="col-12"> - <card> - <div class="row py-2 no-gutters"> - <div class="col-12 col-md-3 py-2 px-3"> - <div class="text-uppercase text-label pb-1">Instrumentation</div> - <h5>{{instrumentationStatusMessage}}</h5> - </div> - <div class="col-12 col-md-3 py-2 px-3"> - <div class="text-uppercase text-label pb-1">Reporters</div> - <h5>{{ activeReporters.length }} Started</h5> - </div> - <div class="col-12 col-md-3 py-2 px-3"> - <div class="text-uppercase text-label pb-1">Metrics</div> - <h5>{{metricsStatusMessage}}</h5> - </div> + <status-section title="Overview"> + <card> + <div class="row py-2 no-gutters"> + <div class="col-12 col-md-3 py-2 px-3"> + <div class="text-uppercase text-label pb-1">Instrumentation</div> + <h5>{{instrumentationStatusMessage}}</h5> </div> - </card> - </div> - </div> + <div class="col-12 col-md-3 py-2 px-3"> + <div class="text-uppercase text-label pb-1">Reporters</div> + <h5>{{ activeReporters.length }} Started</h5> + </div> + <div class="col-12 col-md-3 py-2 px-3"> + <div class="text-uppercase text-label pb-1">Metrics</div> + <h5>{{metricsStatusMessage}}</h5> + </div> + </div> + </card> + </status-section> </template> @@ -26,11 +24,13 @@ import { Component, Vue, Prop } from 'vue-property-decorator' import {Option, none, some} from 'ts-option' import Card from '../components/Card.vue' +import StatusSection from '../components/StatusSection.vue' import {StatusApi, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric, Instrumentation} from '../api/StatusApi' @Component({ components: { - card: Card + 'card': Card, + 'status-section': StatusSection }, }) export default class OverviewCard extends Vue { @@ -57,7 +57,7 @@ export default class OverviewCard extends Vue { } get metricsStatusMessage(): string { - return this.trackedMetrics.map(mc => mc + ' Tracked').getOrElse('Unknown') + return this.trackedMetrics.map(mc => mc + ' Series').getOrElse('Unknown') } private isReporter(module: Module): boolean { diff --git a/kamon-status/src/components/StatusCard.vue b/kamon-status/src/components/StatusCard.vue index 7293940e..c402842f 100644 --- a/kamon-status/src/components/StatusCard.vue +++ b/kamon-status/src/components/StatusCard.vue @@ -1,6 +1,6 @@ <template> <card> - <div class="row status-card"> + <div class="row status-card no-gutters"> <div class="col-auto"> <div class="status-indicator-wrap text-center text-uppercase" :style="indicatorStyle"> <slot name="status-indicator"> @@ -56,15 +56,16 @@ $indicator-size: 6rem; .status-indicator-wrap { height: 100%; min-width: $indicator-size; + max-width: $indicator-size; min-height: $indicator-size; + font-size: 0.9rem; + font-weight: 600; } .status-indicator { line-height: 2rem; - font-size: 0.9rem; - font-weight: 600; - i { + i { font-size: 2.5rem; } } diff --git a/kamon-status/src/components/StatusSection.vue b/kamon-status/src/components/StatusSection.vue new file mode 100644 index 00000000..b94b5bdf --- /dev/null +++ b/kamon-status/src/components/StatusSection.vue @@ -0,0 +1,19 @@ +<template> + <div class="row"> + <div class="col-12 pt-4 pb-2"> + <h3>{{ title }}</h3> + </div> + <div class="col-12 py-1"> + <slot name="default"/> + </div> + </div> +</template> + +<script lang="ts"> +import { Component, Prop, Vue } from 'vue-property-decorator' + +@Component +export default class StatusSection extends Vue { + @Prop() private title!: string +} +</script>
\ No newline at end of file diff --git a/kamon-status/src/styles/main.scss b/kamon-status/src/styles/main.scss index 54e77dfb..6c0c0551 100644 --- a/kamon-status/src/styles/main.scss +++ b/kamon-status/src/styles/main.scss @@ -11,7 +11,13 @@ h1, h2, h3 { font-weight: 300; } +hr { + margin: 0px; + border-color: #f3f3f3; +} + .text-label { + font-size: 16px; color: #b3b3b3; } @@ -22,7 +28,7 @@ h1, h2, h3 { .tag { display: inline-block; overflow-wrap: anywhere; - background-color: #f4f4f4; + background-color: #f5f5f5; margin: 0.3rem; padding: 0.1rem 0.5rem; border-radius: 0.2rem; @@ -36,4 +42,8 @@ h1, h2, h3 { a, a:hover, a:active { color: inherit; text-decoration: none; +} + +.c-pointer { + cursor: pointer; }
\ No newline at end of file diff --git a/kamon-status/src/views/Overview.vue b/kamon-status/src/views/Overview.vue index ae79f0c6..7d200e81 100644 --- a/kamon-status/src/views/Overview.vue +++ b/kamon-status/src/views/Overview.vue @@ -1,16 +1,10 @@ <template> <div class="container"> <div class="row"> - <div class="col-12 pt-4 pb-2"> - <h3>Overview</h3> - </div> <div class="col-12"> <overview-card :module-registry="moduleRegistry" :metric-registry="metricRegistry" :instrumentation="instrumentation"/> </div> - <div class="col-12 pt-4 pb-2"> - <h3>Environment</h3> - </div> <div class="col-12"> <environment-card :environment="environment"/> </div> |