rmqtt-http-api 0.23.0

This plugin provides HTTP APIs for integration with external systems, enabling operations like querying client information and publishing messages.
/* ============================================================
   RMQTT Dashboard — 节点详情弹窗组件
   ============================================================ */
;(function() {
  'use strict';

  window.NodeDetail = {
    name: 'NodeDetail',
    props: {
      node: { type: Object, default: null },
      visible: { type: Boolean, default: false },
    },
    emits: ['close'],
    data: function() {
      return {
        features: null,
        featuresLoading: false,
        featuresError: null,
      };
    },
    watch: {
      visible: function(val) {
        if (val) this.fetchFeatures();
      },
    },
    computed: {
      featureItems: function() {
        var self = this;
        var f = this.features;
        if (!f) return [];
        var keys = [
          { key: 'retain',                labelKey: 'overview.features_retain' },
          { key: 'message_storage',       labelKey: 'overview.features_message_storage' },
          { key: 'session_storage',       labelKey: 'overview.features_session_storage' },
          { key: 'delayed',               labelKey: 'overview.features_delayed' },
          { key: 'shared_subscription',   labelKey: 'overview.features_shared_subscription' },
          { key: 'auto_subscription',     labelKey: 'overview.features_auto_subscription' },
        ];
        return keys.map(function(item) {
          return { key: item.key, label: self.$t(item.labelKey), enabled: !!f[item.key] };
        });
      },
    },
    template: `

      <div v-if="visible && node" class="modal-overlay" @click.self="$emit('close')">

        <div class="modal-panel">

          <div class="modal-header">

            <h3>{{ node.node_name || node.name || '-' }}</h3>

            <button class="btn-icon modal-close" @click="$emit('close')">&times;</button>

          </div>

          <div class="modal-body">

            <table class="detail-table">

              <tr><td class="dt-label">{{ $t('node_detail.node_id') }}</td><td>{{ node.node_id }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.name') }}</td><td>{{ node.node_name || node.name }}</td></tr>

              <tr>

                <td class="dt-label">{{ $t('node_detail.status') }}</td>

                <td>

                  <span :class="(node.node_status === 'Running' || node.running) ? 'status-online' : 'status-offline'">

                    {{ (node.node_status === 'Running' || node.running) ? $t('node_detail.online') : $t('node_detail.offline') }}

                  </span>

                </td>

              </tr>

              <tr><td class="dt-label">{{ $t('node_detail.version') }}</td><td>{{ node.version || '-' }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.connections') }}</td><td>{{ node.connections || 0 }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.cpu') }}</td><td>{{ formatCpu(node.cpuload != null ? node.cpuload : node.load1) }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.memory') }}</td><td>{{ formatMem(node) }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.uptime') }}</td><td>{{ formatUptime(node.uptime) }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.rust_version') }}</td><td>{{ node.rustc_version || '-' }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.sys_desc') }}</td><td>{{ node.sysdescr || '-' }}</td></tr>

              <tr><td class="dt-label">{{ $t('node_detail.datetime') }}</td><td>{{ node.datetime || '-' }}</td></tr>

            </table>

            <h4 class="detail-subs-header" style="margin-top:16px;">{{ $t('node_detail.features_title') }}</h4>

            <table class="detail-table" v-if="features">

              <tr v-for="f in featureItems" :key="f.key">

                <td class="dt-label">{{ f.label }}</td>

                <td>

                  <span class="feature-badge" :class="f.enabled ? 'badge-on' : 'badge-off'">

                    {{ f.enabled ? $t('common.enabled') : $t('common.disabled') }}

                  </span>

                </td>

              </tr>

            </table>

            <div v-else-if="featuresLoading" class="loading-text">{{ $t('common.loading') }}...</div>

            <div v-else-if="featuresError" class="error-text">{{ featuresError }}</div>

          </div>

          <div class="modal-footer">

            <button class="btn btn-primary" @click="$emit('close')">{{ $t('node_detail.close') }}</button>

          </div>

        </div>

      </div>

    `,
    methods: {
      fetchFeatures: function() {
        var self = this;
        var nodeId = this.node && this.node.node_id;
        if (nodeId == null) return;
        this.featuresLoading = true;
        this.featuresError = null;
        this.features = null;
        http.get('/features/' + nodeId).then(function(res) {
          if (res && typeof res === 'object' && res.features) {
            self.features = res.features;
          } else {
            self.featuresError = '功能支持信息获取失败';
          }
          self.featuresLoading = false;
        }).catch(function(e) {
          self.featuresError = (e && e.message) || '功能支持信息获取失败';
          self.featuresLoading = false;
        });
      },
      formatCpu: function(load) {
        return load != null ? load.toFixed(1) + '%' : '-';
      },
      formatUptime: function(str) {
        if (!str) return '-';
        var isZh = window.i18n && window.i18n.locale && window.i18n.locale.indexOf('zh') === 0;
        var parts = [];
        str.replace(/(\d+)\s*(days?|hours?|minutes?|seconds?)/gi, function(m, num, unit) {
          var key = unit.toLowerCase().replace(/s$/, '');
          parts.push({ key: key, num: parseInt(num, 10) });
        });
        if (parts.length === 0) return str;
        var startIdx = -1;
        for (var i = 0; i < parts.length; i++) {
          if (parts[i].num > 0) { startIdx = i; break; }
        }
        if (startIdx === -1) startIdx = parts.length - 1;
        var units = isZh
          ? { day: '', hour: '小时', minute: '', second: '' }
          : { day: ' day ', hour: ' hour ', minute: ' minute ', second: ' second ' };
        var result = '';
        for (var i = startIdx; i < parts.length; i++) {
          var p = parts[i];
          var u = units[p.key] || ' ' + p.key + ' ';
          result += p.num + u;
        }
        return result.trim() || '-';
      },
      formatMem: function(n) {
        var used = n.memory_used;
        var total = n.memory_total;
        if (used == null) return '-';
        var gb = used / (1024 * 1024 * 1024);
        var usedStr = gb >= 1 ? gb.toFixed(1) + 'G' : (used / (1024 * 1024)).toFixed(0) + 'M';
        if (total == null) return usedStr;
        var totalGb = total / (1024 * 1024 * 1024);
        var totalStr = totalGb >= 1 ? totalGb.toFixed(1) + 'G' : (total / (1024 * 1024)).toFixed(0) + 'M';
        return usedStr + ' / ' + totalStr;
      },
    },
  };
})();