dashboard-shell-shell 3.0.5-test.58 → 3.0.5-test.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/translations/en-us.yaml +3 -0
- package/assets/translations/zh-hans.yaml +17 -2
- package/components/ResourceList/Masthead.vue +5 -0
- package/components/actionButton/index.vue +649 -0
- package/components/form/NameNsDescription.vue +1 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +32 -8
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/dingding.vue +32 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/message.vue +52 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/snmp.vue +45 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/work.vue +31 -0
- package/package.json +1 -1
- package/scripts/publish-shell.sh +1 -1
- package/store/index.js +4 -4
- package/utils/errorTranslate.json +5 -0
|
@@ -6477,6 +6477,7 @@ tableHeaders:
|
|
|
6477
6477
|
apikey: API Key
|
|
6478
6478
|
available: Available
|
|
6479
6479
|
attachedVM: Attached VM
|
|
6480
|
+
vpc: vpc
|
|
6480
6481
|
|
|
6481
6482
|
authRoles:
|
|
6482
6483
|
globalDefault: New User Default
|
|
@@ -7652,6 +7653,8 @@ typeDescription:
|
|
|
7652
7653
|
harvesterhci.io.logging.clusteroutput: 定义集群范围的日志输出目标,例如 Elasticsearch、Loki、S3、Kafka 等。可被多个集群日志配置引用,用于集中存储与分析集群级日志数据。
|
|
7653
7654
|
harvesterhci.io.logging.flow: 用于配置资源组(命名空间)内的日志采集与流转规则,仅作用于当前资源组内的日志,并将日志转发至对应的资源组日志外发目标,便于分组级的日志管理。
|
|
7654
7655
|
harvesterhci.io.logging.output: 定义资源组级别的日志输出目标,仅能被同一资源组下的日志配置引用,用于将该资源组内的日志发送至指定的存储或分析系统。
|
|
7656
|
+
kubeovn.io.ovneip: EIP(Elastic IP,弹性公网 IP) 是一种可独立分配、绑定和管理的公网 IPv4 地址资源,使云上实例能够与公网进行双向通信。EIP 具备可移植、可复用和高可用等特性,为云中应用提供灵活的公网访问能力。
|
|
7657
|
+
kubeovn.io.nat: NAT(Network Address Translation,网络地址转换) 是一种让私有子网内的云资源在不暴露自身公网 IP 的情况下访问互联网的网络服务。通过 NAT,实例可以共享统一的公网出口,实现安全、简化的外网访问能力。
|
|
7655
7658
|
typeLabel:
|
|
7656
7659
|
management.cattle.io.oidcclient: |-
|
|
7657
7660
|
{count, plural,
|
|
@@ -2182,7 +2182,7 @@ resource:
|
|
|
2182
2182
|
update: "更新 {name} 出错"
|
|
2183
2183
|
|
|
2184
2184
|
codeMirror:
|
|
2185
|
-
escapeText: 按 Shift+Escape
|
|
2185
|
+
escapeText: 按 Shift+Escape 退出编辑状态
|
|
2186
2186
|
keymap:
|
|
2187
2187
|
tooltip: 键映射首选项
|
|
2188
2188
|
indicatorToolip: "按键映射: {name}"
|
|
@@ -3509,6 +3509,18 @@ monitoringReceiver:
|
|
|
3509
3509
|
opsgenie:
|
|
3510
3510
|
label: Opsgenie
|
|
3511
3511
|
title: Opsgenie 配置
|
|
3512
|
+
dingding:
|
|
3513
|
+
label: 钉钉
|
|
3514
|
+
title: 钉钉 配置
|
|
3515
|
+
weixin:
|
|
3516
|
+
label: 企业微信
|
|
3517
|
+
title: 企业微信 配置
|
|
3518
|
+
snmp:
|
|
3519
|
+
label: SNMP
|
|
3520
|
+
title: SNMP 配置
|
|
3521
|
+
message:
|
|
3522
|
+
label: 短信
|
|
3523
|
+
title: 短信 配置
|
|
3512
3524
|
pagerduty:
|
|
3513
3525
|
label: PagerDuty
|
|
3514
3526
|
title: PagerDuty 配置
|
|
@@ -5543,6 +5555,7 @@ tableHeaders:
|
|
|
5543
5555
|
apikey: API 密钥
|
|
5544
5556
|
available: 可用
|
|
5545
5557
|
attachedVM: 挂载的虚拟机
|
|
5558
|
+
vpc: vpc
|
|
5546
5559
|
|
|
5547
5560
|
authRoles:
|
|
5548
5561
|
globalDefault: 新用户的默认角色
|
|
@@ -6630,7 +6643,7 @@ typeDescription:
|
|
|
6630
6643
|
devices.harvesterhci.io.vgpudevice: vGPU(虚拟 GPU,Virtual GPU)是一种通过虚拟化技术将物理 GPU 资源分割、共享给多个虚拟机(VM)或容器的技术,旨在解决物理 GPU 资源利用率低、部署成本高的问题,同时满足多用户对图形渲染、高性能计算(HPC)、AI 加速等场景的需求。
|
|
6631
6644
|
kubeovn.io.vpc: VPC(Virtual Private Cloud,虚拟私有云)是一种云计算服务模型,允许用户在公有云环境中创建隔离的虚拟网络空间,以实现更高的安全性和灵活性。VPC 使用户能够定义自己的网络拓扑、IP 地址范围、子网、路由表和网络网关等,从而提供类似于传统数据中心的网络控制和配置能力。
|
|
6632
6645
|
kubeovn.io.subnet: 子网是虚拟私有云(VPC)中的一个逻辑分区,用于将网络划分为更小的、可管理的部分。每个子网都有自己的 IP 地址范围和网络配置,允许用户在 VPC 内创建隔离的网络环境,以满足不同应用或服务的需求。
|
|
6633
|
-
kubeovn.io.providernetwork:
|
|
6646
|
+
kubeovn.io.providernetwork: 公共网络是指在虚拟私有云(VPC)中,由云服务提供商管理的网络资源。它通常用于连接 VPC 内的虚拟机、容器和其他资源,并提供对外部网络的访问。提供者网络可以包括物理网络设备、虚拟交换机、路由器等,以实现高效、安全的网络通信。
|
|
6634
6647
|
kubeovn.io.vlan: VLAN(Virtual Local Area Network,虚拟局域网)是一种网络技术,用于在物理网络中创建逻辑隔离的子网络。VLAN 允许用户将不同的设备和服务分组到同一虚拟网络中,从而提高网络的安全性、灵活性和可管理性。每个 VLAN 都有一个唯一的标识符(VLAN ID),用于区分不同的虚拟网络。
|
|
6635
6648
|
management.cattle.io.setting: 统一配置平台基础选项与全局设置,支持CA证书、密码规则、域名、Token时效等核心配置。
|
|
6636
6649
|
management.cattle.io.user: 用于管理用户账号,支持创建、维护用户信息,可设置用户权限、管理密码等,保障系统资源仅由授权用户访问,提升系统安全性。
|
|
@@ -6661,6 +6674,8 @@ typeDescription:
|
|
|
6661
6674
|
harvesterhci.io.logging.clusteroutput: 定义集群范围的日志输出目标,例如 Elasticsearch、Loki、S3、Kafka 等。可被多个集群日志配置引用,用于集中存储与分析集群级日志数据。
|
|
6662
6675
|
harvesterhci.io.logging.flow: 用于配置资源组(命名空间)内的日志采集与流转规则,仅作用于当前资源组内的日志,并将日志转发至对应的资源组日志外发目标,便于分组级的日志管理。
|
|
6663
6676
|
harvesterhci.io.logging.output: 定义资源组级别的日志输出目标,仅能被同一资源组下的日志配置引用,用于将该资源组内的日志发送至指定的存储或分析系统。
|
|
6677
|
+
kubeovn.io.ovneip: EIP(Elastic IP,弹性公网 IP) 是一种可独立分配、绑定和管理的公网 IPv4 地址资源,使云上实例能够与公网进行双向通信。EIP 具备可移植、可复用和高可用等特性,为云中应用提供灵活的公网访问能力。
|
|
6678
|
+
kubeovn.io.nat: NAT(Network Address Translation,网络地址转换) 是一种让私有子网内的云资源在不暴露自身公网 IP 的情况下访问互联网的网络服务。通过 NAT,实例可以共享统一的公网出口,实现安全、简化的外网访问能力。
|
|
6664
6679
|
typeLabel:
|
|
6665
6680
|
management.cattle.io.project: |-
|
|
6666
6681
|
{count, plural,
|
|
@@ -33,6 +33,10 @@ export default {
|
|
|
33
33
|
TabTitle
|
|
34
34
|
},
|
|
35
35
|
props: {
|
|
36
|
+
actionsPositioning: {
|
|
37
|
+
type: String,
|
|
38
|
+
required: '-48px',
|
|
39
|
+
},
|
|
36
40
|
resource: {
|
|
37
41
|
type: String,
|
|
38
42
|
required: true,
|
|
@@ -322,6 +326,7 @@ export default {
|
|
|
322
326
|
<div
|
|
323
327
|
ref="actionsContainer"
|
|
324
328
|
v-if="!(tabList.includes(_typeDisplay))"
|
|
329
|
+
:style="{ bottom: actionsPositioning ? actionsPositioning : '-48px' }"
|
|
325
330
|
class="actions-container actions-positioning"
|
|
326
331
|
style="min-height: 32px;align-self: flex-end;"
|
|
327
332
|
>
|
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { KUBERNETES, PROJECT } from '@shell/config/labels-annotations';
|
|
3
|
+
import { FLEET, NAMESPACE, MANAGEMENT, HELM } from '@shell/config/types';
|
|
4
|
+
import ButtonGroup from '@shell/components/ButtonGroup';
|
|
5
|
+
// import { BadgeState } from '@components/BadgeState';
|
|
6
|
+
import DotState from '@shell/components/DotState.vue';
|
|
7
|
+
import { Banner } from '@components/Banner';
|
|
8
|
+
import { get } from '@shell/utils/object';
|
|
9
|
+
import { NAME as FLEET_NAME } from '@shell/config/product/fleet';
|
|
10
|
+
import { HIDE_SENSITIVE } from '@shell/store/prefs';
|
|
11
|
+
import {
|
|
12
|
+
AS, _DETAIL, _CONFIG, _YAML, MODE, _CREATE, _EDIT, _VIEW, _UNFLAG, _GRAPH
|
|
13
|
+
} from '@shell/config/query-params';
|
|
14
|
+
import { ExtensionPoint, PanelLocation } from '@shell/core/types';
|
|
15
|
+
import ExtensionPanel from '@shell/components/ExtensionPanel';
|
|
16
|
+
import TabTitle from '@shell/components/TabTitle';
|
|
17
|
+
import ActionMenu from '@shell/components/ActionMenuShell.vue';
|
|
18
|
+
import { useRuntimeFlag } from '@shell/composables/useRuntimeFlag';
|
|
19
|
+
import { useStore } from 'vuex';
|
|
20
|
+
|
|
21
|
+
// i18n-uses resourceDetail.header.*
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resource Detail Masthead component.
|
|
25
|
+
*
|
|
26
|
+
* ToDo: this component seem to be picking up a lot of logic from special cases, could be simplified down to parameters and then customized per use-case via wrapper component
|
|
27
|
+
*/
|
|
28
|
+
export default {
|
|
29
|
+
|
|
30
|
+
name: 'MastheadResourceDetail',
|
|
31
|
+
|
|
32
|
+
components: {
|
|
33
|
+
// BadgeState,
|
|
34
|
+
DotState,
|
|
35
|
+
Banner,
|
|
36
|
+
ButtonGroup,
|
|
37
|
+
ExtensionPanel,
|
|
38
|
+
TabTitle,
|
|
39
|
+
ActionMenu,
|
|
40
|
+
},
|
|
41
|
+
props: {
|
|
42
|
+
value: {
|
|
43
|
+
type: Object,
|
|
44
|
+
default: () => {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
isManuallyHide: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: true
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
mode: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: 'create'
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
realMode: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: 'create'
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
as: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: _YAML,
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
hasGraph: {
|
|
70
|
+
type: Boolean,
|
|
71
|
+
default: false
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
hasDetail: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
default: false
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
hasEdit: {
|
|
80
|
+
type: Boolean,
|
|
81
|
+
default: false
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
storeOverride: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: null,
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
resource: {
|
|
90
|
+
type: String,
|
|
91
|
+
default: null,
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
resourceSubtype: {
|
|
95
|
+
type: String,
|
|
96
|
+
default: null,
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
parentRouteOverride: {
|
|
100
|
+
type: String,
|
|
101
|
+
default: null,
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
canViewYaml: {
|
|
105
|
+
type: Boolean,
|
|
106
|
+
default: false,
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
setup() {
|
|
111
|
+
const store = useStore();
|
|
112
|
+
const { featureDropdownMenu } = useRuntimeFlag(store);
|
|
113
|
+
|
|
114
|
+
return { featureDropdownMenu };
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
data() {
|
|
118
|
+
return {
|
|
119
|
+
DETAIL_VIEW: _DETAIL,
|
|
120
|
+
extensionType: ExtensionPoint.PANEL,
|
|
121
|
+
extensionLocation: PanelLocation.DETAILS_MASTHEAD,
|
|
122
|
+
Svg: require('~shell/assets/images/API.svg')
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
computed: {
|
|
127
|
+
dev() {
|
|
128
|
+
return this.$store.getters['prefs/dev'];
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
schema() {
|
|
132
|
+
const inStore = this.storeOverride || this.$store.getters['currentStore'](this.resource);
|
|
133
|
+
|
|
134
|
+
return this.$store.getters[`${ inStore }/schemaFor`]( this.resource );
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
isView() {
|
|
138
|
+
return this.mode === _VIEW;
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
isEdit() {
|
|
142
|
+
return this.mode === _EDIT;
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
isCreate() {
|
|
146
|
+
return this.mode === _CREATE;
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
isNamespace() {
|
|
150
|
+
return this.schema?.id === NAMESPACE;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
isProject() {
|
|
154
|
+
return this.schema?.id === MANAGEMENT.PROJECT;
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
isProjectHelmChart() {
|
|
158
|
+
return this.schema?.id === HELM.PROJECTHELMCHART;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
hasMultipleNamespaces() {
|
|
162
|
+
return !!this.value.namespaces;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
namespace() {
|
|
166
|
+
if (this.value?.metadata?.namespace) {
|
|
167
|
+
return this.value?.metadata?.namespace;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
detailsAction() {
|
|
174
|
+
return this.value?.detailsAction;
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
shouldHifenize() {
|
|
178
|
+
return (this.mode === 'view' || this.mode === 'edit') && this.resourceSubtype?.length && this.value?.nameDisplay?.length;
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
namespaceLocation() {
|
|
182
|
+
if (!this.isNamespace) {
|
|
183
|
+
return this.value.namespaceLocation || {
|
|
184
|
+
name: 'c-cluster-product-resource-id',
|
|
185
|
+
params: {
|
|
186
|
+
cluster: this.$route.params.cluster,
|
|
187
|
+
product: this.$store.getters['productId'],
|
|
188
|
+
resource: NAMESPACE,
|
|
189
|
+
id: this.$route.params.namespace
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
isWorkspace() {
|
|
198
|
+
return this.$store.getters['productId'] === FLEET_NAME && !!this.value?.metadata?.namespace;
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
workspaceLocation() {
|
|
202
|
+
return {
|
|
203
|
+
name: 'c-cluster-product-resource-id',
|
|
204
|
+
params: {
|
|
205
|
+
cluster: this.$route.params.cluster,
|
|
206
|
+
product: this.$store.getters['productId'],
|
|
207
|
+
resource: FLEET.WORKSPACE,
|
|
208
|
+
id: this.$route.params.namespace
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
project() {
|
|
214
|
+
if (this.isNamespace) {
|
|
215
|
+
const cluster = this.$store.getters['currentCluster'];
|
|
216
|
+
|
|
217
|
+
if (cluster) {
|
|
218
|
+
const id = (this.value?.metadata?.labels || {})[PROJECT];
|
|
219
|
+
|
|
220
|
+
return this.$store.getters['management/byId'](MANAGEMENT.PROJECT, `${ cluster.id }/${ id }`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
banner() {
|
|
228
|
+
if (this.value?.stateObj?.error) {
|
|
229
|
+
const defaultErrorMessage = this.t('resourceDetail.masthead.defaultBannerMessage.error', undefined, true);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
color: 'error',
|
|
233
|
+
message: this.value.stateObj.message || defaultErrorMessage
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (this.value?.spec?.paused) {
|
|
238
|
+
return {
|
|
239
|
+
color: 'info',
|
|
240
|
+
message: this.t('asyncButton.pause.description')
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (this.value?.stateObj?.transitioning) {
|
|
245
|
+
const defaultTransitioningMessage = this.t('resourceDetail.masthead.defaultBannerMessage.transitioning', undefined, true);
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
color: 'info',
|
|
249
|
+
message: this.value.stateObj.message || defaultTransitioningMessage
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return null;
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
parent() {
|
|
257
|
+
let displayName = this.value?.parentNameOverride || this.$store.getters['type-map/labelFor'](this.schema);
|
|
258
|
+
const product = this.$store.getters['currentProduct'].name;
|
|
259
|
+
|
|
260
|
+
const defaultLocation = {
|
|
261
|
+
name: 'c-cluster-product-resource',
|
|
262
|
+
params: {
|
|
263
|
+
resource: this.resource,
|
|
264
|
+
product,
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const location = this.value?.parentLocationOverride || defaultLocation;
|
|
269
|
+
|
|
270
|
+
if (this.parentRouteOverride) {
|
|
271
|
+
location.name = this.parentRouteOverride;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const typeOptions = this.$store.getters[`type-map/optionsFor`]( this.resource );
|
|
275
|
+
|
|
276
|
+
// 转换为中文
|
|
277
|
+
const displayName_zh_hans = {
|
|
278
|
+
'GlobalRole': '全局角色',
|
|
279
|
+
'RoleTemplate': '集群角色',
|
|
280
|
+
}
|
|
281
|
+
if (displayName_zh_hans[displayName]) {
|
|
282
|
+
displayName = displayName_zh_hans[displayName]
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (displayName == '集群角色' && (this.$route.query?.roleContext == 'NAMESPACE' || location.hash == '#NAMESPACE')) {
|
|
286
|
+
displayName = '项目或资源组角色'
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const out = {
|
|
290
|
+
displayName, location, ...typeOptions
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
return out;
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
hideSensitiveData() {
|
|
297
|
+
return this.$store.getters['prefs/get'](HIDE_SENSITIVE);
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
sensitiveOptions() {
|
|
301
|
+
return [
|
|
302
|
+
{
|
|
303
|
+
tooltipKey: 'resourceDetail.masthead.sensitive.hide',
|
|
304
|
+
icon: 'icon-hide',
|
|
305
|
+
value: true,
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
tooltipKey: 'resourceDetail.masthead.sensitive.show',
|
|
309
|
+
icon: 'icon-show',
|
|
310
|
+
value: false
|
|
311
|
+
}
|
|
312
|
+
];
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
viewOptions() {
|
|
316
|
+
const out = [];
|
|
317
|
+
|
|
318
|
+
if ( this.hasDetail ) {
|
|
319
|
+
out.push({
|
|
320
|
+
labelKey: 'resourceDetail.masthead.detail',
|
|
321
|
+
value: _DETAIL,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if ( this.hasEdit && this.parent?.showConfigView !== false) {
|
|
326
|
+
out.push({
|
|
327
|
+
labelKey: 'resourceDetail.masthead.config',
|
|
328
|
+
value: _CONFIG,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if ( this.hasGraph ) {
|
|
333
|
+
out.push({
|
|
334
|
+
labelKey: 'resourceDetail.masthead.graph',
|
|
335
|
+
value: _GRAPH,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// if ( this.canViewYaml ) {
|
|
340
|
+
// out.push({
|
|
341
|
+
// labelKey: 'resourceDetail.masthead.yaml',
|
|
342
|
+
// value: _YAML,
|
|
343
|
+
// });
|
|
344
|
+
// }
|
|
345
|
+
|
|
346
|
+
if ( out.length < 2 ) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return out;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
currentView: {
|
|
354
|
+
get() {
|
|
355
|
+
return this.as;
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
set(val) {
|
|
359
|
+
switch ( val ) {
|
|
360
|
+
case _DETAIL:
|
|
361
|
+
this.$router.applyQuery({
|
|
362
|
+
[MODE]: _UNFLAG,
|
|
363
|
+
[AS]: _UNFLAG,
|
|
364
|
+
});
|
|
365
|
+
break;
|
|
366
|
+
case _CONFIG:
|
|
367
|
+
this.$router.applyQuery({
|
|
368
|
+
[MODE]: _UNFLAG,
|
|
369
|
+
[AS]: _CONFIG,
|
|
370
|
+
});
|
|
371
|
+
break;
|
|
372
|
+
case _GRAPH:
|
|
373
|
+
this.$router.applyQuery({
|
|
374
|
+
[MODE]: _UNFLAG,
|
|
375
|
+
[AS]: _GRAPH,
|
|
376
|
+
});
|
|
377
|
+
break;
|
|
378
|
+
case _YAML:
|
|
379
|
+
this.$router.applyQuery({
|
|
380
|
+
[MODE]: _UNFLAG,
|
|
381
|
+
[AS]: _YAML,
|
|
382
|
+
});
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
showSensitiveToggle() {
|
|
389
|
+
return !!this.value.hasSensitiveData && this.mode === _VIEW && this.as !== _YAML;
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
managedWarning() {
|
|
393
|
+
const { value } = this;
|
|
394
|
+
const labels = value?.metadata?.labels || {};
|
|
395
|
+
|
|
396
|
+
const managedBy = labels[KUBERNETES.MANAGED_BY] || '';
|
|
397
|
+
const appName = labels[KUBERNETES.MANAGED_NAME] || labels[KUBERNETES.INSTANCE] || '';
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
show: this.mode === _EDIT && !!managedBy,
|
|
401
|
+
type: value?.kind || '',
|
|
402
|
+
hasName: appName ? 'yes' : 'no',
|
|
403
|
+
appName,
|
|
404
|
+
managedBy,
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
displayName() {
|
|
409
|
+
let displayName = this.value.nameDisplay;
|
|
410
|
+
|
|
411
|
+
if (this.isProjectHelmChart) {
|
|
412
|
+
displayName = this.value.projectDisplayName;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return this.shouldHifenize ? ` - ${ displayName }` : displayName;
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
demoDisplay() {
|
|
419
|
+
const product = this.$store.getters['productId'];
|
|
420
|
+
|
|
421
|
+
const resources = this.location?.params?.resource || this.$route.params?.resource || ''
|
|
422
|
+
|
|
423
|
+
const productId = this.$store.getters['type-map/groupForBasicType'](this.$store.getters['productId'], resources);
|
|
424
|
+
|
|
425
|
+
if (productId === undefined) {
|
|
426
|
+
return '';
|
|
427
|
+
}
|
|
428
|
+
const parts = productId?.split('::') || [];
|
|
429
|
+
const newString = 'root';
|
|
430
|
+
|
|
431
|
+
if (!parts?.includes(newString)) {
|
|
432
|
+
parts.unshift(newString); // 将字符串添加到数组第一位
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const partsEn = parts.map((item) => {
|
|
436
|
+
return this.$store.getters['i18n/t'](`typeLabel."${ item.toLowerCase() }"`);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
return partsEn;
|
|
440
|
+
},
|
|
441
|
+
menuIcon() {
|
|
442
|
+
const product = this.$store.getters['productId'];
|
|
443
|
+
|
|
444
|
+
const resources = this.location?.params?.resource || this.$route.params?.resource || ''
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
return this.$store.getters['type-map/groupsForVirTypes'](product, resources) || 'default menuIcon';
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
location() {
|
|
451
|
+
const { parent } = this;
|
|
452
|
+
|
|
453
|
+
return parent?.location;
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
hideNamespaceLocation() {
|
|
457
|
+
return this.$store.getters['currentProduct'].hideNamespaceLocation || this.value.namespaceLocation === null;
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
resourceExternalLink() {
|
|
461
|
+
return this.value.resourceExternalLink;
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
methods: {
|
|
466
|
+
get,
|
|
467
|
+
|
|
468
|
+
showActions() {
|
|
469
|
+
this.$store.commit('action-menu/show', {
|
|
470
|
+
resources: this.value,
|
|
471
|
+
elem: this.$refs.actions,
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
toggleSensitiveData(e) {
|
|
476
|
+
this.$store.dispatch('prefs/set', { key: HIDE_SENSITIVE, value: !!e });
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
invokeDetailsAction() {
|
|
480
|
+
const action = this.detailsAction;
|
|
481
|
+
|
|
482
|
+
if (action) {
|
|
483
|
+
const fn = this.value[action.action];
|
|
484
|
+
|
|
485
|
+
if (fn) {
|
|
486
|
+
fn.apply(this.value, []);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
</script>
|
|
493
|
+
|
|
494
|
+
<template>
|
|
495
|
+
<div class="actions">
|
|
496
|
+
<button
|
|
497
|
+
v-if="detailsAction && currentView === DETAIL_VIEW && isView"
|
|
498
|
+
type="button"
|
|
499
|
+
class="btn role-primary actions mr-10"
|
|
500
|
+
:disabled="!detailsAction.enabled"
|
|
501
|
+
@click="invokeDetailsAction"
|
|
502
|
+
>
|
|
503
|
+
{{ detailsAction.label }}
|
|
504
|
+
</button>
|
|
505
|
+
<ButtonGroup
|
|
506
|
+
v-if="showSensitiveToggle"
|
|
507
|
+
:value="!!hideSensitiveData"
|
|
508
|
+
icon-size="lg"
|
|
509
|
+
:options="sensitiveOptions"
|
|
510
|
+
class="mr-10"
|
|
511
|
+
@update:value="toggleSensitiveData"
|
|
512
|
+
/>
|
|
513
|
+
<ButtonGroup
|
|
514
|
+
v-if="viewOptions && isView"
|
|
515
|
+
v-model:value="currentView"
|
|
516
|
+
:options="viewOptions"
|
|
517
|
+
class="mr-10"
|
|
518
|
+
/>
|
|
519
|
+
<template v-if="featureDropdownMenu">
|
|
520
|
+
<ActionMenu
|
|
521
|
+
v-if="isView"
|
|
522
|
+
button-role="multiAction"
|
|
523
|
+
button-size="compact"
|
|
524
|
+
:resource="value"
|
|
525
|
+
data-testid="masthead-action-menu"
|
|
526
|
+
/>
|
|
527
|
+
</template>
|
|
528
|
+
<template v-else>
|
|
529
|
+
<button
|
|
530
|
+
v-if="isView"
|
|
531
|
+
ref="actions"
|
|
532
|
+
data-testid="masthead-action-menu"
|
|
533
|
+
aria-haspopup="true"
|
|
534
|
+
type="button"
|
|
535
|
+
class="btn role-multi-action actions"
|
|
536
|
+
@click="showActions"
|
|
537
|
+
>
|
|
538
|
+
<i class="icon icon-actions" />
|
|
539
|
+
</button>
|
|
540
|
+
</template>
|
|
541
|
+
</div>
|
|
542
|
+
</template>
|
|
543
|
+
|
|
544
|
+
<style lang='scss' scoped>
|
|
545
|
+
.masthead {
|
|
546
|
+
padding-bottom: 10px;
|
|
547
|
+
/* border-bottom: 1px solid var(--border); */
|
|
548
|
+
margin-bottom: 10px;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
HEADER {
|
|
552
|
+
margin: 0;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.primaryheader {
|
|
556
|
+
display: flex;
|
|
557
|
+
flex-direction: row;
|
|
558
|
+
align-items: center;
|
|
559
|
+
font-size:14px;
|
|
560
|
+
height: 50px;
|
|
561
|
+
|
|
562
|
+
h1 {
|
|
563
|
+
margin: 0;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.subheader{
|
|
568
|
+
display: flex;
|
|
569
|
+
flex-direction: row;
|
|
570
|
+
color: var(--input-label);
|
|
571
|
+
& > * {
|
|
572
|
+
margin: 5px 20px 5px 0px;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.live-data {
|
|
576
|
+
color: var(--body-text)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.state-banner {
|
|
581
|
+
margin: 3px 0 0 0;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.masthead-state {
|
|
585
|
+
font-size: initial;
|
|
586
|
+
display: inline-block;
|
|
587
|
+
position: relative;
|
|
588
|
+
/* top: -2px; */
|
|
589
|
+
font-size: 12px;
|
|
590
|
+
margin-left: 5px;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.masthead-istio {
|
|
594
|
+
.icon {
|
|
595
|
+
vertical-align: middle;
|
|
596
|
+
color: var(--primary);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.left-right-split {
|
|
601
|
+
display: grid;
|
|
602
|
+
align-items: center;
|
|
603
|
+
|
|
604
|
+
.left-half {
|
|
605
|
+
grid-column: 1;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.right-half {
|
|
609
|
+
grid-column: 2;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.resource-external {
|
|
614
|
+
font-size: 18px;
|
|
615
|
+
}
|
|
616
|
+
.excram-list{
|
|
617
|
+
font-size: 14px;
|
|
618
|
+
margin-bottom: 20px;
|
|
619
|
+
}
|
|
620
|
+
.excram-last-name{
|
|
621
|
+
color: var(--link);
|
|
622
|
+
}
|
|
623
|
+
.valid{
|
|
624
|
+
color: #d7d7d7;
|
|
625
|
+
margin: 0px 10px;
|
|
626
|
+
}
|
|
627
|
+
.detailIcon-span{
|
|
628
|
+
width: 24px;
|
|
629
|
+
height: 24px;
|
|
630
|
+
display: inline-block;
|
|
631
|
+
position: relative;
|
|
632
|
+
background: var(--primary);
|
|
633
|
+
margin-right: 10px;
|
|
634
|
+
}
|
|
635
|
+
.detailIcon{
|
|
636
|
+
position: absolute;
|
|
637
|
+
color: #fff;
|
|
638
|
+
font-size: 38px;
|
|
639
|
+
left: 4px;
|
|
640
|
+
top: -2px;
|
|
641
|
+
}
|
|
642
|
+
.primary-title{
|
|
643
|
+
display: flex;
|
|
644
|
+
align-items: center;
|
|
645
|
+
}
|
|
646
|
+
.detailIcon-span-title{
|
|
647
|
+
font-weight: bold;
|
|
648
|
+
}
|
|
649
|
+
</style>
|
|
@@ -17,14 +17,38 @@ import { fetchAlertManagerConfigSpecs } from '@shell/utils/alertmanagerconfig';
|
|
|
17
17
|
// i18n-uses monitoringReceiver.slack.*, monitoringReceiver.email.*, monitoringReceiver.pagerduty.*
|
|
18
18
|
// i18n-uses monitoringReceiver.opsgenie.*, monitoringReceiver.webhook.*, monitoringReceiver.custom.*
|
|
19
19
|
export const RECEIVERS_TYPES = [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
{
|
|
21
|
+
name: 'message',
|
|
22
|
+
label: 'monitoringReceiver.message.label',
|
|
23
|
+
title: 'monitoringReceiver.message.title',
|
|
24
|
+
info: 'monitoringReceiver.slack.info',
|
|
25
|
+
key: 'slackConfigs',
|
|
26
|
+
logo: require(`@shell/assets/images/vendor/slack.svg`)
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'dingding',
|
|
30
|
+
label: 'monitoringReceiver.dingding.label',
|
|
31
|
+
title: 'monitoringReceiver.dingding.title',
|
|
32
|
+
info: 'monitoringReceiver.slack.info',
|
|
33
|
+
key: 'dingding',
|
|
34
|
+
logo: require(`@shell/assets/images/vendor/slack.svg`)
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'work',
|
|
38
|
+
label: 'monitoringReceiver.weixin.label',
|
|
39
|
+
title: 'monitoringReceiver.weixin.title',
|
|
40
|
+
info: 'monitoringReceiver.slack.info',
|
|
41
|
+
key: 'work',
|
|
42
|
+
logo: require(`@shell/assets/images/vendor/slack.svg`)
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'snmp',
|
|
46
|
+
label: 'monitoringReceiver.snmp.label',
|
|
47
|
+
title: 'monitoringReceiver.snmp.title',
|
|
48
|
+
info: 'monitoringReceiver.slack.info',
|
|
49
|
+
key: 'snmp',
|
|
50
|
+
logo: require(`@shell/assets/images/vendor/slack.svg`)
|
|
51
|
+
},
|
|
28
52
|
{
|
|
29
53
|
name: 'email',
|
|
30
54
|
label: 'monitoringReceiver.email.label',
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-20">
|
|
4
|
+
<LabeledInput
|
|
5
|
+
v-model:value="message"
|
|
6
|
+
:placeholder="'webhook地址'"
|
|
7
|
+
:label="'webhook地址'"
|
|
8
|
+
/>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="mb-20">
|
|
11
|
+
<LabeledInput
|
|
12
|
+
v-model:value="id"
|
|
13
|
+
:placeholder="'签名密钥'"
|
|
14
|
+
:label="'签名密钥'"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
<script>
|
|
20
|
+
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
21
|
+
export default {
|
|
22
|
+
components: { LabeledInput },
|
|
23
|
+
data() {
|
|
24
|
+
return {
|
|
25
|
+
message: '',
|
|
26
|
+
id: '',
|
|
27
|
+
scret: '',
|
|
28
|
+
templateId: ''
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-20">
|
|
4
|
+
<LabeledInput
|
|
5
|
+
v-model:value="message"
|
|
6
|
+
:placeholder="'请输入手机号'"
|
|
7
|
+
:label="'默认手机人电话'"
|
|
8
|
+
/>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="mb-20">
|
|
11
|
+
<LabeledInput
|
|
12
|
+
v-model:value="id"
|
|
13
|
+
:placeholder="'API密钥ID'"
|
|
14
|
+
:label="'默认API密钥ID'"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="mb-20">
|
|
18
|
+
<LabeledInput
|
|
19
|
+
v-model:value="scret"
|
|
20
|
+
:placeholder="'API密钥secret'"
|
|
21
|
+
:label="'默认API密钥secret'"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="mb-20">
|
|
25
|
+
<LabeledInput
|
|
26
|
+
v-model:value="templateId"
|
|
27
|
+
:placeholder="'请输入模板ID'"
|
|
28
|
+
:label="'模板ID'"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
<script>
|
|
34
|
+
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
35
|
+
// import { ref } from 'vue';
|
|
36
|
+
// const id = ref('');
|
|
37
|
+
// const scret = ref('');
|
|
38
|
+
// const message = ref('');
|
|
39
|
+
// const templateId = ref('');
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
components: { LabeledInput },
|
|
43
|
+
data() {
|
|
44
|
+
return {
|
|
45
|
+
message: '',
|
|
46
|
+
id: '',
|
|
47
|
+
scret: '',
|
|
48
|
+
templateId: ''
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-20">
|
|
4
|
+
<LabeledInput
|
|
5
|
+
v-model:value="message"
|
|
6
|
+
:placeholder="'目标主机'"
|
|
7
|
+
:label="'目标主机'"
|
|
8
|
+
/>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="mb-20">
|
|
11
|
+
<LabeledInput
|
|
12
|
+
v-model:value="id"
|
|
13
|
+
:placeholder="'端口'"
|
|
14
|
+
:label="'端口'"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="mb-20">
|
|
18
|
+
<LabeledInput
|
|
19
|
+
v-model:value="scret"
|
|
20
|
+
:placeholder="'团体字'"
|
|
21
|
+
:label="'团体字'"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
<script>
|
|
27
|
+
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
28
|
+
// import { ref } from 'vue';
|
|
29
|
+
// const id = ref('');
|
|
30
|
+
// const scret = ref('');
|
|
31
|
+
// const message = ref('');
|
|
32
|
+
// const templateId = ref('');
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
components: { LabeledInput },
|
|
36
|
+
data() {
|
|
37
|
+
return {
|
|
38
|
+
message: '',
|
|
39
|
+
id: '',
|
|
40
|
+
scret: '',
|
|
41
|
+
templateId: ''
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-20">
|
|
4
|
+
<LabeledInput
|
|
5
|
+
v-model:value="message"
|
|
6
|
+
:placeholder="'webhook地址'"
|
|
7
|
+
:label="'webhook地址'"
|
|
8
|
+
/>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
<script>
|
|
13
|
+
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
14
|
+
// import { ref } from 'vue';
|
|
15
|
+
// const id = ref('');
|
|
16
|
+
// const scret = ref('');
|
|
17
|
+
// const message = ref('');
|
|
18
|
+
// const templateId = ref('');
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
components: { LabeledInput },
|
|
22
|
+
data() {
|
|
23
|
+
return {
|
|
24
|
+
message: '',
|
|
25
|
+
id: '',
|
|
26
|
+
scret: '',
|
|
27
|
+
templateId: ''
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
</script>
|
package/package.json
CHANGED
package/scripts/publish-shell.sh
CHANGED
package/store/index.js
CHANGED
|
@@ -519,10 +519,10 @@ export const getters = {
|
|
|
519
519
|
(!isAll && filteredMap && filteredMap[out] );
|
|
520
520
|
}
|
|
521
521
|
|
|
522
|
-
out = rootGetters['prefs/get'](LAST_NAMESPACE);
|
|
523
|
-
if ( isOk() ) {
|
|
524
|
-
|
|
525
|
-
}
|
|
522
|
+
// out = rootGetters['prefs/get'](LAST_NAMESPACE);
|
|
523
|
+
// if ( isOk() ) {
|
|
524
|
+
// return out;
|
|
525
|
+
// }
|
|
526
526
|
|
|
527
527
|
out = 'default';
|
|
528
528
|
if ( isOk() ) {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"translations": [
|
|
3
|
+
{
|
|
4
|
+
"pattern": "the status of pod readiness gate "kubevirt\\.io/virtual-machine-unpaused" is not "true", but false",
|
|
5
|
+
"replacement": "这个只有当虚拟机处于运行状态(非暂停)时,才能进行暂停操作",
|
|
6
|
+
"flags": "gi"
|
|
7
|
+
},
|
|
3
8
|
{
|
|
4
9
|
"pattern": "can't delete vlanconfig (\\S+) because the storage network nad (\\S+) is still attached",
|
|
5
10
|
"replacement": "无法删除 VLAN 配置 '$1':存储网络附加定义 '$2' 仍在使用",
|