@rancher/shell 3.0.9-rc.4 → 3.0.9-rc.6

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 (218) hide show
  1. package/assets/brand/suse/metadata.json +2 -1
  2. package/assets/images/providers/oci-open-containers.svg +22 -0
  3. package/assets/images/providers/traefik.png +0 -0
  4. package/assets/styles/themes/_dark.scss +2 -0
  5. package/assets/styles/themes/_light.scss +2 -0
  6. package/assets/styles/themes/_modern.scss +6 -0
  7. package/assets/translations/en-us.yaml +218 -26
  8. package/components/ActionMenuShell.vue +1 -1
  9. package/components/CruResource.vue +3 -1
  10. package/components/ExplorerProjectsNamespaces.vue +12 -12
  11. package/components/Inactivity.vue +2 -2
  12. package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
  13. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  14. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  15. package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
  16. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
  17. package/components/Resource/Detail/Masthead/index.vue +11 -4
  18. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  19. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  20. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
  21. package/components/Resource/Detail/Metadata/index.vue +1 -1
  22. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  23. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  24. package/components/Resource/Detail/ResourceRow.vue +3 -3
  25. package/components/ResourceDetail/Masthead/latest.vue +12 -2
  26. package/components/ResourceList/index.vue +12 -0
  27. package/components/ResourceTable.vue +38 -4
  28. package/components/Tabbed/Tab.vue +4 -0
  29. package/components/Tabbed/index.vue +4 -1
  30. package/components/Window/ContainerLogs.vue +48 -37
  31. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  32. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  33. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  34. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  35. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  36. package/components/fleet/GitRepoTargetTab.vue +77 -0
  37. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  38. package/components/fleet/HelmOpChartTab.vue +158 -0
  39. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  40. package/components/fleet/HelmOpTargetTab.vue +84 -0
  41. package/components/fleet/HelmOpValuesTab.vue +147 -0
  42. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  43. package/components/form/ChangePassword.vue +41 -35
  44. package/components/form/NodeScheduling.vue +81 -7
  45. package/components/form/PodAffinity.vue +1 -36
  46. package/components/form/ResourceLabeledSelect.vue +8 -4
  47. package/components/form/ResourceQuota/Namespace.vue +30 -9
  48. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  49. package/components/form/ResourceQuota/Project.vue +150 -51
  50. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  51. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  52. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  53. package/components/form/ResourceQuota/__tests__/Project.test.ts +310 -0
  54. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  55. package/components/form/SchedulingCustomization.vue +14 -6
  56. package/components/form/SelectOrCreateAuthSecret.vue +113 -19
  57. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  58. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  59. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  60. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
  61. package/components/formatter/ClusterLink.vue +8 -0
  62. package/components/formatter/MachineSummaryGraph.vue +10 -2
  63. package/components/formatter/SecretOrigin.vue +79 -0
  64. package/components/nav/TopLevelMenu.helper.ts +50 -2
  65. package/components/nav/TopLevelMenu.vue +14 -0
  66. package/components/nav/Type.vue +5 -0
  67. package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
  68. package/components/nav/__tests__/Type.test.ts +6 -4
  69. package/config/labels-annotations.js +7 -6
  70. package/config/pagination-table-headers.js +6 -4
  71. package/config/product/explorer.js +5 -14
  72. package/config/product/manager.js +18 -1
  73. package/config/query-params.js +3 -0
  74. package/config/router/navigation-guards/authentication.js +8 -9
  75. package/config/settings.ts +15 -2
  76. package/config/table-headers.js +21 -17
  77. package/config/types.js +33 -10
  78. package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
  79. package/detail/management.cattle.io.user.vue +1 -2
  80. package/detail/node.vue +0 -1
  81. package/detail/provisioning.cattle.io.cluster.vue +2 -1
  82. package/detail/workload/index.vue +11 -16
  83. package/dialog/ChangePasswordDialog.vue +8 -0
  84. package/dialog/DeactivateDriverDialog.vue +1 -1
  85. package/dialog/GenericPrompt.vue +20 -3
  86. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  87. package/dialog/ScaleMachineDownDialog.vue +65 -15
  88. package/dialog/ScalePoolDownDialog.vue +2 -2
  89. package/dialog/SearchDialog.vue +10 -2
  90. package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
  91. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
  92. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  93. package/edit/__tests__/management.cattle.io.project.test.js +81 -98
  94. package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
  95. package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
  96. package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
  97. package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
  98. package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
  99. package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
  100. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
  101. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
  102. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
  103. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
  104. package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
  105. package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
  106. package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
  107. package/edit/auth/oidc.vue +1 -1
  108. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  109. package/edit/fleet.cattle.io.gitrepo.vue +161 -276
  110. package/edit/fleet.cattle.io.helmop.vue +190 -332
  111. package/edit/management.cattle.io.project.vue +11 -42
  112. package/edit/management.cattle.io.setting.vue +6 -0
  113. package/edit/management.cattle.io.user.vue +29 -34
  114. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  115. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  116. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  117. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +196 -2
  119. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +112 -0
  120. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +184 -66
  122. package/edit/provisioning.cattle.io.cluster/shared.ts +39 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  124. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +56 -7
  125. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +319 -0
  126. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  127. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -1
  128. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  129. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  130. package/edit/secret/index.vue +1 -1
  131. package/edit/token.vue +68 -29
  132. package/edit/workload/__tests__/index.test.ts +2 -37
  133. package/edit/workload/index.vue +6 -2
  134. package/edit/workload/mixins/workload.js +0 -32
  135. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  136. package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
  137. package/list/group.principal.vue +11 -15
  138. package/list/management.cattle.io.setting.vue +13 -0
  139. package/list/management.cattle.io.user.vue +11 -21
  140. package/list/provisioning.cattle.io.cluster.vue +50 -1
  141. package/list/secret.vue +4 -9
  142. package/list/service.vue +6 -8
  143. package/machine-config/amazonec2.vue +11 -4
  144. package/machine-config/azure.vue +14 -0
  145. package/machine-config/components/EC2Networking.vue +46 -30
  146. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  147. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  148. package/machine-config/digitalocean.vue +3 -3
  149. package/mixins/browser-tab-visibility.js +5 -4
  150. package/mixins/fetch.client.js +6 -0
  151. package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
  152. package/models/__tests__/namespace.test.ts +11 -0
  153. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  154. package/models/__tests__/workload.test.ts +90 -6
  155. package/models/auditlog.cattle.io.auditpolicy.js +46 -0
  156. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  157. package/models/cluster.x-k8s.io.machine.js +1 -1
  158. package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
  159. package/models/event.js +5 -0
  160. package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
  161. package/models/ext.cattle.io.passwordchangerequest.js +15 -0
  162. package/models/ext.cattle.io.selfuser.js +15 -0
  163. package/models/ext.cattle.io.token.js +48 -0
  164. package/models/fleet-application.js +17 -7
  165. package/models/kontainerdriver.js +2 -2
  166. package/models/management.cattle.io.user.js +28 -31
  167. package/models/namespace.js +7 -1
  168. package/models/nodedriver.js +2 -2
  169. package/models/provisioning.cattle.io.cluster.js +28 -7
  170. package/models/schema.js +18 -0
  171. package/models/secret.js +27 -41
  172. package/models/service.js +44 -1
  173. package/models/steve-schema.ts +39 -2
  174. package/models/token.js +4 -0
  175. package/models/workload.js +13 -6
  176. package/package.json +1 -1
  177. package/pages/account/index.vue +108 -72
  178. package/pages/auth/login.vue +15 -8
  179. package/pages/auth/setup.vue +55 -27
  180. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  181. package/pages/c/_cluster/apps/charts/index.vue +93 -4
  182. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  183. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  184. package/pages/c/_cluster/settings/index.vue +3 -1
  185. package/pages/home.vue +9 -3
  186. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  187. package/plugins/dashboard-store/__tests__/resource-class.test.ts +28 -3
  188. package/plugins/dashboard-store/actions.js +10 -8
  189. package/plugins/dashboard-store/getters.js +30 -6
  190. package/plugins/dashboard-store/index.js +3 -2
  191. package/plugins/dashboard-store/mutations.js +8 -1
  192. package/plugins/dashboard-store/resource-class.js +15 -8
  193. package/plugins/steve/__tests__/steve-class.test.ts +128 -0
  194. package/plugins/steve/schema.d.ts +5 -0
  195. package/plugins/steve/steve-class.js +28 -0
  196. package/plugins/steve/steve-pagination-utils.ts +7 -2
  197. package/rancher-components/RcIcon/types.ts +2 -0
  198. package/rancher-components/RcItemCard/RcItemCard.vue +64 -19
  199. package/store/auth.js +57 -19
  200. package/store/notifications.ts +1 -1
  201. package/store/prefs.js +3 -0
  202. package/store/type-map.js +12 -1
  203. package/types/aws-sdk.d.ts +121 -0
  204. package/types/resources/node.ts +15 -0
  205. package/types/shell/index.d.ts +542 -516
  206. package/types/store/dashboard-store.types.ts +7 -0
  207. package/types/store/pagination.types.ts +5 -5
  208. package/utils/__tests__/array.test.ts +1 -29
  209. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  210. package/utils/array.ts +0 -11
  211. package/utils/aws.ts +21 -0
  212. package/utils/cluster.js +22 -2
  213. package/utils/pagination-wrapper.ts +11 -3
  214. package/utils/selector-typed.ts +1 -1
  215. package/vue.config.js +26 -13
  216. package/components/__tests__/ProjectRow.test.ts +0 -146
  217. package/components/form/ResourceQuota/ProjectRow.vue +0 -210
  218. package/edit/provisioning.cattle.io.cluster/defaults.ts +0 -1
@@ -1,16 +1,20 @@
1
1
  <script>
2
- import ArrayList from '@shell/components/form/ArrayList';
3
- import Row from './ProjectRow';
4
2
  import { QUOTA_COMPUTED, TYPES } from './shared';
5
3
  import Banner from '@components/Banner/Banner.vue';
4
+ import ResourceQuota from '@shell/components/form/ResourceQuota/ResourceQuotaEntry.vue';
5
+ import { RcButton } from '@components/RcButton';
6
+ import { uniqueId } from 'lodash';
6
7
 
7
8
  export default {
8
- emits: ['remove', 'input'],
9
+ emits: [
10
+ 'input',
11
+ 'validationChanged',
12
+ ],
9
13
 
10
14
  components: {
11
- ArrayList,
12
- Row,
13
15
  Banner,
16
+ RcButton,
17
+ ResourceQuota,
14
18
  },
15
19
 
16
20
  props: {
@@ -33,7 +37,7 @@ export default {
33
37
  },
34
38
 
35
39
  data() {
36
- return { typeValues: null };
40
+ return { resourceQuotas: [] };
37
41
  },
38
42
 
39
43
  created() {
@@ -41,51 +45,129 @@ export default {
41
45
  this.value.spec['namespaceDefaultResourceQuota'] = this.value.spec.namespaceDefaultResourceQuota || { limit: {} };
42
46
  this.value.spec['resourceQuota'] = this.value.spec.resourceQuota || { limit: {} };
43
47
 
44
- const limit = this.value.spec.resourceQuota.limit;
45
- const extendedKeys = Object.keys(limit.extended || {});
48
+ this.quotasFromSpec();
46
49
 
47
- this.typeValues = Object.keys(limit).flatMap((k) => {
48
- if (k !== TYPES.EXTENDED) {
49
- return k;
50
- }
50
+ /**
51
+ * Register watcher using the imperative API to reduce churn when initialing
52
+ * data on first render
53
+ */
54
+ this.$watch(
55
+ 'resourceQuotas',
56
+ () => {
57
+ const { projectLimit, nsLimit } = this.specFromQuotas();
58
+
59
+ this.$emit('input', { projectLimit, nsLimit });
51
60
 
52
- return extendedKeys.map((ek) => `extended.${ ek }`);
53
- });
61
+ const hasMissingExtendedIdentifier = this.resourceQuotas.some(
62
+ (quota) => quota.resourceType === TYPES.EXTENDED && !quota.resourceIdentifier
63
+ );
64
+
65
+ this.$emit('validationChanged', !hasMissingExtendedIdentifier);
66
+ },
67
+ { deep: true }
68
+ );
54
69
  },
55
70
 
56
71
  computed: { ...QUOTA_COMPUTED },
57
72
 
58
73
  methods: {
59
- updateType(event) {
60
- const { index, type } = event;
74
+ addResource() {
75
+ this.resourceQuotas.push({
76
+ id: uniqueId(),
77
+ resourceType: TYPES.EXTENDED,
78
+ resourceIdentifier: '',
79
+ projectLimit: '',
80
+ namespaceDefaultLimit: '',
81
+ });
82
+ },
61
83
 
62
- this.typeValues[index] = type;
84
+ removeResource(id) {
85
+ this.resourceQuotas = this.resourceQuotas.filter((quota) => quota.id !== id);
63
86
  },
87
+
64
88
  remainingTypes(currentType) {
65
- return this.mappedTypes
66
- .filter((mappedType) => {
67
- if (mappedType.value === TYPES.EXTENDED) {
68
- return true;
89
+ const usedTypes = this.resourceQuotas
90
+ .map((quota) => quota.resourceType)
91
+ .filter((resourceType) => resourceType !== TYPES.EXTENDED);
92
+
93
+ return this.mappedTypes.filter((mappedType) => mappedType.value === TYPES.EXTENDED ||
94
+ !usedTypes.includes(mappedType.value) ||
95
+ mappedType.value === currentType
96
+ );
97
+ },
98
+
99
+ specFromQuotas() {
100
+ const projectLimit = {};
101
+ const nsLimit = {};
102
+
103
+ this.resourceQuotas.forEach((quota) => {
104
+ if (quota.resourceType === TYPES.EXTENDED) {
105
+ if (quota.resourceIdentifier) {
106
+ if (!projectLimit.extended) {
107
+ projectLimit.extended = {};
108
+ }
109
+ if (!nsLimit.extended) {
110
+ nsLimit.extended = {};
111
+ }
112
+ projectLimit.extended[quota.resourceIdentifier] = quota.projectLimit;
113
+ nsLimit.extended[quota.resourceIdentifier] = quota.namespaceDefaultLimit;
69
114
  }
115
+ } else {
116
+ projectLimit[quota.resourceType] = quota.projectLimit;
117
+ nsLimit[quota.resourceType] = quota.namespaceDefaultLimit;
118
+ }
119
+ });
70
120
 
71
- return !this.typeValues.includes(mappedType.value) || mappedType.value === currentType;
72
- });
121
+ return { projectLimit, nsLimit };
73
122
  },
74
- emitRemove(data) {
75
- this.$emit('remove', data.row?.value);
123
+
124
+ quotasFromSpec() {
125
+ const projectLimit = this.value?.spec?.resourceQuota?.limit || {};
126
+ const nsLimit = this.value?.spec?.namespaceDefaultResourceQuota?.limit || {};
127
+
128
+ Object.keys(projectLimit).forEach((key) => {
129
+ if (key !== TYPES.EXTENDED) {
130
+ this.resourceQuotas.push({
131
+ id: uniqueId(),
132
+ resourceType: key,
133
+ resourceIdentifier: key,
134
+ projectLimit: projectLimit[key],
135
+ namespaceDefaultLimit: nsLimit[key] || '',
136
+ });
137
+ } else {
138
+ Object.keys(projectLimit.extended || {}).forEach((extKey) => {
139
+ this.resourceQuotas.push({
140
+ id: uniqueId(),
141
+ resourceType: TYPES.EXTENDED,
142
+ resourceIdentifier: extKey,
143
+ projectLimit: projectLimit.extended[extKey],
144
+ namespaceDefaultLimit: (nsLimit.extended || {})[extKey] || '',
145
+ });
146
+ });
147
+ }
148
+ });
76
149
  }
77
150
  },
78
151
  };
79
152
  </script>
80
153
  <template>
81
- <div>
154
+ <div
155
+ role="grid"
156
+ :aria-label="t('resourceQuota.ariaLabel.grid')"
157
+ >
82
158
  <Banner
83
159
  color="info"
84
160
  label-key="resourceQuota.banner"
85
161
  class="mb-20"
86
162
  />
87
- <div class="headers mb-10">
88
- <div class="mr-10">
163
+ <div
164
+ role="row"
165
+ class="headers mb-10"
166
+ >
167
+ <div
168
+ role="columnheader"
169
+ class="mr-10"
170
+ >
89
171
  <label>
90
172
  {{ t('resourceQuota.headers.resourceType') }}
91
173
  <span
@@ -94,7 +176,10 @@ export default {
94
176
  >*</span>
95
177
  </label>
96
178
  </div>
97
- <div class="mr-20">
179
+ <div
180
+ role="columnheader"
181
+ class="mr-20"
182
+ >
98
183
  <label>
99
184
  {{ t('resourceQuota.headers.resourceIdentifier') }}
100
185
  <span
@@ -107,34 +192,44 @@ export default {
107
192
  />
108
193
  </label>
109
194
  </div>
110
- <div class="mr-20">
195
+ <div
196
+ role="columnheader"
197
+ class="mr-20"
198
+ >
111
199
  <label>{{ t('resourceQuota.headers.projectLimit') }}</label>
112
200
  </div>
113
- <div class="mr-10">
201
+ <div
202
+ role="columnheader"
203
+ class="mr-10"
204
+ >
114
205
  <label>{{ t('resourceQuota.headers.namespaceDefaultLimit') }}</label>
115
206
  </div>
116
207
  </div>
117
- <ArrayList
118
- v-model:value="typeValues"
119
- label="Resources"
120
- :add-label="t('resourceQuota.add.label')"
121
- :default-add-value="remainingTypes()[0] ? remainingTypes()[0].value : ''"
122
- :add-allowed="remainingTypes().length > 0"
123
- :mode="mode"
124
- @remove="emitRemove"
208
+ <template
209
+ v-for="(resourceQuota, resourceQuotaIndex) in resourceQuotas"
210
+ :key="resourceQuota.id"
125
211
  >
126
- <template #columns="props">
127
- <Row
128
- :value="value"
129
- :mode="mode"
130
- :types="remainingTypes(typeValues[props.i])"
131
- :type="typeValues[props.i]"
132
- :index="props.i"
133
- @input="$emit('input', $event)"
134
- @type-change="updateType($event)"
135
- />
136
- </template>
137
- </ArrayList>
212
+ <ResourceQuota
213
+ :id="resourceQuota.id"
214
+ v-model:resource-type="resourceQuota.resourceType"
215
+ v-model:resource-identifier="resourceQuota.resourceIdentifier"
216
+ v-model:project-limit="resourceQuota.projectLimit"
217
+ v-model:namespace-default-limit="resourceQuota.namespaceDefaultLimit"
218
+ :index="resourceQuotaIndex + 1"
219
+ :types="remainingTypes(resourceQuota.resourceType)"
220
+ :mode="mode"
221
+ @remove="removeResource"
222
+ />
223
+ </template>
224
+ <div class="project-quotas-footer">
225
+ <rc-button
226
+ variant="tertiary"
227
+ data-testid="btn-add-resource"
228
+ @click="addResource"
229
+ >
230
+ {{ t('resourceQuota.add.label') }}
231
+ </rc-button>
232
+ </div>
138
233
  </div>
139
234
  </template>
140
235
  <style lang="scss" scoped>
@@ -155,4 +250,8 @@ export default {
155
250
  .required {
156
251
  color: var(--error);
157
252
  }
253
+
254
+ .project-quotas-footer {
255
+ margin-top: 24px;
256
+ }
158
257
  </style>
@@ -0,0 +1,145 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useStore } from 'vuex';
4
+
5
+ import Select from '@shell/components/form/Select';
6
+ import UnitInput from '@shell/components/form/UnitInput';
7
+ import { LabeledInput } from '@components/Form/LabeledInput';
8
+ import { RcButton } from '@components/RcButton';
9
+
10
+ import { TYPES } from './shared';
11
+ import { useI18n } from '@shell/composables/useI18n';
12
+
13
+ const props = defineProps<{
14
+ id: string,
15
+ index: number,
16
+ mode: string,
17
+ types: any[],
18
+ }>();
19
+
20
+ const emit = defineEmits(['remove']);
21
+
22
+ const store = useStore();
23
+ const { t } = useI18n(store);
24
+
25
+ const resourceType = defineModel<string>('resourceType');
26
+ const resourceIdentifier = defineModel<string>('resourceIdentifier');
27
+ const projectLimit = defineModel<string>('projectLimit');
28
+ const namespaceDefaultLimit = defineModel<string>('namespaceDefaultLimit');
29
+
30
+ const typeOption = computed(() => {
31
+ return props.types.find((type) => type.value === resourceType.value) || {};
32
+ });
33
+
34
+ const isCustom = computed(() => {
35
+ return resourceType.value === TYPES.EXTENDED;
36
+ });
37
+
38
+ const removeAriaLabel = computed(() => {
39
+ let identifier;
40
+
41
+ if (isCustom.value) {
42
+ identifier = resourceIdentifier.value || t('generic.ariaLabel.genericRow', { index: String(props.index) });
43
+ } else {
44
+ identifier = typeOption.value?.label || t('generic.ariaLabel.genericRow', { index: String(props.index) });
45
+ }
46
+
47
+ return t('resourceQuota.ariaLabel.remove', { identifier });
48
+ });
49
+
50
+ const customTypeRules = computed(() => {
51
+ // Return a validation rule that makes the field required when isCustom is true
52
+ if (isCustom.value) {
53
+ return [
54
+ (value: string) => {
55
+ if (!value) {
56
+ return t('resourceQuota.errors.customTypeRequired');
57
+ }
58
+
59
+ return undefined;
60
+ }
61
+ ];
62
+ }
63
+
64
+ return [];
65
+ });
66
+
67
+ const remove = (id: string) => {
68
+ emit('remove', id);
69
+ };
70
+
71
+ const updateResourceIdentifier = (resourceType: string) => {
72
+ if (resourceType === TYPES.EXTENDED) {
73
+ return;
74
+ }
75
+
76
+ resourceIdentifier.value = resourceType;
77
+ };
78
+ </script>
79
+
80
+ <template>
81
+ <div
82
+ role="row"
83
+ class="row mb-10"
84
+ >
85
+ <Select
86
+ v-model:value="resourceType"
87
+ class="mr-10"
88
+ :mode="mode"
89
+ :options="types"
90
+ :aria-label="t('resourceQuota.ariaLabel.resourceType', { row: index })"
91
+ data-testid="projectrow-type-input"
92
+ @update:value="updateResourceIdentifier"
93
+ />
94
+ <LabeledInput
95
+ v-model:value="resourceIdentifier"
96
+ :disabled="!isCustom"
97
+ :required="isCustom"
98
+ :mode="mode"
99
+ :placeholder="t('resourceQuota.resourceIdentifier.placeholder')"
100
+ :rules="customTypeRules"
101
+ :require-dirty="false"
102
+ :aria-label="t('resourceQuota.ariaLabel.resourceIdentifier', { row: index })"
103
+ class="mr-10"
104
+ data-testid="projectrow-custom-type-input"
105
+ />
106
+ <UnitInput
107
+ v-model:value="projectLimit"
108
+ class="mr-10"
109
+ :mode="mode"
110
+ :placeholder="typeOption.placeholder"
111
+ :increment="typeOption.increment"
112
+ :input-exponent="typeOption.inputExponent"
113
+ :base-unit="typeOption.baseUnit"
114
+ :output-modifier="true"
115
+ :aria-label="t('resourceQuota.ariaLabel.projectLimit', { row: index })"
116
+ data-testid="projectrow-project-quota-input"
117
+ />
118
+ <UnitInput
119
+ v-model:value="namespaceDefaultLimit"
120
+ :mode="mode"
121
+ :placeholder="typeOption.placeholder"
122
+ :increment="typeOption.increment"
123
+ :input-exponent="typeOption.inputExponent"
124
+ :base-unit="typeOption.baseUnit"
125
+ :output-modifier="true"
126
+ :aria-label="t('resourceQuota.ariaLabel.namespaceDefaultLimit', { row: index })"
127
+ data-testid="projectrow-namespace-quota-input"
128
+ />
129
+ <RcButton
130
+ variant="tertiary"
131
+ :aria-label="removeAriaLabel"
132
+ @click="remove(id)"
133
+ >
134
+ {{ t('generic.remove') }}
135
+ </RcButton>
136
+ </div>
137
+ </template>
138
+
139
+ <style lang='scss' scoped>
140
+ .row {
141
+ display: flex;
142
+ flex-direction: row;
143
+ justify-content: space-evenly;
144
+ }
145
+ </style>