@rancher/shell 3.0.9-rc.5 → 3.0.9

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 (172) hide show
  1. package/assets/images/providers/oci-open-containers.svg +22 -0
  2. package/assets/images/providers/traefik.png +0 -0
  3. package/assets/styles/themes/_dark.scss +2 -0
  4. package/assets/styles/themes/_light.scss +2 -0
  5. package/assets/styles/themes/_modern.scss +6 -0
  6. package/assets/translations/en-us.yaml +129 -25
  7. package/components/CruResource.vue +3 -1
  8. package/components/ExplorerProjectsNamespaces.vue +12 -12
  9. package/components/IconOrSvg.vue +61 -42
  10. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  11. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  12. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  13. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  14. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  15. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  16. package/components/Resource/Detail/ResourceRow.vue +2 -2
  17. package/components/ResourceList/index.vue +7 -4
  18. package/components/SortableTable/index.vue +2 -2
  19. package/components/Window/ContainerLogs.vue +48 -37
  20. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  21. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  22. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  23. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  24. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  25. package/components/fleet/GitRepoTargetTab.vue +77 -0
  26. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  27. package/components/fleet/HelmOpChartTab.vue +158 -0
  28. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  29. package/components/fleet/HelmOpTargetTab.vue +84 -0
  30. package/components/fleet/HelmOpValuesTab.vue +147 -0
  31. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  32. package/components/form/BannerSettings.vue +2 -2
  33. package/components/form/NodeScheduling.vue +81 -7
  34. package/components/form/NotificationSettings.vue +2 -2
  35. package/components/form/PodAffinity.vue +1 -36
  36. package/components/form/ResourceLabeledSelect.vue +8 -4
  37. package/components/form/ResourceQuota/Namespace.vue +30 -9
  38. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  39. package/components/form/ResourceQuota/Project.vue +140 -82
  40. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  41. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  42. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  43. package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
  44. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  45. package/components/form/SchedulingCustomization.vue +14 -6
  46. package/components/form/SelectOrCreateAuthSecret.vue +107 -18
  47. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  48. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  49. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  50. package/components/formatter/ClusterLink.vue +8 -0
  51. package/components/formatter/SecretOrigin.vue +79 -0
  52. package/config/labels-annotations.js +7 -6
  53. package/config/pagination-table-headers.js +6 -4
  54. package/config/product/explorer.js +1 -11
  55. package/config/product/manager.js +0 -1
  56. package/config/query-params.js +3 -0
  57. package/config/settings.ts +15 -2
  58. package/config/table-headers.js +21 -17
  59. package/config/types.js +23 -8
  60. package/detail/fleet.cattle.io.cluster.vue +1 -1
  61. package/detail/workload/index.vue +11 -16
  62. package/dialog/DeactivateDriverDialog.vue +1 -1
  63. package/dialog/FeatureFlagListDialog.vue +1 -1
  64. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  65. package/dialog/ScalePoolDownDialog.vue +2 -2
  66. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  67. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  68. package/edit/__tests__/management.cattle.io.project.test.js +56 -128
  69. package/edit/auth/oidc.vue +1 -1
  70. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  71. package/edit/fleet.cattle.io.gitrepo.vue +153 -283
  72. package/edit/fleet.cattle.io.helmop.vue +190 -332
  73. package/edit/management.cattle.io.project.vue +5 -42
  74. package/edit/management.cattle.io.setting.vue +6 -0
  75. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
  76. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
  77. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
  78. package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
  79. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
  80. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
  81. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
  82. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
  83. package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
  84. package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
  85. package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
  86. package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
  87. package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
  88. package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
  89. package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
  90. package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
  91. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  92. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  93. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  94. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  95. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
  96. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +114 -0
  97. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  98. package/edit/provisioning.cattle.io.cluster/rke2.vue +167 -69
  99. package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
  100. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  101. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +70 -7
  102. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +343 -0
  103. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  104. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  105. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  106. package/edit/secret/index.vue +1 -1
  107. package/edit/token.vue +68 -29
  108. package/edit/workload/__tests__/index.test.ts +2 -37
  109. package/edit/workload/index.vue +6 -2
  110. package/edit/workload/mixins/workload.js +0 -32
  111. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  112. package/list/management.cattle.io.setting.vue +13 -0
  113. package/list/provisioning.cattle.io.cluster.vue +50 -1
  114. package/list/secret.vue +4 -9
  115. package/list/service.vue +6 -8
  116. package/machine-config/amazonec2.vue +11 -4
  117. package/machine-config/components/EC2Networking.vue +46 -30
  118. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  119. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  120. package/machine-config/digitalocean.vue +3 -3
  121. package/models/__tests__/chart.test.ts +2 -2
  122. package/models/__tests__/namespace.test.ts +11 -0
  123. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  124. package/models/__tests__/workload.test.ts +42 -1
  125. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  126. package/models/chart.js +3 -3
  127. package/models/ext.cattle.io.token.js +48 -0
  128. package/models/kontainerdriver.js +2 -2
  129. package/models/namespace.js +7 -1
  130. package/models/nodedriver.js +2 -2
  131. package/models/provisioning.cattle.io.cluster.js +28 -7
  132. package/models/secret.js +0 -17
  133. package/models/service.js +44 -1
  134. package/models/token.js +4 -0
  135. package/models/workload.js +12 -6
  136. package/package.json +1 -1
  137. package/pages/account/index.vue +96 -67
  138. package/pages/auth/setup.vue +5 -14
  139. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +45 -18
  140. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  141. package/pages/c/_cluster/apps/charts/index.vue +82 -3
  142. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  143. package/pages/c/_cluster/explorer/tools/index.vue +1 -1
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  146. package/pages/c/_cluster/settings/index.vue +3 -1
  147. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  148. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  149. package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
  150. package/plugins/dashboard-store/actions.js +3 -8
  151. package/plugins/dashboard-store/getters.js +7 -5
  152. package/plugins/dashboard-store/mutations.js +4 -1
  153. package/plugins/dashboard-store/resource-class.js +3 -3
  154. package/plugins/steve/__tests__/steve-class.test.ts +102 -141
  155. package/plugins/steve/steve-class.js +12 -3
  156. package/plugins/steve/steve-pagination-utils.ts +6 -2
  157. package/rancher-components/RcIcon/types.ts +2 -0
  158. package/rancher-components/RcItemCard/RcItemCard.vue +72 -20
  159. package/store/prefs.js +3 -0
  160. package/types/aws-sdk.d.ts +121 -0
  161. package/types/resources/node.ts +15 -0
  162. package/types/shell/index.d.ts +537 -506
  163. package/types/store/pagination.types.ts +5 -5
  164. package/utils/__tests__/array.test.ts +1 -29
  165. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  166. package/utils/array.ts +0 -11
  167. package/utils/aws.ts +21 -0
  168. package/utils/cluster.js +22 -2
  169. package/utils/selector-typed.ts +1 -1
  170. package/utils/svg-filter.js +4 -3
  171. package/components/__tests__/ProjectRow.test.ts +0 -206
  172. package/components/form/ResourceQuota/ProjectRow.vue +0 -277
@@ -1,20 +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
9
  emits: [
9
- 'remove',
10
10
  'input',
11
11
  'validationChanged',
12
12
  ],
13
13
 
14
14
  components: {
15
- ArrayList,
16
- Row,
17
15
  Banner,
16
+ RcButton,
17
+ ResourceQuota,
18
18
  },
19
19
 
20
20
  props: {
@@ -37,7 +37,7 @@ export default {
37
37
  },
38
38
 
39
39
  data() {
40
- return { typeValues: null };
40
+ return { resourceQuotas: [] };
41
41
  },
42
42
 
43
43
  created() {
@@ -45,85 +45,129 @@ export default {
45
45
  this.value.spec['namespaceDefaultResourceQuota'] = this.value.spec.namespaceDefaultResourceQuota || { limit: {} };
46
46
  this.value.spec['resourceQuota'] = this.value.spec.resourceQuota || { limit: {} };
47
47
 
48
- const limit = this.value.spec.resourceQuota.limit;
49
- const extendedKeys = Object.keys(limit.extended || {});
48
+ this.quotasFromSpec();
50
49
 
51
- this.typeValues = Object.keys(limit).flatMap((k) => {
52
- if (k !== TYPES.EXTENDED) {
53
- return k;
54
- }
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 });
60
+
61
+ const hasMissingExtendedIdentifier = this.resourceQuotas.some(
62
+ (quota) => quota.resourceType === TYPES.EXTENDED && !quota.resourceIdentifier
63
+ );
55
64
 
56
- return extendedKeys.map((ek) => `extended.${ ek }`);
57
- });
65
+ this.$emit('validationChanged', !hasMissingExtendedIdentifier);
66
+ },
67
+ { deep: true }
68
+ );
58
69
  },
59
70
 
60
71
  computed: { ...QUOTA_COMPUTED },
61
72
 
62
73
  methods: {
63
- updateType(event) {
64
- const { index, type } = event;
65
-
66
- this.typeValues[index] = type;
67
-
68
- this.validateTypes();
74
+ addResource() {
75
+ this.resourceQuotas.push({
76
+ id: uniqueId(),
77
+ resourceType: TYPES.EXTENDED,
78
+ resourceIdentifier: '',
79
+ projectLimit: '',
80
+ namespaceDefaultLimit: '',
81
+ });
69
82
  },
70
- updateResourceIdentifier({ type, customType, index }) {
71
- if (type.startsWith(TYPES.EXTENDED)) {
72
- this.typeValues[index] = `extended.${ customType }`;
73
- }
74
83
 
75
- this.validateTypes();
84
+ removeResource(id) {
85
+ this.resourceQuotas = this.resourceQuotas.filter((quota) => quota.id !== id);
76
86
  },
77
- validateTypes(isValid = true) {
78
- if (!isValid) {
79
- this.$emit('validationChanged', false);
80
-
81
- return;
82
- }
83
87
 
84
- const hasMissingExtendedValue = this.typeValues.some((typeValue) => {
85
- if (!typeValue.startsWith(TYPES.EXTENDED)) {
86
- return false;
87
- }
88
+ remainingTypes(currentType) {
89
+ const usedTypes = this.resourceQuotas
90
+ .map((quota) => quota.resourceType)
91
+ .filter((resourceType) => resourceType !== TYPES.EXTENDED);
88
92
 
89
- const [, resourceIdentifier] = typeValue.split('.');
93
+ return this.mappedTypes.filter((mappedType) => mappedType.value === TYPES.EXTENDED ||
94
+ !usedTypes.includes(mappedType.value) ||
95
+ mappedType.value === currentType
96
+ );
97
+ },
90
98
 
91
- return !resourceIdentifier;
92
- });
99
+ specFromQuotas() {
100
+ const projectLimit = {};
101
+ const nsLimit = {};
93
102
 
94
- this.$emit('validationChanged', !hasMissingExtendedValue);
95
- },
96
- remainingTypes(currentType) {
97
- return this.mappedTypes
98
- .filter((mappedType) => {
99
- if (mappedType.value === TYPES.EXTENDED) {
100
- return true;
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;
101
114
  }
115
+ } else {
116
+ projectLimit[quota.resourceType] = quota.projectLimit;
117
+ nsLimit[quota.resourceType] = quota.namespaceDefaultLimit;
118
+ }
119
+ });
102
120
 
103
- return !this.typeValues.includes(mappedType.value) || mappedType.value === currentType;
104
- });
121
+ return { projectLimit, nsLimit };
105
122
  },
106
- emitRemove(data) {
107
- this.typeValues = this.typeValues.filter((_typeValue, index) => {
108
- return index !== data.index;
109
- });
110
123
 
111
- this.$emit('remove', data.row?.value);
124
+ quotasFromSpec() {
125
+ const projectLimit = this.value?.spec?.resourceQuota?.limit || {};
126
+ const nsLimit = this.value?.spec?.namespaceDefaultResourceQuota?.limit || {};
112
127
 
113
- this.validateTypes();
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
+ });
114
149
  }
115
150
  },
116
151
  };
117
152
  </script>
118
153
  <template>
119
- <div>
154
+ <div
155
+ role="grid"
156
+ :aria-label="t('resourceQuota.ariaLabel.grid')"
157
+ >
120
158
  <Banner
121
159
  color="info"
122
160
  label-key="resourceQuota.banner"
123
161
  class="mb-20"
124
162
  />
125
- <div class="headers mb-10">
126
- <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
+ >
127
171
  <label>
128
172
  {{ t('resourceQuota.headers.resourceType') }}
129
173
  <span
@@ -132,7 +176,10 @@ export default {
132
176
  >*</span>
133
177
  </label>
134
178
  </div>
135
- <div class="mr-20">
179
+ <div
180
+ role="columnheader"
181
+ class="mr-20"
182
+ >
136
183
  <label>
137
184
  {{ t('resourceQuota.headers.resourceIdentifier') }}
138
185
  <span
@@ -145,37 +192,44 @@ export default {
145
192
  />
146
193
  </label>
147
194
  </div>
148
- <div class="mr-20">
195
+ <div
196
+ role="columnheader"
197
+ class="mr-20"
198
+ >
149
199
  <label>{{ t('resourceQuota.headers.projectLimit') }}</label>
150
200
  </div>
151
- <div class="mr-10">
201
+ <div
202
+ role="columnheader"
203
+ class="mr-10"
204
+ >
152
205
  <label>{{ t('resourceQuota.headers.namespaceDefaultLimit') }}</label>
153
206
  </div>
154
207
  </div>
155
- <ArrayList
156
- v-model:value="typeValues"
157
- label="Resources"
158
- :add-label="t('resourceQuota.add.label')"
159
- :default-add-value="remainingTypes()[0] ? remainingTypes()[0].value : ''"
160
- :add-allowed="remainingTypes().length > 0"
161
- :mode="mode"
162
- @remove="emitRemove"
163
- @add="validateTypes(false)"
208
+ <template
209
+ v-for="(resourceQuota, resourceQuotaIndex) in resourceQuotas"
210
+ :key="resourceQuota.id"
164
211
  >
165
- <template #columns="props">
166
- <Row
167
- :value="value"
168
- :mode="mode"
169
- :types="remainingTypes(typeValues[props.i])"
170
- :type="typeValues[props.i]"
171
- :type-values="typeValues"
172
- :index="props.i"
173
- @input="$emit('input', $event)"
174
- @type-change="updateType($event)"
175
- @update:resource-identifier="updateResourceIdentifier"
176
- />
177
- </template>
178
- </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>
179
233
  </div>
180
234
  </template>
181
235
  <style lang="scss" scoped>
@@ -196,4 +250,8 @@ export default {
196
250
  .required {
197
251
  color: var(--error);
198
252
  }
253
+
254
+ .project-quotas-footer {
255
+ margin-top: 24px;
256
+ }
199
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>