dashboard-shell-shell 1.0.1000000116 → 1.0.1000000117

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/images/action.svg +6 -0
  2. package/assets/images/pl/logo.png +0 -0
  3. package/assets/styles/base/_functions.scss +0 -0
  4. package/assets/styles/base/_mixins.scss +1 -1
  5. package/assets/styles/global/_button.scss +17 -10
  6. package/assets/styles/global/_form.scss +2 -2
  7. package/assets/styles/global/_labeled-input.scss +6 -2
  8. package/assets/styles/global/_select.scss +6 -7
  9. package/assets/styles/global/_table.scss +3 -2
  10. package/assets/styles/global/_tooltip.scss +8 -1
  11. package/assets/styles/themes/_dark.scss +2 -0
  12. package/assets/styles/themes/_light.scss +5 -2
  13. package/assets/styles/vendor/vue-select.scss +2 -1
  14. package/assets/translations/en-us.yaml +1 -3
  15. package/assets/translations/zh-hans.yaml +51 -28
  16. package/components/ActionDropdown.vue +1 -0
  17. package/components/ActionMenuShell.vue +6 -3
  18. package/components/BrandImage.vue +22 -0
  19. package/components/ClusterIconMenu.vue +1 -1
  20. package/components/CodeMirror.vue +1 -0
  21. package/components/CruResource.vue +1 -1
  22. package/components/CruResourceFooter.vue +1 -1
  23. package/components/ExplorerProjectsNamespaces.vue +4 -24
  24. package/components/GlobalRoleBindings.vue +112 -48
  25. package/components/IndentedPanel.vue +4 -10
  26. package/components/PromptRemove.vue +3 -3
  27. package/components/ResourceDetail/Masthead.vue +190 -242
  28. package/components/ResourceDetail/index.vue +20 -5
  29. package/components/ResourceList/Masthead.vue +146 -84
  30. package/components/ResourceList/ResourceLoadingIndicator.vue +5 -2
  31. package/components/ResourceTable.vue +76 -1
  32. package/components/SideNav.vue +66 -29
  33. package/components/SortableTable/THead.vue +6 -0
  34. package/components/SortableTable/index.vue +481 -388
  35. package/components/Tabbed/index.vue +4 -5
  36. package/components/auth/Principal.vue +3 -2
  37. package/components/auth/RoleDetailEdit.vue +58 -5
  38. package/components/auth/SelectPrincipal.vue +1 -0
  39. package/components/form/BannerSettings.vue +18 -16
  40. package/components/form/ChangePassword.vue +4 -4
  41. package/components/form/ColorInput.vue +32 -8
  42. package/components/form/Footer.vue +1 -1
  43. package/components/form/InputWithSelect.vue +2 -0
  44. package/components/form/KeyValue.vue +31 -7
  45. package/components/form/LabeledSelect.vue +178 -178
  46. package/components/form/Members/ClusterPermissionsEditor.vue +1 -2
  47. package/components/form/Members/MembershipEditor.vue +1 -1
  48. package/components/form/NameNsDescription.vue +24 -11
  49. package/components/form/Password.vue +6 -2
  50. package/components/form/ResourceQuota/Namespace.vue +1 -1
  51. package/components/form/ResourceQuota/NamespaceRow.vue +13 -10
  52. package/components/form/ResourceQuota/ProjectRow.vue +0 -1
  53. package/components/form/Select.vue +2 -2
  54. package/components/nav/Favorite.vue +5 -1
  55. package/components/nav/Group.vue +69 -23
  56. package/components/nav/Header.vue +82 -17
  57. package/components/nav/HeaderPageActionMenu.vue +1 -0
  58. package/components/nav/NamespaceFilter.vue +0 -3
  59. package/components/nav/TopLevelMenu.vue +182 -119
  60. package/components/nav/Type.vue +48 -11
  61. package/composables/useClickOutside.ts +1 -1
  62. package/config/product/auth.js +16 -7
  63. package/config/product/explorer.js +1 -1
  64. package/config/product/settings.js +17 -8
  65. package/config/settings.ts +28 -0
  66. package/edit/management.cattle.io.user.vue +17 -4
  67. package/edit/networking.k8s.io.ingress/RulePath.vue +1 -1
  68. package/edit/token.vue +1 -1
  69. package/list/harvesterhci.io.management.cluster.vue +17 -0
  70. package/list/management.cattle.io.setting.vue +22 -13
  71. package/list/management.cattle.io.user.vue +25 -14
  72. package/list/provisioning.cattle.io.cluster.vue +6 -7
  73. package/mixins/brand.js +17 -0
  74. package/package.json +1 -1
  75. package/pages/auth/login.vue +84 -29
  76. package/pages/c/_cluster/auth/roles/index.vue +61 -14
  77. package/pages/c/_cluster/settings/banners.vue +174 -101
  78. package/pages/c/_cluster/settings/brand.vue +348 -301
  79. package/pages/c/_cluster/settings/performance.vue +61 -38
  80. package/pages/home.vue +70 -21
  81. package/pages/prefs.vue +25 -23
  82. package/pkg/tsconfig.json +9 -9
  83. package/pkg/vue.config.js +1 -1
  84. package/promptRemove/mixin/roleDeletionCheck.js +2 -2
  85. package/scripts/clean +0 -0
  86. package/scripts/extension/bundle +0 -0
  87. package/scripts/extension/helm/scripts/package +0 -0
  88. package/scripts/extension/helm/scripts/patch +0 -0
  89. package/scripts/extension/helm/scripts/version +0 -0
  90. package/scripts/extension/helmpatch +0 -0
  91. package/scripts/extension/parse-tag-name +0 -0
  92. package/scripts/extension/publish +0 -0
  93. package/scripts/publish-shell.sh +86 -60
  94. package/scripts/serve-pkgs +0 -0
  95. package/scripts/sync-shell-deps +0 -0
  96. package/scripts/typegen.sh +44 -28
  97. package/store/i18n.js +5 -5
  98. package/store/prefs.js +17 -5
  99. package/store/type-map.js +2 -1
  100. package/types/shell/index.d.ts +1 -1
  101. package/utils/error.js +4 -0
  102. package/utils/router.js +3 -3
  103. package/vue.config.js +1 -6
  104. package/components/rancherResourceDetail/Masthead.vue +0 -769
  105. package/components/rancherResourceDetail/__tests__/Masthead.test.ts +0 -65
  106. package/components/rancherResourceDetail/index.vue +0 -591
  107. package/components/rancherResourceList/Masthead.vue +0 -375
  108. package/components/rancherResourceList/ResourceLoadingIndicator.vue +0 -140
  109. package/components/rancherResourceList/index.vue +0 -307
  110. package/components/rancherResourceList/resource-list.config.js +0 -7
  111. package/components/rancherResourceTable.vue +0 -783
  112. package/components/rancherSortableTable/THead.vue +0 -561
  113. package/components/rancherSortableTable/actions.js +0 -153
  114. package/components/rancherSortableTable/advanced-filtering.js +0 -272
  115. package/components/rancherSortableTable/debug.js +0 -117
  116. package/components/rancherSortableTable/filtering.js +0 -290
  117. package/components/rancherSortableTable/grouping.js +0 -48
  118. package/components/rancherSortableTable/index.vue +0 -2712
  119. package/components/rancherSortableTable/paging.js +0 -155
  120. package/components/rancherSortableTable/selection.js +0 -629
  121. package/components/rancherSortableTable/sortable-config.ts +0 -4
  122. package/components/rancherSortableTable/sorting.js +0 -129
  123. package/types/cloud-shell/index.d.ts +0 -11014
  124. /package/components/{rancherResourceList → ResourceList}/Masthead-btn.vue +0 -0
@@ -368,13 +368,12 @@ export default {
368
368
  padding: 0;
369
369
 
370
370
  &.horizontal {
371
- border: solid thin var(--border);
372
- border-bottom: 0;
371
+ border-bottom: solid 1px var(--border);
373
372
  display: flex;
374
373
  flex-direction: row;
375
374
 
376
375
  + .tab-container {
377
- border: solid thin var(--border);
376
+ // border: solid thin var(--border);
378
377
  }
379
378
 
380
379
  .tab.active {
@@ -443,7 +442,7 @@ export default {
443
442
  }
444
443
 
445
444
  .tab-container {
446
- padding: 20px;
445
+ padding: 20px 0;
447
446
 
448
447
  &.no-content {
449
448
  padding: 0 0 3px 0;
@@ -499,7 +498,7 @@ margin: 0px -20px;
499
498
  // }
500
499
  & .tab {
501
500
  /* width: 100%; */
502
- min-width: 120px;
501
+ min-width: 130px !important;
503
502
  height: 36px;
504
503
  /* border-top: solid 5px transparent; */
505
504
  display: flex;
@@ -154,7 +154,7 @@ export default {
154
154
  </template>
155
155
 
156
156
  <style lang="scss" scoped>
157
- $size: 40px;
157
+ $size: 20px;
158
158
 
159
159
  .principal {
160
160
  display: grid;
@@ -162,7 +162,7 @@ export default {
162
162
  "avatar name"
163
163
  "avatar description";
164
164
  grid-template-columns: $size auto;
165
- grid-template-rows: auto math.div($size, 2);
165
+ // grid-template-rows: auto math.div($size, 2);
166
166
  column-gap: 10px;
167
167
 
168
168
  th {
@@ -189,6 +189,7 @@ export default {
189
189
  .avatar {
190
190
  grid-area: avatar;
191
191
  text-align: center;
192
+
192
193
 
193
194
  DIV.empty {
194
195
  border: 1px solid var(--border);
@@ -331,7 +331,7 @@ export default {
331
331
  return this.value.listLocation;
332
332
  },
333
333
  ruleClass() {
334
- return `col ${ this.isNamespaced ? 'span-4' : 'span-3' }`;
334
+ return `ruleCls col ${ this.isNamespaced ? 'span-3' : 'span-3' }`;
335
335
  },
336
336
  // Detail View
337
337
  rules() {
@@ -536,7 +536,11 @@ export default {
536
536
  </script>
537
537
 
538
538
  <template>
539
+
540
+ <!-- 加载状态下显示 Loading 组件 -->
539
541
  <Loading v-if="$fetchState.pending" />
542
+
543
+ <!-- 主表单容器,基于 CruResource 封装 -->
540
544
  <CruResource
541
545
  v-else
542
546
  class="receiver"
@@ -550,7 +554,10 @@ export default {
550
554
  @finish="save"
551
555
  @cancel="cancel"
552
556
  >
557
+ <!-- ========== 详情模式显示规则表 ========== -->
553
558
  <template v-if="isDetail">
559
+
560
+ <!-- 当前规则列表 -->
554
561
  <SortableTable
555
562
  key-field="index"
556
563
  :rows="rules"
@@ -559,6 +566,8 @@ export default {
559
566
  :row-actions="false"
560
567
  :search="false"
561
568
  />
569
+
570
+ <!-- 继承的规则列表 -->
562
571
  <div
563
572
  v-for="(inherited, index) of inheritedRules"
564
573
  :key="index"
@@ -580,7 +589,11 @@ export default {
580
589
  />
581
590
  </div>
582
591
  </template>
592
+
593
+ <!-- ========== 表单编辑/创建模式 ========== -->
583
594
  <template v-else>
595
+
596
+ <!-- 名称 & 命名空间 & 描述输入 -->
584
597
  <NameNsDescription
585
598
  :value="value"
586
599
  :namespaced="isNamespaced"
@@ -591,11 +604,15 @@ export default {
591
604
  :rules="{ name: fvGetAndReportPathRules('displayName') }"
592
605
  @update:value="$emit('input', $event)"
593
606
  />
607
+
608
+ <!-- Rancher 相关的默认选项设置 -->
594
609
  <div
595
610
  v-if="isRancherType"
596
611
  class="row"
597
612
  >
598
613
  <div class="col span-6">
614
+
615
+ <!-- 默认角色选项 -->
599
616
  <RadioGroup
600
617
  v-model:value="defaultValue"
601
618
  name="storageSource"
@@ -606,6 +623,8 @@ export default {
606
623
  :mode="mode"
607
624
  />
608
625
  </div>
626
+
627
+ <!-- 锁定角色模板选项 -->
609
628
  <div
610
629
  v-if="isRancherRoleTemplate"
611
630
  class="col span-6"
@@ -622,17 +641,25 @@ export default {
622
641
  </div>
623
642
  </div>
624
643
  <div class="spacer" />
644
+
645
+ <!-- ========== 选项卡容器 ========== -->
625
646
  <Tabbed :side-tabs="true">
647
+
648
+ <!-- ---------- 授权资源 Tab ---------- -->
626
649
  <Tab
627
650
  name="grant-resources"
628
651
  :label="t('rbac.roletemplate.tabs.grantResources.label')"
629
652
  :weight="1"
630
653
  >
654
+
655
+ <!-- 校验错误展示 -->
631
656
  <Error
632
657
  :value="value.rules"
633
658
  :rules="fvGetAndReportPathRules('rules')"
634
659
  as-banner
635
660
  />
661
+
662
+ <!-- 规则编辑列表 -->
636
663
  <ArrayList
637
664
  v-model:value="value.rules"
638
665
  label="Resources"
@@ -642,9 +669,11 @@ export default {
642
669
  :default-add-value="defaultRule"
643
670
  :initial-empty-row="true"
644
671
  :show-header="true"
645
- add-label="Add Resource"
672
+ add-label="添加资源"
646
673
  :mode="mode"
647
674
  >
675
+
676
+ <!-- 列表头部 -->
648
677
  <template #column-headers>
649
678
  <div class="column-headers row">
650
679
  <div :class="ruleClass">
@@ -676,8 +705,12 @@ export default {
676
705
  </div>
677
706
  </div>
678
707
  </template>
708
+
709
+ <!-- 列内容渲染 -->
679
710
  <template #columns="props">
680
711
  <div class="columns row mr-20">
712
+
713
+ <!-- 动作(verbs)选择 -->
681
714
  <div :class="ruleClass">
682
715
  <!-- Select verbs -->
683
716
  <Select
@@ -694,8 +727,11 @@ export default {
694
727
  @update:value="updateSelectValue(props.row.value, 'verbs', $event)"
695
728
  />
696
729
  </div>
730
+
731
+ <!-- 资源(resources)选择 -->
697
732
  <div :class="ruleClass">
698
733
  <Select
734
+ style="width: auto;"
699
735
  :value="getRule('resources', props.row.value)"
700
736
  :disabled="isBuiltin"
701
737
  :options="resourceOptions"
@@ -709,6 +745,8 @@ export default {
709
745
  @createdListItem="setRule('resources', props.row.value, $event)"
710
746
  />
711
747
  </div>
748
+
749
+ <!-- API Groups 输入 -->
712
750
  <div :class="ruleClass">
713
751
  <LabeledInput
714
752
  :value="getRule('apiGroups', props.row.value)"
@@ -718,6 +756,8 @@ export default {
718
756
  @input="setRule('apiGroups', props.row.value, $event.target.value)"
719
757
  />
720
758
  </div>
759
+
760
+ <!-- 非命名空间 URL 输入(仅非命名空间模式显示) -->
721
761
  <div
722
762
  v-if="!isNamespaced"
723
763
  :class="ruleClass"
@@ -734,6 +774,8 @@ export default {
734
774
  </template>
735
775
  </ArrayList>
736
776
  </Tab>
777
+
778
+ <!-- ---------- 继承角色模板 Tab(仅 Rancher 角色模板可用) ---------- -->
737
779
  <Tab
738
780
  v-if="isRancherRoleTemplate"
739
781
  name="inherit-from"
@@ -746,7 +788,7 @@ export default {
746
788
  :remove-allowed="!isBuiltin"
747
789
  :add-allowed="!isBuiltin"
748
790
  label="Resources"
749
- add-label="Add Resource"
791
+ add-label="添加资源"
750
792
  :mode="mode"
751
793
  >
752
794
  <template #columns="props">
@@ -777,13 +819,16 @@ export default {
777
819
  </template>
778
820
 
779
821
  <style lang="scss" scoped>
822
+ .ruleCls {
823
+ margin-right: 10px;
824
+ }
780
825
  .required {
781
826
  color: var(--error);
782
827
  }
783
828
 
784
829
  :deep() {
785
830
  .column-headers {
786
- margin-right: 75px;
831
+ margin-right: 95px;
787
832
  margin-bottom: 5px;
788
833
  }
789
834
 
@@ -793,7 +838,6 @@ export default {
793
838
  .remove {
794
839
  display: flex;
795
840
  flex-direction: column;
796
- justify-content: center;
797
841
  align-items: flex-end;
798
842
  }
799
843
  }
@@ -805,4 +849,13 @@ export default {
805
849
  }
806
850
  }
807
851
  }
852
+
853
+ .columns {
854
+ :deep() {
855
+ .unlabeled-select {
856
+ width: auto;
857
+ }
858
+ }
859
+
860
+ }
808
861
  </style>
@@ -212,6 +212,7 @@ export default {
212
212
  <Principal
213
213
  :value="option.label"
214
214
  :use-muted="false"
215
+ style="padding-top: 6px;"
215
216
  />
216
217
  </template>
217
218
 
@@ -104,10 +104,10 @@ export default ({
104
104
  </script>
105
105
 
106
106
  <template>
107
- <div class="row mb-20">
107
+ <div class="row">
108
108
  <div class="col span-12">
109
109
  <div class="row">
110
- <div class="col span-6">
110
+ <div class="col span-5">
111
111
  <LabeledInput
112
112
  v-model:value="value[bannerType].text"
113
113
  :disabled="isUiDisabled"
@@ -154,18 +154,20 @@ export default ({
154
154
  <h3>
155
155
  {{ t('banner.bannerDecoration.label') }}
156
156
  </h3>
157
- <div
158
- v-for="(o, i) in textDecorationOptions"
159
- :key="i"
160
- >
161
- <Checkbox
162
- v-model:value="value[bannerType][o.style]"
163
- name="bannerDecoration"
164
- :data-testid="`banner_decoration_checkbox${bannerType}${o.label}`"
165
- class="banner-decoration-checkbox"
166
- :mode="mode"
167
- :label="o.label"
168
- />
157
+ <div style="display: flex;">
158
+ <div
159
+ v-for="(o, i) in textDecorationOptions"
160
+ :key="i"
161
+ >
162
+ <Checkbox
163
+ v-model:value="value[bannerType][o.style]"
164
+ name="bannerDecoration"
165
+ :data-testid="`banner_decoration_checkbox${bannerType}${o.label}`"
166
+ class="banner-decoration-checkbox"
167
+ :mode="mode"
168
+ :label="o.label"
169
+ />
170
+ </div>
169
171
  </div>
170
172
  </div>
171
173
  <div class="col span-2">
@@ -179,7 +181,7 @@ export default ({
179
181
  </div>
180
182
  </div>
181
183
  <div class="row mt-10">
182
- <div class="col span-6">
184
+ <div class="col span-5">
183
185
  <ColorInput
184
186
  v-model:value="value[bannerType].color"
185
187
  :default-value="themeVars.bannerTextColor"
@@ -187,7 +189,7 @@ export default ({
187
189
  :mode="mode"
188
190
  />
189
191
  </div>
190
- <div class="col span-6">
192
+ <div class="col span-7">
191
193
  <ColorInput
192
194
  v-model:value="value[bannerType].background"
193
195
  :default-value="themeVars.bannerBgColor"
@@ -353,7 +353,7 @@ export default {
353
353
  v-if="isRandomGenerated"
354
354
  :class="{'row': isCreateEdit}"
355
355
  >
356
- <div :class="{'col': isCreateEdit, 'span-8': isCreateEdit}">
356
+ <div :class="{'col': isCreateEdit, 'span-6': isCreateEdit}">
357
357
  <Password
358
358
  v-model:value="passwordGen"
359
359
  class="mt-10"
@@ -368,7 +368,7 @@ export default {
368
368
  class="userGen"
369
369
  :class="{'row': isCreateEdit}"
370
370
  >
371
- <div :class="{'col': isCreateEdit, 'span-4': isCreateEdit}">
371
+ <div :class="{'col': isCreateEdit, 'span-6': isCreateEdit}">
372
372
  <Password
373
373
  v-model:value="passwordNew"
374
374
  data-testid="account__new_password"
@@ -378,7 +378,7 @@ export default {
378
378
  :ignore-password-managers="!isChange"
379
379
  />
380
380
  </div>
381
- <div :class="{'col': isCreateEdit, 'span-4': isCreateEdit}">
381
+ <div :class="{'col': isCreateEdit, 'span-6': isCreateEdit}">
382
382
  <Password
383
383
  v-model:value="passwordConfirm"
384
384
  data-testid="account__confirm_password"
@@ -403,7 +403,7 @@ export default {
403
403
  class="text-error"
404
404
  :class="{'row': isCreateEdit}"
405
405
  >
406
- <div :class="{'col': isCreateEdit, 'span-8': isCreateEdit}">
406
+ <div :class="{'col': isCreateEdit, 'span-6': isCreateEdit}">
407
407
  <Banner
408
408
  v-for="(err, i) in errorMessages"
409
409
  :key="i"
@@ -93,8 +93,7 @@ export default {
93
93
 
94
94
  <template>
95
95
  <div
96
- class="color-input"
97
- :class="{[mode]:mode, disabled: isDisabled}"
96
+ class="color-input-box"
98
97
  :data-testid="componentTestid + '-color-input'"
99
98
  :tabindex="isDisabled ? -1 : 0"
100
99
  @keydown.space.prevent
@@ -107,6 +106,7 @@ export default {
107
106
  />{{ label }}</label>
108
107
  <div
109
108
  :data-testid="componentTestid + '-color-input_preview-container'"
109
+ :class="{[mode]:mode, disabled: isDisabled}"
110
110
  class="preview-container"
111
111
  @click.stop="$refs.input.click($event)"
112
112
  >
@@ -115,27 +115,30 @@ export default {
115
115
  class="color-display"
116
116
  >
117
117
  <input
118
+ style="margin-bottom: 6px;"
118
119
  ref="input"
119
120
  :aria-disabled="isDisabled ? 'true' : 'false'"
120
121
  :aria-label="t('generic.colorPicker')"
121
122
  type="color"
123
+ class="color-input"
122
124
  :disabled="isDisabled"
123
125
  tabindex="-1"
124
126
  :value="inputValue"
125
127
  @input="$emit('update:value', $event.target.value)"
126
128
  >
127
129
  </span>
128
- <span class="text-muted color-value">{{ inputValue }}</span>
130
+ <span class="color-value">{{ inputValue }}</span>
129
131
  </div>
130
132
  </div>
131
133
  </template>
132
134
 
133
135
  <style lang='scss' scoped>
134
- .color-input {
135
- border: 1px solid var(--border);
136
- border-radius: var(--border-radius);
137
- padding: 10px;
138
-
136
+ .color-input-box {
137
+ display: flex;
138
+ .text-label {
139
+ display: flex;
140
+ align-items: center;
141
+ }
139
142
  &:focus-visible {
140
143
  @include focus-outline;
141
144
  }
@@ -172,6 +175,26 @@ export default {
172
175
 
173
176
  .color-value {
174
177
  margin-left: 4px;
178
+ color: var(--input-label);
179
+ }
180
+
181
+ &.disabled, &.disabled .selected, &[disabled], &[disabled]:hover {
182
+ color: var(--input-disabled-text);
183
+ outline-width: 0;
184
+ border-color: var(--input-disabled-border);
185
+ cursor: not-allowed;
186
+
187
+ label, span, div, input {
188
+ cursor: not-allowed !important;
189
+ }
190
+
191
+ .color-value {
192
+ color: var(--muted);
193
+ }
194
+
195
+ &::placeholder {
196
+ color: var(--input-disabled-placeholder);
197
+ }
175
198
  }
176
199
  }
177
200
 
@@ -197,4 +220,5 @@ export default {
197
220
  }
198
221
  }
199
222
  }
223
+
200
224
  </style>
@@ -109,7 +109,7 @@ export default defineComponent({
109
109
  /* grid-area: right;
110
110
  text-align: right; */
111
111
  display: flex;
112
-
112
+ justify-content: flex-end;
113
113
  .btn, button {
114
114
  /* margin: 0 0 0 $column-gutter; */
115
115
  margin: 0 10px 0 0px;
@@ -217,6 +217,7 @@ export default {
217
217
 
218
218
  &.labeled-select {
219
219
  .selected {
220
+ background-color: var(--input-bg);
220
221
  color: var(--input-text);
221
222
  text-align: center;
222
223
  margin-right: 1em;
@@ -303,6 +304,7 @@ export default {
303
304
  // position: relative;
304
305
 
305
306
  .vs__selected {
307
+ background-color: var(--input-bg);
306
308
  color: var(--input-text);
307
309
  }
308
310
 
@@ -389,10 +389,10 @@ export default {
389
389
  }
390
390
 
391
391
  rows = rows.map((item) => {
392
- if (item.key.includes('harvester')) {
392
+ if (item.key && item.key.includes('harvester')) {
393
393
  item.key = item.key.replace('harvester', 'cloud');
394
394
  }
395
- if (item.value.includes('harvester')) {
395
+ if (item.key && item.value.includes('harvester')) {
396
396
  item.value = item.value.replace('harvester', 'cloud');
397
397
  }
398
398
 
@@ -409,14 +409,22 @@ export default {
409
409
  [this.valueName]: value,
410
410
  };
411
411
 
412
- obj.key = obj.key.replace('harvester', 'cloud');
413
- obj.value = obj.value.replace('harvester', 'cloud');
412
+ obj.key = obj.key?.replace('harvester', 'cloud');
413
+ obj.value = obj.value?.replace('harvester', 'cloud');
414
414
  obj.binary = false;
415
415
  obj.canEncode = this.handleBase64;
416
416
  obj.supported = true;
417
- // 上传镜像问题
418
- const s = this.rows.filter(item => item.key !== obj.key);
419
- this.rows = s
417
+
418
+ // 上传镜像问题
419
+ const s = this.rows.filter(item => {
420
+ // 如果 item.key 或 obj.key 为空,直接保留
421
+ if (!item.key || !obj.key) {
422
+ return true;
423
+ }
424
+ // 两者都有值时才比较
425
+ return item.key !== obj.key;
426
+ });
427
+ this.rows = s;
420
428
 
421
429
  this.rows.push(obj);
422
430
  this.queueUpdate();
@@ -600,6 +608,7 @@ export default {
600
608
  </script>
601
609
  <template>
602
610
  <div class="key-value">
611
+ <!-- 标题部分:如果传了 title 或者插槽 title 才显示 -->
603
612
  <div
604
613
  v-if="title || $slots.title"
605
614
  class="clearfix"
@@ -607,6 +616,7 @@ export default {
607
616
  <slot name="title">
608
617
  <h3>
609
618
  {{ title }}
619
+ <!-- 标题右侧提示信息 -->
610
620
  <i
611
621
  v-if="titleProtip"
612
622
  v-clean-tooltip="titleProtip"
@@ -615,6 +625,8 @@ export default {
615
625
  </h3>
616
626
  </slot>
617
627
  </div>
628
+
629
+ <!-- 整个 Key-Value 表格容器 -->
618
630
  <div
619
631
  class="kv-container"
620
632
  role="grid"
@@ -622,6 +634,8 @@ export default {
622
634
  :aria-colcount="extraColumns.length + 2"
623
635
  :style="containerStyle"
624
636
  >
637
+
638
+ <!-- 表头:有数据或者处于 view 模式才显示 -->
625
639
  <template v-if="rows.length || isView">
626
640
  <div class="rowgroup">
627
641
  <div class="row">
@@ -660,6 +674,8 @@ export default {
660
674
  </div>
661
675
  </div>
662
676
  </template>
677
+
678
+ <!-- 如果是 view 模式但没有数据,显示占位符 -->
663
679
  <template v-if="!rows.length && isView">
664
680
  <div class="rowgroup">
665
681
  <div class="row">
@@ -678,6 +694,8 @@ export default {
678
694
  </div>
679
695
  </div>
680
696
  </template>
697
+
698
+ <!-- 遍历 rows 渲染每一行 -->
681
699
  <template
682
700
  v-for="(row,i) in rows"
683
701
  v-else
@@ -855,6 +873,8 @@ export default {
855
873
  </div>
856
874
  </template>
857
875
  </div>
876
+
877
+ <!-- 底部新增/从文件导入 -->
858
878
  <div
859
879
  v-if="(addAllowed || readAllowed) && !isView"
860
880
  class="footer mt-10"
@@ -863,6 +883,8 @@ export default {
863
883
  name="add"
864
884
  :add="add"
865
885
  >
886
+
887
+ <!-- 添加按钮 -->
866
888
  <button
867
889
  v-if="addAllowed"
868
890
  type="button"
@@ -878,6 +900,8 @@ export default {
878
900
  :class="loading ? ['icon-lg', 'icon-spinner','icon-spin']: [addIcon]"
879
901
  /> {{ _addLabel }}
880
902
  </button>
903
+
904
+ <!-- 从文件读取 -->
881
905
  <FileSelector
882
906
  v-if="readAllowed"
883
907
  :disabled="isView"