This is an automated email from the ASF dual-hosted git repository.

zhaoqingran pushed a commit to branch bulletin
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/bulletin by this push:
     new bdda79bca [feat(bulletin)] implement tree structure for metric 
selection
bdda79bca is described below

commit bdda79bcaa9fa65ff1f1971defa5c0f8ad03aa71
Author: zqr10159 <[email protected]>
AuthorDate: Wed Jul 10 17:30:42 2024 +0800

    [feat(bulletin)] implement tree structure for metric selection
    
    Replace the existing metric dropdown in the bulletin component with a
    tree structure to facilitate hierarchical metric selection. This new
    interface allows users to navigate and choose metrics in a more intuitive
    and visual manner.
---
 .../manager/controller/AppController.java          |  12 ++
 .../hertzbeat/manager/service/AppService.java      |   9 +
 .../manager/service/impl/AppServiceImpl.java       |  87 +++++++-
 .../app/routes/bulletin/bulletin.component.html    | 134 +++---------
 .../src/app/routes/bulletin/bulletin.component.ts  | 226 ++++++++++++---------
 web-app/src/app/routes/routes.module.ts            |  40 ++--
 web-app/src/app/service/app-define.service.ts      |  10 +
 7 files changed, 289 insertions(+), 229 deletions(-)

diff --git 
a/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java
 
b/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java
index 7d6b5cc84..5472f0f39 100644
--- 
a/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java
+++ 
b/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java
@@ -161,6 +161,18 @@ public class AppController {
         return ResponseEntity.ok(Message.success(appHierarchies));
     }
 
+    @GetMapping(path = "/hierarchy/{app}")
+    @Operation(summary = "Query all monitor metrics level, output in a 
hierarchical structure", description = "Query all monitor metrics level, output 
in a hierarchical structure")
+    public ResponseEntity<Message<List<Hierarchy>>> queryAppsHierarchyByApp(
+            @Parameter(description = "en: language type",
+                    example = "zh-CN")
+            @RequestParam(name = "lang", required = false) String lang,
+            @Parameter(description = "en: Monitoring type name", example = 
"api") @PathVariable("app") final String app) {
+        lang = getLang(lang);
+        List<Hierarchy> appHierarchies = appService.getAppHierarchy(app, lang);
+        return ResponseEntity.ok(Message.success(appHierarchies));
+    }
+
     @GetMapping(path = "/defines")
     @Operation(summary = "Query all monitor types", description = "Query all 
monitor types")
     public ResponseEntity<Message<Map<String, String>>> getAllAppDefines(
diff --git 
a/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java 
b/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java
index 2e4de7a5b..21144afb6 100644
--- a/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java
+++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java
@@ -96,6 +96,15 @@ public interface AppService {
      */
     List<Hierarchy> getAllAppHierarchy(String lang);
 
+    /**
+     * Get the monitoring hierarchy based on the monitoring type
+     *
+     * @param app monitoring type
+     * @param lang language
+     * @return hierarchy information
+     */
+    List<Hierarchy> getAppHierarchy(String app, String lang);
+
     /**
      * Get all app define
      *
diff --git 
a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java
 
b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java
index 1e861a006..1fbf998d4 100644
--- 
a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java
+++ 
b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java
@@ -340,13 +340,95 @@ public class AppServiceImpl implements AppService, 
CommandLineRunner {
         return hierarchies;
     }
 
+    @Override
+    public List<Hierarchy> getAppHierarchy(String app, String lang) {
+        LinkedList<Hierarchy> hierarchies = new LinkedList<>();
+        Job job = appDefines.get(app.toLowerCase());
+        // TODO temporarily filter out push to solve the front-end problem, 
and open it after the subsequent design optimization
+        if (DispatchConstants.PROTOCOL_PUSH.equalsIgnoreCase(job.getApp())) {
+            return hierarchies;
+        }
+        var hierarchyApp = new Hierarchy();
+        hierarchyApp.setCategory(job.getCategory());
+        hierarchyApp.setValue(job.getApp());
+        hierarchyApp.setHide(job.isHide());
+        var nameMap = job.getName();
+        if (nameMap != null && !nameMap.isEmpty()) {
+            var i18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, 
nameMap);
+            if (i18nName != null) {
+                hierarchyApp.setLabel(i18nName);
+            }
+        }
+        List<Hierarchy> hierarchyMetricList = new LinkedList<>();
+        if 
(DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(job.getApp())) {
+            List<Monitor> monitors = 
monitorDao.findMonitorsByAppEquals(job.getApp());
+            for (Monitor monitor : monitors) {
+                List<CollectRep.MetricsData> metricsDataList = 
warehouseService.queryMonitorMetricsData(monitor.getId());
+                for (CollectRep.MetricsData metricsData : metricsDataList) {
+                    var hierarchyMetric = new Hierarchy();
+                    hierarchyMetric.setValue(metricsData.getMetrics());
+                    hierarchyMetric.setLabel(metricsData.getMetrics());
+                    List<Hierarchy> hierarchyFieldList = 
metricsData.getFieldsList().stream()
+                            .map(item -> {
+                                var hierarchyField = new Hierarchy();
+                                hierarchyField.setValue(item.getName());
+                                hierarchyField.setLabel(item.getName());
+                                hierarchyField.setIsLeaf(true);
+                                hierarchyField.setType((byte) item.getType());
+                                hierarchyField.setUnit(item.getUnit());
+                                return hierarchyField;
+                            }).collect(Collectors.toList());
+                    hierarchyMetric.setChildren(hierarchyFieldList);
+                    // combine Hierarchy Metrics
+                    combineHierarchyMetrics(hierarchyMetricList, 
hierarchyMetric);
+                }
+            }
+            hierarchyApp.setChildren(hierarchyMetricList);
+            hierarchies.addFirst(hierarchyApp);
+        } else {
+            if (job.getMetrics() != null) {
+                for (var metrics : job.getMetrics()) {
+                    var hierarchyMetric = new Hierarchy();
+                    hierarchyMetric.setValue(metrics.getName());
+                    var metricsI18nName = 
CommonUtil.getLangMappingValueFromI18nMap(lang, metrics.getI18n());
+                    hierarchyMetric.setLabel(metricsI18nName != null ? 
metricsI18nName : metrics.getName());
+                    List<Hierarchy> hierarchyFieldList = new LinkedList<>();
+                    if (metrics.getFields() != null) {
+                        for (var field : metrics.getFields()) {
+                            var hierarchyField = new Hierarchy();
+                            hierarchyField.setValue(field.getField());
+                            var metricI18nName = 
CommonUtil.getLangMappingValueFromI18nMap(lang, field.getI18n());
+                            hierarchyField.setLabel(metricI18nName != null ? 
metricI18nName : field.getField());
+                            hierarchyField.setIsLeaf(true);
+                            // for metric
+                            hierarchyField.setType(field.getType());
+                            hierarchyField.setUnit(field.getUnit());
+                            hierarchyFieldList.add(hierarchyField);
+                        }
+                        hierarchyMetric.setChildren(hierarchyFieldList);
+                    }
+                    hierarchyMetricList.add(hierarchyMetric);
+                }
+            }
+            hierarchyApp.setChildren(hierarchyMetricList);
+            hierarchies.add(hierarchyApp);
+        }
+        return hierarchies;
+    }
+
+
     private void combineHierarchyMetrics(List<Hierarchy> hierarchyMetricList, 
Hierarchy hierarchyMetric) {
         Optional<Hierarchy> preHierarchyOptional = hierarchyMetricList.stream()
-                .filter(item -> 
item.getValue().equals(hierarchyMetric.getValue())).findFirst();
+                .filter(item -> 
item.getValue().equals(hierarchyMetric.getValue()))
+                .findFirst();
+
         if (preHierarchyOptional.isPresent()) {
             Hierarchy preHierarchy = preHierarchyOptional.get();
             List<Hierarchy> children = preHierarchy.getChildren();
-            Set<String> childrenKey = 
children.stream().map(Hierarchy::getValue).collect(Collectors.toSet());
+            Set<String> childrenKey = children.stream()
+                    .map(Hierarchy::getValue)
+                    .collect(Collectors.toSet());
+
             for (Hierarchy child : hierarchyMetric.getChildren()) {
                 if (!childrenKey.contains(child.getValue())) {
                     children.add(child);
@@ -357,6 +439,7 @@ public class AppServiceImpl implements AppService, 
CommandLineRunner {
         }
     }
 
+
     @Override
     public Map<String, Job> getAllAppDefines() {
         return appDefines;
diff --git a/web-app/src/app/routes/bulletin/bulletin.component.html 
b/web-app/src/app/routes/bulletin/bulletin.component.html
index 0529d92db..ddd35a741 100644
--- a/web-app/src/app/routes/bulletin/bulletin.component.html
+++ b/web-app/src/app/routes/bulletin/bulletin.component.html
@@ -146,7 +146,6 @@
             [(ngModel)]="define.app"
             (nzOnSearch)="onSearchAppDefines()"
             (ngModelChange)="onAppChange($event)"
-
           >
             <ng-container *ngFor="let app of appEntries">
               <nz-option *ngIf="isAppListLoading" [nzValue]="app.key" 
[nzLabel]="app.key + '/' + app.value"></nz-option>
@@ -155,116 +154,31 @@
         </nz-form-control>
       </nz-form-item>
       <nz-form-item>
-        <nz-transfer
-          name="metrics"
-          [nzDataSource]="this.currentMetrics"
-          [(ngModel)]="define.metrics"
-          nzShowSearch
-        ></nz-transfer>
+        <nz-form-label [nzSpan]="7" nzFor="dropdown" nzRequired="true">{{ 
'dropdown.label' | i18n }}</nz-form-label>
+        <nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | 
i18n">
+          <nz-transfer [nzDataSource]="treeNodes" 
[nzRenderList]="[leftRenderList, null]" (nzChange)="transferChange($event)">
+            <ng-template #leftRenderList let-items 
let-onItemSelect="onItemSelect">
+              <nz-tree
+                [nzData]="treeNodes"
+                nzExpandAll
+                nzBlockNode
+                nzCheckable
+                nzCheckStrictly
+                (nzCheckBoxChange)="treeCheckBoxChange($event, onItemSelect)"
+              >
+                <ng-template let-node>
+                  <span
+                    (click)="checkBoxChange(node, onItemSelect)"
+                    class="ant-tree-node-content-wrapper 
ant-tree-node-content-wrapper-open"
+                  >
+                    {{ node.title }}
+                  </span>
+                </ng-template>
+              </nz-tree>
+            </ng-template>
+          </nz-transfer>
+        </nz-form-control>
       </nz-form-item>
     </form>
   </div>
 </nz-modal>
-
-<!-- 选择TAG弹出框 -->
-<nz-modal
-  [(nzVisible)]="isTagManageModalVisible"
-  [nzTitle]="'tag.bind' | i18n"
-  (nzOnCancel)="onTagManageModalCancel()"
-  (nzOnOk)="onTagManageModalOk()"
-  nzMaskClosable="false"
-  nzWidth="30%"
-  [nzOkLoading]="isTagManageModalOkLoading"
->
-  <div *nzModalContent class="-inner-content">
-    <input
-      style="margin-left: 5px; width: 50%; text-align: center"
-      nz-input
-      type="text"
-      [placeholder]="'tag.search' | i18n"
-      nzSize="default"
-      (keyup.enter)="loadTagsTable()"
-      [(ngModel)]="tagSearch"
-    />
-    <button nz-button nzType="primary" routerLink="/setting/tags" 
style="margin-left: 5px">
-      <i nz-icon nzType="setting" nzTheme="outline"></i>
-      {{ 'tag.setting' | i18n }}
-    </button>
-    <nz-table #smallTable nzSize="small" [nzData]="tags" [nzPageSize]="8" 
[nzLoading]="tagTableLoading">
-      <thead>
-        <tr>
-          <th nzAlign="center" nzLeft nzWidth="4%" 
[(nzChecked)]="tagCheckedAll" (nzCheckedChange)="onTagAllChecked($event)"></th>
-          <th nzAlign="left">{{ 'tag' | i18n }}</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr *ngFor="let data of smallTable.data">
-          <td nzAlign="center" nzLeft [nzChecked]="checkedTags.has(data)" 
(nzCheckedChange)="onTagItemChecked(data, $event)"></td>
-          <td nzAlign="left">
-            <nz-tag *ngIf="data.tagValue == undefined || data.tagValue.trim() 
== ''" [nzColor]="data.color">{{ data.name }}</nz-tag>
-            <nz-tag *ngIf="data.tagValue != undefined && data.tagValue.trim() 
!= ''" [nzColor]="data.color">
-              {{ data.name + ':' + data.tagValue }}
-            </nz-tag>
-          </td>
-        </tr>
-      </tbody>
-    </nz-table>
-  </div>
-</nz-modal>
-
-<!-- 关联告警定义与监控关系弹出框 -->
-
-<nz-modal
-  [(nzVisible)]="isConnectModalVisible"
-  [nzTitle]="'alert.setting.connect' | i18n"
-  (nzOnCancel)="onConnectModalCancel()"
-  (nzOnOk)="onConnectModalOk()"
-  nzMaskClosable="false"
-  nzWidth="60%"
-  [nzOkLoading]="isConnectModalOkLoading"
->
-  <nz-transfer
-    *nzModalContent
-    [nzDataSource]="transferData"
-    nzShowSearch="true"
-    nzShowSelectAll="false"
-    [nzRenderList]="[renderList, renderList]"
-    (nzChange)="change($event)"
-    style="overflow-x: scroll"
-  >
-    <ng-template
-      #renderList
-      let-items
-      let-direction="direction"
-      let-stat="stat"
-      let-onItemSelectAll="onItemSelectAll"
-      let-onItemSelect="onItemSelect"
-    >
-      <nz-table #t [nzData]="$asTransferItems(items)" nzSize="small">
-        <thead>
-          <tr>
-            <th [nzChecked]="stat.checkAll" [nzIndeterminate]="stat.checkHalf" 
(nzCheckedChange)="onItemSelectAll($event)"></th>
-            <th *ngIf="direction == 'left'">{{ 'alert.setting.connect.left' | 
i18n }}</th>
-            <th *ngIf="direction == 'right'">{{ 'alert.setting.connect.right' 
| i18n }}</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr *ngFor="let data of t.data" (click)="onItemSelect(data)">
-            <td [nzChecked]="!!data.checked" 
(nzCheckedChange)="onItemSelect(data)"></td>
-            <td>{{ data.name }}</td>
-          </tr>
-        </tbody>
-      </nz-table>
-    </ng-template>
-  </nz-transfer>
-</nz-modal>
-
-<!-- 导出告警定义弹出框 -->
-<nz-modal
-  [(nzVisible)]="isSwitchExportTypeModalVisible"
-  [nzTitle]="'alert.export.switch-type' | i18n"
-  (nzOnCancel)="onExportTypeModalCancel()"
-  nzOkDisabled="true"
-  [nzFooter]="switchExportTypeModalFooter"
->
-</nz-modal>
diff --git a/web-app/src/app/routes/bulletin/bulletin.component.ts 
b/web-app/src/app/routes/bulletin/bulletin.component.ts
index 2291d4427..90e2d83b7 100644
--- a/web-app/src/app/routes/bulletin/bulletin.component.ts
+++ b/web-app/src/app/routes/bulletin/bulletin.component.ts
@@ -25,7 +25,7 @@ import { ModalButtonOptions, NzModalService } from 
'ng-zorro-antd/modal';
 import { NzNotificationService } from 'ng-zorro-antd/notification';
 import { NzTableQueryParams } from 'ng-zorro-antd/table';
 import { TransferChange, TransferItem } from 'ng-zorro-antd/transfer';
-import { NzUploadChangeParam } from 'ng-zorro-antd/upload';
+import { NzFormatEmitEvent, NzTreeNode, NzTreeNodeOptions } from 
'ng-zorro-antd/tree';
 import { zip } from 'rxjs';
 import { finalize, map } from 'rxjs/operators';
 
@@ -67,44 +67,16 @@ export class BulletinComponent implements OnInit {
   checkedDefineIds = new Set<number>();
   isSwitchExportTypeModalVisible = false;
   isAppListLoading = false;
-  appHierarchies!: any[];
-  appMap =  new Map<string, string>();
-  appEntries: { value: any; key: string }[] = [];
+  treeNodes!: NzTreeNodeOptions[];
+  hierarchies: TransferItem[] = [];
+  appMap = new Map<string, string>();
+  appEntries: Array<{ value: any; key: string }> = [];
+  checkedNodeList: NzTreeNode[] = [];
   switchExportTypeModalFooter: ModalButtonOptions[] = [
     { label: this.i18nSvc.fanyi('common.button.cancel'), type: 'default', 
onClick: () => (this.isSwitchExportTypeModalVisible = false) }
   ];
   ngOnInit(): void {
     this.loadBulletinDefineTable();
-    // 查询监控层级
-    const getHierarchy$ = this.appDefineSvc
-      .getAppHierarchy(this.i18nSvc.defaultLang)
-      .pipe(
-        finalize(() => {
-          getHierarchy$.unsubscribe();
-        })
-      )
-      .subscribe(
-        message => {
-          if (message.code === 0) {
-            this.appHierarchies = message.data;
-            this.appHierarchies.forEach(item => {
-              if (item.children == undefined) {
-                item.children = [];
-              }
-              item.children.unshift({
-                value: AVAILABILITY,
-                label: this.i18nSvc.fanyi('monitor.availability'),
-                isLeaf: true
-              });
-            });
-          } else {
-            console.warn(message.msg);
-          }
-        },
-        error => {
-          console.warn(error.msg);
-        }
-      );
   }
 
   sync() {
@@ -319,7 +291,6 @@ export class BulletinComponent implements OnInit {
   isManageModalAdd = true;
   define: BulletinDefine = new BulletinDefine();
   cascadeValues: string[] = [];
-  currentMetrics: TransferItem[] = [];
   alertRules: any[] = [{}];
   isExpr = false;
   caseInsensitiveFilter: NzCascaderFilter = (i, p) => {
@@ -329,7 +300,6 @@ export class BulletinComponent implements OnInit {
     });
   };
 
-
   onManageModalCancel() {
     this.isExpr = false;
     this.isManageModalVisible = false;
@@ -548,81 +518,141 @@ export class BulletinComponent implements OnInit {
         }
       );
   }
-  change(ret: TransferChange): void {
-    const listKeys = ret.list.map(l => l.key);
-    const hasOwnKey = (e: TransferItem): boolean => e.hasOwnProperty('key');
-    this.transferData = this.transferData.map(e => {
-      if (listKeys.includes(e.key) && hasOwnKey(e)) {
-        if (ret.to === 'left') {
-          delete e.hide;
-        } else if (ret.to === 'right') {
-          e.hide = false;
+  // 获取应用定义
+  onSearchAppDefines(): void {
+    this.appDefineSvc
+      .getAppDefines(this.i18nSvc.defaultLang)
+      .pipe()
+      .subscribe(
+        message => {
+          if (message.code === 0) {
+            this.appMap = message.data;
+            this.appEntries = Object.entries(this.appMap).map(([key, value]) 
=> ({ key, value }));
+            if (this.appEntries != null) {
+              this.isAppListLoading = true;
+            }
+          } else {
+            console.warn(message.msg);
+          }
+        },
+        error => {
+          console.warn(error.msg);
         }
-      }
-      return e;
-    });
+      );
   }
-  filterMetrics(currentMetrics: any[], cascadeValues: any): any[] {
-    if (cascadeValues.length !== 3) {
-      return currentMetrics;
+
+  // 应用改变事件
+  onAppChange(appKey: string): void {
+    if (appKey) {
+      this.onSearchTreeNodes(appKey);
+    } else {
+      this.hierarchies = [];
+      this.treeNodes = [];
     }
-    // sort the cascadeValues[2] to first
-    return currentMetrics.sort((a, b) => {
-      if (a.value !== cascadeValues[2]) {
-        return 1;
-      } else {
-        return -1;
-      }
-    });
   }
-  // end 告警定义与监控关联model
 
-  // SearchAppDefines
-  onSearchAppDefines(): void {
-    this.appDefineSvc.getAppDefines(this.i18nSvc.defaultLang).pipe().subscribe(
-      message => {
-        if (message.code === 0) {
-          this.appMap = message.data;
-          this.appEntries = Object.entries(this.appMap).map(([key, value]) => 
({ key, value }));
-          if (this.appEntries != null) {
-            this.isAppListLoading = true;
+
+  // 获取树节点
+  onSearchTreeNodes(app: string): void {
+    this.appDefineSvc
+      .getAppHierarchyByName(this.i18nSvc.defaultLang, app)
+      .pipe()
+      .subscribe(
+        message => {
+          if (message.code === 0) {
+            this.hierarchies = this.transformToTransferItems(message.data);
+            this.treeNodes = this.transformToTreeData(message.data);
+          } else {
+            console.warn(message.msg);
           }
-        } else {
-          console.warn(message.msg);
+        },
+        error => {
+          console.warn(error.msg);
         }
-      },
-      error => {
-        console.warn(error.msg);
-      }
-    )
+      );
   }
 
-  onAppChange(appKey: string): void {
-    if (appKey) {
-      this.onSearchMetricsByApp(appKey);
+  // 转换为 TransferItem
+  transformToTransferItems(data: any[]): TransferItem[] {
+    const result: TransferItem[] = [];
+    const traverse = (nodes: any[], parentKey: string | null = null) => {
+      nodes.forEach(node => {
+        if (node.isLeaf) {
+          const key = parentKey ? `${parentKey}-${node.value}` : node.value;
+          result.push({
+            key,
+            title: node.label,
+            direction: 'left',
+            checked: false,
+            isLeaf: node.isLeaf,
+            hide: node.hide
+          });
+        }
+        if (node.children) {
+          traverse(node.children, parentKey ? `${parentKey}-${node.value}` : 
node.value);
+        }
+      });
+    };
+    traverse(data);
+    return result;
+  }
+
+
+
+  transformToTreeData(data: any[]): NzTreeNodeOptions[] {
+    const transformNode = (node: any): NzTreeNodeOptions => {
+      return {
+        title: node.label,
+        key: node.value,
+        isLeaf: node.isLeaf,
+        children: node.children ? node.children.map(transformNode) : []
+      };
+    };
+
+    // 假设最外层节点只有一个
+    const rootNode = data[0];
+    return rootNode.children ? rootNode.children.map(transformNode) : [];
+  }
+
+
+
+  // 树节点勾选事件
+  treeCheckBoxChange(event: NzFormatEmitEvent, onItemSelect: (item: 
TransferItem) => void): void {
+    this.checkBoxChange(event.node!, onItemSelect);
+  }
+
+  // 勾选改变事件
+  checkBoxChange(node: NzTreeNode, onItemSelect: (item: TransferItem) => 
void): void {
+    if (node.isDisabled) {
+      return;
+    }
+    if (node.isChecked) {
+      this.checkedNodeList.push(node);
     } else {
-      this.currentMetrics = [];
+      const idx = this.checkedNodeList.indexOf(node);
+      if (idx !== -1) {
+        this.checkedNodeList.splice(idx, 1);
+      }
+    }
+    const item = this.treeNodes.find(w => w.key === node.key);
+    if (item) {
+      item.checked = node.isChecked;
+      onItemSelect(item);
     }
   }
 
-  onSearchMetricsByApp(app: string): void {
-    this.monitorSvc.getMonitorByApp(app).pipe().subscribe(
-      message => {
-        if (message.code === 0) {
-          if (message.data != null && message.data.length > 0) {
-          this.currentMetrics = message.data.map((metric: any) => ({
-            key: metric,
-            title: metric,
-            description: metric,
-            direction: 'left'
-          }));}
-        } else {
-          console.warn(message.msg);
-        }
-      },
-      error => {
-        console.warn(error.msg);
+  // 穿梭框改变事件
+  transferChange(ret: TransferChange): void {
+    const isDisabled = ret.to === 'right';
+    this.checkedNodeList.forEach(node => {
+      node.isDisabled = isDisabled;
+      node.isChecked = isDisabled;
+    });
+    this.treeNodes = this.treeNodes.map(item => {
+      if (ret.list.some(i => i.key === item.key)) {
+        item.direction = ret.to;
       }
-    )
+      return item;
+    });
   }
 }
diff --git a/web-app/src/app/routes/routes.module.ts 
b/web-app/src/app/routes/routes.module.ts
index 89029ec6e..e0dc6740f 100644
--- a/web-app/src/app/routes/routes.module.ts
+++ b/web-app/src/app/routes/routes.module.ts
@@ -24,29 +24,31 @@ import {NzUploadModule} from "ng-zorro-antd/upload";
 import {NzCascaderModule} from "ng-zorro-antd/cascader";
 import {NzTransferModule} from "ng-zorro-antd/transfer";
 import {NzSwitchComponent} from "ng-zorro-antd/switch";
+import {NzTreeComponent} from "ng-zorro-antd/tree";
 
 const COMPONENTS: Array<Type<void>> = [DashboardComponent, UserLoginComponent, 
UserLockComponent, StatusPublicComponent, BulletinComponent];
 
 @NgModule({
-  imports: [
-    SharedModule,
-    RouteRoutingModule,
-    NgxEchartsModule,
-    NzTagModule,
-    NzTimelineModule,
-    SlickCarouselModule,
-    TagCloudComponent,
-    NzDividerModule,
-    LayoutModule,
-    NzCollapseModule,
-    NzListModule,
-    CommonModule,
-    NzRadioModule,
-    NzUploadModule,
-    NzCascaderModule,
-    NzTransferModule,
-    NzSwitchComponent
-  ],
+    imports: [
+        SharedModule,
+        RouteRoutingModule,
+        NgxEchartsModule,
+        NzTagModule,
+        NzTimelineModule,
+        SlickCarouselModule,
+        TagCloudComponent,
+        NzDividerModule,
+        LayoutModule,
+        NzCollapseModule,
+        NzListModule,
+        CommonModule,
+        NzRadioModule,
+        NzUploadModule,
+        NzCascaderModule,
+        NzTransferModule,
+        NzSwitchComponent,
+        NzTreeComponent
+    ],
   declarations: COMPONENTS
 })
 export class RoutesModule {}
diff --git a/web-app/src/app/service/app-define.service.ts 
b/web-app/src/app/service/app-define.service.ts
index 0474aa2a3..b9e246b30 100644
--- a/web-app/src/app/service/app-define.service.ts
+++ b/web-app/src/app/service/app-define.service.ts
@@ -98,6 +98,16 @@ export class AppDefineService {
     return this.http.get<Message<any>>(app_hierarchy, options);
   }
 
+  public getAppHierarchyByName(lang: string | undefined, app: string): 
Observable<Message<any>> {
+    if (lang == undefined) {
+      lang = 'en_US';
+    }
+    let httpParams = new HttpParams().append('lang', lang);
+    const options = { params: httpParams };
+    return this.http.get<Message<any>>(`${app_hierarchy}/${app}`, options);
+  }
+
+
   public getAppDefines(lang: string | undefined): Observable<Message<any>>  {
     if (lang == undefined) {
       lang = 'en_US';


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to