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]