index.vue 6.81 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
<!--
  - 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">
    <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 metrics failed.
          </strong>
          <p v-text="error.message"/>
        </div>
      </div>
29 30
      <div v-if="isOldMetrics" class="message is-warning">
        <div class="message-body">
31
          Metrics are not supported for Spring Boot 1.x applications.
32 33 34 35
        </div>
      </div>
      <form @submit.prevent="handleSubmit" class="field" v-else-if="availableMetrics.length > 0">
        <div class="field">
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
          <div class="control">
            <div class="select">
              <select v-model="selectedMetric">
                <option v-for="metric in availableMetrics" v-text="metric" :key="metric"/>
              </select>
            </div>
          </div>
        </div>
        <div>
          <p v-if="stateFetchingTags === 'executing'" class="is-loading">Fetching available tags</p>

          <div class="box" v-if="availableTags">
            <div class="field is-horizontal" v-for="tag in availableTags" :key="tag.tag">
              <div class="field-label">
                <label class="label" v-text="tag.tag"/>
              </div>
              <div class="field-body">
                <div class="control">
                  <div class="select">
                    <select v-model="selectedTags[tag.tag]">
                      <option :value="undefined">-</option>
                      <option v-for="value in tag.values" :key="value" :value="value" v-text="value"/>
                    </select>
                  </div>
                </div>
              </div>
            </div>
            <p v-if="availableTags && availableTags.length === 0">
              No tags available.
            </p>
            <div class="field is-grouped is-grouped-right">
              <div class="control">
                <button type="submit" class="button is-primary">Add Metric</button>
              </div>
            </div>
          </div>
        </div>
      </form>

      <metric v-for="metric in metrics"
              :key="metric.name"
              :metric-name="metric.name"
              :tag-selections="metric.tagSelections"
79
              :statistic-types="metric.types"
80 81
              :instance="instance"
              @remove="removeMetric"
82
              @type-select="handleTypeSelect"
83 84 85 86 87 88 89 90 91 92
      />
    </div>
  </section>
</template>

<script>
  import Instance from '@/services/instance';
  import _ from 'lodash';
  import Metric from './metric';

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
  const stringify = metrics => {
    return {q: metrics.map(JSON.stringify)};
  };

  const parse = query => {
    if (!query.q) {
      return [];
    }
    if (query.q instanceof Array) {
      return query.q.map(JSON.parse);
    } else {
      return JSON.parse(query.q);
    }
  };

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
  export default {
    components: {Metric},
    props: {
      instance: {
        type: Instance,
        required: true
      }
    },
    data: () => ({
      metrics: [],
      error: null,
      availableMetrics: [],
      selectedMetric: null,
      stateFetchingTags: null,
      availableTags: null,
123 124
      selectedTags: null,
      isOldMetrics: false
125 126 127 128 129
    }),
    created() {
      this.fetchMetricIndex();
    },
    watch: {
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
      selectedMetric: 'fetchAvailableTags',
      metrics: {
        deep: true,
        handler() {
          this.$router.replace({
            name: 'instance/metrics',
            query: stringify(this.metrics)
          })
        }
      },
      '$route.query': {
        immediate: true,
        handler() {
          this.metrics = parse(this.$route.query);
        }
      }
146 147 148 149 150
    },
    methods: {
      handleSubmit() {
        this.addMetric(this.selectedMetric, this.selectedTags)
      },
151 152 153 154 155 156
      handleTypeSelect(metricName, statistic, type) {
        const metric = this.metrics.find(m => m.name === metricName);
        if (metric) {
          metric.types = {...metric.types, [statistic]: type}
        }
      },
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
      removeMetric(metricName, idxTagSelection) {
        const idxMetric = this.metrics.findIndex(m => m.name === metricName);
        if (idxMetric >= 0) {
          const metric = this.metrics[idxMetric];
          if (idxTagSelection < metric.tagSelections.length) {
            metric.tagSelections.splice(idxTagSelection, 1);
          }
          if (metric.tagSelections.length === 0) {
            this.metrics.splice(idxMetric, 1)
          }
        }
      },
      addMetric(metricName, tagSelection = {}) {
        if (metricName) {
          const metric = this.metrics.find(m => m.name === metricName);
          if (metric) {
            metric.tagSelections = [...metric.tagSelections, {...tagSelection}]
          } else {
            this.metrics = _.sortBy([...this.metrics, {
              name: metricName,
177 178
              tagSelections: [{...tagSelection}],
              types: {}
179 180 181 182 183 184 185 186
            }], [m => m.name]);
          }
        }
      },
      async fetchMetricIndex() {
        this.error = null;
        try {
          const res = await this.instance.fetchMetrics();
187 188 189 190 191 192 193
          if (res.headers['content-type'].includes('application/vnd.spring-boot.actuator.v2')) {
            this.availableMetrics = res.data.names;
            this.availableMetrics.sort();
            this.selectedMetric = this.availableMetrics[0];
          } else {
            this.isOldMetrics = true;
          }
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
        } catch (error) {
          console.warn('Fetching metric index failed:', error);
          this.hasLoaded = true;
          this.error = error;
        }
      },
      async fetchAvailableTags(metricName) {
        this.availableTags = null;
        this.stateFetchingTags = 'executing';
        try {
          const response = await this.instance.fetchMetric(metricName);
          this.availableTags = response.data.availableTags;
          this.stateFetchingTags = 'completed';
          this.selectedTags = {};
          this.availableTags.forEach(t => this.selectedTags[t.tag] = undefined);
        } catch (error) {
          console.warn('Fetching metric tags failed:', error);
          this.stateFetchingTags = 'failed';
        }
      }
    }
  }
</script>