aboutsummaryrefslogtreecommitdiff
path: root/kamon-status-page/src/main/vue/src
diff options
context:
space:
mode:
Diffstat (limited to 'kamon-status-page/src/main/vue/src')
-rw-r--r--kamon-status-page/src/main/vue/src/App.vue47
-rw-r--r--kamon-status-page/src/main/vue/src/api/StatusApi.ts137
-rw-r--r--kamon-status-page/src/main/vue/src/assets/logo.svg26
-rw-r--r--kamon-status-page/src/main/vue/src/components/Card.vue20
-rw-r--r--kamon-status-page/src/main/vue/src/components/EnvironmentCard.vue66
-rw-r--r--kamon-status-page/src/main/vue/src/components/InstrumentationModuleList.vue44
-rw-r--r--kamon-status-page/src/main/vue/src/components/InstrumentationModuleStatusCard.vue40
-rw-r--r--kamon-status-page/src/main/vue/src/components/MetricList.vue129
-rw-r--r--kamon-status-page/src/main/vue/src/components/MetricListItem.vue108
-rw-r--r--kamon-status-page/src/main/vue/src/components/ModuleList.vue86
-rw-r--r--kamon-status-page/src/main/vue/src/components/ModuleStatusCard.vue52
-rw-r--r--kamon-status-page/src/main/vue/src/components/OverviewCard.vue71
-rw-r--r--kamon-status-page/src/main/vue/src/components/StatusCard.vue85
-rw-r--r--kamon-status-page/src/main/vue/src/components/StatusSection.vue19
-rw-r--r--kamon-status-page/src/main/vue/src/main.ts13
-rw-r--r--kamon-status-page/src/main/vue/src/router.ts15
-rw-r--r--kamon-status-page/src/main/vue/src/shims-tsx.d.ts13
-rw-r--r--kamon-status-page/src/main/vue/src/shims-vue.d.ts4
-rw-r--r--kamon-status-page/src/main/vue/src/styles/main.scss49
-rw-r--r--kamon-status-page/src/main/vue/src/views/Overview.vue125
20 files changed, 1149 insertions, 0 deletions
diff --git a/kamon-status-page/src/main/vue/src/App.vue b/kamon-status-page/src/main/vue/src/App.vue
new file mode 100644
index 00000000..fd612828
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/App.vue
@@ -0,0 +1,47 @@
+<template>
+ <div id="app">
+ <div class="header w-100 mb-1 sticky-top">
+ <div class="container h-100">
+ <div class="row h-100 justify-content-between">
+ <div class="col-auto h-100">
+ <img class="logo h-100 img-fluid" src="./assets/logo.svg" alt="">
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <router-view/>
+ </div>
+
+</template>
+
+<style lang="scss">
+
+$header-height: 85px;
+
+.header {
+ height: $header-height;
+ background-color: white;
+ box-shadow: 0 5px 9px 1px rgba(0,0,0,0.1);
+
+ .navigation {
+ line-height: $header-height;
+
+ .navigation-link, a {
+ display: inline-block;
+ padding: 0 0.5rem;
+ text-transform: uppercase;
+ text-decoration: none;
+ color: #b3b3b3;
+
+ &:hover {
+ color: #888888;
+ }
+ }
+ }
+
+ .logo {
+ padding: 1rem 0rem;
+ }
+}
+</style>
diff --git a/kamon-status-page/src/main/vue/src/api/StatusApi.ts b/kamon-status-page/src/main/vue/src/api/StatusApi.ts
new file mode 100644
index 00000000..ee596b8d
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/api/StatusApi.ts
@@ -0,0 +1,137 @@
+import axios, { AxiosResponse } from 'axios'
+
+export interface Environment {
+ service: string
+ host: string
+ instance: string
+ tags: { [key: string]: string }
+}
+
+export interface Settings {
+ version: string
+ environment: Environment
+ config: any
+}
+
+export enum ModuleKind {
+ Combined = 'combined',
+ Metric = 'metric',
+ Span = 'span',
+ Plain = 'plain',
+ Unknown = 'unknown'
+}
+
+export interface Module {
+ name: string
+ description: string
+ clazz: string
+ kind: ModuleKind
+ programmaticallyRegistered: boolean
+ enabled: boolean
+ started: boolean
+}
+
+export interface Metric {
+ name: string
+ type: string
+ unitDimension: string
+ unitMagnitude: string
+ tags: { [key: string ]: string }
+ search: string
+}
+
+export interface ModuleRegistry {
+ modules: Module[]
+}
+
+export interface MetricRegistry {
+ metrics: Metric[]
+}
+
+export interface InstrumentationModule {
+ name: string
+ description: string
+ enabled: boolean
+ active: boolean
+}
+
+export interface Instrumentation {
+ active: boolean
+ modules: InstrumentationModule[]
+ errors: { [key: string]: string[]}
+}
+
+
+export class StatusApi {
+
+ public static settings(): Promise<Settings> {
+ return axios.get('/status/settings').then(response => {
+ const config = JSON.parse(response.data.config)
+ return {
+ version: response.data.version,
+ environment: response.data.environment,
+ config
+ }
+ })
+ }
+
+ public static moduleRegistryStatus(): Promise<ModuleRegistry> {
+ return axios.get('/status/modules').then(response => {
+ return response.data as ModuleRegistry
+ })
+ }
+
+ public static metricRegistryStatus(): Promise<MetricRegistry> {
+ return axios.get('/status/metrics').then(response => {
+ const metricRegistry = response.data as MetricRegistry
+ const pair = (key: string, value: string) => key + ':' + value + ' '
+
+ metricRegistry.metrics.forEach(metric => {
+ // Fixes the display name for range samplers
+ if (metric.type === 'RangeSampler') {
+ metric.type = 'Range Sampler'
+ }
+
+
+ // Calculate the "search" string and inject it in all metrics.
+ let tagsSearch = ''
+ Object.keys(metric.tags).forEach(tag => {
+ tagsSearch += pair(tag, metric.tags[tag])
+ })
+
+ metric.search =
+ pair('name', metric.name.toLowerCase()) +
+ pair('type', metric.type.toLowerCase()) +
+ tagsSearch
+ })
+
+ return metricRegistry
+ })
+ }
+
+ public static instrumentationStatus(): Promise<Instrumentation> {
+ return axios.get('/status/instrumentation').then(response => {
+ const instrumentation: Instrumentation = {
+ active: response.data.active as boolean,
+ modules: [],
+ errors: {}
+ }
+
+ const rawModules = response.data.modules
+ Object.keys(rawModules).forEach(key => {
+ const rawModule = JSON.parse(rawModules[key])
+ instrumentation.modules.push({
+ name: key,
+ ...rawModule
+ })
+ })
+
+ const rawErrors = response.data.errors
+ Object.keys(rawErrors).forEach(key => {
+ instrumentation.errors[key] = JSON.parse(rawErrors[key])
+ })
+
+ return instrumentation
+ })
+ }
+}
diff --git a/kamon-status-page/src/main/vue/src/assets/logo.svg b/kamon-status-page/src/main/vue/src/assets/logo.svg
new file mode 100644
index 00000000..d351c48a
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/assets/logo.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" width="1529.7" height="283.96">
+ <g id="XMLID_903_-5" fill="#616161" transform="matrix(.99987 0 0 .99987 .19 0)">
+ <path id="XMLID_914_-7" d="M388.3 82.3v62.3l34.9-40h34l-38.9 41.7 39.3 66.7h-34l-20-37c-3.6-6.4-7.2-9.6-10.8-9.6-3 .6-4.5 2.6-4.5 5.5v41H360V68.4h14.7c7.5 0 13.6 6.4 13.6 13.9z" class="st0"/>
+ <path id="XMLID_911_-4" d="M492.9 129.9h-28.3c3.4-20.8 18.1-31 44-31 31 0 46.8 10.2 47.4 31v38.3c0 31-18.7 44.8-49.1 47-27 2.1-45.7-10.4-45.7-35.3.6-27 20.2-34.2 48.7-37 12.1-1.5 18.3-5.1 18.3-11.3-.6-6.4-6.6-9.6-18.3-9.6-10 0-15.5 2.5-17 7.9zm35.7 36.9v-10.4c-6.6 2.8-14.2 5.1-22.5 6.8-11.3 2.1-17 7.4-17 15.7.6 8.9 5.3 13.2 14.2 13.2 15.7 0 25.3-9.3 25.3-25.3z" class="st0"/>
+ <path id="XMLID_909_-1" d="M598.3 141.1V213h-28.7v-66.1c0-32.1 16.2-48 48.2-48 14.5 0 25.5 3.2 33.6 9.6 8.1-6.2 19.1-9.1 33.6-9.1 32.1 0 48 15.9 47.8 48v66.1h-14.7c-9.3-.6-14-5.1-14-14v-57.8c-.6-11.7-7-17.4-19.6-17.4-12.5 0-18.9 5.7-19.1 17.4V213H637v-71.8c-.6-11.7-7-17.4-19.6-17.4-12.6 0-18.9 5.6-19.1 17.3z" class="st0"/>
+ <path id="XMLID_906_-8" d="M850.6 157.3c0 38.7-17.2 58.2-51.2 58.2s-51-19.6-51-58.2c0-39.1 17-58.4 51-58.4s51.2 19.3 51.2 58.4zm-73.6 0c.2 21.9 7.6 32.9 22.1 32.9 14.5 0 21.7-11.5 21.9-33.8 0-21.9-7.2-32.7-21.7-32.7-14.8 0-22.3 11.3-22.3 33.6z" class="st0"/>
+ <path id="XMLID_904_-5" d="M945.5 213c-9.3-.6-14-5.1-14-14v-57.8c-.6-11.7-7-17.4-19.6-17.4-12.6 0-18.9 5.7-19.1 17.4V213h-28.7v-66.1c0-32.1 16.2-48 48.2-48 32.1 0 47.8 15.9 47.6 48V213z" class="st0"/>
+ </g>
+ <g id="XMLID_899_-9" transform="matrix(.99987 0 0 .99987 .19 0)">
+ <path id="XMLID_902_-7" fill="#dadada" d="M153.7 244l36.5 38c1.2 1.2 3.1 2 5 2h89.5c5.2 0 8.3-4.7 5.2-8.1l-83-102.5c-1.8-2-5.2-1.9-6.9.1L153.4 238c-1.6 1.8-1.4 4.2.3 6z"/>
+ <linearGradient id="linearGradient936" x1="63.89" x2="63.89" y1="198.13" y2="-214.95" gradientUnits="userSpaceOnUse" xlink:href="#XMLID_15_-2">
+ <stop id="stop1003-5" offset="0" stop-color="#145643"/>
+ <stop id="stop1005-3" offset="1" stop-color="#14c441"/>
+ </linearGradient>
+ <path id="XMLID_901_-8" fill="#199053" d="M77 12.8C69.8 4.7 60.2 0 50.1 0 32.8 0 17.5 13.6 12.7 33.4 5.9 62 1.8 91.2.5 120.6l9.8 13.4c17.5 24.1 47.9 25.2 66.7 4.5 2.8-2.3 5.4-4.9 7.8-7.8l42.6-51.5c-.1 0-47.9-64.2-50.4-66.4z"/>
+ <path id="XMLID_900_-8" fill="#34cc5b" d="M284.6 0h-73.3c-12.9 0-24.8 5.9-31.7 15.7l-52.4 63.5-42.6 51.5c-2.4 2.9-5 5.4-7.8 7.8-18.8 20.7-49.2 19.6-66.7-4.5L.3 120.6c-1.4 30.8.2 61.7 4.9 92.2v.2c.4 2.3.7 4.7 1.1 7 .1.4.1.8.2 1.2a130.15 130.15 0 0 0 1.4 7.6l1.2 5.9.3 1.4c.5 2.2.9 4.4 1.4 6.6 0 .2.1.4.1.5 4.9 22.3 20.9 38.4 39.9 40.5.2 0 .3.1.5.1 1.3.1 2.5.2 3.8.2h.1c32.3 0 50.7-18.2 59.9-30.8L291.7 11.8c3.7-5.1-.3-11.8-7.1-11.8z"/>
+ <g aria-label="STATUS" style="line-height:125%;-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="text950" fill="#b3b3b3" stroke-width="3.12" font-family="Montserrat" font-size="118.63" font-weight="400" letter-spacing="0" word-spacing="0">
+ <path d="M1039.3 138.08q-6.37 0-10.5 2.62-4.12 2.62-4.12 8 0 5.25 4.12 8.12 4.13 2.75 17.5 6 13.49 3.24 20.24 9.12 6.87 5.87 6.87 17.36 0 11.37-8.62 18.5-8.62 7.12-22.62 7.12-20.49 0-36.36-14.12l9.25-11.12q13.24 11.5 27.49 11.5 7.12 0 11.24-3 4.25-3.13 4.25-8.13 0-5.12-4-7.87-3.87-2.87-13.5-5.12-9.61-2.37-14.61-4.25-5-2-8.87-5.12-7.75-5.87-7.75-18 0-12.11 8.75-18.6 8.87-6.63 21.86-6.63 8.37 0 16.62 2.75 8.25 2.75 14.24 7.74l-7.87 11.12q-3.87-3.5-10.5-5.74-6.61-2.25-13.11-2.25z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path952"/>
+ <path d="M1119.42 140.08v73.84h-14.74v-73.84h-26.5v-13.5h67.73v13.5z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path954"/>
+ <path d="M1160.15 194.05l-8.74 19.87h-15.75l38.48-87.34h15.75l38.48 87.34h-15.74l-8.75-19.87zm37.73-13.62l-15.86-35.98-15.87 35.98z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path956"/>
+ <path d="M1258.58 140.08v73.84h-14.74v-73.84h-26.5v-13.5h67.73v13.5z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path958"/>
+ <path d="M1315.3 193.93q5.99 7 16.24 7 10.24 0 16.24-7 6-7 6-19v-48.35h14.74v48.98q0 18.87-10.37 29.11-10.37 10.12-26.61 10.12-16.25 0-26.62-10.12-10.37-10.24-10.37-29.11v-48.98h14.75v48.36q0 11.99 6 18.99z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path960"/>
+ <path d="M1407.73 138.08q-6.37 0-10.5 2.62-4.12 2.62-4.12 8 0 5.25 4.12 8.12 4.13 2.75 17.5 6 13.49 3.24 20.24 9.12 6.87 5.87 6.87 17.36 0 11.37-8.62 18.5-8.62 7.12-22.62 7.12-20.49 0-36.36-14.12l9.25-11.12q13.24 11.5 27.49 11.5 7.12 0 11.24-3 4.25-3.13 4.25-8.13 0-5.12-4-7.87-3.87-2.87-13.5-5.12-9.61-2.37-14.61-4.25-5-2-8.87-5.12-7.75-5.87-7.75-18 0-12.11 8.75-18.6 8.87-6.63 21.86-6.63 8.37 0 16.62 2.75 8.25 2.75 14.24 7.74l-7.87 11.12q-3.87-3.5-10.5-5.74-6.61-2.25-13.11-2.25z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path962"/>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/kamon-status-page/src/main/vue/src/components/Card.vue b/kamon-status-page/src/main/vue/src/components/Card.vue
new file mode 100644
index 00000000..d3300336
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/Card.vue
@@ -0,0 +1,20 @@
+<template>
+ <div class="card-wrapper">
+ <slot/>
+ </div>
+</template>
+
+<script lang="ts">
+import { Component, Vue } from 'vue-property-decorator'
+
+@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);
+}
+</style>
diff --git a/kamon-status-page/src/main/vue/src/components/EnvironmentCard.vue b/kamon-status-page/src/main/vue/src/components/EnvironmentCard.vue
new file mode 100644
index 00000000..98dc3f9f
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/EnvironmentCard.vue
@@ -0,0 +1,66 @@
+<template>
+ <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 v-else>
+ <h6>None</h6>
+ </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,
+ 'status-section': StatusSection
+ }
+})
+export default class EnvironmentCard extends Vue {
+ @Prop() private environment: Option<Environment> = none
+
+ get instance(): string {
+ return this.environment.map(e => e.instance).getOrElse('Unknown')
+ }
+
+ get host(): string {
+ return this.environment.map(e => e.host).getOrElse('Unknown')
+ }
+
+ get service(): string {
+ return this.environment.map(e => e.service).getOrElse('Unknown')
+ }
+
+ get environmentTags(): { [key: string]: string } {
+ return this.environment.map(e => e.tags).getOrElse({})
+ }
+}
+</script> \ No newline at end of file
diff --git a/kamon-status-page/src/main/vue/src/components/InstrumentationModuleList.vue b/kamon-status-page/src/main/vue/src/components/InstrumentationModuleList.vue
new file mode 100644
index 00000000..224f6716
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/InstrumentationModuleList.vue
@@ -0,0 +1,44 @@
+<template>
+ <div class="row">
+ <div class="col-12 pt-4 pb-2" v-if="modules.length > 0">
+ <h2>Instrumentation Modules</h2>
+ </div>
+ <div class="col-12 py-1" v-for="module in sortedModules" :key="module.name">
+ <instrumentation-module-status-card :module="module"/>
+ </div>
+ </div>
+</template>
+
+<script lang="ts">
+import { Component, Prop, Vue } from 'vue-property-decorator'
+import {InstrumentationModule} from '../api/StatusApi'
+import InstrumentationModuleStatusCard from './InstrumentationModuleStatusCard.vue'
+
+
+@Component({
+ components: {
+ 'instrumentation-module-status-card': InstrumentationModuleStatusCard
+ }
+})
+export default class ModuleList extends Vue {
+ @Prop() private modules!: InstrumentationModule[]
+
+ get sortedModules(): InstrumentationModule[] {
+ return this.modules.sort((left, right) => {
+ if (left.active === right.active) {
+ return left.name.localeCompare(right.name)
+ } else {
+ return left.active ? -1 : 1
+ }
+ })
+ }
+}
+</script>
+
+<style lang="scss">
+.apm-suggestion {
+ .kind-label {
+ background-color: #d0f3f0;
+ }
+}
+</style>
diff --git a/kamon-status-page/src/main/vue/src/components/InstrumentationModuleStatusCard.vue b/kamon-status-page/src/main/vue/src/components/InstrumentationModuleStatusCard.vue
new file mode 100644
index 00000000..ed74143a
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/InstrumentationModuleStatusCard.vue
@@ -0,0 +1,40 @@
+<template>
+ <status-card :indicator-text="runStatus.message" :indicator-icon="runStatus.icon" :indicator-background-color="runStatus.color">
+ <div slot="default" class="py-3 pl-4">
+ <h5 class="mb-0">{{ module.name }}</h5>
+ <div class="text-label">
+ {{ module.description }}
+ </div>
+ </div>
+ </status-card>
+</template>
+
+<script lang="ts">
+import { Component, Prop, Vue } from 'vue-property-decorator'
+import {InstrumentationModule} from '../api/StatusApi'
+import StatusCard from './StatusCard.vue'
+
+
+@Component({
+ components: {
+ 'status-card': StatusCard
+ }
+})
+export default class InstrumentationModuleStatusCard extends Vue {
+ @Prop() private module!: InstrumentationModule
+
+ get runStatus(): { message: string, color: string, icon: string } {
+ if (!this.module.enabled) {
+ return { message: 'disabled', color: '#ff9898', icon: 'fa-stop-circle' }
+ } else {
+ return this.module.active ?
+ { message: 'active', color: '#7ade94', icon: 'fa-check' } :
+ { message: 'available', color: '#bbbbbb', icon: 'fa-stop-circle' }
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+
+</style>
diff --git a/kamon-status-page/src/main/vue/src/components/MetricList.vue b/kamon-status-page/src/main/vue/src/components/MetricList.vue
new file mode 100644
index 00000000..f0f4637b
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/MetricList.vue
@@ -0,0 +1,129 @@
+<template>
+ <div class="row no-gutters">
+ <div class="col-12">
+ <div class="search-box mb-3">
+ <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" 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>
+ <hr v-if="index < (groups.length - 1)" class="w-100">
+ </div>
+ </div>
+ </div>
+</template>
+
+<script lang="ts">
+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,
+ 'status-card': StatusCard,
+ 'metric-list-item': MetricListItem
+ }
+})
+export default class MetricList extends Vue {
+ @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 'showing ' + this.matchedMetrics.length + ' out of ' + this.totalMetrics + ' series'
+ } else {
+ return this.totalMetrics + ' series'
+ }
+ }
+
+ get matchedMetrics(): Metric[] {
+ if (this.filterPattern.length > 0) {
+ return this.metrics.filter(m => m.search.match(this.filterRegex) != null)
+ } else {
+ return this.metrics
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+.search-box {
+ input {
+ color: #676767;
+ caret-color: #676767;
+ height: 3rem;
+ border: none;
+ 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;
+ }
+ }
+
+ ::placeholder {
+ 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: 3rem;
+ right: 0;
+ padding-right: 1rem;
+ }
+}
+
+</style>
diff --git a/kamon-status-page/src/main/vue/src/components/MetricListItem.vue b/kamon-status-page/src/main/vue/src/components/MetricListItem.vue
new file mode 100644
index 00000000..1414bbaf
--- /dev/null
+++ b/kamon-status-page/src/main/vue/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-page/src/main/vue/src/components/ModuleList.vue b/kamon-status-page/src/main/vue/src/components/ModuleList.vue
new file mode 100644
index 00000000..ac1e7963
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/ModuleList.vue
@@ -0,0 +1,86 @@
+<template>
+ <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/apm/?utm_source=kamon&utm_medium=status-page&utm_campaign=kamon-status" target="_blank">
+ <module-status-card :is-suggestion="true" :module="apmModuleSuggestion" />
+ </a>
+ </div>
+ </div>
+ </status-section>
+
+ <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>
+
+<script lang="ts">
+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
+ }
+})
+export default class ModuleList extends Vue {
+ @Prop() private modules!: Module[]
+ private apmModuleSuggestion: Module = {
+ name: 'Kamon APM',
+ description: 'See your metrics and trace data for free with a Starter account.',
+ kind: ModuleKind.Combined,
+ programmaticallyRegistered: false,
+ enabled: false,
+ started: false,
+ clazz: ''
+ }
+
+ get sortedModules(): Module[] {
+ return this.modules.sort((left, right) => {
+ if (left.started === right.started) {
+ return left.name.localeCompare(right.name)
+ } else {
+ return left.started ? -1 : 1
+ }
+ })
+ }
+
+
+ get reporterModules(): Module[] {
+ return this.sortedModules.filter(this.isReporter)
+ }
+
+ get plainModules(): Module[] {
+ return this.sortedModules.filter(m => !this.isReporter(m))
+ }
+
+ get hasApmModule(): boolean {
+ const knownApmClasses = [
+ 'kamon.kamino.KaminoReporter'
+ ]
+
+ return this.modules.find(m => knownApmClasses.indexOf(m.clazz) > 0) !== undefined
+ }
+
+ private isReporter(module: Module): boolean {
+ return [ModuleKind.Combined, ModuleKind.Span, ModuleKind.Metric].indexOf(module.kind) > 0
+ }
+
+ private isStarted(module: Module): boolean {
+ return module.started
+ }
+}
+</script> \ No newline at end of file
diff --git a/kamon-status-page/src/main/vue/src/components/ModuleStatusCard.vue b/kamon-status-page/src/main/vue/src/components/ModuleStatusCard.vue
new file mode 100644
index 00000000..18e2b038
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/ModuleStatusCard.vue
@@ -0,0 +1,52 @@
+<template>
+ <status-card :indicator-text="runStatus.message" :indicator-icon="runStatus.icon" :indicator-background-color="runStatus.color">
+ <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>
+ <span class="tag">{{ discoveryStatus }}</span>
+ </div>
+
+ <div class="text-label">
+ {{ module.description }}
+ </div>
+ </div>
+ </status-card>
+</template>
+
+<script lang="ts">
+import { Component, Prop, Vue } from 'vue-property-decorator'
+import {Module} from '../api/StatusApi'
+import StatusCard from './StatusCard.vue'
+
+
+@Component({
+ components: {
+ 'status-card': StatusCard
+ }
+})
+export default class ModuleStatusCard extends Vue {
+ @Prop({ default: false }) private isSuggestion!: boolean
+ @Prop() private module!: Module
+
+ get discoveryStatus(): string {
+ return this.module.programmaticallyRegistered ? 'manual' : 'automatic'
+ }
+
+ get runStatus(): { message: string, color: string, icon: string } {
+ if (this.isSuggestion) {
+ return { message: 'suggested', color: '#5fd7cc', icon: 'fa-plug' }
+ } else if (!this.module.enabled) {
+ return { message: 'disabled', color: '#ff9898', icon: 'fa-stop-circle' }
+ } else {
+ return this.module.started ?
+ { message: 'active', color: '#7ade94', icon: 'fa-check' } :
+ { message: 'available', color: '#bbbbbb', icon: 'fa-check' }
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+
+</style>
diff --git a/kamon-status-page/src/main/vue/src/components/OverviewCard.vue b/kamon-status-page/src/main/vue/src/components/OverviewCard.vue
new file mode 100644
index 00000000..6746d761
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/OverviewCard.vue
@@ -0,0 +1,71 @@
+<template>
+ <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>
+ <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>
+
+
+<script lang="ts">
+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,
+ 'status-section': StatusSection
+ },
+})
+export default class OverviewCard extends Vue {
+ @Prop() private moduleRegistry: Option<ModuleRegistry> = none
+ @Prop() private metricRegistry: Option<MetricRegistry> = none
+ @Prop() private instrumentation: Option<Instrumentation> = none
+
+ get reporterModules(): Module[] {
+ return this.moduleRegistry
+ .map(moduleRegistry => moduleRegistry.modules.filter(this.isReporter))
+ .getOrElse([])
+ }
+
+ get activeReporters(): Module[] {
+ return this.reporterModules.filter(this.isStarted)
+ }
+
+ get trackedMetrics(): Option<number> {
+ return this.metricRegistry.map(metricRegistry => metricRegistry.metrics.length)
+ }
+
+ get instrumentationStatusMessage(): string {
+ return this.instrumentation.map(i => (i.active ? 'Active' : 'Disabled') as string).getOrElse('Unknown')
+ }
+
+ get metricsStatusMessage(): string {
+ return this.trackedMetrics.map(mc => mc + ' Series').getOrElse('Unknown')
+ }
+
+ private isReporter(module: Module): boolean {
+ return [ModuleKind.Combined, ModuleKind.Span, ModuleKind.Metric].indexOf(module.kind) > 0
+ }
+
+ private isStarted(module: Module): boolean {
+ return module.started
+ }
+}
+</script>
diff --git a/kamon-status-page/src/main/vue/src/components/StatusCard.vue b/kamon-status-page/src/main/vue/src/components/StatusCard.vue
new file mode 100644
index 00000000..c402842f
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/components/StatusCard.vue
@@ -0,0 +1,85 @@
+<template>
+ <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">
+ <div class="status-indicator h-100 pt-3">
+ <i class="fas fa-fw" :class="indicatorIcon"></i>
+ <div>
+ {{ indicatorText }}
+ </div>
+ </div>
+ </slot>
+ </div>
+ </div>
+ <div class="col">
+ <slot name="default">
+
+ </slot>
+ </div>
+ </div>
+ </card>
+</template>
+
+<script lang="ts">
+import { Component, Prop, Vue } from 'vue-property-decorator'
+import Card from './Card.vue'
+
+@Component({
+ components: {
+ card: Card
+ }
+})
+export default class StatusCard extends Vue {
+ @Prop({ default: 'white' }) private indicatorColor!: string
+ @Prop({ default: '#989898' }) private indicatorBackgroundColor!: string
+ @Prop({ default: 'fa-question' }) private indicatorIcon!: string
+ @Prop({ default: 'Unknown' }) private indicatorText!: string
+
+ get indicatorStyle() {
+ return {
+ color: this.indicatorColor,
+ backgroundColor: this.indicatorBackgroundColor
+ }
+ }
+}
+
+</script>
+
+<style lang="scss">
+
+$indicator-size: 6rem;
+.status-card {
+ min-height: $indicator-size;
+
+ .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;
+
+ i {
+ font-size: 2.5rem;
+ }
+ }
+
+ .critical {
+ background-color: #dadada;
+ }
+
+ .healthy {
+ background-color: #7ade94;
+ }
+
+ .suggested {
+ background-color: #5fd7cc;
+ }
+}
+</style>
diff --git a/kamon-status-page/src/main/vue/src/components/StatusSection.vue b/kamon-status-page/src/main/vue/src/components/StatusSection.vue
new file mode 100644
index 00000000..b94b5bdf
--- /dev/null
+++ b/kamon-status-page/src/main/vue/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-page/src/main/vue/src/main.ts b/kamon-status-page/src/main/vue/src/main.ts
new file mode 100644
index 00000000..a0a1ac2c
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/main.ts
@@ -0,0 +1,13 @@
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+import 'bootstrap/dist/css/bootstrap.min.css'
+import '@fortawesome/fontawesome-free/css/all.min.css'
+import './styles/main.scss'
+
+Vue.config.productionTip = false
+
+new Vue({
+ router,
+ render: h => h(App),
+}).$mount('#app')
diff --git a/kamon-status-page/src/main/vue/src/router.ts b/kamon-status-page/src/main/vue/src/router.ts
new file mode 100644
index 00000000..1f11ff0a
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/router.ts
@@ -0,0 +1,15 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import Overview from './views/Overview.vue'
+
+Vue.use(Router)
+
+export default new Router({
+ routes: [
+ {
+ path: '/',
+ name: 'overview',
+ component: Overview,
+ },
+ ],
+})
diff --git a/kamon-status-page/src/main/vue/src/shims-tsx.d.ts b/kamon-status-page/src/main/vue/src/shims-tsx.d.ts
new file mode 100644
index 00000000..3b88b582
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/shims-tsx.d.ts
@@ -0,0 +1,13 @@
+import Vue, { VNode } from 'vue';
+
+declare global {
+ namespace JSX {
+ // tslint:disable no-empty-interface
+ interface Element extends VNode {}
+ // tslint:disable no-empty-interface
+ interface ElementClass extends Vue {}
+ interface IntrinsicElements {
+ [elem: string]: any;
+ }
+ }
+}
diff --git a/kamon-status-page/src/main/vue/src/shims-vue.d.ts b/kamon-status-page/src/main/vue/src/shims-vue.d.ts
new file mode 100644
index 00000000..8f6f4102
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/shims-vue.d.ts
@@ -0,0 +1,4 @@
+declare module '*.vue' {
+ import Vue from 'vue';
+ export default Vue;
+}
diff --git a/kamon-status-page/src/main/vue/src/styles/main.scss b/kamon-status-page/src/main/vue/src/styles/main.scss
new file mode 100644
index 00000000..6c0c0551
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/styles/main.scss
@@ -0,0 +1,49 @@
+body {
+ background-color: #f7f7f7;
+ font-size: 14px;
+ color: #929292;
+ font-family: 'Open Sans', Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+h1, h2, h3 {
+ font-weight: 300;
+}
+
+hr {
+ margin: 0px;
+ border-color: #f3f3f3;
+}
+
+.text-label {
+ font-size: 16px;
+ color: #b3b3b3;
+}
+
+.tag-container {
+ margin: 0rem -0.3rem;
+}
+
+.tag {
+ display: inline-block;
+ overflow-wrap: anywhere;
+ background-color: #f5f5f5;
+ margin: 0.3rem;
+ padding: 0.1rem 0.5rem;
+ border-radius: 0.2rem;
+}
+
+.tag-value {
+ overflow-wrap: anywhere;
+ color: #676767;
+}
+
+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-page/src/main/vue/src/views/Overview.vue b/kamon-status-page/src/main/vue/src/views/Overview.vue
new file mode 100644
index 00000000..424987c1
--- /dev/null
+++ b/kamon-status-page/src/main/vue/src/views/Overview.vue
@@ -0,0 +1,125 @@
+<template>
+ <div class="container">
+ <div class="row">
+ <div class="col-12">
+ <overview-card :module-registry="moduleRegistry" :metric-registry="metricRegistry" :instrumentation="instrumentation"/>
+ </div>
+
+ <div class="col-12">
+ <environment-card :environment="environment"/>
+ </div>
+
+ <div class="col-12">
+ <module-list :modules="modules"/>
+ </div>
+
+ <div class="col-12 pt-4 pb-2" v-if="metrics.length > 0">
+ <h2>Metrics</h2>
+ </div>
+ <div class="col-12" v-if="metrics.length > 0">
+ <metric-list :metrics="metrics"/>
+ </div>
+ <div class="col-12 mb-5">
+ <instrumentation-module-list :modules="instrumentationModules"/>
+ </div>
+
+ </div>
+ </div>
+</template>
+
+<script lang="ts">
+import { Component, Vue } from 'vue-property-decorator'
+import {Option, none, some} from 'ts-option'
+import ModuleList from '../components/ModuleList.vue'
+import InstrumentationModuleList from '../components/InstrumentationModuleList.vue'
+import MetricList from '../components/MetricList.vue'
+import EnvironmentCard from '../components/EnvironmentCard.vue'
+import OverviewCard from '../components/OverviewCard.vue'
+import {StatusApi, Settings, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric,
+ Instrumentation, Environment, InstrumentationModule} from '../api/StatusApi'
+
+@Component({
+ components: {
+ 'overview-card': OverviewCard,
+ 'module-list': ModuleList,
+ 'instrumentation-module-list': InstrumentationModuleList,
+ 'metric-list': MetricList,
+ 'environment-card': EnvironmentCard
+ },
+})
+export default class Overview extends Vue {
+ private settings: Option<Settings> = none
+ private moduleRegistry: Option<ModuleRegistry> = none
+ private metricRegistry: Option<MetricRegistry> = none
+ private instrumentation: Option<Instrumentation> = none
+
+ get reporterModules(): Module[] {
+ return this.moduleRegistry
+ .map(moduleRegistry => moduleRegistry.modules.filter(this.isReporter))
+ .getOrElse([])
+ }
+
+ get activeReporters(): Module[] {
+ return this.reporterModules.filter(this.isStarted)
+ }
+
+ get plainModules(): Module[] {
+ return this.moduleRegistry
+ .map(moduleRegistry => moduleRegistry.modules.filter(m => !this.isReporter(m)))
+ .getOrElse([])
+ }
+
+ get trackedMetrics(): Option<number> {
+ return this.metricRegistry.map(metricRegistry => metricRegistry.metrics.length)
+ }
+
+ get instrumentationStatusMessage(): string {
+ return this.instrumentation.map(i => (i.active ? 'Active' : 'Disabled') as string).getOrElse('Unknown')
+ }
+
+ get metricsStatusMessage(): string {
+ return this.trackedMetrics.map(mc => mc + ' Tracked').getOrElse('Unknown')
+ }
+
+ get metrics(): Metric[] {
+ return this.metricRegistry
+ .map(mr => mr.metrics)
+ .getOrElse([])
+ }
+
+ get modules(): Module[] {
+ return this.moduleRegistry
+ .map(mr => mr.modules)
+ .getOrElse([])
+ }
+
+ get instrumentationModules(): InstrumentationModule[] {
+ return this.instrumentation
+ .map(i => i.modules)
+ .getOrElse([])
+ }
+
+ get environment(): Option<Environment> {
+ return this.settings.map(s => s.environment)
+ }
+
+ public mounted() {
+ this.refreshData()
+ }
+
+ private refreshData(): void {
+ StatusApi.settings().then(settings => { this.settings = some(settings) })
+ StatusApi.metricRegistryStatus().then(metricRegistry => { this.metricRegistry = some(metricRegistry) })
+ StatusApi.moduleRegistryStatus().then(moduleRegistry => {this.moduleRegistry = some(moduleRegistry) })
+ StatusApi.instrumentationStatus().then(instrumentation => {this.instrumentation = some(instrumentation) })
+ }
+
+ private isReporter(module: Module): boolean {
+ return [ModuleKind.Combined, ModuleKind.Span, ModuleKind.Metric].indexOf(module.kind) > 0
+ }
+
+ private isStarted(module: Module): boolean {
+ return module.started
+ }
+}
+</script>