@rancher/shell 0.3.7 → 0.3.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 (48) hide show
  1. package/assets/translations/en-us.yaml +55 -14
  2. package/babel.config.js +17 -4
  3. package/components/CodeMirror.vue +146 -14
  4. package/components/ContainerResourceLimit.vue +14 -1
  5. package/components/CruResource.vue +21 -5
  6. package/components/ExplorerProjectsNamespaces.vue +5 -1
  7. package/components/GroupPanel.vue +57 -0
  8. package/components/Inactivity.vue +229 -0
  9. package/components/YamlEditor.vue +2 -2
  10. package/components/form/ArrayList.vue +1 -1
  11. package/components/form/KeyValue.vue +34 -1
  12. package/components/form/MatchExpressions.vue +120 -21
  13. package/components/form/NodeAffinity.vue +54 -4
  14. package/components/form/PodAffinity.vue +160 -47
  15. package/components/form/Tolerations.vue +40 -4
  16. package/components/form/__tests__/ArrayList.test.ts +3 -3
  17. package/components/form/__tests__/MatchExpressions.test.ts +1 -1
  18. package/components/nav/Header.vue +2 -0
  19. package/config/settings.ts +10 -1
  20. package/core/plugins-loader.js +0 -2
  21. package/creators/app/files/.gitignore +73 -0
  22. package/creators/app/init +1 -0
  23. package/edit/configmap.vue +33 -6
  24. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +326 -0
  25. package/edit/provisioning.cattle.io.cluster/index.vue +1 -0
  26. package/edit/provisioning.cattle.io.cluster/rke2.vue +63 -15
  27. package/edit/workload/mixins/workload.js +12 -4
  28. package/layouts/blank.vue +4 -0
  29. package/layouts/default.vue +3 -0
  30. package/layouts/home.vue +4 -1
  31. package/layouts/plain.vue +4 -1
  32. package/mixins/chart.js +1 -1
  33. package/models/batch.cronjob.js +18 -3
  34. package/models/provisioning.cattle.io.cluster.js +24 -0
  35. package/models/workload.js +1 -1
  36. package/package.json +2 -3
  37. package/pages/auth/login.vue +1 -0
  38. package/pages/c/_cluster/explorer/index.vue +1 -4
  39. package/pages/c/_cluster/settings/performance.vue +61 -7
  40. package/pages/prefs.vue +18 -2
  41. package/pkg/vue.config.js +0 -1
  42. package/plugins/codemirror.js +158 -0
  43. package/public/index.html +1 -1
  44. package/store/index.js +36 -21
  45. package/types/shell/index.d.ts +20 -1
  46. package/utils/create-yaml.js +105 -8
  47. package/utils/settings.ts +12 -0
  48. package/vue.config.js +2 -2
@@ -1249,7 +1249,7 @@ cluster:
1249
1249
  memory: Memory
1250
1250
  disk: Disk
1251
1251
  image: Image
1252
- network:
1252
+ network:
1253
1253
  title: Networks
1254
1254
  network: Network
1255
1255
  addNetwork: Add Network
@@ -1611,6 +1611,27 @@ cluster:
1611
1611
  additionalManifest:
1612
1612
  title: Additional Manifest
1613
1613
  tooltip: 'Additional Kubernetes Manifest YAML to be applied to the cluster on startup.'
1614
+ agentConfig:
1615
+ tabs:
1616
+ cluster: Cluster Agent
1617
+ fleet: Fleet Agent
1618
+ groups:
1619
+ deploymentLabels: Deployment Labels
1620
+ selector: Selector
1621
+ podAffinity: Affinity
1622
+ podTolerations: Tolerations
1623
+ podRequestsAndLimits: Requests and Limits
1624
+ subGroups:
1625
+ podAffinityAnti: Pod Affinity/Anti-Affinity
1626
+ nodeAffinity: Node Affinity
1627
+ banners:
1628
+ advanced: These are advanced configuration options. Generally, they should be left as-is.
1629
+ tolerations: Additional Pod Tolerations will be added to the default Tolerations applied by Rancher.
1630
+ limits: Pod Requests and Limits do not have a default configuration.
1631
+ windowsCompatibility: "We do not recommended removing the Node Affinity rule that prevents the <b>agent</b> from running on Windows nodes as this is not a supported configuration."
1632
+ affinity:
1633
+ default: Use default affinity rules defined by Rancher
1634
+ custom: Use custom affinity rules
1614
1635
  advanced:
1615
1636
  argInfo:
1616
1637
  title: Additional Kubelet Args
@@ -1827,7 +1848,7 @@ cluster:
1827
1848
  option: Default - RKE2 Embedded
1828
1849
  defaultPodSecurityAdmissionConfigurationTemplateName:
1829
1850
  label: Pod Security Admission Configuration Template
1830
- option:
1851
+ option:
1831
1852
  none: (None)
1832
1853
  default: Default - RKE2 Embedded
1833
1854
  cisProfile:
@@ -2202,16 +2223,16 @@ fleet:
2202
2223
  error: Error
2203
2224
  ready: Ready
2204
2225
  errors: Errors
2205
- workspaces:
2226
+ workspaces:
2206
2227
  tabs:
2207
2228
  restrictions: Allowed Target Namespaces
2208
2229
  timeout: Workspace creation timeout. It's possible the workspace was created. We suggest checking the workspace page before trying to create another.
2209
- restrictions:
2230
+ restrictions:
2210
2231
  addTitle: 'allowedTargetNamespaces'
2211
2232
  addLabel: Add
2212
2233
  banner: "{count, plural,
2213
2234
  =0 { Adding namespaces here will create a GitRepoRestriction. }
2214
- other { Only the Git Repo Restriction's <code>allowedTargetNamespaces</code> is managed here. You can make additional changes to the Git Repo Restriction}
2235
+ other { Only the Git Repo Restriction's <code>allowedTargetNamespaces</code> is managed here. You can make additional changes to the Git Repo Restriction}
2215
2236
  }"
2216
2237
  footer:
2217
2238
  docs: Docs
@@ -2260,7 +2281,7 @@ gatekeeperIndex:
2260
2281
  unavailable: OPA + Gatekeeper is not available in the system-charts catalog.
2261
2282
  violations: Violations
2262
2283
 
2263
- gatekeeperInstall:
2284
+ gatekeeperInstall:
2264
2285
  auditInterval: Auto Interval
2265
2286
  constraintViolationsLimit: Constraint Violations Limit
2266
2287
  runtimeDefaultSeccompProfile: Enable Runtime Default Seccomp Profile
@@ -3952,6 +3973,15 @@ podDisruptionBudget:
3952
3973
  maxUnavailable:
3953
3974
  label: Max. unavailable Pods
3954
3975
 
3976
+ inactivity:
3977
+ title: Session expiring
3978
+ titleExpired: Session expired
3979
+ banner: Your session is about to expire due to inactivity. Any unsaved changes will be lost.
3980
+ bannerExpired: Your session has expired in this tab due to inactivity.
3981
+ content: Click “Resume Session” to keep the session in this tab active or refresh the browser after the session has expired.
3982
+ contentExpired: To return to this page click “Refresh” below or refresh the browser.
3983
+ cta: Resume Session
3984
+ ctaExpired: Refresh
3955
3985
 
3956
3986
  # Rancher Extensions
3957
3987
  plugins:
@@ -4051,7 +4081,7 @@ plugins:
4051
4081
  podSecurityAdmission:
4052
4082
  name: Pod Security Admission
4053
4083
  description: Define the admission control mode you want to use for the pod security
4054
- banner:
4084
+ banner:
4055
4085
  modifications: 'Changing any template that is currently in use will cause an update to those live clusters the next time the cluster is updated'
4056
4086
  labels:
4057
4087
  enforce: Enforce
@@ -4065,7 +4095,7 @@ podSecurityAdmission:
4065
4095
  restricted: restricted
4066
4096
  version:
4067
4097
  placeholder: 'Version (default: latest)'
4068
- exemptions:
4098
+ exemptions:
4069
4099
  title: Exemptions
4070
4100
  description: Allow the creation of pods for specific Usernames, RuntimeClassNames, and Namespaces that would otherwise be prohibited due to the policies set above.
4071
4101
  placeholder: Enter a comma separated list of {psaExemptionsControl}
@@ -5186,7 +5216,7 @@ storageClass:
5186
5216
  warning: 'The {provisioner} in-tree plugin is deprecated: Find a CSI driver <a target="_blank" rel="noopener noreferrer nofollow" href="https://kubernetes-csi.github.io/docs/drivers.html">here.</a>'
5187
5217
  harvesterhci:
5188
5218
  title: Harvester (CSI)
5189
- warning:
5219
+ warning:
5190
5220
  unSatisfiesVersion: Please upgrade your Harvester CSI driver version to use this feature. (csi-driver >= v0.1.15)
5191
5221
  hostStorageClass:
5192
5222
  label: Host Storage Class
@@ -5935,15 +5965,19 @@ workload:
5935
5965
  antiAffinityTitle: Run pods on nodes without pods matching these selectors
5936
5966
  affinityOption: Affinity
5937
5967
  antiAffinityOption: Anti-Affinity
5968
+ matchFields:
5969
+ label: Fields
5938
5970
  matchExpressions:
5971
+ label: Expressions
5939
5972
  addRule: Add Rule
5940
5973
  doesNotExist: is not set
5941
5974
  exists: is set
5942
5975
  greaterThan: ">"
5943
5976
  in: in list
5944
- inNamespaces: "Pods in these namespaces:"
5977
+ inNamespaces: "Specific namespaces"
5945
5978
  key: Key
5946
5979
  lessThan: <
5980
+ matchType: Match Type
5947
5981
  namespaces: Namespaces
5948
5982
  notIn: not in list
5949
5983
  operator: Operator
@@ -5959,6 +5993,7 @@ workload:
5959
5993
  schedulingRules: Run pods on node(s) matching scheduling rules
5960
5994
  specificNode: Run pods on specific node(s)
5961
5995
  thisPodNamespace: This pod's namespace
5996
+ allNamespaces: All namespaces
5962
5997
  topologyKey:
5963
5998
  label: Topology Key
5964
5999
  placeholder: e.g. failure-domain.beta.kubernetes.io/zone
@@ -5986,7 +6021,7 @@ workload:
5986
6021
  effectOptions:
5987
6022
  all: All
5988
6023
  noExecute: NoExecute
5989
- noSchedule: "NoSchedule,"
6024
+ noSchedule: "NoSchedule"
5990
6025
  preferNoSchedule: PreferNoSchedule
5991
6026
  labelKey: Label Key
5992
6027
  operator: Operator
@@ -6131,8 +6166,6 @@ workload:
6131
6166
  pod: Pod
6132
6167
  containers: Containers
6133
6168
 
6134
-
6135
-
6136
6169
  ##############################
6137
6170
  # Model Properties
6138
6171
  ##############################
@@ -6778,7 +6811,7 @@ typeLabel:
6778
6811
  {count, plural,
6779
6812
  one { Cluster Registration Token }
6780
6813
  other { Cluster Registration Tokens }
6781
- }
6814
+ }
6782
6815
 
6783
6816
  action:
6784
6817
  clone: Clone
@@ -7002,6 +7035,14 @@ performance:
7002
7035
  label: Websocket Web Worker
7003
7036
  description: Updates to resources pushed to the UI come via WebSocket and are handled in the UI thread. Enable this option to handle cluster WebSocket updates in a Web Worker in a separate thread. This should help the responsiveness of the UI in systems where resources change often.
7004
7037
  checkboxLabel: Enable Advanced Websocket Web Worker
7038
+ inactivity:
7039
+ title: Inactivity
7040
+ checkboxLabel: Enable inactivity session expiration
7041
+ inputLabel: Inactivity timeout (minutes)
7042
+ information: To change the automatic logout behaviour, edit the authorisation and/or session token timeout values (<code>auth-user-session-ttl-minutes</code> and <code>auth-token-max-ttl-minutes</code>) in the Settings page.
7043
+ description: When enabled and the user is inactive past the specified timeout, the UI will no longer fresh page content and the user must reload the page to continue.
7044
+ authUserTTL: This timeout cannot be higher than the user session timeout auth-user-session-ttl-minutes, which is currently {current} minutes.
7045
+
7005
7046
 
7006
7047
  banner:
7007
7048
  label: Fixed Banners
package/babel.config.js CHANGED
@@ -1,5 +1,6 @@
1
- module.exports = {
2
- presets: [
1
+ module.exports = function(api) {
2
+ api.cache(true);
3
+ const presets = [
3
4
  [
4
5
  '@vue/cli-plugin-babel/preset',
5
6
  { useBuiltIns: false }
@@ -8,6 +9,18 @@ module.exports = {
8
9
  '@babel/preset-env',
9
10
  { targets: { node: 'current' } }
10
11
  ]
11
- ],
12
- env: { test: { presets: [['@babel/env', { targets: { node: 'current' } }]] } }
12
+ ];
13
+ const env = { test: { presets: [['@babel/env', { targets: { node: 'current' } }]] } };
14
+
15
+ const plugins = [];
16
+
17
+ if (process.env.NODE_ENV === 'test') {
18
+ plugins.push('transform-require-context');
19
+ }
20
+
21
+ return {
22
+ presets,
23
+ plugins,
24
+ env
25
+ };
13
26
  };
@@ -1,9 +1,18 @@
1
1
  <script>
2
2
  import { KEYMAP } from '@shell/store/prefs';
3
+ import { _EDIT, _VIEW } from '@shell/config/query-params';
3
4
 
4
5
  export default {
5
6
  name: 'CodeMirror',
6
7
  props: {
8
+ /**
9
+ * Sets the edit mode for Text Area.
10
+ * @values _EDIT, _VIEW
11
+ */
12
+ mode: {
13
+ type: String,
14
+ default: _EDIT
15
+ },
7
16
  value: {
8
17
  type: String,
9
18
  required: true,
@@ -11,14 +20,26 @@ export default {
11
20
  options: {
12
21
  type: Object,
13
22
  default: () => {}
23
+ },
24
+ asTextArea: {
25
+ type: Boolean,
26
+ default: false
14
27
  }
15
28
  },
16
29
 
17
30
  data() {
18
- return { loaded: false };
31
+ return {
32
+ codeMirrorRef: null,
33
+ loaded: false
34
+ };
19
35
  },
20
36
 
21
37
  computed: {
38
+
39
+ isDisabled() {
40
+ return this.mode === _VIEW;
41
+ },
42
+
22
43
  combinedOptions() {
23
44
  const theme = this.$store.getters['prefs/theme'];
24
45
  const keymap = this.$store.getters['prefs/get'](KEYMAP);
@@ -39,6 +60,12 @@ export default {
39
60
  showCursorWhenSelecting: true,
40
61
  };
41
62
 
63
+ if (this.asTextArea) {
64
+ out.lineNumbers = false;
65
+ out.tabSize = 0;
66
+ out.extraKeys = { Tab: false };
67
+ }
68
+
42
69
  Object.assign(out, this.options);
43
70
 
44
71
  return out;
@@ -58,35 +85,44 @@ export default {
58
85
  methods: {
59
86
 
60
87
  focus() {
61
- if ( this.$refs.cm ) {
62
- this.$refs.cm.codemirror.focus();
88
+ if ( this.$refs.codeMirrorRef ) {
89
+ this.$refs.codeMirrorRef.codemirror.focus();
63
90
  }
64
91
  },
65
92
 
66
93
  refresh() {
67
- if ( this.$refs.cm ) {
68
- this.$refs.cm.refresh();
94
+ if ( this.$refs.codeMirrorRef ) {
95
+ this.$refs.codeMirrorRef.refresh();
69
96
  }
70
97
  },
71
98
 
72
- onReady(cm) {
99
+ onReady(codeMirrorRef) {
73
100
  this.$nextTick(() => {
74
- cm.refresh();
101
+ codeMirrorRef.refresh();
102
+ this.codeMirrorRef = codeMirrorRef;
75
103
  });
76
- this.$emit('onReady', cm);
104
+ this.$emit('onReady', codeMirrorRef);
77
105
  },
78
106
 
79
107
  onInput(newCode) {
80
108
  this.$emit('onInput', newCode);
81
109
  },
82
110
 
83
- onChanges(cm, changes) {
84
- this.$emit('onChanges', cm, changes);
111
+ onChanges(codeMirrorRef, changes) {
112
+ this.$emit('onChanges', codeMirrorRef, changes);
113
+ },
114
+
115
+ onFocus() {
116
+ this.$emit('onFocus', true);
117
+ },
118
+
119
+ onBlur() {
120
+ this.$emit('onFocus', false);
85
121
  },
86
122
 
87
123
  updateValue(value) {
88
- if ( this.$refs.cm ) {
89
- this.$refs.cm.codemirror.doc.setValue(value);
124
+ if ( this.$refs.codeMirrorRef ) {
125
+ this.$refs.codeMirrorRef.codemirror.doc.setValue(value);
90
126
  }
91
127
  }
92
128
  }
@@ -95,15 +131,21 @@ export default {
95
131
 
96
132
  <template>
97
133
  <client-only placeholder=" Loading...">
98
- <div class="code-mirror">
134
+ <div
135
+ class="code-mirror"
136
+ :class="{['as-text-area']: asTextArea}"
137
+ >
99
138
  <codemirror
100
139
  v-if="loaded"
101
- ref="cm"
140
+ ref="codeMirrorRef"
102
141
  :value="value"
103
142
  :options="combinedOptions"
143
+ :disabled="isDisabled"
104
144
  @ready="onReady"
105
145
  @input="onInput"
106
146
  @changes="onChanges"
147
+ @focus="onFocus"
148
+ @blur="onBlur"
107
149
  />
108
150
  <div v-else>
109
151
  Loading...
@@ -120,5 +162,95 @@ export default {
120
162
  height: initial;
121
163
  background: none
122
164
  }
165
+
166
+ &.as-text-area {
167
+ min-height: 40px;
168
+ position: relative;
169
+ display: block;
170
+ box-sizing: border-box;
171
+ width: 100%;
172
+ padding: 10px;
173
+ background-color: var(--input-bg);
174
+ border-radius: var(--border-radius);
175
+ border: solid var(--border-width) var(--input-border);
176
+ color: var(--input-text);
177
+
178
+ &:hover {
179
+ border-color: var(--input-hover-border);
180
+ }
181
+
182
+ &:focus, &.focus {
183
+ outline: none;
184
+ border-color: var(--outline);
185
+ }
186
+
187
+ .CodeMirror-wrap pre {
188
+ word-break: break-word;
189
+ }
190
+ .CodeMirror-code {
191
+ .CodeMirror-line {
192
+ &:not(:last-child)>span:after,
193
+ .cm-markdown-single-trailing-space-odd:before,
194
+ .cm-markdown-single-trailing-space-even:before {
195
+ color: var(--muted);
196
+ position: absolute;
197
+ line-height: 20px;
198
+ pointer-events: none;
199
+ }
200
+ &:not(:last-child)>span:after {
201
+ content: '↵';
202
+ margin-left: 2px;
203
+ }
204
+ .cm-markdown-single-trailing-space-odd:before,
205
+ .cm-markdown-single-trailing-space-even:before {
206
+ font-weight: bold;
207
+ content: '·';
208
+ }
209
+ }
210
+ }
211
+
212
+ .CodeMirror-lines {
213
+ color: var(--input-text);
214
+ padding: 0;
215
+
216
+ .CodeMirror-line > span > span {
217
+ &.cm-overlay {
218
+ font-family: monospace;
219
+ }
220
+ }
221
+
222
+ .CodeMirror-line > span {
223
+ font-family: $body-font;
224
+ }
225
+ }
226
+
227
+ .CodeMirror-sizer {
228
+ min-height: 20px;
229
+ }
230
+
231
+ .CodeMirror-selected {
232
+ background-color: var(--primary) !important;
233
+ }
234
+
235
+ .CodeMirror-selectedtext {
236
+ color: var(--primary-text);
237
+ }
238
+
239
+ .CodeMirror-line::selection,
240
+ .CodeMirror-line > span::selection,
241
+ .CodeMirror-line > span > span::selection {
242
+ color: var(--primary-text);
243
+ background-color: var(--primary);
244
+ }
245
+
246
+ .CodeMirror-line::-moz-selection,
247
+ .CodeMirror-line > span::-moz-selection,
248
+ .CodeMirror-line > span > span::-moz-selection {
249
+ color: var(--primary-text);
250
+ background-color: var(--primary);
251
+ }
252
+ }
253
+
123
254
  }
255
+
124
256
  </style>
@@ -26,6 +26,11 @@ export default {
26
26
  }
27
27
  },
28
28
 
29
+ handleGpuLimit: {
30
+ type: Boolean,
31
+ default: true
32
+ },
33
+
29
34
  registerBeforeHook: {
30
35
  type: Function,
31
36
  default: null
@@ -180,6 +185,7 @@ export default {
180
185
  :input-exponent="-1"
181
186
  :output-modifier="true"
182
187
  :base-unit="t('suffix.cpus')"
188
+ data-testid="cpu-reservation"
183
189
  @input="updateLimits"
184
190
  />
185
191
  </span>
@@ -192,6 +198,7 @@ export default {
192
198
  :input-exponent="2"
193
199
  :increment="1024"
194
200
  :output-modifier="true"
201
+ data-testid="memory-reservation"
195
202
  @input="updateLimits"
196
203
  />
197
204
  </span>
@@ -207,6 +214,7 @@ export default {
207
214
  :input-exponent="-1"
208
215
  :output-modifier="true"
209
216
  :base-unit="t('suffix.cpus')"
217
+ data-testid="cpu-limit"
210
218
  @input="updateLimits"
211
219
  />
212
220
  </span>
@@ -219,11 +227,15 @@ export default {
219
227
  :input-exponent="2"
220
228
  :increment="1024"
221
229
  :output-modifier="true"
230
+ data-testid="memory-limit"
222
231
  @input="updateLimits"
223
232
  />
224
233
  </span>
225
234
  </div>
226
- <div class="row">
235
+ <div
236
+ v-if="handleGpuLimit"
237
+ class="row"
238
+ >
227
239
  <span class="col span-6">
228
240
  <UnitInput
229
241
  v-model="limitsGpu"
@@ -231,6 +243,7 @@ export default {
231
243
  :label="t('containerResourceLimit.limitsGpu')"
232
244
  :mode="mode"
233
245
  :base-unit="t('suffix.gpus')"
246
+ data-testid="gpu-limit"
234
247
  @input="updateLimits"
235
248
  />
236
249
  </span>
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import isEmpty from 'lodash/isEmpty';
3
- import { createYaml } from '@shell/utils/create-yaml';
3
+ import { createYamlWithOptions } from '@shell/utils/create-yaml';
4
4
  import { clone, get } from '@shell/utils/object';
5
5
  import { SCHEMA, NAMESPACE } from '@shell/config/types';
6
6
  import ResourceYaml from '@shell/components/ResourceYaml';
@@ -101,6 +101,11 @@ export default {
101
101
  default: null,
102
102
  },
103
103
 
104
+ preventEnterSubmit: {
105
+ type: Boolean,
106
+ default: false,
107
+ },
108
+
104
109
  applyHooks: {
105
110
  type: Function,
106
111
  default: null,
@@ -141,6 +146,11 @@ export default {
141
146
  description: {
142
147
  type: String,
143
148
  default: ''
149
+ },
150
+
151
+ yamlModifiers: {
152
+ type: Object,
153
+ default: undefined
144
154
  }
145
155
  },
146
156
 
@@ -296,7 +306,7 @@ export default {
296
306
  }
297
307
  },
298
308
 
299
- createResourceYaml() {
309
+ createResourceYaml(modifiers) {
300
310
  const resource = this.resource;
301
311
 
302
312
  if ( typeof this.generateYaml === 'function' ) {
@@ -306,7 +316,7 @@ export default {
306
316
  const schemas = this.$store.getters[`${ inStore }/all`](SCHEMA);
307
317
  const clonedResource = clone(resource);
308
318
 
309
- const out = createYaml(schemas, resource.type, clonedResource);
319
+ const out = createYamlWithOptions(schemas, resource.type, clonedResource, modifiers);
310
320
 
311
321
  return out;
312
322
  }
@@ -317,7 +327,7 @@ export default {
317
327
  await this.applyHooks(BEFORE_SAVE_HOOKS);
318
328
  }
319
329
 
320
- const resourceYaml = this.createResourceYaml();
330
+ const resourceYaml = this.createResourceYaml(this.yamlModifiers);
321
331
 
322
332
  this.resourceYaml = resourceYaml;
323
333
  this.showAsForm = false;
@@ -380,6 +390,12 @@ export default {
380
390
  throw new Error(`Could not create the new namespace. ${ e.message }`);
381
391
  }
382
392
  }
393
+ },
394
+
395
+ onPressEnter(event) {
396
+ if (this.preventEnterSubmit) {
397
+ event.preventDefault();
398
+ }
383
399
  }
384
400
  }
385
401
  };
@@ -398,7 +414,7 @@ export default {
398
414
  :is="(isView? 'div' : 'form')"
399
415
  class="create-resource-container cru__form"
400
416
  @submit.prevent
401
- @keydown.enter.prevent
417
+ @keydown.enter="onPressEnter($event)"
402
418
  >
403
419
  <div
404
420
  v-if="hasErrors"
@@ -11,6 +11,7 @@ import MoveModal from '@shell/components/MoveModal';
11
11
  import { defaultTableSortGenerationFn } from '@shell/components/ResourceTable.vue';
12
12
  import { NAMESPACE_FILTER_ALL_ORPHANS } from '@shell/utils/namespace-filter';
13
13
  import ResourceFetch from '@shell/mixins/resource-fetch';
14
+ import DOMPurify from 'dompurify';
14
15
 
15
16
  export default {
16
17
  name: 'ListProjectNamespace',
@@ -319,7 +320,10 @@ export default {
319
320
  const row = group.rows[0];
320
321
 
321
322
  if (row.isFake) {
322
- return this.t('resourceTable.groupLabel.project', { name: row.project?.nameDisplay }, true);
323
+ return DOMPurify.sanitize(
324
+ this.t('resourceTable.groupLabel.project', { name: row.project?.nameDisplay }, true),
325
+ { ALLOWED_TAGS: ['span'] }
326
+ );
323
327
  }
324
328
 
325
329
  return row.groupByLabel;
@@ -0,0 +1,57 @@
1
+ <script>
2
+ export default {
3
+ props: {
4
+ /**
5
+ * Label for the group
6
+ */
7
+ label: {
8
+ type: String,
9
+ default: null
10
+ },
11
+ /**
12
+ * The i18n key to use for the label
13
+ */
14
+ labelKey: {
15
+ type: String,
16
+ default: null
17
+ },
18
+ }
19
+ };
20
+ </script>
21
+ <template>
22
+ <div class="group-panel-outer">
23
+ <div class="group-panel">
24
+ <div class="group-panel-title">
25
+ <t
26
+ v-if="labelKey"
27
+ :k="labelKey"
28
+ />
29
+ <template v-else-if="label">
30
+ {{ label }}
31
+ </template>
32
+ </div>
33
+ <div class="group-panel-content">
34
+ <slot />
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </template>
39
+
40
+ <style lang="scss" scoped>
41
+ .group-panel {
42
+ border: 1px solid var(--border);
43
+ border-radius: 5px;
44
+ padding: 10px;
45
+ position: relative;
46
+ margin-top: 10px;
47
+ .group-panel-title {
48
+ position: absolute;
49
+ top: -7px;
50
+ background-color: var(--body-bg);
51
+ padding: 0 5px;
52
+ }
53
+ .group-panel-content {
54
+ position: relative;
55
+ }
56
+ }
57
+ </style>