<!-- - Copyright 2014-2018 the original author or authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. --> <template> <section class="section" :class="{ 'is-loading' : !hasLoaded }"> <div class="container"> <div v-if="error" class="message is-danger"> <div class="message-body"> <strong> <font-awesome-icon class="has-text-danger" icon="exclamation-triangle"/> Fetching JMX Beans failed. </strong> <p v-text="error.message"/> </div> </div> <div class="columns"> <div class="column is-narrow"> <nav class="menu domain-list" v-sticks-below="['#navigation', '#instance-tabs']"> <p class="menu-label">domains</p> <ul class="menu-list"> <li> <a class="" v-for="domain in domains" :key="domain.domain" :class="{'is-active' : domain === selectedDomain}" v-text="domain.domain" @click="select(domain)"/> </li> </ul> </nav> </div> <div class="column" v-if="selectedDomain"> <h1 class="heading">MBeans</h1> <div class="m-bean card" :class="{'is-active': mBean === selectedMBean}" v-for="mBean in selectedDomain.mBeans" :key="mBean.descriptor.raw" :id="mBean.descriptor.raw" v-on-clickaway="() => mBean === selectedMBean && select(selectedDomain)"> <header class="m-bean--header hero" :class="{'is-primary': mBean === selectedMBean, 'is-selectable' : mBean !== selectedMBean }" @click="select(selectedDomain, mBean)"> <div class="level is-clipped"> <div class="level-left"> <div class="level-item is-narrow" v-for="attribute in mBean.descriptor.attributes" :key="`mBean-desc-${attribute.name}`"> <div class="is-clipped" :title="`${attribute.name} ${attribute.value}`"> <p class="heading" v-text="attribute.name"/> <p class="title is-size-6" v-text="attribute.value"/> </div> </div> </div> </div> <sba-icon-button v-if="mBean === selectedMBean" :icon="['far', 'times-circle']" class="m-bean--header--close has-text-white" @click.stop="select(selectedDomain)"/> <div class="hero-foot tabs is-boxed" v-if="mBean === selectedMBean"> <ul> <li v-if="mBean.attr" :class="{'is-active' : selected.view === 'attributes' }"> <a @click.stop="select(selectedDomain, selectedMBean, 'attributes')">Attributes</a> </li> <li v-if="mBean.op" :class="{'is-active' : selected.view === 'operations' }"> <a @click.stop="select(selectedDomain, selectedMBean, 'operations')">Operations</a> </li> </ul> </div> </header> <div class="card-content" v-if="mBean === selectedMBean"> <m-bean-attributes v-if="selected.view === 'attributes'" :instance="instance" :domain="selectedDomain.domain" :m-bean="mBean"/> <m-bean-operations v-if="selected.view === 'operations'" :instance="instance" :domain="selectedDomain.domain" :m-bean="mBean"/> </div> </div> </div> </div> </div> </section> </template> <script> import Instance from '@/services/instance'; import _ from 'lodash'; import {directive as onClickaway} from 'vue-clickaway'; import mBeanAttributes from './m-bean-attributes'; import mBeanOperations from './m-bean-operations'; import sticksBelow from '@/directives/sticks-below'; const getOperationName = (name, descriptor) => { const params = descriptor.args.map(arg => arg.type).join(','); return `${name}(${params})`; }; class MBeanDescriptor { constructor(raw) { Object.assign(this, MBeanDescriptor.parse(raw)); this.raw = raw; } static parse(raw) { const attributes = raw.split(',') .map(attribute => attribute.split('=')) .map(([name, value]) => ({name, value})); const displayName = attributes.map(({value}) => value).join(' ').trim(); return {attributes, displayName} } } export class MBean { constructor({descriptor, op, ...mBean}) { Object.assign(this, mBean); this.descriptor = new MBeanDescriptor(descriptor); const flattenedOps = _.toPairs(op).flatMap(([name, value]) => { if (Array.isArray(value)) { return value.map(v => [name, v]); } else { return [[name, value]]; } }).map(([name, operation]) => [getOperationName(name, operation), operation]); this.op = flattenedOps.length > 0 ? _.fromPairs(flattenedOps) : null; } } export default { props: { instance: { type: Instance, required: true } }, components: {mBeanOperations, mBeanAttributes}, directives: {onClickaway, sticksBelow}, data: () => ({ hasLoaded: false, error: null, domains: null, selected: { domain: null, mBean: null, view: null } }), computed: { selectedDomain() { return this.domains && this.domains.find(d => d.domain === this.selected.domain) }, selectedMBean() { return this.selectedDomain && this.selectedDomain.mBeans.find(b => b.descriptor.raw === this.selected.mBean) } }, created() { this.fetchMBeans(); }, watch: { '$route.query': { immediate: true, handler() { this.selected = this.$route.query; } }, selected() { if (!_.isEqual(this.selected, !this.$route.query)) { this.$router.replace({ name: 'instance/jolokia', query: this.selected }); } }, async selectedMBean(newVal) { if (newVal) { await this.$nextTick(); const el = document.getElementById(newVal.descriptor.raw); if (el) { const scrollingEl = document.scrollingElement; const instanceTabs = document.querySelector('#instance-tabs'); const navbarOffset = (instanceTabs ? instanceTabs.getBoundingClientRect().bottom : 120) + 10; const top = scrollingEl.scrollTop + el.getBoundingClientRect().top - navbarOffset; if (scrollingEl.scrollTo) { scrollingEl.scrollTo({top, behavior: 'smooth'}) } else { scrollingEl.scrollTop = top; } } } } }, methods: { async fetchMBeans() { this.error = null; try { const res = await this.instance.listMBeans(); const domains = _.sortBy(res.data, [d => d.domain]); this.domains = domains.map(domain => ({ ...domain, mBeans: _.sortBy(domain.mBeans.map(mBean => new MBean(mBean)), [b => b.descriptor.displayName]) })); if (!this.selectedDomain && this.domains.length > 0) { this.select(this.domains[0]); } } catch (error) { console.warn('Fetching MBeans failed:', error); this.error = error; } this.hasLoaded = true; }, select(domain, mBean, view) { this.selected = { domain: domain && domain.domain, mBean: mBean && mBean.descriptor.raw, view: view || (mBean ? (mBean.attr ? 'attributes' : (mBean.op ? 'operations' : null)) : null) }; } } } </script> <style lang="scss"> @import "~@/assets/css/utilities"; .m-bean { transition: all $easing $speed; &.is-active { margin: 0.75rem -0.75rem; max-width: unset; } &.is-active .m-bean--header { padding-bottom: 0; } &:not(.is-active) .m-bean--header:hover { background-color: $white-bis; } &--header { & .level .level-left { width: 100%; & .level-item { min-width: 0; flex-shrink: 1; & p { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } } } &--close { position: absolute; right: 0.75rem; top: 0.75rem; } } } </style>