dashboard-shell-shell 1.0.1000000117 → 1.0.1000000118

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.
Files changed (124) hide show
  1. package/assets/styles/base/_functions.scss +0 -0
  2. package/assets/styles/base/_mixins.scss +1 -1
  3. package/assets/styles/global/_button.scss +10 -17
  4. package/assets/styles/global/_form.scss +2 -2
  5. package/assets/styles/global/_labeled-input.scss +2 -6
  6. package/assets/styles/global/_select.scss +7 -6
  7. package/assets/styles/global/_table.scss +2 -3
  8. package/assets/styles/global/_tooltip.scss +1 -8
  9. package/assets/styles/themes/_dark.scss +0 -2
  10. package/assets/styles/themes/_light.scss +2 -5
  11. package/assets/styles/vendor/vue-select.scss +1 -2
  12. package/assets/translations/en-us.yaml +3 -1
  13. package/assets/translations/zh-hans.yaml +28 -51
  14. package/components/ActionDropdown.vue +0 -1
  15. package/components/ActionMenuShell.vue +3 -6
  16. package/components/BrandImage.vue +0 -22
  17. package/components/ClusterIconMenu.vue +1 -1
  18. package/components/CodeMirror.vue +0 -1
  19. package/components/CruResource.vue +1 -1
  20. package/components/CruResourceFooter.vue +1 -1
  21. package/components/ExplorerProjectsNamespaces.vue +24 -4
  22. package/components/GlobalRoleBindings.vue +48 -112
  23. package/components/IndentedPanel.vue +10 -4
  24. package/components/PromptRemove.vue +3 -3
  25. package/components/ResourceDetail/Masthead.vue +242 -190
  26. package/components/ResourceDetail/index.vue +5 -20
  27. package/components/ResourceList/Masthead.vue +84 -146
  28. package/components/ResourceList/ResourceLoadingIndicator.vue +2 -5
  29. package/components/ResourceTable.vue +1 -76
  30. package/components/SideNav.vue +29 -66
  31. package/components/SortableTable/THead.vue +0 -6
  32. package/components/SortableTable/index.vue +388 -481
  33. package/components/Tabbed/index.vue +5 -4
  34. package/components/auth/Principal.vue +2 -3
  35. package/components/auth/RoleDetailEdit.vue +5 -58
  36. package/components/auth/SelectPrincipal.vue +0 -1
  37. package/components/form/BannerSettings.vue +16 -18
  38. package/components/form/ChangePassword.vue +4 -4
  39. package/components/form/ColorInput.vue +8 -32
  40. package/components/form/Footer.vue +1 -1
  41. package/components/form/InputWithSelect.vue +0 -2
  42. package/components/form/KeyValue.vue +7 -31
  43. package/components/form/LabeledSelect.vue +178 -178
  44. package/components/form/Members/ClusterPermissionsEditor.vue +2 -1
  45. package/components/form/Members/MembershipEditor.vue +1 -1
  46. package/components/form/NameNsDescription.vue +11 -24
  47. package/components/form/Password.vue +2 -6
  48. package/components/form/ResourceQuota/Namespace.vue +1 -1
  49. package/components/form/ResourceQuota/NamespaceRow.vue +10 -13
  50. package/components/form/ResourceQuota/ProjectRow.vue +1 -0
  51. package/components/form/Select.vue +2 -2
  52. package/components/nav/Favorite.vue +1 -5
  53. package/components/nav/Group.vue +23 -69
  54. package/components/nav/Header.vue +17 -82
  55. package/components/nav/HeaderPageActionMenu.vue +0 -1
  56. package/components/nav/NamespaceFilter.vue +3 -0
  57. package/components/nav/TopLevelMenu.vue +119 -182
  58. package/components/nav/Type.vue +11 -48
  59. package/components/rancherResourceDetail/Masthead.vue +769 -0
  60. package/components/rancherResourceDetail/__tests__/Masthead.test.ts +65 -0
  61. package/components/rancherResourceDetail/index.vue +591 -0
  62. package/components/rancherResourceList/Masthead.vue +375 -0
  63. package/components/rancherResourceList/ResourceLoadingIndicator.vue +140 -0
  64. package/components/rancherResourceList/index.vue +307 -0
  65. package/components/rancherResourceList/resource-list.config.js +7 -0
  66. package/components/rancherResourceTable.vue +783 -0
  67. package/components/rancherSortableTable/THead.vue +561 -0
  68. package/components/rancherSortableTable/actions.js +153 -0
  69. package/components/rancherSortableTable/advanced-filtering.js +272 -0
  70. package/components/rancherSortableTable/debug.js +117 -0
  71. package/components/rancherSortableTable/filtering.js +290 -0
  72. package/components/rancherSortableTable/grouping.js +48 -0
  73. package/components/rancherSortableTable/index.vue +2712 -0
  74. package/components/rancherSortableTable/paging.js +155 -0
  75. package/components/rancherSortableTable/selection.js +629 -0
  76. package/components/rancherSortableTable/sortable-config.ts +4 -0
  77. package/components/rancherSortableTable/sorting.js +129 -0
  78. package/composables/useClickOutside.ts +1 -1
  79. package/config/product/auth.js +7 -16
  80. package/config/product/explorer.js +1 -1
  81. package/config/product/settings.js +8 -17
  82. package/config/settings.ts +0 -28
  83. package/edit/management.cattle.io.user.vue +4 -17
  84. package/edit/networking.k8s.io.ingress/RulePath.vue +1 -1
  85. package/edit/token.vue +1 -1
  86. package/list/harvesterhci.io.management.cluster.vue +0 -17
  87. package/list/management.cattle.io.setting.vue +13 -22
  88. package/list/management.cattle.io.user.vue +14 -25
  89. package/list/provisioning.cattle.io.cluster.vue +7 -6
  90. package/mixins/brand.js +0 -17
  91. package/package.json +1 -1
  92. package/pages/auth/login.vue +29 -84
  93. package/pages/c/_cluster/auth/roles/index.vue +14 -61
  94. package/pages/c/_cluster/settings/banners.vue +101 -174
  95. package/pages/c/_cluster/settings/brand.vue +301 -348
  96. package/pages/c/_cluster/settings/performance.vue +38 -61
  97. package/pages/home.vue +21 -70
  98. package/pages/prefs.vue +23 -25
  99. package/pkg/tsconfig.json +9 -9
  100. package/pkg/vue.config.js +1 -1
  101. package/promptRemove/mixin/roleDeletionCheck.js +2 -2
  102. package/scripts/clean +0 -0
  103. package/scripts/extension/bundle +0 -0
  104. package/scripts/extension/helm/scripts/package +0 -0
  105. package/scripts/extension/helm/scripts/patch +0 -0
  106. package/scripts/extension/helm/scripts/version +0 -0
  107. package/scripts/extension/helmpatch +0 -0
  108. package/scripts/extension/parse-tag-name +0 -0
  109. package/scripts/extension/publish +0 -0
  110. package/scripts/publish-shell.sh +60 -86
  111. package/scripts/serve-pkgs +0 -0
  112. package/scripts/sync-shell-deps +0 -0
  113. package/scripts/typegen.sh +28 -44
  114. package/store/i18n.js +5 -5
  115. package/store/prefs.js +5 -17
  116. package/store/type-map.js +1 -2
  117. package/types/cloud-shell/index.d.ts +11014 -0
  118. package/types/shell/index.d.ts +1 -1
  119. package/utils/error.js +0 -4
  120. package/utils/router.js +3 -3
  121. package/vue.config.js +6 -1
  122. package/assets/images/action.svg +0 -6
  123. package/assets/images/pl/logo.png +0 -0
  124. /package/components/{ResourceList → rancherResourceList}/Masthead-btn.vue +0 -0
@@ -0,0 +1,769 @@
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
+ import { harvesterhci2cloud, cloud2harvesterhci } from '@shell/utils/router'
21
+
22
+ // i18n-uses resourceDetail.header.*
23
+
24
+ /**
25
+ * Resource Detail Masthead component.
26
+ *
27
+ * 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
28
+ */
29
+ export default {
30
+
31
+ name: 'MastheadResourceDetail',
32
+
33
+ components: {
34
+ // BadgeState,
35
+ DotState,
36
+ Banner,
37
+ ButtonGroup,
38
+ ExtensionPanel,
39
+ TabTitle,
40
+ ActionMenu,
41
+ },
42
+ props: {
43
+ value: {
44
+ type: Object,
45
+ default: () => {
46
+ return {};
47
+ }
48
+ },
49
+
50
+ mode: {
51
+ type: String,
52
+ default: 'create'
53
+ },
54
+
55
+ realMode: {
56
+ type: String,
57
+ default: 'create'
58
+ },
59
+
60
+ as: {
61
+ type: String,
62
+ default: _YAML,
63
+ },
64
+
65
+ hasGraph: {
66
+ type: Boolean,
67
+ default: false
68
+ },
69
+
70
+ hasDetail: {
71
+ type: Boolean,
72
+ default: false
73
+ },
74
+
75
+ hasEdit: {
76
+ type: Boolean,
77
+ default: false
78
+ },
79
+
80
+ storeOverride: {
81
+ type: String,
82
+ default: null,
83
+ },
84
+
85
+ resource: {
86
+ type: String,
87
+ default: null,
88
+ },
89
+
90
+ resourceSubtype: {
91
+ type: String,
92
+ default: null,
93
+ },
94
+
95
+ parentRouteOverride: {
96
+ type: String,
97
+ default: null,
98
+ },
99
+
100
+ canViewYaml: {
101
+ type: Boolean,
102
+ default: false,
103
+ }
104
+ },
105
+
106
+ setup() {
107
+ const store = useStore();
108
+ const { featureDropdownMenu } = useRuntimeFlag(store);
109
+
110
+ return { featureDropdownMenu };
111
+ },
112
+
113
+ data() {
114
+ return {
115
+ DETAIL_VIEW: _DETAIL,
116
+ extensionType: ExtensionPoint.PANEL,
117
+ extensionLocation: PanelLocation.DETAILS_MASTHEAD,
118
+ Svg: require('~shell/assets/images/API.svg')
119
+ };
120
+ },
121
+
122
+ computed: {
123
+ dev() {
124
+ return this.$store.getters['prefs/dev'];
125
+ },
126
+
127
+ schema() {
128
+ const inStore = this.storeOverride || this.$store.getters['currentStore'](this.resource);
129
+
130
+ return this.$store.getters[`${ inStore }/schemaFor`]( this.resource );
131
+ },
132
+
133
+ isView() {
134
+ return this.mode === _VIEW;
135
+ },
136
+
137
+ isEdit() {
138
+ return this.mode === _EDIT;
139
+ },
140
+
141
+ isCreate() {
142
+ return this.mode === _CREATE;
143
+ },
144
+
145
+ isNamespace() {
146
+ return this.schema?.id === NAMESPACE;
147
+ },
148
+
149
+ isProject() {
150
+ return this.schema?.id === MANAGEMENT.PROJECT;
151
+ },
152
+
153
+ isProjectHelmChart() {
154
+ return this.schema?.id === HELM.PROJECTHELMCHART;
155
+ },
156
+
157
+ hasMultipleNamespaces() {
158
+ return !!this.value.namespaces;
159
+ },
160
+
161
+ namespace() {
162
+ if (this.value?.metadata?.namespace) {
163
+ return this.value?.metadata?.namespace;
164
+ }
165
+
166
+ return null;
167
+ },
168
+
169
+ detailsAction() {
170
+ return this.value?.detailsAction;
171
+ },
172
+
173
+ shouldHifenize() {
174
+ return (this.mode === 'view' || this.mode === 'edit') && this.resourceSubtype?.length && this.value?.nameDisplay?.length;
175
+ },
176
+
177
+ namespaceLocation() {
178
+ if (!this.isNamespace) {
179
+ return this.value.namespaceLocation || {
180
+ name: 'c-cluster-product-resource-id',
181
+ params: {
182
+ cluster: this.$route.params.cluster,
183
+ product: this.$store.getters['productId'],
184
+ resource: NAMESPACE,
185
+ id: this.$route.params.namespace
186
+ }
187
+ };
188
+ }
189
+
190
+ return null;
191
+ },
192
+
193
+ isWorkspace() {
194
+ return this.$store.getters['productId'] === FLEET_NAME && !!this.value?.metadata?.namespace;
195
+ },
196
+
197
+ workspaceLocation() {
198
+ return {
199
+ name: 'c-cluster-product-resource-id',
200
+ params: {
201
+ cluster: this.$route.params.cluster,
202
+ product: this.$store.getters['productId'],
203
+ resource: FLEET.WORKSPACE,
204
+ id: this.$route.params.namespace
205
+ }
206
+ };
207
+ },
208
+
209
+ project() {
210
+ if (this.isNamespace) {
211
+ const cluster = this.$store.getters['currentCluster'];
212
+
213
+ if (cluster) {
214
+ const id = (this.value?.metadata?.labels || {})[PROJECT];
215
+
216
+ return this.$store.getters['management/byId'](MANAGEMENT.PROJECT, `${ cluster.id }/${ id }`);
217
+ }
218
+ }
219
+
220
+ return null;
221
+ },
222
+
223
+ banner() {
224
+ if (this.value?.stateObj?.error) {
225
+ const defaultErrorMessage = this.t('resourceDetail.masthead.defaultBannerMessage.error', undefined, true);
226
+
227
+ return {
228
+ color: 'error',
229
+ message: this.value.stateObj.message || defaultErrorMessage
230
+ };
231
+ }
232
+
233
+ if (this.value?.spec?.paused) {
234
+ return {
235
+ color: 'info',
236
+ message: this.t('asyncButton.pause.description')
237
+ };
238
+ }
239
+
240
+ if (this.value?.stateObj?.transitioning) {
241
+ const defaultTransitioningMessage = this.t('resourceDetail.masthead.defaultBannerMessage.transitioning', undefined, true);
242
+
243
+ return {
244
+ color: 'info',
245
+ message: this.value.stateObj.message || defaultTransitioningMessage
246
+ };
247
+ }
248
+
249
+ return null;
250
+ },
251
+
252
+ parent() {
253
+ const displayName = this.value?.parentNameOverride || this.$store.getters['type-map/labelFor'](this.schema);
254
+ const product = this.$store.getters['currentProduct'].name;
255
+
256
+ const defaultLocation = {
257
+ name: 'c-cluster-product-resource',
258
+ params: {
259
+ resource: this.resource,
260
+ product,
261
+ }
262
+ };
263
+
264
+ const location = this.value?.parentLocationOverride || defaultLocation;
265
+
266
+ if (this.parentRouteOverride) {
267
+ location.name = this.parentRouteOverride;
268
+ }
269
+
270
+ const typeOptions = this.$store.getters[`type-map/optionsFor`]( this.resource );
271
+ const out = {
272
+ displayName, location, ...typeOptions
273
+ };
274
+
275
+ return out;
276
+ },
277
+
278
+ hideSensitiveData() {
279
+ return this.$store.getters['prefs/get'](HIDE_SENSITIVE);
280
+ },
281
+
282
+ sensitiveOptions() {
283
+ return [
284
+ {
285
+ tooltipKey: 'resourceDetail.masthead.sensitive.hide',
286
+ icon: 'icon-hide',
287
+ value: true,
288
+ },
289
+ {
290
+ tooltipKey: 'resourceDetail.masthead.sensitive.show',
291
+ icon: 'icon-show',
292
+ value: false
293
+ }
294
+ ];
295
+ },
296
+
297
+ viewOptions() {
298
+ const out = [];
299
+
300
+ if ( this.hasDetail ) {
301
+ out.push({
302
+ labelKey: 'resourceDetail.masthead.detail',
303
+ value: _DETAIL,
304
+ });
305
+ }
306
+
307
+ if ( this.hasEdit && this.parent?.showConfigView !== false) {
308
+ out.push({
309
+ labelKey: 'resourceDetail.masthead.config',
310
+ value: _CONFIG,
311
+ });
312
+ }
313
+
314
+ if ( this.hasGraph ) {
315
+ out.push({
316
+ labelKey: 'resourceDetail.masthead.graph',
317
+ value: _GRAPH,
318
+ });
319
+ }
320
+
321
+ if ( this.canViewYaml ) {
322
+ out.push({
323
+ labelKey: 'resourceDetail.masthead.yaml',
324
+ value: _YAML,
325
+ });
326
+ }
327
+
328
+ if ( out.length < 2 ) {
329
+ return null;
330
+ }
331
+
332
+ return out;
333
+ },
334
+
335
+ currentView: {
336
+ get() {
337
+ return this.as;
338
+ },
339
+
340
+ set(val) {
341
+ switch ( val ) {
342
+ case _DETAIL:
343
+ this.$router.applyQuery({
344
+ [MODE]: _UNFLAG,
345
+ [AS]: _UNFLAG,
346
+ });
347
+ break;
348
+ case _CONFIG:
349
+ this.$router.applyQuery({
350
+ [MODE]: _UNFLAG,
351
+ [AS]: _CONFIG,
352
+ });
353
+ break;
354
+ case _GRAPH:
355
+ this.$router.applyQuery({
356
+ [MODE]: _UNFLAG,
357
+ [AS]: _GRAPH,
358
+ });
359
+ break;
360
+ case _YAML:
361
+ this.$router.applyQuery({
362
+ [MODE]: _UNFLAG,
363
+ [AS]: _YAML,
364
+ });
365
+ break;
366
+ }
367
+ },
368
+ },
369
+
370
+ showSensitiveToggle() {
371
+ return !!this.value.hasSensitiveData && this.mode === _VIEW && this.as !== _YAML;
372
+ },
373
+
374
+ managedWarning() {
375
+ const { value } = this;
376
+ const labels = value?.metadata?.labels || {};
377
+
378
+ const managedBy = labels[KUBERNETES.MANAGED_BY] || '';
379
+ const appName = labels[KUBERNETES.MANAGED_NAME] || labels[KUBERNETES.INSTANCE] || '';
380
+
381
+ return {
382
+ show: this.mode === _EDIT && !!managedBy,
383
+ type: value?.kind || '',
384
+ hasName: appName ? 'yes' : 'no',
385
+ appName,
386
+ managedBy,
387
+ };
388
+ },
389
+
390
+ displayName() {
391
+ let displayName = this.value.nameDisplay;
392
+
393
+ if (this.isProjectHelmChart) {
394
+ displayName = this.value.projectDisplayName;
395
+ }
396
+
397
+ return this.shouldHifenize ? ` - ${ displayName }` : displayName;
398
+ },
399
+
400
+ demoDisplay() {
401
+ const product = this.$store.getters['productId'];
402
+
403
+ const productId = this.$store.getters['type-map/groupForBasicType'](this.$store.getters['productId'], this.location.params.resource);
404
+
405
+ if (productId === undefined) {
406
+ return '';
407
+ }
408
+ const parts = productId.split('::');
409
+ const newString = 'root';
410
+
411
+ if (!parts?.includes(newString)) {
412
+ parts.unshift(newString); // 将字符串添加到数组第一位
413
+ }
414
+
415
+ const partsEn = parts.map((item) => {
416
+ return this.$store.getters['i18n/t'](`typeLabel."${ item.toLowerCase() }"`);
417
+ });
418
+
419
+ return partsEn;
420
+ },
421
+ menuIcon() {
422
+ const product = this.$store.getters['productId'];
423
+
424
+ const resources = this.location?.params?.resource || ''
425
+
426
+ return this.$store.getters['type-map/groupsForVirTypes'](product, resources);
427
+ },
428
+
429
+ location() {
430
+ const { parent } = this;
431
+
432
+ return parent?.location;
433
+ },
434
+
435
+ hideNamespaceLocation() {
436
+ return this.$store.getters['currentProduct'].hideNamespaceLocation || this.value.namespaceLocation === null;
437
+ },
438
+
439
+ resourceExternalLink() {
440
+ return this.value.resourceExternalLink;
441
+ },
442
+ },
443
+
444
+ methods: {
445
+ get,
446
+
447
+ showActions() {
448
+ this.$store.commit('action-menu/show', {
449
+ resources: this.value,
450
+ elem: this.$refs.actions,
451
+ });
452
+ },
453
+
454
+ toggleSensitiveData(e) {
455
+ this.$store.dispatch('prefs/set', { key: HIDE_SENSITIVE, value: !!e });
456
+ },
457
+
458
+ invokeDetailsAction() {
459
+ const action = this.detailsAction;
460
+
461
+ if (action) {
462
+ const fn = this.value[action.action];
463
+
464
+ if (fn) {
465
+ fn.apply(this.value, []);
466
+ }
467
+ }
468
+ }
469
+ }
470
+ };
471
+ </script>
472
+
473
+ <template>
474
+
475
+ <!-- 顶部区域的容器 -->
476
+ <div class="masthead">
477
+ <div class="title">
478
+
479
+ <!-- 面包屑导航(创建 API 密钥时不显示) -->
480
+ <div
481
+ v-if="!(parentRouteOverride === 'account' && resource=== 'token')"
482
+ class="excram-list"
483
+ >
484
+
485
+ <!-- 遍历 demoDisplay 生成面包屑路径 -->
486
+ <span
487
+ v-for="(item,index) in demoDisplay"
488
+ :key="index"
489
+ >
490
+ <span v-if="item">{{ item }}</span>
491
+ <span v-if="item">/</span>
492
+ </span>
493
+
494
+ <!-- 最后一个面包屑显示当前操作(查看/编辑/创建)+ 父资源名称 -->
495
+ <span class="excram-last-name">
496
+ {{ (realMode === 'view'? '查看': realMode === 'edit' ? '编辑':'创建') + parent.displayName }}
497
+ </span>
498
+ </div>
499
+
500
+ <header>
501
+ <div class="title">
502
+ <div class="primaryheader">
503
+ <span class="primary-title">
504
+
505
+ <!-- 图标区 -->
506
+ <span v-if="menuIcon && !(parentRouteOverride === 'account' && resource=== 'token')" class="detailIcon-span">
507
+ <!-- 如果是账户 API token,则显示图片,否则显示 icon -->
508
+ <img
509
+ v-if="parentRouteOverride === 'account' && resource=== 'token'"
510
+ :src="Svg"
511
+ style="margin-top: 4px; margin-left: 5px;"
512
+ >
513
+ <i
514
+ v-else
515
+ :class="'icon-'+ menuIcon + ' detailIcon'"
516
+ />
517
+ </span>
518
+
519
+ <!-- 资源标题(创建时只显示“创建+名称”,否则显示“名称:”) -->
520
+ <span class="detailIcon-span-title">{{ realMode=== 'create'? '创建': '' }}{{ parent.displayName }}{{ realMode=== 'create'? '': '名称:' }}</span>
521
+
522
+ <!-- 如果不是创建模式,显示操作描述 -->
523
+ <span v-if="realMode !== 'create'">
524
+
525
+ <!-- 如果有覆盖方法,优先显示覆盖内容 -->
526
+ <span v-if="value.detailPageHeaderActionOverride && value.detailPageHeaderActionOverride(realMode)">{{ value.detailPageHeaderActionOverride(realMode) }}</span>
527
+
528
+ <!-- 否则用 t 组件国际化显示 -->
529
+ <t
530
+ v-else
531
+ :k="'resourceDetail.header.' + realMode"
532
+ :subtype="resourceSubtype"
533
+ :name="displayName"
534
+ :escapehtml="false"
535
+ />
536
+ </span>
537
+
538
+ <!-- 状态点(DotState 组件) -->
539
+ <DotState
540
+ v-if="!isCreate && parent.showState"
541
+ class="masthead-state"
542
+ :value="value"
543
+ />
544
+
545
+ <!-- Istio 注入状态 -->
546
+ <span
547
+ v-if="!isCreate && value.injectionEnabled"
548
+ class="masthead-istio"
549
+ >
550
+ <i
551
+ v-clean-tooltip="t('projectNamespaces.isIstioInjectionEnabled')"
552
+ class="icon icon-sm icon-istio"
553
+ />
554
+ </span>
555
+ </span>
556
+
557
+ <!-- 如果有返回路径,显示分隔符和“返回”链接 -->
558
+ <span
559
+ v-if="location"
560
+ class="valid"
561
+ >|</span>
562
+ <router-link
563
+ v-if="location"
564
+ :to="location"
565
+ >
566
+ 返回
567
+ </router-link>
568
+ </div>
569
+ </div>
570
+
571
+ <!-- 右侧操作按钮区域(支持通过 slot 覆盖) -->
572
+ <slot name="right">
573
+ <div class="actions-container align-start" style="padding-right: 15px;">
574
+ <div class="actions">
575
+
576
+ <!-- 详情模式下的主操作按钮 -->
577
+ <button
578
+ v-if="detailsAction && currentView === DETAIL_VIEW && isView"
579
+ type="button"
580
+ class="btn role-primary actions mr-10"
581
+ :disabled="!detailsAction.enabled"
582
+ @click="invokeDetailsAction"
583
+ >
584
+ {{ detailsAction.label }}
585
+ </button>
586
+
587
+ <!-- 敏感信息显示/隐藏切换 -->
588
+ <ButtonGroup
589
+ v-if="showSensitiveToggle"
590
+ :value="!!hideSensitiveData"
591
+ icon-size="lg"
592
+ :options="sensitiveOptions"
593
+ class="mr-10"
594
+ @update:value="toggleSensitiveData"
595
+ />
596
+
597
+ <!-- 视图切换按钮 -->
598
+ <ButtonGroup
599
+ v-if="viewOptions && isView"
600
+ v-model:value="currentView"
601
+ :options="viewOptions"
602
+ class="mr-10"
603
+ />
604
+
605
+ <!-- 功能菜单:优先使用 ActionMenu 组件 -->
606
+ <template v-if="featureDropdownMenu">
607
+ <ActionMenu
608
+ v-if="isView"
609
+ button-role="multiAction"
610
+ button-size="compact"
611
+ :resource="value"
612
+ data-testid="masthead-action-menu"
613
+ />
614
+ </template>
615
+
616
+ <!-- 如果没有 featureDropdownMenu,则使用普通的多操作按钮 -->
617
+ <template v-else>
618
+ <button
619
+ v-if="isView"
620
+ ref="actions"
621
+ data-testid="masthead-action-menu"
622
+ aria-haspopup="true"
623
+ type="button"
624
+ class="btn role-multi-action actions"
625
+ @click="showActions"
626
+ >
627
+ <i class="icon icon-actions" />
628
+ </button>
629
+ </template>
630
+ </div>
631
+ </div>
632
+ </slot>
633
+ </header>
634
+
635
+ <!-- 扩展区域 -->
636
+ <ExtensionPanel
637
+ :resource="value"
638
+ :type="extensionType"
639
+ :location="extensionLocation"
640
+ />
641
+
642
+ <!-- 顶部状态 Banner -->
643
+ <Banner
644
+ v-if="banner && isView && !parent.hideBanner"
645
+ class="state-banner mb-10"
646
+ :color="banner.color"
647
+ :label="banner.message"
648
+ />
649
+
650
+ <!-- 管理警告 Banner -->
651
+ <Banner
652
+ v-if="managedWarning.show"
653
+ color="warning"
654
+ class="mb-20"
655
+ :label="t('resourceDetail.masthead.managedWarning', managedWarning)"
656
+ />
657
+
658
+ <!-- 内容插槽 -->
659
+ <slot />
660
+ </div>
661
+ </div>
662
+ </template>
663
+
664
+ <style lang='scss' scoped>
665
+ .masthead {
666
+ padding-bottom: 10px;
667
+ /* border-bottom: 1px solid var(--border); */
668
+ margin-bottom: 10px;
669
+ }
670
+
671
+ HEADER {
672
+ margin: 0;
673
+ }
674
+
675
+ .primaryheader {
676
+ display: flex;
677
+ flex-direction: row;
678
+ align-items: center;
679
+ font-size:14px;
680
+ height: 50px;
681
+
682
+ h1 {
683
+ margin: 0;
684
+ }
685
+ }
686
+
687
+ .subheader{
688
+ display: flex;
689
+ flex-direction: row;
690
+ color: var(--input-label);
691
+ & > * {
692
+ margin: 5px 20px 5px 0px;
693
+ }
694
+
695
+ .live-data {
696
+ color: var(--body-text)
697
+ }
698
+ }
699
+
700
+ .state-banner {
701
+ margin: 3px 0 0 0;
702
+ }
703
+
704
+ .masthead-state {
705
+ font-size: initial;
706
+ display: inline-block;
707
+ position: relative;
708
+ /* top: -2px; */
709
+ font-size: 12px;
710
+ margin-left: 5px;
711
+ }
712
+
713
+ .masthead-istio {
714
+ .icon {
715
+ vertical-align: middle;
716
+ color: var(--primary);
717
+ }
718
+ }
719
+
720
+ .left-right-split {
721
+ display: grid;
722
+ align-items: center;
723
+
724
+ .left-half {
725
+ grid-column: 1;
726
+ }
727
+
728
+ .right-half {
729
+ grid-column: 2;
730
+ }
731
+ }
732
+
733
+ .resource-external {
734
+ font-size: 18px;
735
+ }
736
+ .excram-list{
737
+ font-size: 14px;
738
+ margin-bottom: 20px;
739
+ }
740
+ .excram-last-name{
741
+ color: var(--link);
742
+ }
743
+ .valid{
744
+ color: #d7d7d7;
745
+ margin: 0px 10px;
746
+ }
747
+ .detailIcon-span{
748
+ width: 24px;
749
+ height: 24px;
750
+ display: inline-block;
751
+ position: relative;
752
+ background: var(--primary);
753
+ margin-right: 10px;
754
+ }
755
+ .detailIcon{
756
+ position: absolute;
757
+ color: #fff;
758
+ font-size: 38px;
759
+ left: 4px;
760
+ top: -2px;
761
+ }
762
+ .primary-title{
763
+ display: flex;
764
+ align-items: center;
765
+ }
766
+ .detailIcon-span-title{
767
+ font-weight: bold;
768
+ }
769
+ </style>