@rancher/shell 3.0.9-rc.1 → 3.0.9-rc.3

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 (208) hide show
  1. package/assets/styles/base/_color.scss +1 -0
  2. package/assets/styles/base/_typography.scss +14 -5
  3. package/assets/styles/themes/_light.scss +1 -1
  4. package/assets/styles/themes/_modern.scss +1 -1
  5. package/assets/translations/en-us.yaml +104 -33
  6. package/assets/translations/zh-hans.yaml +13 -2
  7. package/components/ActionMenu.vue +7 -8
  8. package/components/ActionMenuShell.vue +23 -24
  9. package/components/CodeMirror.vue +4 -3
  10. package/components/DetailText.vue +54 -7
  11. package/components/Drawer/Chrome.vue +11 -4
  12. package/components/Drawer/DrawerCard.vue +19 -0
  13. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
  14. package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
  15. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
  16. package/components/Drawer/types.ts +1 -0
  17. package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
  18. package/components/LocaleSelector.vue +1 -1
  19. package/components/Markdown.vue +1 -1
  20. package/components/PopoverCard.vue +3 -3
  21. package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
  22. package/components/Resource/Detail/Card/Scaler.vue +10 -2
  23. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
  24. package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
  25. package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
  26. package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
  27. package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +14 -10
  28. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
  29. package/components/Resource/Detail/Cards.vue +27 -0
  30. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
  31. package/components/Resource/Detail/Masthead/index.vue +5 -0
  32. package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
  33. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
  34. package/components/Resource/Detail/ResourceRow.types.ts +14 -0
  35. package/components/Resource/Detail/ResourceRow.vue +23 -35
  36. package/components/Resource/Detail/StatusRow.vue +5 -2
  37. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
  38. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
  39. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  40. package/components/Resource/Detail/TitleBar/index.vue +41 -6
  41. package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
  42. package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
  43. package/components/ResourceDetail/Masthead/index.vue +1 -0
  44. package/components/ResourceDetail/Masthead/latest.vue +8 -1
  45. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  46. package/components/ResourceTable.vue +1 -1
  47. package/components/Setting.vue +1 -1
  48. package/components/SortableTable/index.vue +25 -0
  49. package/components/SortableTable/selection.js +25 -12
  50. package/components/SortableTable/sorting.js +1 -1
  51. package/components/Tabbed/Tab.vue +5 -0
  52. package/components/Tabbed/index.vue +40 -9
  53. package/components/Window/ContainerShell.vue +10 -13
  54. package/components/__tests__/ProjectRow.test.ts +102 -15
  55. package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
  56. package/components/fleet/FleetClusterTargets/index.vue +82 -29
  57. package/components/fleet/FleetClusters.vue +26 -12
  58. package/components/fleet/FleetGitRepoPaths.vue +2 -2
  59. package/components/fleet/FleetResources.vue +14 -0
  60. package/components/fleet/FleetValuesFrom.vue +2 -2
  61. package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
  62. package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
  63. package/components/fleet/dashboard/ResourceDetails.vue +96 -123
  64. package/components/form/Conditions.vue +1 -15
  65. package/components/form/HookOption.vue +5 -0
  66. package/components/form/LabeledSelect.vue +1 -1
  67. package/components/form/LifecycleHooks.vue +2 -6
  68. package/components/form/ResourceLabeledSelect.vue +12 -1
  69. package/components/form/ResourceQuota/Project.vue +59 -8
  70. package/components/form/ResourceQuota/ProjectRow.vue +116 -21
  71. package/components/form/ResourceQuota/shared.js +42 -18
  72. package/components/form/SeccompProfile.vue +113 -0
  73. package/components/form/Security.vue +244 -133
  74. package/components/form/__tests__/LabeledSelect.test.ts +1 -1
  75. package/components/form/__tests__/SeccompProfile.test.js +124 -0
  76. package/components/form/__tests__/Security.test.ts +125 -37
  77. package/components/formatter/Autoscaler.vue +2 -2
  78. package/components/formatter/FleetSummaryGraph.vue +4 -1
  79. package/components/formatter/LinkName.vue +3 -2
  80. package/components/nav/Group.vue +5 -0
  81. package/components/nav/Header.vue +3 -3
  82. package/components/nav/HeaderPageActionMenu.vue +1 -1
  83. package/components/nav/NamespaceFilter.vue +6 -6
  84. package/components/nav/NotificationCenter/index.vue +1 -1
  85. package/components/nav/TopLevelMenu.helper.ts +41 -16
  86. package/components/nav/TopLevelMenu.vue +45 -25
  87. package/components/nav/WorkspaceSwitcher.vue +1 -1
  88. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
  89. package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
  90. package/components/templates/default.vue +0 -3
  91. package/components/templates/home.vue +0 -3
  92. package/components/templates/plain.vue +0 -3
  93. package/composables/useClickOutside.ts +1 -1
  94. package/config/product/explorer.js +2 -3
  95. package/config/table-headers.js +9 -7
  96. package/config/types.js +45 -9
  97. package/detail/__tests__/workload.test.ts +8 -16
  98. package/detail/catalog.cattle.io.app.vue +5 -0
  99. package/detail/fleet.cattle.io.cluster.vue +6 -0
  100. package/detail/management.cattle.io.oidcclient.vue +15 -4
  101. package/detail/workload/index.vue +7 -109
  102. package/edit/__tests__/management.cattle.io.project.test.js +137 -0
  103. package/edit/__tests__/projectsecret.test.ts +42 -0
  104. package/edit/auth/__tests__/oidc.test.ts +50 -0
  105. package/edit/auth/oidc.vue +68 -44
  106. package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
  107. package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
  108. package/edit/management.cattle.io.project.vue +36 -6
  109. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +16 -3
  110. package/edit/projectsecret.vue +29 -0
  111. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
  112. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
  113. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
  114. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
  115. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
  116. package/edit/workload/__tests__/index.test.ts +3 -4
  117. package/edit/workload/index.vue +47 -28
  118. package/edit/workload/mixins/workload.js +66 -31
  119. package/initialize/install-plugins.js +0 -2
  120. package/list/catalog.cattle.io.clusterrepo.vue +1 -1
  121. package/list/projectsecret.vue +2 -2
  122. package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
  123. package/machine-config/amazonec2.vue +2 -2
  124. package/machine-config/vmwarevsphere.vue +58 -4
  125. package/mixins/__tests__/chart.test.ts +63 -0
  126. package/mixins/chart.js +56 -51
  127. package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
  128. package/models/__tests__/workload.test.ts +333 -0
  129. package/models/catalog.cattle.io.app.js +8 -0
  130. package/models/management.cattle.io.cluster.js +22 -30
  131. package/models/pod.js +14 -0
  132. package/models/provisioning.cattle.io.cluster.js +2 -2
  133. package/models/secret.js +1 -1
  134. package/models/workload.js +93 -27
  135. package/package.json +4 -4
  136. package/pages/__tests__/diagnostic.test.ts +71 -0
  137. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
  138. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  139. package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
  140. package/pages/c/_cluster/explorer/tools/index.vue +23 -5
  141. package/pages/c/_cluster/fleet/index.vue +14 -8
  142. package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
  143. package/pages/c/_cluster/monitoring/alertmanagerconfig/_alertmanagerconfigid/receiver.vue +18 -5
  144. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  145. package/pages/c/_cluster/uiplugins/index.vue +41 -9
  146. package/pages/diagnostic.vue +17 -3
  147. package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
  148. package/plugins/dashboard-store/actions.js +9 -8
  149. package/plugins/dashboard-store/resource-class.js +97 -1
  150. package/plugins/steve/__tests__/revision.test.ts +84 -0
  151. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
  152. package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
  153. package/plugins/steve/revision.ts +26 -0
  154. package/plugins/steve/steve-pagination-utils.ts +6 -5
  155. package/plugins/steve/subscribe.js +188 -49
  156. package/plugins/subscribe-events.ts +2 -2
  157. package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
  158. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
  159. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +2 -1
  160. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
  161. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
  162. package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
  163. package/rancher-components/Pill/index.ts +4 -0
  164. package/rancher-components/RcButton/RcButton.test.ts +53 -9
  165. package/rancher-components/RcButton/RcButton.vue +217 -25
  166. package/rancher-components/RcButton/types.ts +27 -1
  167. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
  168. package/rancher-components/RcDropdown/types.ts +3 -3
  169. package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
  170. package/rancher-components/RcIcon/RcIcon.vue +9 -6
  171. package/rancher-components/RcIcon/types.ts +13 -9
  172. package/rancher-components/RcItemCard/RcItemCard.test.ts +16 -6
  173. package/rancher-components/RcItemCard/RcItemCard.vue +13 -23
  174. package/rancher-components/utils/status.test.ts +10 -15
  175. package/rancher-components/utils/status.ts +5 -6
  176. package/store/__tests__/auth.test.ts +21 -5
  177. package/store/auth.js +6 -3
  178. package/store/aws.js +18 -12
  179. package/store/index.js +4 -8
  180. package/store/type-map.utils.ts +1 -1
  181. package/types/kube/kube-api.ts +29 -3
  182. package/types/rancher/steve.api.ts +40 -0
  183. package/types/shell/index.d.ts +262 -156
  184. package/types/store/pagination.types.ts +1 -0
  185. package/types/store/subscribe-events.types.ts +1 -0
  186. package/utils/__tests__/azure.test.ts +56 -0
  187. package/utils/__tests__/back-off.test.ts +364 -245
  188. package/utils/__tests__/error.test.ts +44 -0
  189. package/utils/__tests__/fleet.test.ts +8 -1
  190. package/utils/__tests__/pagination-wrapper.test.ts +167 -0
  191. package/utils/__tests__/version.test.ts +55 -1
  192. package/utils/azure.js +12 -0
  193. package/utils/back-off.ts +302 -69
  194. package/utils/dynamic-content/__tests__/index.test.ts +1 -1
  195. package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
  196. package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
  197. package/utils/dynamic-content/index.ts +1 -6
  198. package/utils/dynamic-content/new-release.ts +5 -3
  199. package/utils/dynamic-content/types.d.ts +0 -1
  200. package/utils/error.js +9 -0
  201. package/utils/fleet.ts +2 -2
  202. package/utils/inactivity.ts +2 -3
  203. package/utils/pagination-wrapper.ts +99 -15
  204. package/utils/validators/formRules/index.ts +3 -0
  205. package/utils/version.js +38 -0
  206. package/components/auth/AzureWarning.vue +0 -77
  207. /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
  208. /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
@@ -1,20 +1,26 @@
1
1
  <script lang="ts">
2
2
  import { PropType } from 'vue';
3
3
  import { FLEET } from '@shell/config/types';
4
- import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
5
4
  import FleetResources from '@shell/components/fleet/FleetResources.vue';
6
- import { RcButton } from '@components/RcButton';
7
5
  import { FleetDashboardState } from '@shell/types/fleet';
8
6
  import FleetApplicationSource from '@shell/components/formatter/FleetApplicationSource.vue';
7
+ import FleetClusters from '@shell/components/fleet/FleetClusters.vue';
8
+ import Tabbed from '@shell/components/Tabbed/index.vue';
9
+ import Tab from '@shell/components/Tabbed/Tab.vue';
10
+ import Drawer from '@shell/components/Drawer/Chrome.vue';
11
+ import DrawerCard from '@shell/components/Drawer/DrawerCard.vue';
9
12
 
10
13
  export default {
11
14
  name: 'FleetDashboardResourceDetails',
12
15
 
13
16
  components: {
14
- LabeledSelect,
15
17
  FleetResources,
16
18
  FleetApplicationSource,
17
- RcButton,
19
+ FleetClusters,
20
+ Tabbed,
21
+ Tab,
22
+ Drawer,
23
+ DrawerCard,
18
24
  },
19
25
 
20
26
  props: {
@@ -46,20 +52,18 @@ export default {
46
52
  },
47
53
 
48
54
  mounted() {
49
- this.clusterId = this.clusters[0]?.value || '';
55
+ this.clusterId = '';
50
56
  },
51
57
 
52
58
  computed: {
53
- clusters() {
54
- return this.value.targetClusters.map((cluster: { id: string, nameDisplay: string }) => ({
55
- label: cluster.nameDisplay,
56
- value: cluster.id
57
- }));
58
- },
59
-
60
59
  noResources() {
61
60
  return !this.value.resourcesStatuses?.length;
62
61
  },
62
+
63
+ clusterSchema() {
64
+ return this.$store.getters['management/schemaFor'](FLEET.CLUSTER);
65
+ }
66
+
63
67
  },
64
68
 
65
69
  methods: {
@@ -71,124 +75,93 @@ export default {
71
75
  </script>
72
76
 
73
77
  <template>
74
- <div class="details-panel">
75
- <div
76
- class="header"
78
+ <Drawer
79
+ :ariaTarget="value.id"
80
+ :removeFooter="true"
81
+ @close="closePanel"
82
+ >
83
+ <template
84
+ #title
77
85
  :data-testid="'fleet-dashboard-resource-details-header'"
78
86
  >
79
- <h3 class="title">
80
- <i :class="value.dashboardIcon" />
81
- <router-link
82
- class="label"
83
- :to="detailLocation"
84
- >
85
- {{ value.id }}
86
- </router-link>
87
- <i
88
- v-if="statePanel.id !== 'success'"
89
- class="ml-5 state-icon"
90
- :class="statePanel.icon"
91
- :style="{ color: statePanel.color }"
92
- />
93
- </h3>
94
- <RcButton
95
- small
96
- ghost
97
- data-testid="slide-in-close"
98
- :aria-label="'slide-in-close'"
99
- tabindex="0"
100
- @click="closePanel"
101
- @keydown.space.enter.stop.prevent="closePanel"
102
- >
103
- <i class="icon icon-close" />
104
- </RcButton>
105
- </div>
106
-
107
- <h4>
108
- {{ t('fleet.dashboard.source') }}
109
- </h4>
110
- <div class="mb-15">
111
- <FleetApplicationSource
112
- v-if="value.source.value"
113
- :row="value"
87
+ <i
88
+ class="icon-lg mmr-3"
89
+ :class="value.dashboardIcon"
114
90
  />
115
- <div
116
- v-else
117
- class="text-muted"
91
+ <router-link
92
+ class="label"
93
+ :to="detailLocation"
118
94
  >
119
- &mdash;
120
- </div>
121
- </div>
122
-
123
- <h4>
124
- {{ t('fleet.dashboard.resources') }}
125
- </h4>
126
- <FleetResources
127
- :rows="value.resourcesStatuses"
128
- :cluster-id="clusterId"
129
- :search="!noResources"
130
- >
131
- <template
132
- v-if="!noResources"
133
- #header-left
95
+ {{ value.id }}
96
+ </router-link>
97
+ <i
98
+ v-if="statePanel.id !== 'success'"
99
+ class="ml-5 state-icon icon-lg"
100
+ :class="statePanel.icon"
101
+ :style="{ color: statePanel.color }"
102
+ />
103
+ </template>
104
+ <template #body>
105
+ <Tabbed
106
+ v-bind="$attrs"
107
+ :default-tab="'clusters'"
108
+ :resource="value"
109
+ :use-hash="true"
110
+ :remove-borders="true"
134
111
  >
135
- <div class="row">
136
- <div class="col span-10">
137
- <LabeledSelect
138
- v-model:value="clusterId"
139
- :label="t('fleet.cluster.label')"
140
- :options="clusters"
141
- :mode="'edit'"
142
- :disabled="workspace.id === 'fleet-local'"
112
+ <Tab
113
+ :label="t('fleet.dashboard.resourceDetails.clusters')"
114
+ name="clusters"
115
+ >
116
+ <DrawerCard>
117
+ <FleetClusters
118
+ :schema="clusterSchema"
119
+ :rows="value.targetClusters"
120
+ :table-actions="false"
121
+ :row-actions="false"
122
+ :search="true"
123
+ :remove-sub-rows="true"
124
+ :ignore-filter="true"
125
+ paging-label="sortableTable.paging.resource"
143
126
  />
144
- </div>
145
- </div>
146
- </template>
147
- </FleetResources>
148
- </div>
127
+ </DrawerCard>
128
+ </Tab>
129
+ <Tab
130
+ :label="t('fleet.dashboard.resourceDetails.resources')"
131
+ name="resources"
132
+ >
133
+ <DrawerCard>
134
+ <FleetResources
135
+ :rows="value.resourcesStatuses"
136
+ :cluster-id="clusterId"
137
+ :search="true"
138
+ />
139
+ </DrawerCard>
140
+ </Tab>
141
+ <Tab
142
+ :label="t('fleet.dashboard.resourceDetails.source')"
143
+ name="source"
144
+ >
145
+ <DrawerCard class="mmb-4">
146
+ <FleetApplicationSource
147
+ v-if="value.source.value"
148
+ :row="value"
149
+ />
150
+ <div
151
+ v-else
152
+ class="text-muted"
153
+ >
154
+ &mdash;
155
+ </div>
156
+ </DrawerCard>
157
+ </Tab>
158
+ </Tabbed>
159
+ </template>
160
+ </Drawer>
149
161
  </template>
150
162
 
151
- <style lang="scss">
152
- .details-panel {
153
- padding: 10px;
154
-
155
- .sortable-table-header {
156
- .fixed-header-actions {
157
- align-items: center;
158
- }
159
- }
160
-
161
- .header {
162
- display: flex;
163
- align-items: center;
164
- padding: 0;
165
- margin: 0 0 20px 0;
166
-
167
- .title {
168
- display: flex;
169
- align-items: center;
170
- flex: 1;
171
- margin-bottom: 0;
172
-
173
- .icon {
174
- font-size: 2em;
175
- margin-right: 16px;
176
- }
177
-
178
- .label {
179
- margin-right: 8px;
180
- }
181
-
182
- .state-icon {
183
- font-size: 1.5em;
184
- }
185
- }
186
- }
187
- }
188
-
189
- .col {
190
- .labeled-select {
191
- min-width: 250px;
192
- }
163
+ <style lang="scss" scoped>
164
+ .icon-lg {
165
+ font-size: 24px;
193
166
  }
194
167
  </style>
@@ -52,21 +52,7 @@ export default {
52
52
  },
53
53
 
54
54
  rows() {
55
- return (this.value.status?.conditions || []).map((cond) => {
56
- let message = cond.message || '';
57
-
58
- if ( cond.reason ) {
59
- message = `[${ cond.reason }] ${ message }`.trim();
60
- }
61
-
62
- return {
63
- condition: cond.type || 'Unknown',
64
- status: cond.status || 'Unknown',
65
- error: cond.error,
66
- time: cond.lastProbeTime || cond.lastUpdateTime || cond.lastTransitionTime,
67
- message,
68
- };
69
- });
55
+ return this.value.resourceConditions;
70
56
  },
71
57
  }
72
58
  };
@@ -20,6 +20,10 @@ export default {
20
20
  default: () => {
21
21
  return {};
22
22
  }
23
+ },
24
+ label: {
25
+ type: String,
26
+ default: null
23
27
  }
24
28
  },
25
29
 
@@ -123,6 +127,7 @@ export default {
123
127
  t('workload.container.lifecycleHook.httpGet.add'),
124
128
  ]"
125
129
  :mode="mode"
130
+ :label="label"
126
131
  @update:value="update"
127
132
  />
128
133
  </div>
@@ -386,7 +386,7 @@ export default {
386
386
  :dropdown-should-open="dropdownShouldOpen"
387
387
  :tabindex="-1"
388
388
  :uid="generatedUid"
389
- :aria-label="'-'"
389
+ :aria-label="`- ${value}`"
390
390
  @update:modelValue="$emit('selecting', $event); $emit('update:value', $event)"
391
391
  @search:blur="onBlur"
392
392
  @search:focus="onFocus"
@@ -63,23 +63,19 @@ export default {
63
63
  <template>
64
64
  <div>
65
65
  <div class="mb-20">
66
- <h3 class="clearfix">
67
- {{ t('workload.container.lifecycleHook.postStart.label') }}
68
- </h3>
69
66
  <HookOption
70
67
  v-model:value="postStart"
71
68
  :mode="mode"
69
+ :label="t('workload.container.lifecycleHook.postStart.label')"
72
70
  @update:value="update"
73
71
  />
74
72
  </div>
75
73
 
76
74
  <div>
77
- <h3 class="clearfix">
78
- {{ t('workload.container.lifecycleHook.preStop.label') }}
79
- </h3>
80
75
  <HookOption
81
76
  v-model:value="preStop"
82
77
  :mode="mode"
78
+ :label="t('workload.container.lifecycleHook.preStop.label')"
83
79
  @update:value="update"
84
80
  />
85
81
  </div>
@@ -165,5 +165,16 @@ export default defineComponent({
165
165
  :paginate="paginateType"
166
166
  :multiple="$attrs.multiple || false"
167
167
  @update:value="$emit('update:value', $event)"
168
- />
168
+ >
169
+ <template
170
+ v-for="(_, slot) in $slots"
171
+ :key="slot"
172
+ #[slot]="scope"
173
+ >
174
+ <slot
175
+ :name="slot"
176
+ v-bind="scope"
177
+ />
178
+ </template>
179
+ </LabeledSelect>
169
180
  </template>
@@ -1,12 +1,17 @@
1
1
  <script>
2
2
  import ArrayList from '@shell/components/form/ArrayList';
3
3
  import Row from './ProjectRow';
4
- import { QUOTA_COMPUTED } from './shared';
4
+ import { QUOTA_COMPUTED, TYPES } from './shared';
5
+ import Banner from '@components/Banner/Banner.vue';
5
6
 
6
7
  export default {
7
8
  emits: ['remove', 'input'],
8
9
 
9
- components: { ArrayList, Row },
10
+ components: {
11
+ ArrayList,
12
+ Row,
13
+ Banner,
14
+ },
10
15
 
11
16
  props: {
12
17
  mode: {
@@ -36,18 +41,35 @@ export default {
36
41
  this.value.spec['namespaceDefaultResourceQuota'] = this.value.spec.namespaceDefaultResourceQuota || { limit: {} };
37
42
  this.value.spec['resourceQuota'] = this.value.spec.resourceQuota || { limit: {} };
38
43
 
39
- this.typeValues = Object.keys(this.value.spec.resourceQuota.limit);
44
+ const limit = this.value.spec.resourceQuota.limit;
45
+ const extendedKeys = Object.keys(limit.extended || {});
46
+
47
+ this.typeValues = Object.keys(limit).flatMap((k) => {
48
+ if (k !== TYPES.EXTENDED) {
49
+ return k;
50
+ }
51
+
52
+ return extendedKeys.map((ek) => `extended.${ ek }`);
53
+ });
40
54
  },
41
55
 
42
56
  computed: { ...QUOTA_COMPUTED },
43
57
 
44
58
  methods: {
45
- updateType(i, type) {
46
- this.typeValues[i] = type;
59
+ updateType(event) {
60
+ const { index, type } = event;
61
+
62
+ this.typeValues[index] = type;
47
63
  },
48
64
  remainingTypes(currentType) {
49
65
  return this.mappedTypes
50
- .filter((mappedType) => !this.typeValues.includes(mappedType.value) || mappedType.value === currentType);
66
+ .filter((mappedType) => {
67
+ if (mappedType.value === TYPES.EXTENDED) {
68
+ return true;
69
+ }
70
+
71
+ return !this.typeValues.includes(mappedType.value) || mappedType.value === currentType;
72
+ });
51
73
  },
52
74
  emitRemove(data) {
53
75
  this.$emit('remove', data.row?.value);
@@ -57,9 +79,33 @@ export default {
57
79
  </script>
58
80
  <template>
59
81
  <div>
82
+ <Banner
83
+ color="info"
84
+ label-key="resourceQuota.banner"
85
+ class="mb-20"
86
+ />
60
87
  <div class="headers mb-10">
61
88
  <div class="mr-10">
62
- <label>{{ t('resourceQuota.headers.resourceType') }}</label>
89
+ <label>
90
+ {{ t('resourceQuota.headers.resourceType') }}
91
+ <span
92
+ class="required mr-5"
93
+ aria-hidden="true"
94
+ >*</span>
95
+ </label>
96
+ </div>
97
+ <div class="mr-20">
98
+ <label>
99
+ {{ t('resourceQuota.headers.resourceIdentifier') }}
100
+ <span
101
+ class="required mr-5"
102
+ aria-hidden="true"
103
+ >*</span>
104
+ <i
105
+ v-clean-tooltip="t('resourceQuota.resourceIdentifier.tooltip')"
106
+ class="icon icon-info"
107
+ />
108
+ </label>
63
109
  </div>
64
110
  <div class="mr-20">
65
111
  <label>{{ t('resourceQuota.headers.projectLimit') }}</label>
@@ -83,8 +129,9 @@ export default {
83
129
  :mode="mode"
84
130
  :types="remainingTypes(typeValues[props.i])"
85
131
  :type="typeValues[props.i]"
132
+ :index="props.i"
86
133
  @input="$emit('input', $event)"
87
- @type-change="updateType(props.i, $event)"
134
+ @type-change="updateType($event)"
88
135
  />
89
136
  </template>
90
137
  </ArrayList>
@@ -104,4 +151,8 @@ export default {
104
151
  width: 100%;
105
152
  }
106
153
  }
154
+
155
+ .required {
156
+ color: var(--error);
157
+ }
107
158
  </style>
@@ -1,12 +1,17 @@
1
1
  <script>
2
2
  import Select from '@shell/components/form/Select';
3
3
  import UnitInput from '@shell/components/form/UnitInput';
4
- import { ROW_COMPUTED } from './shared';
4
+ import { LabeledInput } from '@components/Form/LabeledInput';
5
+ import { ROW_COMPUTED, TYPES } from './shared';
5
6
 
6
7
  export default {
7
8
  emits: ['type-change'],
8
9
 
9
- components: { Select, UnitInput },
10
+ components: {
11
+ Select,
12
+ UnitInput,
13
+ LabeledInput,
14
+ },
10
15
 
11
16
  props: {
12
17
  mode: {
@@ -26,35 +31,95 @@ export default {
26
31
  default: () => {
27
32
  return {};
28
33
  }
34
+ },
35
+ index: {
36
+ type: Number,
37
+ required: true,
38
+ }
39
+ },
40
+
41
+ data() {
42
+ return { customType: '' };
43
+ },
44
+
45
+ created() {
46
+ if (this.type.startsWith(TYPES.EXTENDED)) {
47
+ this.customType = this.type.substring(`${ TYPES.EXTENDED }.`.length);
48
+ } else {
49
+ this.customType = this.type;
29
50
  }
30
51
  },
31
52
 
32
53
  computed: {
33
54
  ...ROW_COMPUTED,
34
55
 
35
- resourceQuotaLimit: {
36
- get() {
37
- return this.value.spec.resourceQuota?.limit || {};
38
- },
56
+ localType() {
57
+ return this.type.startsWith(TYPES.EXTENDED) ? this.type.split('.')[0] : this.type;
39
58
  },
40
59
 
41
- namespaceDefaultResourceQuotaLimit: {
42
- get() {
43
- return this.value.spec.namespaceDefaultResourceQuota?.limit || {};
44
- },
60
+ isCustom() {
61
+ return this.localType === TYPES.EXTENDED;
62
+ },
63
+
64
+ resourceQuotaLimit() {
65
+ if (this.isCustom) {
66
+ return this.value.spec.resourceQuota?.limit.extended || {};
67
+ }
68
+
69
+ return this.value.spec.resourceQuota?.limit || {};
70
+ },
71
+
72
+ namespaceDefaultResourceQuotaLimit() {
73
+ if (this.isCustom) {
74
+ return this.value.spec.namespaceDefaultResourceQuota?.limit.extended || {};
75
+ }
76
+
77
+ return this.value.spec.namespaceDefaultResourceQuota?.limit || {};
78
+ },
79
+
80
+ currentResourceType() {
81
+ return this.isCustom ? this.customType : this.localType;
82
+ },
83
+
84
+ customTypeRules() {
85
+ // Return a validation rule that makes the field required when isCustom is true
86
+ if (this.isCustom) {
87
+ return [
88
+ (value) => {
89
+ if (!value) {
90
+ return this.t('resourceQuota.errors.customTypeRequired');
91
+ }
92
+
93
+ return undefined;
94
+ }
95
+ ];
96
+ }
97
+
98
+ return [];
45
99
  }
46
100
  },
47
101
 
48
102
  methods: {
49
103
  updateType(type) {
50
- if (typeof this.value.spec.resourceQuota?.limit[this.type] !== 'undefined') {
51
- delete this.value.spec.resourceQuota.limit[this.type];
52
- }
53
- if (typeof this.value.spec.namespaceDefaultResourceQuota?.limit[this.type] !== 'undefined') {
54
- delete this.value.spec.namespaceDefaultResourceQuota.limit[this.type];
104
+ const oldResourceKey = this.isCustom ? this.customType : this.localType;
105
+
106
+ this.deleteResourceLimits(oldResourceKey);
107
+
108
+ if (type === TYPES.EXTENDED) {
109
+ this.customType = '';
110
+ } else {
111
+ this.customType = type;
55
112
  }
56
113
 
57
- this.$emit('type-change', type);
114
+ this.$emit('type-change', { index: this.index, type });
115
+ },
116
+
117
+ updateCustomType(type) {
118
+ const oldType = this.customType;
119
+
120
+ this.deleteResourceLimits(oldType);
121
+
122
+ this.customType = type;
58
123
  },
59
124
 
60
125
  updateQuotaLimit(prop, type, val) {
@@ -62,7 +127,26 @@ export default {
62
127
  this.value.spec[prop] = { limit: { } };
63
128
  }
64
129
 
130
+ if (this.isCustom) {
131
+ if (!this.value.spec[prop].limit.extended) {
132
+ this.value.spec[prop].limit.extended = { };
133
+ }
134
+
135
+ this.value.spec[prop].limit.extended[type] = val;
136
+
137
+ return;
138
+ }
139
+
65
140
  this.value.spec[prop].limit[type] = val;
141
+ },
142
+
143
+ deleteResourceLimits(resourceKey) {
144
+ if (typeof this.value.spec.resourceQuota?.limit[resourceKey] !== 'undefined') {
145
+ delete this.value.spec.resourceQuota.limit[resourceKey];
146
+ }
147
+ if (typeof this.value.spec.namespaceDefaultResourceQuota?.limit[resourceKey] !== 'undefined') {
148
+ delete this.value.spec.namespaceDefaultResourceQuota.limit[resourceKey];
149
+ }
66
150
  }
67
151
  },
68
152
  };
@@ -73,15 +157,26 @@ export default {
73
157
  class="row"
74
158
  >
75
159
  <Select
76
- :value="type"
160
+ :value="localType"
77
161
  class="mr-10"
78
162
  :mode="mode"
79
163
  :options="types"
80
164
  data-testid="projectrow-type-input"
81
165
  @update:value="updateType($event)"
82
166
  />
167
+ <LabeledInput
168
+ :value="customType"
169
+ :disabled="!isCustom"
170
+ :required="isCustom"
171
+ :mode="mode"
172
+ :placeholder="t('resourceQuota.resourceIdentifier.placeholder')"
173
+ :rules="customTypeRules"
174
+ class="mr-10"
175
+ data-testid="projectrow-custom-type-input"
176
+ @update:value="updateCustomType($event)"
177
+ />
83
178
  <UnitInput
84
- :value="resourceQuotaLimit[type]"
179
+ :value="resourceQuotaLimit[currentResourceType]"
85
180
  class="mr-10"
86
181
  :mode="mode"
87
182
  :placeholder="typeOption.placeholder"
@@ -90,10 +185,10 @@ export default {
90
185
  :base-unit="typeOption.baseUnit"
91
186
  :output-modifier="true"
92
187
  data-testid="projectrow-project-quota-input"
93
- @update:value="updateQuotaLimit('resourceQuota', type, $event)"
188
+ @update:value="updateQuotaLimit('resourceQuota', currentResourceType, $event)"
94
189
  />
95
190
  <UnitInput
96
- :value="namespaceDefaultResourceQuotaLimit[type]"
191
+ :value="namespaceDefaultResourceQuotaLimit[currentResourceType]"
97
192
  :mode="mode"
98
193
  :placeholder="typeOption.placeholder"
99
194
  :increment="typeOption.increment"
@@ -101,7 +196,7 @@ export default {
101
196
  :base-unit="typeOption.baseUnit"
102
197
  :output-modifier="true"
103
198
  data-testid="projectrow-namespace-quota-input"
104
- @update:value="updateQuotaLimit('namespaceDefaultResourceQuota', type, $event)"
199
+ @update:value="updateQuotaLimit('namespaceDefaultResourceQuota', currentResourceType, $event)"
105
200
  />
106
201
  </div>
107
202
  </template>