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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7178c444f [feature](web-app)Add Alarm Voice Alerts (#2906)
7178c444f is described below

commit 7178c444f26e282dab27f16d9e307969ea455dfc
Author: Logic <[email protected]>
AuthorDate: Sun Dec 29 11:25:48 2024 +0800

    [feature](web-app)Add Alarm Voice Alerts (#2906)
    
    Signed-off-by: Logic <[email protected]>
    Co-authored-by: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
    Co-authored-by: aias00 <[email protected]>
---
 .../common/constants/GeneralConfigTypeEnum.java    |   7 +-
 .../manager/config/ConfigInitializer.java          |  12 +++
 .../hertzbeat/manager/pojo/dto/MuteConfig.java     |  39 +++++++
 .../service/impl/MuteGeneralConfigServiceImpl.java |  81 +++++++++++++++
 web-app/angular.json                               |   7 +-
 web-app/package.json                               |   1 +
 .../app/layout/basic/widgets/notify.component.ts   | 114 ++++++++++++++++-----
 web-app/src/app/pojo/Mute.ts                       |  22 ++++
 web-app/src/app/service/alert-sound.service.ts     |  46 +++++++++
 web-app/src/assets/audio/default-alert-CN.mp3      | Bin 0 -> 12096 bytes
 web-app/src/assets/audio/default-alert-EN.mp3      | Bin 0 -> 11664 bytes
 web-app/src/assets/i18n/en-US.json                 |   1 +
 web-app/src/assets/i18n/zh-CN.json                 |   1 +
 web-app/src/assets/i18n/zh-TW.json                 |   1 +
 web-app/yarn.lock                                  |  10 +-
 15 files changed, 312 insertions(+), 30 deletions(-)

diff --git 
a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java
 
b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java
index 6d8d0d74e..9a461dfcd 100644
--- 
a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java
+++ 
b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java
@@ -22,6 +22,11 @@ package org.apache.hertzbeat.common.constants;
  */
 public enum GeneralConfigTypeEnum {
 
+    /**
+     * mute config
+     */
+    mute,
+
     /**
      * template config
      */
@@ -50,5 +55,5 @@ public enum GeneralConfigTypeEnum {
     /**
      * system store config
      */
-    oss;
+    oss
 }
diff --git 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/config/ConfigInitializer.java
 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/config/ConfigInitializer.java
index 3837a76d0..fe7d98d68 100644
--- 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/config/ConfigInitializer.java
+++ 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/config/ConfigInitializer.java
@@ -30,10 +30,12 @@ import 
org.apache.hertzbeat.common.constants.CommonConstants;
 import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
 import org.apache.hertzbeat.common.util.TimeZoneUtil;
 import org.apache.hertzbeat.manager.dao.GeneralConfigDao;
+import org.apache.hertzbeat.manager.pojo.dto.MuteConfig;
 import org.apache.hertzbeat.manager.pojo.dto.SystemConfig;
 import org.apache.hertzbeat.manager.pojo.dto.SystemSecret;
 import org.apache.hertzbeat.manager.pojo.dto.TemplateConfig;
 import org.apache.hertzbeat.manager.service.AppService;
+import org.apache.hertzbeat.manager.service.impl.MuteGeneralConfigServiceImpl;
 import 
org.apache.hertzbeat.manager.service.impl.SystemGeneralConfigServiceImpl;
 import org.apache.hertzbeat.manager.service.impl.SystemSecretServiceImpl;
 import org.apache.hertzbeat.manager.service.impl.TemplateConfigServiceImpl;
@@ -69,6 +71,9 @@ public class ConfigInitializer implements SmartLifecycle {
     @Resource
     private TemplateConfigServiceImpl templateConfigService;
 
+    @Resource
+    private MuteGeneralConfigServiceImpl muteGeneralConfigService;
+
     @Resource
     private AppService appService;
 
@@ -128,6 +133,13 @@ public class ConfigInitializer implements SmartLifecycle {
         // else use the user custom jwt secret
         // set the jwt secret token in util
         JsonWebTokenUtil.setDefaultSecretKey(currentJwtSecret);
+
+        // init web-app mute config
+        MuteConfig muteConfig = muteGeneralConfigService.getConfig();
+        if (muteConfig == null) {
+            muteConfig = MuteConfig.builder().mute(true).build();
+            muteGeneralConfigService.saveConfig(muteConfig);
+        }
     }
 
     @Override
diff --git 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/pojo/dto/MuteConfig.java
 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/pojo/dto/MuteConfig.java
new file mode 100644
index 000000000..808ebbc40
--- /dev/null
+++ 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/pojo/dto/MuteConfig.java
@@ -0,0 +1,39 @@
+/*
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements.  See the NOTICE file distributed with
+ *   this work for additional information regarding copyright ownership.
+ *   The ASF licenses this file to You 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.
+ *
+ */
+
+package org.apache.hertzbeat.manager.pojo.dto;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Mute Configuration
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MuteConfig {
+    /**
+     * mute
+     */
+    private boolean mute;
+}
diff --git 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MuteGeneralConfigServiceImpl.java
 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MuteGeneralConfigServiceImpl.java
new file mode 100644
index 000000000..ca1c29ec9
--- /dev/null
+++ 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MuteGeneralConfigServiceImpl.java
@@ -0,0 +1,81 @@
+/*
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements.  See the NOTICE file distributed with
+ *   this work for additional information regarding copyright ownership.
+ *   The ASF licenses this file to You 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.
+ *
+ */
+
+package org.apache.hertzbeat.manager.service.impl;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.lang.reflect.Type;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
+import org.apache.hertzbeat.manager.dao.GeneralConfigDao;
+import org.apache.hertzbeat.manager.pojo.dto.MuteConfig;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@Service
+@Slf4j
+public class MuteGeneralConfigServiceImpl extends 
AbstractGeneralConfigServiceImpl<MuteConfig> {
+
+    /**
+     * <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
+     *
+     * @param generalConfigDao Dao object
+     * @param objectMapper     JSON tool object
+     */
+    protected MuteGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, 
ObjectMapper objectMapper) {
+        super(generalConfigDao, objectMapper);
+    }
+
+    /**
+     * <p>Get TypeReference object of configuration type.</p>
+     *
+     * @return TypeReference object
+     */
+    @Override
+    public TypeReference<MuteConfig> getTypeReference() {
+        return new TypeReference<>() {
+            @Override
+            public Type getType() {
+                return MuteConfig.class;
+            }
+        };
+    }
+
+    /**
+     * config type: email, sms
+     *
+     * @return type string
+     */
+    @Override
+    public String type() {
+        return GeneralConfigTypeEnum.mute.name();
+    }
+
+    /**
+     * handler after save config
+     *
+     * @param config config
+     */
+    @Override
+    public void handler(MuteConfig config) {
+        log.info("handler mute config: {}", config);
+    }
+}
diff --git a/web-app/angular.json b/web-app/angular.json
index bca121a42..477dfeb62 100644
--- a/web-app/angular.json
+++ b/web-app/angular.json
@@ -27,8 +27,13 @@
             "tsConfig": "tsconfig.app.json",
             "polyfills": "src/polyfills.ts",
             "assets": [
-              "src/assets",
               "src/favicon.ico",
+              "src/assets",
+              {
+                "glob": "**/*",
+                "input": "src/assets/audio",
+                "output": "/assets/audio"
+              },
               {
                 "glob": "**/*",
                 "input": 
"./node_modules/@ant-design/icons-angular/src/inline-svg/",
diff --git a/web-app/package.json b/web-app/package.json
index a08f9148e..b559afcb9 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -45,6 +45,7 @@
     "@angular/compiler": "17.3.10",
     "@angular/core": "17.3.10",
     "@angular/forms": "17.3.10",
+    "@ant-design/icons-angular": "19.0.0",
     "@angular/platform-browser": "17.3.10",
     "@angular/platform-browser-dynamic": "17.3.10",
     "@angular/router": "17.3.10",
diff --git a/web-app/src/app/layout/basic/widgets/notify.component.ts 
b/web-app/src/app/layout/basic/widgets/notify.component.ts
index 5d8a940ce..7fd108fb8 100644
--- a/web-app/src/app/layout/basic/widgets/notify.component.ts
+++ b/web-app/src/app/layout/basic/widgets/notify.component.ts
@@ -6,35 +6,57 @@ import { NzModalService } from 'ng-zorro-antd/modal';
 import { NzNotificationService } from 'ng-zorro-antd/notification';
 import { finalize } from 'rxjs/operators';
 
+import { Mute } from '../../../pojo/Mute';
+import { AlertSoundService } from '../../../service/alert-sound.service';
 import { AlertService } from '../../../service/alert.service';
+import { GeneralConfigService } from '../../../service/general-config.service';
 
 @Component({
   selector: 'header-notify',
   template: `
-    <ng-template #badgeTpl>
-      <nz-badge [nzCount]="count" ngClass="alain-default__nav-item" 
[nzStyle]="{ 'box-shadow': 'none' }">
-        <i nz-icon nzType="bell" ngClass="alain-default__nav-item-icon"></i>
-      </nz-badge>
-    </ng-template>
-    @if (data!.length <= 0) {<ng-template [ngTemplateOutlet]="badgeTpl" />} 
@else {<div
-      nz-dropdown
-      (nzVisibleChange)="onPopoverVisibleChange($event)"
-      [(nzVisible)]="popoverVisible"
-      nzTrigger="click"
-      nzPlacement="bottomRight"
-      nzOverlayClassName="header-dropdown notice-icon"
-      [nzDropdownMenu]="noticeMenu"
-    >
+    <div style="display: flex; align-items: center;">
+      <ng-template #badgeTpl>
+        <nz-badge [nzCount]="count" ngClass="alain-default__nav-item" 
[nzStyle]="{ 'box-shadow': 'none' }">
+          <i nz-icon nzType="bell" ngClass="alain-default__nav-item-icon"></i>
+        </nz-badge>
+      </ng-template>
+      @if (data!.length <= 0) {
       <ng-template [ngTemplateOutlet]="badgeTpl" />
+      } @else {
+      <div
+        nz-dropdown
+        (nzVisibleChange)="onPopoverVisibleChange($event)"
+        [(nzVisible)]="popoverVisible"
+        nzTrigger="click"
+        nzPlacement="bottomRight"
+        nzOverlayClassName="header-dropdown notice-icon"
+        [nzDropdownMenu]="noticeMenu"
+      >
+        <ng-template [ngTemplateOutlet]="badgeTpl" />
+      </div>
+      }
+      <nz-badge ngClass="alain-default__nav-item" [nzStyle]="{ 'box-shadow': 
'none' }">
+        <i
+          nz-icon
+          [nzType]="mute.mute ? 'muted' : 'sound'"
+          ngClass="alain-default__nav-item-icon"
+          (click)="toggleMute($event)"
+          nz-tooltip
+          [nzTooltipTitle]="'common.mute' | i18n"
+        ></i>
+      </nz-badge>
     </div>
     <nz-dropdown-menu #noticeMenu="nzDropdownMenu">
-      @if (data[0].title) {<div class="ant-modal-title" style="line-height: 
44px; text-align: center;">{{ data[0].title }}</div>
+      @if (data[0].title) {
+      <div class="ant-modal-title" style="line-height: 44px; text-align: 
center;">{{ data[0].title }}</div>
       }
       <nz-spin [nzSpinning]="loading" [nzDelay]="0">
-        @if (data[0].list && data[0].list.length > 0) {<ng-template 
[ngTemplateOutlet]="listTpl" />} @else {<div
-          class="notice-icon__notfound"
-        >
-          @if (data[0].emptyImage) {<img class="notice-icon__notfound-img" 
[attr.src]="data[0].emptyImage" alt="not found" />
+        @if (data[0].list && data[0].list.length > 0) {
+        <ng-template [ngTemplateOutlet]="listTpl" />
+        } @else {
+        <div class="notice-icon__notfound">
+          @if (data[0].emptyImage) {
+          <img class="notice-icon__notfound-img" 
[attr.src]="data[0].emptyImage" alt="not found" />
           }
           <p>
             <ng-container *nzStringTemplateOutlet="data[0].emptyText">
@@ -45,12 +67,11 @@ import { AlertService } from 
'../../../service/alert.service';
         }
       </nz-spin>
       <div style="display: flex; align-items: center; border-top: 1px solid 
#f0f0f0;">
-        <div class="notice-icon__clear" style="flex: 1; border-top: none;" 
(click)="onClearAllAlerts()">{{ data[0].clearText }}</div>
+        <div class="notice-icon__clear" style="flex: 1; border-top: none;" 
(click)="onClearAllAlerts()">{{ data[0].clearText }} </div>
         <nz-divider nzType="vertical"></nz-divider>
-        <div class="notice-icon__clear" style="flex: 1; border-top: none;" 
(click)="gotoAlertCenter()">{{ data[0].enterText }}</div>
+        <div class="notice-icon__clear" style="flex: 1; border-top: none;" 
(click)="gotoAlertCenter()">{{ data[0].enterText }} </div>
       </div>
     </nz-dropdown-menu>
-    }
     <ng-template #listTpl>
       <nz-list [nzDataSource]="data[0].list" [nzRenderItem]="item">
         <ng-template #item let-item>
@@ -60,18 +81,21 @@ import { AlertService } from 
'../../../service/alert.service';
                 <ng-container *nzStringTemplateOutlet="item.title; context: { 
$implicit: item }">
                   <a (click)="gotoDetail(item.monitorId)">{{ item.title }}</a>
                 </ng-container>
-                @if (item.extra) {<div class="notice-icon__item-extra">
+                @if (item.extra) {
+                <div class="notice-icon__item-extra">
                   <nz-tag [nzColor]="item.color">{{ item.extra }}</nz-tag>
                 </div>
                 }
               </ng-template>
               <ng-template #nzDescription>
-                @if (item.description) {<div class="notice-icon__item-desc">
+                @if (item.description) {
+                <div class="notice-icon__item-desc">
                   <ng-container *nzStringTemplateOutlet="item.description; 
context: { $implicit: item }">
                     {{ item.description }}
                   </ng-container>
                 </div>
-                } @if (item.datetime) {<div class="notice-icon__item-time">{{ 
item.datetime }}</div>
+                } @if (item.datetime) {
+                <div class="notice-icon__item-time">{{ item.datetime }}</div>
                 }
               </ng-template>
             </nz-list-item-meta>
@@ -110,20 +134,43 @@ export class HeaderNotifyComponent implements OnInit, 
OnDestroy {
   loading = false;
   popoverVisible = false;
   refreshInterval: any;
+  private previousCount = 0;
+  mute!: Mute;
   constructor(
     private router: Router,
     @Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService,
     private notifySvc: NzNotificationService,
+    private configSvc: GeneralConfigService,
     private alertSvc: AlertService,
     private modal: NzModalService,
-    private cdr: ChangeDetectorRef
+    private cdr: ChangeDetectorRef,
+    private alertSound: AlertSoundService
   ) {}
 
   ngOnInit(): void {
+    let muteInit$ = this.configSvc
+      .getGeneralConfig('mute')
+      .pipe(
+        finalize(() => {
+          muteInit$.unsubscribe();
+        })
+      )
+      .subscribe(
+        message => {
+          if (message.code === 0) {
+            this.mute = message.data;
+          } else {
+            console.warn(message.msg);
+          }
+        },
+        error => {
+          console.error(error);
+        }
+      );
     this.loadData();
     this.refreshInterval = setInterval(() => {
       this.loadData();
-    }, 30000); // every 30 seconds refresh the tabs
+    }, 10000); // every 10 seconds refresh the tabs
   }
 
   ngOnDestroy() {
@@ -184,6 +231,11 @@ export class HeaderNotifyComponent implements OnInit, 
OnDestroy {
               list.push(item);
             });
             this.data = this.updateNoticeData(list);
+
+            if (page.totalElements > this.previousCount && !this.mute.mute) {
+              this.alertSound.playAlertSound(this.i18nSvc.currentLang);
+            }
+            this.previousCount = page.totalElements;
             this.count = page.totalElements;
           } else {
             console.warn(message.msg);
@@ -226,6 +278,7 @@ export class HeaderNotifyComponent implements OnInit, 
OnDestroy {
         deleteAlerts$.unsubscribe();
         if (message.code === 0) {
           
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.clear-success'), '');
+          this.previousCount = 0;
           this.loadData();
         } else {
           this.notifySvc.error(this.i18nSvc.fanyi('common.notify.clear-fail'), 
message.msg);
@@ -259,4 +312,11 @@ export class HeaderNotifyComponent implements OnInit, 
OnDestroy {
     this.popoverVisible = false;
     this.router.navigateByUrl(`/monitors/${monitorId}`);
   }
+
+  toggleMute(event: MouseEvent): void {
+    event.stopPropagation();
+    this.mute.mute = !this.mute.mute;
+    this.configSvc.saveGeneralConfig(this.mute, 'mute');
+    this.cdr.markForCheck();
+  }
 }
diff --git a/web-app/src/app/pojo/Mute.ts b/web-app/src/app/pojo/Mute.ts
new file mode 100644
index 000000000..304970868
--- /dev/null
+++ b/web-app/src/app/pojo/Mute.ts
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+export class Mute {
+  mute!: boolean;
+}
diff --git a/web-app/src/app/service/alert-sound.service.ts 
b/web-app/src/app/service/alert-sound.service.ts
new file mode 100644
index 000000000..d01bf5017
--- /dev/null
+++ b/web-app/src/app/service/alert-sound.service.ts
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { Injectable } from '@angular/core';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AlertSoundService {
+  private audio: HTMLAudioElement;
+
+  constructor() {
+    this.audio = new Audio();
+    this.audio.src = '/assets/audio/default-alert-CN.mp3';
+    this.audio.load();
+  }
+
+  playAlertSound(lang: string): void {
+    if (lang === 'zh-CN' || lang === 'zh-TW') {
+      this.audio.src = '/assets/audio/default-alert-CN.mp3';
+    } else {
+      this.audio.src = '/assets/audio/default-alert-EN.mp3';
+    }
+
+    this.audio.load();
+    this.audio.play().catch(error => {
+      console.warn('Failed to play alert sound:', error);
+    });
+  }
+}
diff --git a/web-app/src/assets/audio/default-alert-CN.mp3 
b/web-app/src/assets/audio/default-alert-CN.mp3
new file mode 100644
index 000000000..14ffac5fc
Binary files /dev/null and b/web-app/src/assets/audio/default-alert-CN.mp3 
differ
diff --git a/web-app/src/assets/audio/default-alert-EN.mp3 
b/web-app/src/assets/audio/default-alert-EN.mp3
new file mode 100644
index 000000000..fde5e971d
Binary files /dev/null and b/web-app/src/assets/audio/default-alert-EN.mp3 
differ
diff --git a/web-app/src/assets/i18n/en-US.json 
b/web-app/src/assets/i18n/en-US.json
index 72ec0c40e..6a047be34 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -470,6 +470,7 @@
   "common.disable": "Disable",
   "common.copy": "Copy To Clipboard",
   "common.copy.button": "Copy",
+  "common.mute": "Mute",
   "common.notify.no-select-edit": "No items selected for editing!",
   "common.notify.one-select-edit": "Only one selection can be edited!",
   "common.confirm.delete": "Please confirm whether to delete!",
diff --git a/web-app/src/assets/i18n/zh-CN.json 
b/web-app/src/assets/i18n/zh-CN.json
index 944234c2e..38a0197c8 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -471,6 +471,7 @@
   "common.disable": "关闭",
   "common.copy": "点击复制",
   "common.copy.button": "复制",
+  "common.mute": "静音",
   "common.notify.no-select-edit": "未选中任何待编辑项!",
   "common.notify.one-select-edit": "只能对一个选中项进行编辑!",
   "common.confirm.delete": "请确认是否删除!",
diff --git a/web-app/src/assets/i18n/zh-TW.json 
b/web-app/src/assets/i18n/zh-TW.json
index 9c533a67c..8f13bac1d 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -484,6 +484,7 @@
   "common.disable": "關閉",
   "common.copy": "點擊複製",
   "common.copy.button": "複製",
+  "common.mute": "靜音",
   "common.notify.no-select-edit": "未選中任何待編輯項!",
   "common.notify.one-select-edit": "只能對一個選中項進行編輯!",
   "common.confirm.delete": "請確認是否刪除!",
diff --git a/web-app/yarn.lock b/web-app/yarn.lock
index 5ca1ca4f3..fa288a7e9 100644
--- a/web-app/yarn.lock
+++ b/web-app/yarn.lock
@@ -298,9 +298,17 @@
   dependencies:
     "@ctrl/tinycolor" "^3.6.1"
 
+"@ant-design/[email protected]":
+  version "19.0.0"
+  resolved 
"https://registry.npmmirror.com/@ant-design/icons-angular/-/icons-angular-19.0.0.tgz#3cf43d2fcf516d07949fd609966b7cdec09e7126";
+  integrity 
sha512-bBWFA1cTZwLAFTgpozkeNIHX1nXyZuiUaRzTcAfFEt85eW1X3ypMcBfS/XEVVVzkdTw5Td+E1vwzgfuUlKiYSA==
+  dependencies:
+    "@ant-design/colors" "^7.0.0"
+    tslib "^2.0.0"
+
 "@ant-design/icons-angular@^17.0.0":
   version "17.0.0"
-  resolved 
"https://registry.yarnpkg.com/@ant-design/icons-angular/-/icons-angular-17.0.0.tgz#5e072f4be7fa0bcef1498be735de8b54ada23620";
+  resolved 
"https://registry.npmmirror.com/@ant-design/icons-angular/-/icons-angular-17.0.0.tgz#5e072f4be7fa0bcef1498be735de8b54ada23620";
   integrity 
sha512-MNEh3UbkSl6gkdb5MQRNHEuWI1DnU1dME9zSymnWCipEXN7MB0mcYHSfyYTqKL1j45ftp6l1UnsLvhokRYyhXA==
   dependencies:
     "@ant-design/colors" "^7.0.0"


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

Reply via email to