@rancher/shell 3.0.8 → 3.0.9-rc.2

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 (192) hide show
  1. package/apis/intf/modal.ts +38 -0
  2. package/apis/intf/slide-in.ts +3 -1
  3. package/apis/shell/__tests__/slide-in.test.ts +36 -0
  4. package/apis/shell/slide-in.ts +5 -1
  5. package/assets/styles/base/_color.scss +1 -0
  6. package/assets/styles/base/_typography.scss +14 -5
  7. package/assets/styles/themes/_light.scss +1 -1
  8. package/assets/styles/themes/_modern.scss +1 -1
  9. package/assets/translations/en-us.yaml +94 -33
  10. package/assets/translations/zh-hans.yaml +0 -2
  11. package/components/ActionMenuShell.vue +4 -4
  12. package/components/CodeMirror.vue +4 -3
  13. package/components/DetailText.vue +54 -7
  14. package/components/Drawer/Chrome.vue +11 -4
  15. package/components/Drawer/DrawerCard.vue +19 -0
  16. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
  17. package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
  18. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
  19. package/components/Drawer/types.ts +1 -0
  20. package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
  21. package/components/LocaleSelector.vue +1 -1
  22. package/components/Markdown.vue +1 -1
  23. package/components/PopoverCard.vue +3 -3
  24. package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
  25. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
  26. package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
  27. package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
  28. package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
  29. package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
  30. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
  31. package/components/Resource/Detail/Cards.vue +27 -0
  32. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
  33. package/components/Resource/Detail/Masthead/index.vue +5 -0
  34. package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
  35. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
  36. package/components/Resource/Detail/ResourceRow.types.ts +14 -0
  37. package/components/Resource/Detail/ResourceRow.vue +23 -35
  38. package/components/Resource/Detail/StatusRow.vue +5 -2
  39. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
  40. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
  41. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  42. package/components/Resource/Detail/TitleBar/index.vue +41 -6
  43. package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
  44. package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
  45. package/components/ResourceDetail/Masthead/index.vue +1 -0
  46. package/components/ResourceDetail/Masthead/latest.vue +8 -1
  47. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  48. package/components/Setting.vue +1 -1
  49. package/components/SortableTable/index.vue +25 -0
  50. package/components/SortableTable/selection.js +25 -12
  51. package/components/SortableTable/sorting.js +1 -1
  52. package/components/Tabbed/Tab.vue +1 -0
  53. package/components/Tabbed/index.vue +29 -6
  54. package/components/Window/ContainerShell.vue +10 -13
  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/SeccompProfile.vue +113 -0
  70. package/components/form/Security.vue +244 -133
  71. package/components/form/__tests__/LabeledSelect.test.ts +1 -1
  72. package/components/form/__tests__/SeccompProfile.test.js +124 -0
  73. package/components/form/__tests__/Security.test.ts +125 -37
  74. package/components/formatter/Autoscaler.vue +2 -2
  75. package/components/formatter/FleetSummaryGraph.vue +4 -1
  76. package/components/nav/Group.vue +5 -0
  77. package/components/nav/Header.vue +3 -3
  78. package/components/nav/HeaderPageActionMenu.vue +1 -1
  79. package/components/nav/NamespaceFilter.vue +6 -6
  80. package/components/nav/NotificationCenter/index.vue +1 -1
  81. package/components/nav/TopLevelMenu.helper.ts +41 -16
  82. package/components/nav/TopLevelMenu.vue +45 -25
  83. package/components/nav/WorkspaceSwitcher.vue +1 -1
  84. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
  85. package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
  86. package/components/templates/default.vue +0 -3
  87. package/components/templates/home.vue +0 -3
  88. package/components/templates/plain.vue +0 -3
  89. package/composables/useClickOutside.ts +1 -1
  90. package/config/product/explorer.js +1 -2
  91. package/config/types.js +41 -8
  92. package/detail/__tests__/workload.test.ts +8 -16
  93. package/detail/catalog.cattle.io.app.vue +6 -0
  94. package/detail/fleet.cattle.io.cluster.vue +6 -0
  95. package/detail/workload/index.vue +7 -109
  96. package/edit/__tests__/projectsecret.test.ts +42 -0
  97. package/edit/auth/__tests__/oidc.test.ts +50 -0
  98. package/edit/auth/oidc.vue +68 -44
  99. package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
  100. package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
  101. package/edit/projectsecret.vue +29 -0
  102. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
  103. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
  104. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
  105. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
  106. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
  107. package/edit/workload/__tests__/index.test.ts +122 -85
  108. package/edit/workload/index.vue +48 -29
  109. package/edit/workload/mixins/workload.js +85 -32
  110. package/list/catalog.cattle.io.clusterrepo.vue +1 -1
  111. package/list/projectsecret.vue +2 -2
  112. package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
  113. package/machine-config/amazonec2.vue +2 -2
  114. package/machine-config/vmwarevsphere.vue +58 -4
  115. package/mixins/__tests__/brand.spec.ts +18 -13
  116. package/mixins/__tests__/chart.test.ts +63 -0
  117. package/mixins/chart.js +56 -51
  118. package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
  119. package/models/__tests__/workload.test.ts +333 -0
  120. package/models/catalog.cattle.io.app.js +8 -0
  121. package/models/pod.js +14 -0
  122. package/models/secret.js +1 -1
  123. package/models/workload.js +93 -27
  124. package/package.json +4 -4
  125. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
  126. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  127. package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
  128. package/pages/c/_cluster/fleet/index.vue +18 -12
  129. package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
  130. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  131. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  132. package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
  133. package/plugins/dashboard-store/actions.js +9 -8
  134. package/plugins/dashboard-store/resource-class.js +97 -1
  135. package/plugins/steve/__tests__/revision.test.ts +84 -0
  136. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
  137. package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
  138. package/plugins/steve/mutations.js +9 -0
  139. package/plugins/steve/revision.ts +26 -0
  140. package/plugins/steve/steve-pagination-utils.ts +6 -5
  141. package/plugins/steve/subscribe.js +211 -51
  142. package/plugins/subscribe-events.ts +2 -2
  143. package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
  144. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
  145. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
  146. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
  147. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
  148. package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
  149. package/rancher-components/Pill/index.ts +4 -0
  150. package/rancher-components/RcButton/RcButton.test.ts +53 -9
  151. package/rancher-components/RcButton/RcButton.vue +217 -25
  152. package/rancher-components/RcButton/types.ts +27 -1
  153. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
  154. package/rancher-components/RcDropdown/types.ts +3 -3
  155. package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
  156. package/rancher-components/RcIcon/RcIcon.vue +9 -6
  157. package/rancher-components/RcIcon/types.ts +13 -9
  158. package/rancher-components/utils/status.test.ts +10 -15
  159. package/rancher-components/utils/status.ts +5 -6
  160. package/store/aws.js +18 -12
  161. package/store/index.js +4 -8
  162. package/store/type-map.utils.ts +1 -1
  163. package/types/kube/kube-api.ts +29 -3
  164. package/types/rancher/steve.api.ts +40 -0
  165. package/types/shell/index.d.ts +99 -0
  166. package/types/store/dashboard-store.types.ts +29 -7
  167. package/types/store/pagination.types.ts +1 -0
  168. package/types/store/subscribe-events.types.ts +1 -0
  169. package/utils/__tests__/azure.test.ts +56 -0
  170. package/utils/__tests__/back-off.test.ts +364 -245
  171. package/utils/__tests__/error.test.ts +44 -0
  172. package/utils/__tests__/fleet.test.ts +8 -1
  173. package/utils/__tests__/pagination-wrapper.test.ts +167 -0
  174. package/utils/__tests__/version.test.ts +55 -1
  175. package/utils/azure.js +12 -0
  176. package/utils/back-off.ts +302 -69
  177. package/utils/cspAdaptor.ts +32 -14
  178. package/utils/dynamic-content/__tests__/index.test.ts +1 -1
  179. package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
  180. package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
  181. package/utils/dynamic-content/index.ts +1 -6
  182. package/utils/dynamic-content/new-release.ts +5 -3
  183. package/utils/dynamic-content/types.d.ts +0 -1
  184. package/utils/error.js +9 -0
  185. package/utils/fleet.ts +2 -2
  186. package/utils/inactivity.ts +2 -3
  187. package/utils/pagination-wrapper.ts +101 -17
  188. package/utils/validators/formRules/index.ts +3 -0
  189. package/utils/version.js +38 -0
  190. package/components/auth/AzureWarning.vue +0 -77
  191. /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
  192. /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
@@ -1,9 +1,15 @@
1
1
  <script>
2
- import { RadioGroup } from '@components/Form/Radio';
2
+ import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
3
3
  import { LabeledInput } from '@components/Form/LabeledInput';
4
- import { _VIEW } from '@shell/config/query-params';
4
+ import { _VIEW, _EDIT } from '@shell/config/query-params';
5
5
  import { mapGetters } from 'vuex';
6
6
  import LabeledSelect from '@shell/components/form/LabeledSelect';
7
+ import SeccompProfile from '@shell/components/form/SeccompProfile';
8
+
9
+ export const FORM_TYPES = {
10
+ CONTAINER: 'container',
11
+ POD: 'pod'
12
+ };
7
13
 
8
14
  const allCapabilities = ['ALL',
9
15
  'AUDIT_CONTROL',
@@ -48,9 +54,10 @@ export default {
48
54
  emits: ['update:value'],
49
55
 
50
56
  components: {
51
- RadioGroup,
57
+ Checkbox,
52
58
  LabeledInput,
53
- LabeledSelect
59
+ LabeledSelect,
60
+ SeccompProfile
54
61
  },
55
62
 
56
63
  props: {
@@ -60,31 +67,45 @@ export default {
60
67
  return {};
61
68
  }
62
69
  },
63
- mode: { type: String, default: 'edit' }
70
+ formType: { type: String, default: FORM_TYPES.CONTAINER },
71
+ mode: { type: String, default: _EDIT },
72
+ seccompProfileTypes: {
73
+ type: Array,
74
+ default: () => []
75
+ }
64
76
  },
65
77
 
66
78
  data() {
67
- return {
68
- privileged: this.value.privileged || false,
69
- allowPrivilegeEscalation: this.value.allowPrivilegeEscalation || false,
70
- allCapabilities,
71
- runAsNonRoot: this.value.runAsNonRoot || false,
72
- readOnlyRootFilesystem: this.value.readOnlyRootFilesystem || false,
73
- add: [],
74
- drop: [],
75
- runAsUser: this.value.runAsUser
76
- };
77
- },
78
-
79
- created() {
80
- const { capabilities = {} } = this.value;
81
- const {
82
- add = [],
83
- drop = []
84
- } = capabilities;
85
-
86
- this.add = add;
87
- this.drop = drop;
79
+ if (this.formType === FORM_TYPES.CONTAINER) {
80
+ return {
81
+ allCapabilities,
82
+ securityContext: {
83
+ ...this.value,
84
+ privileged: this.value.privileged || false,
85
+ allowPrivilegeEscalation: this.value.allowPrivilegeEscalation || false,
86
+ runAsNonRoot: this.value.runAsNonRoot || false,
87
+ readOnlyRootFilesystem: this.value.readOnlyRootFilesystem || false,
88
+ capabilities: this.value.capabilities || { add: [], drop: [] },
89
+ runAsUser: this.value.runAsUser,
90
+ seccompProfile: this.value.seccompProfile,
91
+ fsGroup: this.value.fsGroup,
92
+ },
93
+ afterPrivilegedTickedMessage: '',
94
+ FORM_TYPES,
95
+ };
96
+ } else {
97
+ return {
98
+ securityContext: {
99
+ ...this.value,
100
+ fsGroup: this.value.fsGroup,
101
+ runAsNonRoot: this.value.runAsNonRoot || false,
102
+ runAsUser: this.value.runAsUser,
103
+ seccompProfile: this.value.seccompProfile,
104
+ },
105
+ afterPrivilegedTickedMessage: '',
106
+ FORM_TYPES,
107
+ };
108
+ }
88
109
  },
89
110
 
90
111
  computed: {
@@ -95,25 +116,38 @@ export default {
95
116
  ...mapGetters({ t: 'i18n/t' })
96
117
  },
97
118
 
98
- watch: {
99
- privileged(neu) {
100
- if (neu) {
101
- this.allowPrivilegeEscalation = true;
119
+ methods: {
120
+ focus() {
121
+ const firstFocusable = this.$refs.firstFocusable;
122
+
123
+ // First, try the preferred declarative approach using the ref.
124
+ if (firstFocusable && typeof firstFocusable.focus === 'function') {
125
+ firstFocusable.focus();
102
126
  }
103
- }
104
- },
127
+ },
105
128
 
106
- methods: {
107
129
  update() {
108
130
  const securityContext = {
109
- runAsNonRoot: this.runAsNonRoot,
110
- readOnlyRootFilesystem: this.readOnlyRootFilesystem,
111
- capabilities: { add: this.add, drop: this.drop },
112
- privileged: this.privileged,
113
- allowPrivilegeEscalation: this.allowPrivilegeEscalation,
114
- runAsUser: this.runAsUser,
131
+ ...this.value,
132
+ ...this.securityContext,
115
133
  };
116
134
 
135
+ if (securityContext.privileged) {
136
+ securityContext.allowPrivilegeEscalation = true;
137
+ delete securityContext.seccompProfile;
138
+ this.afterPrivilegedTickedMessage = this.t('workload.container.security.privileged.afterTick.true');
139
+ } else {
140
+ this.afterPrivilegedTickedMessage = this.t('workload.container.security.privileged.afterTick.false');
141
+ }
142
+
143
+ if (securityContext.fsGroup === '') {
144
+ delete securityContext.fsGroup;
145
+ }
146
+
147
+ if (securityContext.runAsUser === '') {
148
+ delete securityContext.runAsUser;
149
+ }
150
+
117
151
  this.$emit('update:value', securityContext);
118
152
  }
119
153
 
@@ -122,124 +156,201 @@ export default {
122
156
  </script>
123
157
 
124
158
  <template>
125
- <div>
126
- <div>
127
- <div class="row">
128
- <div
129
- data-testid="input-security-privileged"
130
- class="col span-6"
131
- >
132
- <RadioGroup
133
- v-model:value="privileged"
134
- name="privileged"
135
- :label="t('workload.container.security.privileged.label')"
136
- :options="[false,true]"
137
- :labels="[t('workload.container.security.privileged.false'), t('workload.container.security.privileged.true')]"
159
+ <div
160
+ v-if="formType === FORM_TYPES.POD"
161
+ >
162
+ <div class="row">
163
+ <div
164
+ class="col span-6"
165
+ data-testid="input-security-fsGroup"
166
+ >
167
+ <fieldset>
168
+ <legend
169
+ id="pod-fs-group-label"
170
+ class="h3-legend"
171
+ >
172
+ {{ t('workload.container.security.podFsGroup') }}
173
+ </legend>
174
+ <LabeledInput
175
+ ref="firstFocusable"
176
+ v-model:value.number="securityContext.fsGroup"
177
+ type="number"
138
178
  :mode="mode"
179
+ :label="t('workload.container.security.fsGroup')"
139
180
  @update:value="update"
140
181
  />
141
- </div>
142
- <div
143
- v-if="!privileged"
144
- data-testid="input-security-allowPrivilegeEscalation"
145
- class="col span-6"
146
- >
147
- <RadioGroup
148
- v-model:value="allowPrivilegeEscalation"
149
- name="allowPrivilegeEscalation"
150
- :label="t('workload.container.security.allowPrivilegeEscalation.label')"
151
- :disabled="privileged"
152
- :options="[false,true]"
153
- :labels="[t('workload.container.security.allowPrivilegeEscalation.false'), t('workload.container.security.allowPrivilegeEscalation.true')]"
182
+ </fieldset>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ <div
187
+ v-if="formType === FORM_TYPES.CONTAINER"
188
+ >
189
+ <div
190
+ class="row"
191
+ >
192
+ <div class="col span-6">
193
+ <fieldset>
194
+ <legend class="h3-legend">
195
+ {{ t('workload.container.security.privileged.title') }}
196
+ </legend>
197
+ <div data-testid="input-security-privileged">
198
+ <Checkbox
199
+ ref="firstFocusable"
200
+ v-model:value="securityContext.privileged"
201
+ :mode="mode"
202
+ label-key="workload.container.security.privileged.true"
203
+ aria-describedby="privilege-help"
204
+ @update:value="update"
205
+ />
206
+ <p
207
+ id="privilege-help"
208
+ class="sr-only"
209
+ aria-hidden="true"
210
+ >
211
+ {{ t('workload.container.security.privileged.help') }}
212
+ </p>
213
+ </div>
214
+ </fieldset>
215
+ </div>
216
+ <p
217
+ id="after-privileged-ticked"
218
+ role="status"
219
+ aria-live="polite"
220
+ class="sr-only"
221
+ >
222
+ {{ afterPrivilegedTickedMessage }}
223
+ </p>
224
+
225
+ <div
226
+ v-if="!securityContext.privileged"
227
+ class="col span-6"
228
+ >
229
+ <fieldset>
230
+ <legend class="h3-legend">
231
+ {{ t('workload.container.security.allowPrivilegeEscalation.title') }}
232
+ </legend>
233
+ <div data-testid="input-security-allowPrivilegeEscalation">
234
+ <Checkbox
235
+ v-model:value="securityContext.allowPrivilegeEscalation"
236
+ :mode="mode"
237
+ label-key="workload.container.security.allowPrivilegeEscalation.true"
238
+ @update:value="update"
239
+ />
240
+ </div>
241
+ </fieldset>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ <div v-if="!securityContext.privileged && formType === FORM_TYPES.CONTAINER || formType === FORM_TYPES.POD">
246
+ <div class="spacer" />
247
+ <SeccompProfile
248
+ v-model:value="securityContext.seccompProfile"
249
+ :mode="mode"
250
+ :seccomp-profile-types="seccompProfileTypes"
251
+ :title="t('workload.container.security.seccompProfile.container')"
252
+ @update:value="update"
253
+ />
254
+ </div>
255
+ <div>
256
+ <div class="spacer" />
257
+ <div class="row">
258
+ <div class="col span-6">
259
+ <fieldset>
260
+ <legend class="h3-legend">
261
+ {{ t('workload.container.security.runAsNonRoot.title') }}
262
+ </legend>
263
+ <div data-testid="input-security-runasNonRoot">
264
+ <Checkbox
265
+ v-model:value="securityContext.runAsNonRoot"
266
+ :mode="mode"
267
+ label-key="workload.container.security.runAsNonRoot.true"
268
+ @update:value="update"
269
+ />
270
+ </div>
271
+ </fieldset>
272
+ </div>
273
+ <div
274
+ class="col span-6"
275
+ data-testid="input-security-runAsUser"
276
+ >
277
+ <fieldset>
278
+ <legend
279
+ id="run-as-user-label"
280
+ class="h3-legend"
281
+ >
282
+ {{ t('workload.container.security.runAsUser.title') }}
283
+ </legend>
284
+ <LabeledInput
285
+ v-model:value.number="securityContext.runAsUser"
286
+ :label="t('workload.container.security.runAsUser.label')"
154
287
  :mode="mode"
155
288
  @update:value="update"
156
289
  />
157
- </div>
290
+ </fieldset>
158
291
  </div>
159
292
  </div>
293
+ </div>
294
+ <div v-if="formType === FORM_TYPES.CONTAINER">
160
295
  <div class="spacer" />
161
-
162
- <div>
296
+ <div class="row">
297
+ <div class="col span-6">
298
+ <fieldset>
299
+ <legend class="h3-legend">
300
+ {{ t('workload.container.security.readOnlyRootFilesystem.title') }}
301
+ </legend>
302
+ <div data-testid="input-security-readOnlyRootFilesystem">
303
+ <Checkbox
304
+ v-model:value="securityContext.readOnlyRootFilesystem"
305
+ :mode="mode"
306
+ label-key="workload.container.security.readOnlyRootFilesystem.true"
307
+ @update:value="update"
308
+ />
309
+ </div>
310
+ </fieldset>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ <div v-if="formType === FORM_TYPES.CONTAINER">
315
+ <div class="spacer" />
316
+ <fieldset>
317
+ <legend class="h3-legend">
318
+ {{ t('workload.container.security.capabilities.title') }}
319
+ </legend>
163
320
  <div class="row">
164
321
  <div
165
- data-testid="input-security-runasNonRoot"
322
+ data-testid="input-security-add"
166
323
  class="col span-6"
167
324
  >
168
- <RadioGroup
169
- v-model:value="runAsNonRoot"
170
- name="runasNonRoot"
171
- :label="t('workload.container.security.runAsNonRoot.label')"
172
- :options="[false, true]"
173
- :labels="[t('workload.container.security.runAsNonRoot.false'), t('workload.container.security.runAsNonRoot.true')]"
325
+ <LabeledSelect
326
+ v-model:value="securityContext.capabilities.add"
327
+ :taggable="true"
328
+ :close-on-select="false"
174
329
  :mode="mode"
330
+ :multiple="true"
331
+ :label="t('workload.container.security.capabilities.add')"
332
+ :options="allCapabilities"
333
+ :disabled="mode==='view'"
175
334
  @update:value="update"
176
335
  />
177
336
  </div>
178
337
  <div
179
- data-testid="input-security-readOnlyRootFilesystem"
338
+ data-testid="input-security-drop"
180
339
  class="col span-6"
181
340
  >
182
- <RadioGroup
183
- v-model:value="readOnlyRootFilesystem"
184
- name="readOnlyRootFilesystem"
185
- :label="t('workload.container.security.readOnlyRootFilesystem.label')"
186
- :options="[false, true]"
187
- :labels="[t('workload.container.security.readOnlyRootFilesystem.false'), t('workload.container.security.readOnlyRootFilesystem.true')]"
341
+ <LabeledSelect
342
+ v-model:value="securityContext.capabilities.drop"
343
+ :close-on-select="false"
344
+ :taggable="true"
345
+ :multiple="true"
188
346
  :mode="mode"
347
+ :label="t('workload.container.security.capabilities.drop')"
348
+ :options="allCapabilities"
349
+ :disabled="mode==='view'"
189
350
  @update:value="update"
190
351
  />
191
352
  </div>
192
353
  </div>
193
- </div>
194
- <div class="spacer" />
195
-
196
- <div
197
- data-testid="input-security-runAsUser"
198
- class="row mb-10"
199
- >
200
- <div class="col span-6">
201
- <LabeledInput
202
- v-model:value.number="runAsUser"
203
- :label="t('workload.container.security.runAsUser')"
204
- :mode="mode"
205
- @update:value="update"
206
- />
207
- </div>
208
- </div>
209
-
210
- <div class="row">
211
- <div
212
- data-testid="input-security-add"
213
- class="col span-6"
214
- >
215
- <LabeledSelect
216
- v-model:value="add"
217
- :taggable="true"
218
- :close-on-select="false"
219
- :mode="mode"
220
- :multiple="true"
221
- :label="t('workload.container.security.addCapabilities')"
222
- :options="allCapabilities"
223
- :disabled="mode==='view'"
224
- @update:value="update"
225
- />
226
- </div>
227
- <div
228
- data-testid="input-security-drop"
229
- class="col span-6"
230
- >
231
- <LabeledSelect
232
- v-model:value="drop"
233
- :close-on-select="false"
234
- :taggable="true"
235
- :multiple="true"
236
- :mode="mode"
237
- :label="t('workload.container.security.dropCapabilities')"
238
- :options="allCapabilities"
239
- :disabled="mode==='view'"
240
- @update:value="update"
241
- />
242
- </div>
243
- </div>
354
+ </fieldset>
244
355
  </div>
245
356
  </template>
@@ -253,7 +253,7 @@ describe('component: LabeledSelect', () => {
253
253
  // in the current architecture of the component
254
254
  // screen readers won't pick up the default "Select option" aria-label
255
255
  // from the library
256
- expect(vSelectInput.attributes('aria-label')).toBe('-');
256
+ expect(vSelectInput.attributes('aria-label')).toBe(`- ${ value }`);
257
257
  });
258
258
 
259
259
  it('pressing space key while focused on search should not prevent event propagation', async() => {
@@ -0,0 +1,124 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import SeccompProfile from '@shell/components/form/SeccompProfile.vue';
3
+
4
+ describe('component: SeccompProfile', () => {
5
+ // A mock for the i18n t function
6
+ const mockT = (key) => key;
7
+
8
+ const defaultMountOptions = {
9
+ props: {
10
+ mode: 'mode',
11
+ title: 'Test Title',
12
+ seccompProfileTypes: [
13
+ { label: 'None', value: 'None' },
14
+ { label: 'RuntimeDefault', value: 'RuntimeDefault' },
15
+ { label: 'Localhost', value: 'Localhost' },
16
+ { label: 'Unconfined', value: 'Unconfined' },
17
+ ]
18
+ },
19
+ global: { mocks: { $store: { getters: { 'i18n/t': mockT } } } }
20
+ };
21
+
22
+ it('should initialize with type "None" when no seccompProfile is provided', () => {
23
+ const wrapper = mount(SeccompProfile, {
24
+ ...defaultMountOptions,
25
+ props: {
26
+ ...defaultMountOptions.props,
27
+ value: {} // Empty securityContext
28
+ }
29
+ });
30
+
31
+ expect(wrapper.vm.type).toBe('None');
32
+ });
33
+
34
+ it('should initialize with the correct type when seccompProfile is provided', () => {
35
+ const wrapper = mount(SeccompProfile, {
36
+ ...defaultMountOptions,
37
+ props: {
38
+ ...defaultMountOptions.props,
39
+ value: { type: 'Unconfined' }
40
+ }
41
+ });
42
+
43
+ expect(wrapper.vm.type).toBe('Unconfined');
44
+ });
45
+
46
+ it('should initialize with localhostProfile when type is Localhost', () => {
47
+ const wrapper = mount(SeccompProfile, {
48
+ ...defaultMountOptions,
49
+ props: {
50
+ ...defaultMountOptions.props,
51
+ value: { type: 'Localhost', localhostProfile: '/my/path' }
52
+ }
53
+ });
54
+
55
+ expect(wrapper.vm.type).toBe('Localhost');
56
+ expect(wrapper.vm.localhostProfile).toBe('/my/path');
57
+ // The localhost input should be visible
58
+ expect(wrapper.find('[data-testid="seccomp-localhost-input"]').exists()).toBe(true);
59
+ });
60
+
61
+ it('should emit an update event to remove seccompProfile when type is changed to "None"', async() => {
62
+ const wrapper = mount(SeccompProfile, {
63
+ ...defaultMountOptions,
64
+ props: {
65
+ ...defaultMountOptions.props,
66
+ value: { seccompProfile: { type: 'Unconfined' } }
67
+ }
68
+ });
69
+
70
+ // Simulate changing select to 'None'
71
+ wrapper.vm.type = 'None';
72
+ await wrapper.vm.update();
73
+
74
+ const emitted = wrapper.emitted('update:value');
75
+
76
+ expect(emitted).toHaveLength(1);
77
+ // It should remove seccompProfile
78
+ expect(emitted[0][0]).toBeUndefined();
79
+ });
80
+
81
+ it('should emit an update event when the type is changed', async() => {
82
+ const wrapper = mount(SeccompProfile, {
83
+ ...defaultMountOptions,
84
+ props: {
85
+ ...defaultMountOptions.props,
86
+ value: { seccompProfile: { type: 'RuntimeDefault' } }
87
+ }
88
+ });
89
+
90
+ // Simulate changing the select
91
+ wrapper.vm.type = 'Unconfined';
92
+ await wrapper.vm.update();
93
+
94
+ const emitted = wrapper.emitted('update:value');
95
+
96
+ expect(emitted).toHaveLength(1);
97
+ expect(emitted[0][0]).toStrictEqual({ type: 'Unconfined' });
98
+ });
99
+
100
+ it('should emit an update event with localhostProfile when type is Localhost', async() => {
101
+ const wrapper = mount(SeccompProfile, {
102
+ ...defaultMountOptions,
103
+ props: {
104
+ ...defaultMountOptions.props,
105
+ value: { seccompProfile: { type: 'RuntimeDefault' } }
106
+ }
107
+ });
108
+
109
+ // Simulate changing to Localhost and filling the input
110
+ wrapper.vm.type = 'Localhost';
111
+ wrapper.vm.localhostProfile = '/my/local/path';
112
+ await wrapper.vm.update();
113
+
114
+ const emitted = wrapper.emitted('update:value');
115
+
116
+ expect(emitted).toHaveLength(1);
117
+ expect(emitted[0][0]).toStrictEqual(
118
+ {
119
+ type: 'Localhost',
120
+ localhostProfile: '/my/local/path'
121
+ }
122
+ );
123
+ });
124
+ });