diff options
author | Ivan Topolnjak <ivantopo@gmail.com> | 2019-02-06 15:45:09 +0100 |
---|---|---|
committer | Ivan Topolnjak <ivantopo@gmail.com> | 2019-02-06 15:45:09 +0100 |
commit | 77d0fa78a8dba17e710a0caedbd91272218bfcee (patch) | |
tree | fc1cc3343402b60b416dc2c6a551162357979770 /kamon-status | |
parent | 7e9cb172cb7ead8e5579ffb1f0b0ba3ffef90605 (diff) | |
download | Kamon-77d0fa78a8dba17e710a0caedbd91272218bfcee.tar.gz Kamon-77d0fa78a8dba17e710a0caedbd91272218bfcee.tar.bz2 Kamon-77d0fa78a8dba17e710a0caedbd91272218bfcee.zip |
better component structure, included a Kamon APM suggestion
Diffstat (limited to 'kamon-status')
-rw-r--r-- | kamon-status/package-lock.json | 6 | ||||
-rw-r--r-- | kamon-status/package.json | 1 | ||||
-rw-r--r-- | kamon-status/src/api/StatusApi.ts | 1 | ||||
-rw-r--r-- | kamon-status/src/components/Card.vue | 4 | ||||
-rw-r--r-- | kamon-status/src/components/EnvironmentCard.vue | 66 | ||||
-rw-r--r-- | kamon-status/src/components/MetricList.vue | 2 | ||||
-rw-r--r-- | kamon-status/src/components/ModuleCard.vue | 91 | ||||
-rw-r--r-- | kamon-status/src/components/ModuleList.vue | 90 | ||||
-rw-r--r-- | kamon-status/src/components/ModuleStatus.vue | 76 | ||||
-rw-r--r-- | kamon-status/src/components/StatusCard.vue | 71 | ||||
-rw-r--r-- | kamon-status/src/main.ts | 1 | ||||
-rw-r--r-- | kamon-status/src/styles/main.scss | 5 | ||||
-rw-r--r-- | kamon-status/src/views/Overview.vue | 102 |
13 files changed, 358 insertions, 158 deletions
diff --git a/kamon-status/package-lock.json b/kamon-status/package-lock.json index fdcb7690..d49faf54 100644 --- a/kamon-status/package-lock.json +++ b/kamon-status/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@fortawesome/fontawesome-free": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.7.1.tgz", + "integrity": "sha512-gukWJ7Mwf0WXQbkcwcm5zi8+H8aT5MMnphf5hpydOw898H1ibgm2cyejHgk6Km/FTvrPp5ppUHLrlFwt0QxsQw==", + "dev": true + }, "@intervolga/optimize-cssnano-plugin": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz", diff --git a/kamon-status/package.json b/kamon-status/package.json index ae317311..0d622f35 100644 --- a/kamon-status/package.json +++ b/kamon-status/package.json @@ -17,6 +17,7 @@ "vue-router": "^3.0.1" }, "devDependencies": { + "@fortawesome/fontawesome-free": "^5.7.1", "@vue/cli-plugin-typescript": "^3.3.0", "@vue/cli-service": "^3.3.0", "node-sass": "^4.9.0", diff --git a/kamon-status/src/api/StatusApi.ts b/kamon-status/src/api/StatusApi.ts index 25d525d2..3add6161 100644 --- a/kamon-status/src/api/StatusApi.ts +++ b/kamon-status/src/api/StatusApi.ts @@ -24,6 +24,7 @@ export enum ModuleKind { export interface Module { name: string description: string + clazz: string kind: ModuleKind isProgrammaticallyRegistered: boolean isStarted: boolean diff --git a/kamon-status/src/components/Card.vue b/kamon-status/src/components/Card.vue index 745a1caf..d77596da 100644 --- a/kamon-status/src/components/Card.vue +++ b/kamon-status/src/components/Card.vue @@ -1,5 +1,5 @@ <template> - <div class="outer py-1"> + <div class="outer"> <slot/> </div> </template> @@ -13,7 +13,7 @@ } hr { - margin: 1px; + margin: 0px; border-color: #f3f3f3; } </style> diff --git a/kamon-status/src/components/EnvironmentCard.vue b/kamon-status/src/components/EnvironmentCard.vue new file mode 100644 index 00000000..97e79d28 --- /dev/null +++ b/kamon-status/src/components/EnvironmentCard.vue @@ -0,0 +1,66 @@ +<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> + </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> + </div> + </div> +</template> + +<script lang="ts"> +import { Component, Prop, Vue } from 'vue-property-decorator' +import {Environment} from '../api/StatusApi' +import Card from './Card.vue' +import {Option, none} from 'ts-option' + + +@Component({ + components: { + card: Card + } +}) +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/src/components/MetricList.vue b/kamon-status/src/components/MetricList.vue index b9b6a92a..60f7499a 100644 --- a/kamon-status/src/components/MetricList.vue +++ b/kamon-status/src/components/MetricList.vue @@ -10,7 +10,7 @@ <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-1 pb-3"> + <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"> diff --git a/kamon-status/src/components/ModuleCard.vue b/kamon-status/src/components/ModuleCard.vue new file mode 100644 index 00000000..ea3a0c68 --- /dev/null +++ b/kamon-status/src/components/ModuleCard.vue @@ -0,0 +1,91 @@ +<template> + <card> + <div class="row module-card"> + <div class="col-auto"> + <div class="status-indicator h-100 text-center pt-3" :class="runStatus.class"> + <i class="fas fa-fw" :class="runStatus.icon"></i> + <div> + {{ runStatus.message }} + </div> + </div> + </div> + <div class="col"> + <div class="py-3"> + <h5 class="mb-0">{{ moduleStatus.name }} </h5> + <div class="text-label"> + {{ moduleStatus.description }} + </div> + </div> + </div> + </div> + </card> +</template> + +<script lang="ts"> +import { Component, Prop, Vue } from 'vue-property-decorator' +import {Module} from '../api/StatusApi' +import Card from './Card.vue' + + +@Component({ + components: { + card: Card + } +}) +export default class ModuleCard extends Vue { + @Prop({ default: true }) private showStatusIndicators!: boolean + @Prop({ default: false }) private isSuggestion!: boolean + @Prop() private moduleStatus!: Module + + get discoveryStatus(): { message: string, class: string } { + return this.moduleStatus.isProgrammaticallyRegistered ? + { message: 'manual', class: '' } : + { message: 'automatic', class: '' } + } + + get runStatus(): { message: string, class: string, icon: string } { + if (this.isSuggestion) { + return { message: 'suggested', class: 'suggested', icon: 'fa-plug' } + } else { + return this.moduleStatus.isStarted ? + { message: 'started', class: 'healthy', icon: 'fa-check' } : + { message: 'disabled', class: 'critical', icon: 'fa-power-off' } + } + } +} +</script> + +<style lang="scss"> + +$indicator-size: 6rem; +.module-card { + min-height: $indicator-size; + + .status-indicator { + text-transform: uppercase; + min-width: $indicator-size; + min-height: $indicator-size; + line-height: 2rem; + font-size: 0.9rem; + font-weight: 600; + color: white; + background-color: #cccccc; + + i { + font-size: 2.5rem; + } + } + + .critical { + background-color: #dadada; + } + + .healthy { + background-color: #7ade94; + } + + .suggested { + background-color: #5fd7cc; + } +} +</style> diff --git a/kamon-status/src/components/ModuleList.vue b/kamon-status/src/components/ModuleList.vue new file mode 100644 index 00000000..746ccf16 --- /dev/null +++ b/kamon-status/src/components/ModuleList.vue @@ -0,0 +1,90 @@ +<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-card :moduleStatus="reporter" /> + </div> + <div v-if="!hasApmModule" class="col-12 py-1 apm-suggestion"> + <a href="https://kamon.io/" target="_blank"> + <module-card :is-suggestion="true" :show-status-indicators="false" :moduleStatus="apmModuleSuggestion" /> + </a> + </div> + + + <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-card :moduleStatus="module"/> + </div> + </div> +</template> + +<script lang="ts"> +import { Component, Prop, Vue } from 'vue-property-decorator' +import {Module, ModuleKind} from '../api/StatusApi' +import ModuleCard from './ModuleCard.vue' + + +@Component({ + components: { + 'module-card': ModuleCard + } +}) +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, + isProgrammaticallyRegistered: false, + isStarted: false, + clazz: '' + } + + get sortedModules(): Module[] { + return this.modules.sort((left, right) => { + if (left.isStarted === right.isStarted) { + return left.name.localeCompare(right.name) + } else { + return left.isStarted ? -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.isStarted + } +} +</script> + +<style lang="scss"> +.apm-suggestion { + .kind-label { + background-color: #d0f3f0; + } +} +</style> diff --git a/kamon-status/src/components/ModuleStatus.vue b/kamon-status/src/components/ModuleStatus.vue deleted file mode 100644 index fff3373e..00000000 --- a/kamon-status/src/components/ModuleStatus.vue +++ /dev/null @@ -1,76 +0,0 @@ -<template> - <card> - <div class="row"> - <div class="col-12"> - <div class="py-2 px-3 text-uppercase text-label"> - {{ moduleStatus.kind }} - </div> - <hr> - </div> - <div class="col"> - <div class="px-3 py-3"> - <h5 class="mb-0">{{ moduleStatus.name }}</h5> - <div class="text-label"> - {{ moduleStatus.description }} - </div> - </div> - </div> - <div class="col-auto"> - <div class="status-indicator text-center" :class="runStatus.class">{{ runStatus.message }}</div> - <div class="status-indicator text-center">{{ discoveryStatus.message }}</div> - </div> - </div> - </card> -</template> - -<script lang="ts"> -import { Component, Prop, Vue } from 'vue-property-decorator' -import {Module} from '../api/StatusApi' -import Card from './Card.vue' - - -@Component({ - components: { - card: Card - } -}) -export default class ModuleStatus extends Vue { - @Prop() private moduleStatus!: Module - - get discoveryStatus(): { message: string, class: string } { - return this.moduleStatus.isProgrammaticallyRegistered ? - { message: 'manual', class: '' } : - { message: 'automatic', class: '' } - } - - get runStatus(): { message: string, class: string } { - return this.moduleStatus.isStarted ? - { message: 'started', class: 'healthy' } : - { message: 'disabled', class: 'critical' } - } -} -</script> - -<style scoped lang="scss"> -.status-indicator { - font-size: 0.9rem; - border-radius: 0.3rem; - color: white; - margin: 0.5rem 1rem; - padding: 0.1rem 1rem; - background-color: #cccccc; -} - -.critical { - background-color: #ff6e6b; -} - -.healthy { - background-color: #6ada87; -} - -hr { - margin: 1px; - border-color: #f3f3f3; -} -</style> diff --git a/kamon-status/src/components/StatusCard.vue b/kamon-status/src/components/StatusCard.vue new file mode 100644 index 00000000..193744df --- /dev/null +++ b/kamon-status/src/components/StatusCard.vue @@ -0,0 +1,71 @@ +<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> + </div> + </card> + </div> + </div> +</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 {StatusApi, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric, Instrumentation} from '../api/StatusApi' + +@Component({ + components: { + card: Card + }, +}) +export default class Overview 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.isActive ? 'Active' : 'Disabled') as string).getOrElse('Unknown') + } + + get metricsStatusMessage(): string { + return this.trackedMetrics.map(mc => mc + ' Tracked').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.isStarted + } +} +</script> diff --git a/kamon-status/src/main.ts b/kamon-status/src/main.ts index 02a4b217..a0a1ac2c 100644 --- a/kamon-status/src/main.ts +++ b/kamon-status/src/main.ts @@ -2,6 +2,7 @@ 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 diff --git a/kamon-status/src/styles/main.scss b/kamon-status/src/styles/main.scss index 271da8af..54e77dfb 100644 --- a/kamon-status/src/styles/main.scss +++ b/kamon-status/src/styles/main.scss @@ -32,3 +32,8 @@ h1, h2, h3 { overflow-wrap: anywhere; color: #676767; } + +a, a:hover, a:active { + color: inherit; + text-decoration: none; +}
\ No newline at end of file diff --git a/kamon-status/src/views/Overview.vue b/kamon-status/src/views/Overview.vue index 75b39eb0..830f5110 100644 --- a/kamon-status/src/views/Overview.vue +++ b/kamon-status/src/views/Overview.vue @@ -5,69 +5,18 @@ <h3>Status</h3> </div> <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> - </div> - </card> + <status-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"> - <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> + <environment-card :environment="environment"/> </div> - <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 :moduleStatus="reporter" /> - </div> - - <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 :moduleStatus="module" /> + <div class="col-12"> + <module-list :modules="modules"/> </div> <div class="col-12 pt-4 pb-2" v-if="metrics.length > 0"> @@ -83,22 +32,25 @@ <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import {Option, none, some} from 'ts-option' -import ModuleStatus from '../components/ModuleStatus.vue' -import Card from '../components/Card.vue' +import ModuleList from '../components/ModuleList.vue' import MetricList from '../components/MetricList.vue' -import {StatusApi, Settings, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric, Instrumentation} from '../api/StatusApi' +import EnvironmentCard from '../components/EnvironmentCard.vue' +import StatusCard from '../components/StatusCard.vue' +import {StatusApi, Settings, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric, + Instrumentation, Environment} from '../api/StatusApi' @Component({ components: { - 'card': Card, - 'module-status': ModuleStatus, - 'metric-list': MetricList + 'status-card': StatusCard, + 'module-list': ModuleList, + 'metric-list': MetricList, + 'environment-card': EnvironmentCard }, }) export default class Overview extends Vue { private settings: Option<Settings> = none private moduleRegistry: Option<ModuleRegistry> = none - private metricsRegistry: Option<MetricRegistry> = none + private metricRegistry: Option<MetricRegistry> = none private instrumentation: Option<Instrumentation> = none get reporterModules(): Module[] { @@ -118,7 +70,7 @@ export default class Overview extends Vue { } get trackedMetrics(): Option<number> { - return this.metricsRegistry.map(metricRegistry => metricRegistry.metrics.length) + return this.metricRegistry.map(metricRegistry => metricRegistry.metrics.length) } get instrumentationStatusMessage(): string { @@ -130,36 +82,28 @@ export default class Overview extends Vue { } get metrics(): Metric[] { - return this.metricsRegistry + return this.metricRegistry .map(mr => mr.metrics) .getOrElse([]) } - get instance(): string { - return this.settings.map(s => s.environment.instance).getOrElse('Unknown') - } - - get host(): string { - return this.settings.map(s => s.environment.host).getOrElse('Unknown') - } - - get service(): string { - return this.settings.map(s => s.environment.service).getOrElse('Unknown') + get modules(): Module[] { + return this.moduleRegistry + .map(mr => mr.modules) + .getOrElse([]) } - get environmentTags(): { [key: string]: string } { - return this.settings.map(s => s.environment.tags).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(metricsRegistry => { this.metricsRegistry = some(metricsRegistry) }) + StatusApi.metricRegistryStatus().then(metricRegistry => { this.metricRegistry = some(metricRegistry) }) StatusApi.moduleRegistryStatus().then(moduleRegistry => {this.moduleRegistry = some(moduleRegistry) }) StatusApi.instrumentationStatus().then(instrumentation => {this.instrumentation = some(instrumentation) }) } |