@rancher/shell 0.3.24 → 0.3.25

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 (111) hide show
  1. package/assets/styles/themes/_light.scss +1 -1
  2. package/assets/translations/en-us.yaml +29 -7
  3. package/assets/translations/zh-hans.yaml +1 -1
  4. package/components/ClusterIconMenu.vue +143 -0
  5. package/components/CruResource.vue +7 -1
  6. package/components/ExplorerProjectsNamespaces.vue +11 -1
  7. package/components/FixedBanner.vue +17 -1
  8. package/components/Markdown.vue +1 -1
  9. package/components/Questions/__tests__/Yaml.test.ts +3 -2
  10. package/components/SortableTable/index.vue +3 -2
  11. package/components/auth/RoleDetailEdit.vue +15 -2
  12. package/components/auth/login/saml.vue +12 -1
  13. package/components/form/LabeledSelect.vue +12 -5
  14. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  15. package/components/form/Members/MembershipEditor.vue +6 -1
  16. package/components/form/__tests__/KeyValue.test.ts +6 -3
  17. package/components/form/__tests__/LabeledSelect.test.ts +18 -0
  18. package/components/formatter/PodsUsage.vue +11 -36
  19. package/components/formatter/PrincipalGroupBindings.vue +8 -5
  20. package/components/formatter/__tests__/PodsUsage.test.ts +36 -19
  21. package/components/nav/Group.vue +25 -27
  22. package/components/nav/Header.vue +12 -5
  23. package/components/nav/Pinned.vue +47 -0
  24. package/components/nav/TopLevelMenu.vue +233 -60
  25. package/components/nav/Type.vue +57 -3
  26. package/config/home-links.js +1 -1
  27. package/config/product/istio.js +15 -5
  28. package/config/router.js +3 -9
  29. package/config/table-headers.js +5 -6
  30. package/config/uiplugins.js +1 -0
  31. package/core/plugin-helpers.js +3 -0
  32. package/core/types.ts +6 -1
  33. package/creators/app/files/.vscode/settings.json +0 -1
  34. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +118 -0
  35. package/detail/autoscaling.horizontalpodautoscaler/index.vue +4 -4
  36. package/detail/provisioning.cattle.io.cluster.vue +7 -5
  37. package/edit/__tests__/management.cattle.io.clusterroletemplatebinding.test.ts +58 -0
  38. package/edit/__tests__/namespace.test.ts +5 -3
  39. package/edit/management.cattle.io.clusterroletemplatebinding.vue +3 -11
  40. package/edit/namespace.vue +8 -4
  41. package/edit/provisioning.cattle.io.cluster/Basics.vue +662 -0
  42. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +6 -0
  43. package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +13 -8
  44. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -2
  45. package/edit/provisioning.cattle.io.cluster/MemberRoles.vue +40 -0
  46. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +237 -0
  47. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +71 -23
  48. package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +52 -0
  49. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -142
  50. package/edit/provisioning.cattle.io.cluster/rke2.vue +194 -598
  51. package/edit/workload/storage/__tests__/Storage.test.ts +2 -2
  52. package/edit/workload/storage/persistentVolumeClaim/__tests__/persistentvolumeclaim.test.ts +36 -0
  53. package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +15 -7
  54. package/initialize/index.js +5 -5
  55. package/layouts/default.vue +6 -6
  56. package/layouts/home.vue +6 -2
  57. package/layouts/plain.vue +9 -2
  58. package/list/fleet.cattle.io.cluster.vue +2 -2
  59. package/list/management.cattle.io.feature.vue +1 -1
  60. package/machine-config/vmwarevsphere.vue +48 -7
  61. package/mixins/brand.js +0 -8
  62. package/mixins/child-hook.js +2 -2
  63. package/mixins/create-edit-view/impl.js +3 -3
  64. package/models/__tests__/management.cattle.io.node.ts +96 -0
  65. package/models/__tests__/node.ts +74 -0
  66. package/models/cluster/node.js +6 -5
  67. package/models/cluster.x-k8s.io.machinedeployment.js +2 -2
  68. package/models/management.cattle.io.cluster.js +22 -1
  69. package/models/management.cattle.io.clusterroletemplatebinding.js +3 -3
  70. package/models/management.cattle.io.globalrole.js +17 -2
  71. package/models/management.cattle.io.node.js +6 -4
  72. package/models/management.cattle.io.projectroletemplatebinding.js +3 -3
  73. package/models/management.cattle.io.roletemplate.js +17 -2
  74. package/package.json +2 -6
  75. package/pages/about.vue +2 -0
  76. package/pages/auth/setup.vue +5 -4
  77. package/pages/c/_cluster/monitoring/index.vue +8 -3
  78. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +9 -66
  79. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +182 -0
  80. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +15 -32
  81. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +8 -46
  82. package/pages/c/_cluster/uiplugins/index.vue +64 -64
  83. package/pages/diagnostic.vue +0 -39
  84. package/pages/home.vue +1 -1
  85. package/plugins/dashboard-store/normalize.js +4 -4
  86. package/plugins/int-number.js +5 -2
  87. package/plugins/positive-int-number.js +19 -0
  88. package/plugins/steve/__tests__/getters.spec.ts +15 -0
  89. package/plugins/steve/getters.js +22 -10
  90. package/rancher-components/Form/LabeledInput/LabeledInput.vue +0 -8
  91. package/rancher-components/Form/Radio/RadioButton.test.ts +3 -7
  92. package/store/index.js +4 -0
  93. package/store/prefs.js +1 -0
  94. package/types/shell/index.d.ts +13 -4
  95. package/utils/__tests__/cluster.test.ts +55 -0
  96. package/utils/__tests__/object.test.ts +21 -2
  97. package/utils/cluster.js +47 -1
  98. package/utils/object.js +12 -5
  99. package/utils/validators/formRules/__tests__/index.test.ts +13 -1
  100. package/utils/validators/formRules/index.ts +4 -0
  101. package/utils/validators/role-template.js +9 -1
  102. package/utils/version.js +1 -1
  103. package/yarn-error.log +16 -16
  104. package/components/ClusterProviderIconMenu.vue +0 -161
  105. package/content/docs/en-us/getting-started.md +0 -224
  106. package/content/docs/en-us/whats-new.md +0 -29
  107. package/content/docs/zh-hans/getting-started.md +0 -224
  108. package/content/docs/zh-hans/whats-new.md +0 -28
  109. package/pages/docs/_doc.vue +0 -345
  110. package/pages/docs/toc.js +0 -27
  111. package/plugins/console.js +0 -34
@@ -320,7 +320,7 @@ BODY, .theme-light {
320
320
  --header-height : 55px;
321
321
  --header-border : #{$medium};
322
322
  --header-border-size : 1px;
323
- --nav-width : 300px;
323
+ --nav-width : 250px;
324
324
  --nav-bg : #{$lightest};
325
325
  --nav-active : #{$light};
326
326
  --nav-hover : #{$medium};
@@ -760,7 +760,7 @@ backupRestoreOperator:
760
760
  warning: 'This {type} does not have its reclaim policy set to "Retain". Your backups may be lost if the volume is changed or becomes unbound.'
761
761
  encryption: Encryption
762
762
  encryptionConfigName:
763
- backuptip: 'Any secret in the <code>cattle-resource-system</code> namespace that has an <code>encryption-provider-config.yaml</code> key. <br/>The contents of this file are necessary to perform a restore from this backup, and are not stored by Rancher Backup.'
763
+ backuptip: 'Any secret in the <code>cattle-resources-system</code> namespace that has an <code>encryption-provider-config.yaml</code> key. <br/>The contents of this file are necessary to perform a restore from this backup, and are not stored by Rancher Backup.'
764
764
  label: Encryption Config Secret
765
765
  options:
766
766
  none: Store the contents of the backup unencrypted
@@ -1137,6 +1137,7 @@ cluster:
1137
1137
  nodeRole:
1138
1138
  label: Node Role
1139
1139
  detail: Choose what roles the node will have in the cluster. The cluster needs to have at least one node with each role.
1140
+ warning: The cluster needs to have at least one node with each role to be usable.
1140
1141
  advanced:
1141
1142
  label: Advanced
1142
1143
  detail: Additional control over how the node will be registered. These values will often need to be different for each node registered.
@@ -1672,6 +1673,11 @@ cluster:
1672
1673
  haveArgInfo: Configuration information is not available for the selected Kubernetes version. The options available on this screen will be limited; you may want to use the YAML editor.
1673
1674
  deprecatedPsp: Pod Security Policies are deprecated as of Kubernetes v1.21, and have been removed in Kubernetes v1.25.
1674
1675
  removedPsp: Pod Security Policies have been removed in Kubernetes v1.25. Use Pod Security Admission instead.
1676
+ machinePoolError: |-
1677
+ {count, plural,
1678
+ =1 { {pool_name}: The provided value for {fields} was not found in the list of expected values. This can happen with clusters provisioned outside of Rancher or when options for the provider have changed. }
1679
+ other { {pool_name}: The provided values for {fields} were not found in the list of expected values. This can happen with clusters provisioned outside of Rancher or when options for the provider have changed. }
1680
+ }
1675
1681
  rkeTemplateUpgrade: Template revision {name} available for upgrade
1676
1682
 
1677
1683
  availabilityWarnings:
@@ -1752,6 +1758,7 @@ cluster:
1752
1758
  searchPlaceholder: Start typing to search
1753
1759
  noResults: No results found
1754
1760
  privateRegistry:
1761
+ header: Registry for Rancher System Container Images
1755
1762
  label: Enable cluster scoped container registry for Rancher system container images
1756
1763
  description: "If enabled, Rancher will pull container images from this registry during cluster provisioning. By default, Rancher will also use this registry when installing Rancher's official Helm chart apps. If the cluster scoped registry is disabled, system images are pulled from the System Default Registry in the global settings."
1757
1764
  docsLinkRke2: "For help configuring private registry mirrors, see the RKE2 <a href=\"https://docs.rke2.io/install/containerd_registry_configuration/\" target=\"_blank\">documentation.</a>"
@@ -1864,6 +1871,8 @@ cluster:
1864
1871
  option:
1865
1872
  none: (None)
1866
1873
  default: Default - RKE2 Embedded
1874
+ secretEncryption:
1875
+ label: Encrypt Secrets
1867
1876
  cisProfile:
1868
1877
  option: (None)
1869
1878
  enableNetworkPolicy:
@@ -1872,6 +1881,7 @@ cluster:
1872
1881
  workNode:
1873
1882
  label: Worker Nodes
1874
1883
  controlPlaneConcurrency:
1884
+ header: Control Plane
1875
1885
  label: Control Plane Concurrency
1876
1886
  toolTip: "This can be either a fixed number of nodes (e.g. 1) at a time or a percentage (e.g. 10%)"
1877
1887
  workerConcurrency:
@@ -1879,8 +1889,20 @@ cluster:
1879
1889
  toolTip: "This can be either a fixed number of nodes (e.g. 1) at a time or a percentage (e.g. 10%)"
1880
1890
  drain:
1881
1891
  label: Drain Nodes
1882
- toolTip: Draining preemptively removes the pods on each node so there are no running workloads on the nodes being upgraded. Upgrading without draining is faster and causes less shuffling around, but pods may still be restarted depending on the upgrade being performed.
1883
- deleteEmptyDir: "By default, pods using emptyDir volumes will be deleted on upgrade. Operations reliant on emptyDir volumes persisting through the pod's lifecycle may be impacted."
1892
+ toolTip: Draining preemptively removes the pods on each node so there are no running workloads on the nodes being upgraded. Upgrading without draining is faster and causes less shuffling around, but pods may still be restarted depending on the upgrade being performed.
1893
+ deleteEmptyDir:
1894
+ warning: "By default, pods using emptyDir volumes will be deleted on upgrade. Operations reliant on emptyDir volumes persisting through the pod's lifecycle may be impacted."
1895
+ label: Delete pods using emptyDir volumes
1896
+ tooltip: emptyDir volumes are often used for ephemeral data, but the data will be permanently deleted. Draining will fail if this is not set and there are pods using emptyDir.
1897
+ force:
1898
+ label: Delete standalone pods
1899
+ tooltip: Delete standalone pods which are not managed by a Workload controller (Deployment, Job, etc). Draining will fail if this is not set and there are standalone pods.
1900
+ gracePeriod:
1901
+ checkboxLabel: Override pod termination grace periods
1902
+ inputLabel: Grace Period
1903
+ timeout:
1904
+ checkboxLabel: Timeout after
1905
+ inputLabel: Drain Timeout
1884
1906
  truncateHostnames: Truncate hostnames to 15 characters for NetBIOS compatibility.
1885
1907
  address:
1886
1908
  tooltip: Cluster networking values cannot be changed after the cluster is created.
@@ -4124,9 +4146,8 @@ plugins:
4124
4146
  image:
4125
4147
  name: images
4126
4148
  label: Deployment Image
4127
- cacheState:
4128
- name: cacheState
4129
- label: Cache State
4149
+ repository:
4150
+ label: Repository Name
4130
4151
  tabs:
4131
4152
  all: All
4132
4153
  available: Available
@@ -4153,7 +4174,7 @@ plugins:
4153
4174
  label: Uninstall
4154
4175
  title: "Uninstall Extension: {name}"
4155
4176
  prompt: "Are you sure that you want to uninstall this Extension?"
4156
- custom: "Are you sure that you want to uninstall this Extension Image? This will also remove any Extensions provided by this image."
4177
+ catalog: "Are you sure that you want to uninstall this Extension Catalog Image? This will also remove any Extensions provided by this image."
4157
4178
  upgradeAvailable: A newer version of this Extension is available
4158
4179
  reload: Extensions changed - reload required
4159
4180
  safeMode:
@@ -5704,6 +5725,7 @@ validation:
5704
5725
  missingResource: You must specify a Resource for each resource grant
5705
5726
  missingApiGroup: You must specify an API Group for each resource grant
5706
5727
  missingOneResource: You must specify at least one Resource, Non-Resource URL or API Group for each resource grant
5728
+ noResourceAndNonResource: Each rule may contain Resources or Non-Resource URLs but not both
5707
5729
  service:
5708
5730
  externalName:
5709
5731
  none: External Name is required on an ExternalName Service.
@@ -758,7 +758,7 @@ backupRestoreOperator:
758
758
  warning: '此 {type} 没有将其回收策略设置为 "保留"。如果卷被更改或未绑定,你的备份可能会丢失。'
759
759
  encryption: 加密
760
760
  encryptionConfigName:
761
- backuptip: '<code>cattle-resource-system</code>命名空间中具有<code>encryption-provider-config.yaml</code>密钥的任何密文。<br/>此文件的内容是还原此备份所必需的,Rancher Backup 不会存储这些内容。'
761
+ backuptip: '<code>cattle-resources-system</code>命名空间中具有<code>encryption-provider-config.yaml</code>密钥的任何密文。<br/>此文件的内容是还原此备份所必需的,Rancher Backup 不会存储这些内容。'
762
762
  label: 加密配置密文
763
763
  options:
764
764
  none: 存储未加密的备份内容。
@@ -0,0 +1,143 @@
1
+ <script>
2
+ import { abbreviateClusterName } from '@shell/utils/cluster';
3
+
4
+ export default {
5
+ props: {
6
+ cluster: {
7
+ type: Object,
8
+ required: true,
9
+ },
10
+ },
11
+ computed: {
12
+ isEnabled() {
13
+ return !!this.cluster?.ready;
14
+ },
15
+ showLocalIcon() {
16
+ return this.cluster.isLocal && !this.cluster.isHarvester && !this.cluster.badge?.iconText;
17
+ },
18
+ badgeLogoBorderBottom() {
19
+ const color = this.cluster.badge?.color;
20
+
21
+ return color ? `4px solid ${ color }` : '';
22
+ }
23
+ },
24
+ methods: {
25
+ smallIdentifier(input) {
26
+ if (this.cluster.badge?.iconText) {
27
+ return this.cluster.badge?.iconText;
28
+ }
29
+
30
+ if (this.cluster.isLocal && !this.cluster.badge?.iconText) {
31
+ return undefined;
32
+ }
33
+
34
+ return abbreviateClusterName(input);
35
+ }
36
+ }
37
+ };
38
+ </script>
39
+
40
+ <template>
41
+ <div
42
+ v-if="cluster"
43
+ class="cluster-icon-menu"
44
+ >
45
+ <div
46
+ class="cluster-badge-logo"
47
+ :class="{ 'disabled': !isEnabled }"
48
+ :style="{ borderBottom: badgeLogoBorderBottom }"
49
+ >
50
+ <span
51
+ class="cluster-badge-logo-text"
52
+ >
53
+ {{ smallIdentifier(cluster.label) }}
54
+ </span>
55
+ <svg
56
+ v-if="showLocalIcon"
57
+ class="cluster-local-logo"
58
+ version="1.1"
59
+ xmlns="http://www.w3.org/2000/svg"
60
+ xmlns:xlink="http://www.w3.org/1999/xlink"
61
+ x="0px"
62
+ y="0px"
63
+ viewBox="0 0 100 100"
64
+ style="enable-background:new 0 0 100 100;"
65
+ xml:space="preserve"
66
+ >
67
+ <g>
68
+ <g>
69
+ <path
70
+ class="rancher-icon-fill"
71
+ d="M26.0862026,44.4953918H8.6165142c-5.5818157,0-9.3979139-4.6252708-8.4802637-10.1311035l2.858391-17.210701
72
+ C3.912292,11.6477556,6.1382647,7.1128125,7.8419709,7.1128125s3.1788611,4.5368752,3.1788611,10.1186218v4.4837742
73
+ c0,5.5817471,4.4044495,9.5409164,9.9862652,9.5409164h5.0791054V44.4953918z"
74
+ />
75
+ </g>
76
+ <path
77
+ class="rancher-icon-fill"
78
+ d="M63.0214729,92.8871841H37.0862045c-6.0751343,0-11.0000019-4.9248657-11.0000019-11V30.3864384
79
+ c0-6.0751324,4.9248676-11,11.0000019-11h25.9352684c6.0751305,0,11.0000038,4.9248676,11.0000038,11v51.5007477
80
+ C74.0214767,87.9623184,69.0966034,92.8871841,63.0214729,92.8871841z"
81
+ />
82
+ <g>
83
+ <path
84
+ class="rancher-icon-fill"
85
+ d="M73.9137955,44.4953918h17.4696884c5.5818176,0,9.3979187-4.6252708,8.4802628-10.1311035
86
+ l-2.8583908-17.210701c-0.9176483-5.5058317-3.1436234-10.0407753-4.8473282-10.0407753
87
+ s-3.1788635,4.5368752-3.1788635,10.1186218v4.4837742c0,5.5817471-4.4044418,9.5409164-9.9862595,9.5409164h-5.0791092
88
+ V44.4953918z"
89
+ />
90
+ </g>
91
+ </g>
92
+ </svg>
93
+ </div>
94
+ <i
95
+ v-if="cluster.pinned"
96
+ class="icon icon-pin cluster-pin-icon"
97
+ />
98
+ </div>
99
+ </template>
100
+
101
+ <style lang="scss" scoped>
102
+
103
+ .cluster-icon-menu {
104
+ position: relative;
105
+ align-items: center;
106
+ display: flex;
107
+ height: 28px;
108
+ justify-content: center;
109
+ width: 42px;
110
+ }
111
+ .cluster-pin-icon {
112
+ position: absolute;
113
+ top: -6px;
114
+ right: -4px;
115
+ font-size: 12px;
116
+ transform: scaleX(-1);
117
+ color: var(--body-text);
118
+ }
119
+
120
+ .cluster-local-logo {
121
+ width: 20px;
122
+ }
123
+
124
+ .cluster-badge-logo {
125
+ width: 42px;
126
+ height: 28px;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ color: var(--default-active-text);
131
+ font-weight: bold;
132
+ background: var(--nav-icon-badge-bg);
133
+ border: 1px solid var(--default-border);
134
+ border-radius: 5px;
135
+ font-size: 12px;
136
+ text-transform: uppercase;
137
+
138
+ &.disabled {
139
+ filter: grayscale(1);
140
+ color: var(--muted);
141
+ }
142
+ }
143
+ </style>
@@ -17,6 +17,8 @@ import {
17
17
  import { BEFORE_SAVE_HOOKS } from '@shell/mixins/child-hook';
18
18
  import Wizard from '@shell/components/Wizard';
19
19
 
20
+ export const CONTEXT_HOOK_EDIT_YAML = 'show-preview-yaml';
21
+
20
22
  export default {
21
23
 
22
24
  name: 'CruResource',
@@ -110,6 +112,7 @@ export default {
110
112
  type: Function,
111
113
  default: null,
112
114
  },
115
+
113
116
  steps: {
114
117
  type: Array,
115
118
  default: () => []
@@ -324,7 +327,10 @@ export default {
324
327
 
325
328
  async showPreviewYaml() {
326
329
  if ( this.applyHooks ) {
327
- await this.applyHooks(BEFORE_SAVE_HOOKS);
330
+ await this.applyHooks(
331
+ BEFORE_SAVE_HOOKS,
332
+ CONTEXT_HOOK_EDIT_YAML,
333
+ );
328
334
  }
329
335
 
330
336
  const resourceYaml = this.createResourceYaml(this.yamlModifiers);
@@ -5,6 +5,8 @@ import { STATE, AGE, NAME } from '@shell/config/table-headers';
5
5
  import { uniq } from '@shell/utils/array';
6
6
  import { MANAGEMENT, NAMESPACE, VIRTUAL_TYPES } from '@shell/config/types';
7
7
  import { PROJECT_ID, FLAT_VIEW } from '@shell/config/query-params';
8
+ import { PanelLocation, ExtensionPoint } from '@shell/core/types';
9
+ import ExtensionPanel from '@shell/components/ExtensionPanel';
8
10
  import Masthead from '@shell/components/ResourceList/Masthead';
9
11
  import { mapPref, GROUP_RESOURCES, ALL_NAMESPACES } from '@shell/store/prefs';
10
12
  import MoveModal from '@shell/components/MoveModal';
@@ -16,7 +18,7 @@ import DOMPurify from 'dompurify';
16
18
  export default {
17
19
  name: 'ListProjectNamespace',
18
20
  components: {
19
- Masthead, MoveModal, ResourceTable
21
+ ExtensionPanel, Masthead, MoveModal, ResourceTable
20
22
  },
21
23
  mixins: [ResourceFetch],
22
24
 
@@ -56,6 +58,8 @@ export default {
56
58
  schema: null,
57
59
  projects: [],
58
60
  projectSchema: null,
61
+ extensionType: ExtensionPoint.PANEL,
62
+ extensionLocation: PanelLocation.RESOURCE_LIST,
59
63
  MANAGEMENT,
60
64
  VIRTUAL_TYPES,
61
65
  defaultCreateProjectLocation: {
@@ -379,6 +383,12 @@ export default {
379
383
  </n-link>
380
384
  </template>
381
385
  </Masthead>
386
+ <!-- Extensions area -->
387
+ <ExtensionPanel
388
+ :resource="{}"
389
+ :type="extensionType"
390
+ :location="extensionLocation"
391
+ />
382
392
  <ResourceTable
383
393
  ref="table"
384
394
  class="table"
@@ -93,6 +93,19 @@ export default {
93
93
  },
94
94
  showAsDialog() {
95
95
  return this.consent && !!this.banner.button;
96
+ },
97
+
98
+ // ID to place on the Banner DIV
99
+ id() {
100
+ if (this.header) {
101
+ return 'banner-header';
102
+ } else if (this.consent) {
103
+ return 'banner-consent';
104
+ } else if (this.footer) {
105
+ return 'banner-footer';
106
+ }
107
+
108
+ return 'banner';
96
109
  }
97
110
  },
98
111
 
@@ -130,7 +143,10 @@ export default {
130
143
  </script>
131
144
 
132
145
  <template>
133
- <div v-if="showBanner">
146
+ <div
147
+ v-if="showBanner"
148
+ :id="id"
149
+ >
134
150
  <div
135
151
  v-if="!showAsDialog"
136
152
  class="banner"
@@ -39,7 +39,7 @@ export default {
39
39
  const renderer = new marked.Renderer();
40
40
  const linkRenderer = renderer.link;
41
41
 
42
- const base = this.$router.resolve(this.$route).href.replace(/#.*$/, '');
42
+ const base = this.$router ? this.$router.resolve(this.$route).href.replace(/#.*$/, '') : '';
43
43
 
44
44
  renderer.link = function(href, title, text) {
45
45
  let external = true;
@@ -2,8 +2,9 @@ import Questions from '@shell/components/Questions';
2
2
  import { mount } from '@vue/test-utils';
3
3
  import { _EDIT } from '@shell/config/query-params';
4
4
  const defaultStubs = {
5
- Tab: true,
6
- Tabbed: true,
5
+ Tab: true,
6
+ Tabbed: true,
7
+ CodeMirror: true
7
8
  };
8
9
  const defaultGetters = {
9
10
  currentStore: () => 'current_store',
@@ -1,6 +1,7 @@
1
1
  <script>
2
2
  import { mapGetters } from 'vuex';
3
3
  import day from 'dayjs';
4
+ import isEmpty from 'lodash/isEmpty';
4
5
  import { dasherize, ucFirst } from '@shell/utils/string';
5
6
  import { get, clone } from '@shell/utils/object';
6
7
  import { removeObject } from '@shell/utils/array';
@@ -801,11 +802,11 @@ export default {
801
802
 
802
803
  // Can the action of interest be applied to the specified resource?
803
804
  canRunBulkActionOfInterest(resource) {
804
- if (!this.actionOfInterest) {
805
+ if ( !this.actionOfInterest || isEmpty(resource?.availableActions) ) {
805
806
  return false;
806
807
  }
807
808
 
808
- const matchingResourceAction = resource.availableActions.find((a) => a.action === this.actionOfInterest.action);
809
+ const matchingResourceAction = resource.availableActions?.find((a) => a.action === this.actionOfInterest.action);
809
810
 
810
811
  return matchingResourceAction?.enabled;
811
812
  },
@@ -2,6 +2,8 @@
2
2
  import { MANAGEMENT, RBAC } from '@shell/config/types';
3
3
  import CruResource from '@shell/components/CruResource';
4
4
  import CreateEditView from '@shell/mixins/create-edit-view';
5
+ import FormValidation from '@shell/mixins/form-validation';
6
+ import Error from '@shell/components/form/Error';
5
7
  import { RadioGroup } from '@components/Form/Radio';
6
8
  import Select from '@shell/components/form/Select';
7
9
  import ArrayList from '@shell/components/form/ArrayList';
@@ -58,9 +60,10 @@ export default {
58
60
  Tabbed,
59
61
  SortableTable,
60
62
  Loading,
63
+ Error
61
64
  },
62
65
 
63
- mixins: [CreateEditView],
66
+ mixins: [CreateEditView, FormValidation],
64
67
 
65
68
  async fetch() {
66
69
  // We don't want to get all schemas from the cluster because there are
@@ -109,6 +112,9 @@ export default {
109
112
  scopedResources: SCOPED_RESOURCES,
110
113
  defaultValue: false,
111
114
  selectFocused: null,
115
+ fvFormRuleSets: [
116
+ { path: 'displayName', rules: ['required'] }
117
+ ],
112
118
  };
113
119
  },
114
120
 
@@ -524,7 +530,8 @@ export default {
524
530
  :can-yaml="!isCreate"
525
531
  :mode="mode"
526
532
  :resource="value"
527
- :errors="errors"
533
+ :errors="fvUnreportedValidationErrors"
534
+ :validation-passed="fvFormIsValid"
528
535
  :cancel-event="true"
529
536
  @error="e=>errors = e"
530
537
  @finish="save"
@@ -568,6 +575,7 @@ export default {
568
575
  name-key="displayName"
569
576
  description-key="description"
570
577
  label="Name"
578
+ :rules="{ name: fvGetAndReportPathRules('displayName') }"
571
579
  />
572
580
  <div
573
581
  v-if="isRancherType"
@@ -606,6 +614,11 @@ export default {
606
614
  :label="t('rbac.roletemplate.tabs.grantResources.label')"
607
615
  :weight="1"
608
616
  >
617
+ <Error
618
+ :value="value.rules"
619
+ :rules="fvGetAndReportPathRules('rules')"
620
+ as-banner
621
+ />
609
622
  <ArrayList
610
623
  v-model="value.rules"
611
624
  label="Resources"
@@ -5,7 +5,18 @@ export default {
5
5
 
6
6
  methods: {
7
7
  async login() {
8
- const res = await this.$store.dispatch('auth/login', { provider: this.name, body: { finalRedirectUrl: window.location.origin } });
8
+ const { requestId, publicKey, responseType } = this.$route.query;
9
+
10
+ const res = await this.$store.dispatch('auth/login', {
11
+ provider: this.name,
12
+ body: {
13
+ finalRedirectUrl: window.location.origin,
14
+ requestId,
15
+ publicKey,
16
+ responseType
17
+ }
18
+ });
19
+
9
20
  const { idpRedirectUrl } = res;
10
21
 
11
22
  window.location.href = idpRedirectUrl;
@@ -149,11 +149,10 @@ export default {
149
149
  return;
150
150
  }
151
151
 
152
- // Force to update the option label if prop has been changed
153
- const isOutdated = !this.options.find((opt) => option[this.optionLabel] === opt[this.optionLabel]);
154
-
155
- if (isOutdated && this.options) {
156
- const newOption = this.options.find((opt) => isEqual(this.reduce(option), this.reduce(opt)));
152
+ // This check is only needed if its possible for an option's label to change without the option's value changing - we can skip this if options are just strings or numbers
153
+ // HOWEVER even if strings are passed to v-select the 'option' in the slot is normalized to {label: <option>} so we have to check the options prop here instead of the 'option' itself
154
+ if (typeof this.options[0] === 'object') {
155
+ const newOption = this.getUpdatedOption(option);
157
156
 
158
157
  if (newOption) {
159
158
  const label = get(newOption, this.optionLabel);
@@ -178,6 +177,14 @@ export default {
178
177
  }
179
178
  },
180
179
 
180
+ // If the option's label changed in parent but value did not, the label wont be automatically updated here
181
+ // Ensure that the label being shown is still present in the options prop and find the new one if not
182
+ getUpdatedOption(option) {
183
+ const isOutdated = this.options && !this.options.find((opt) => option[this.optionLabel] === opt[this.optionLabel]);
184
+
185
+ return isOutdated ? this.options.find((opt) => isEqual(this.reduce(option), this.reduce(opt))) : undefined;
186
+ },
187
+
181
188
  positionDropdown(dropdownList, component, { width }) {
182
189
  calculatePosition(dropdownList, component, width, this.placement);
183
190
  },
@@ -198,7 +198,7 @@ export default {
198
198
  async principalProperty() {
199
199
  const principal = await this.principal;
200
200
 
201
- return principal.principalType === 'group' ? 'groupPrincipalId' : 'userPrincipalId';
201
+ return principal?.principalType === 'group' ? 'groupPrincipalId' : 'userPrincipalId';
202
202
  },
203
203
 
204
204
  onAdd(principalId) {
@@ -56,8 +56,13 @@ export default {
56
56
  },
57
57
 
58
58
  async fetch() {
59
+ const roleBindingRequestParams = { type: this.type, opt: { force: true } };
60
+
61
+ if (this.type === NORMAN.PROJECT_ROLE_TEMPLATE_BINDING && this.parentId) {
62
+ Object.assign(roleBindingRequestParams, { opt: { filter: { projectId: this.parentId.split('/').join(':') } } });
63
+ }
59
64
  const userHydration = [
60
- this.schema ? this.$store.dispatch(`rancher/findAll`, { type: this.type, opt: { force: true } }) : [],
65
+ this.schema ? this.$store.dispatch(`rancher/findAll`, roleBindingRequestParams) : [],
61
66
  this.$store.dispatch('rancher/findAll', { type: NORMAN.PRINCIPAL }),
62
67
  this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.ROLE_TEMPLATE }),
63
68
  this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.USER })
@@ -8,7 +8,8 @@ describe('component: KeyValue', () => {
8
8
  const wrapper = mount(KeyValue, {
9
9
  propsData: { value: { value } },
10
10
  mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
11
- directives: { t }
11
+ directives: { t },
12
+ stubs: { CodeMirror: true }
12
13
  });
13
14
 
14
15
  const inputValue = wrapper.find('textarea').element as HTMLInputElement;
@@ -24,7 +25,8 @@ describe('component: KeyValue', () => {
24
25
  valueMarkdownMultiline: true,
25
26
  },
26
27
  mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
27
- directives: { t }
28
+ directives: { t },
29
+ stubs: { CodeMirror: true }
28
30
  });
29
31
 
30
32
  const inputFieldTextArea = wrapper.find('textarea').element;
@@ -41,7 +43,8 @@ describe('component: KeyValue', () => {
41
43
  valueMarkdownMultiline: false,
42
44
  },
43
45
  mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
44
- directives: { t }
46
+ directives: { t },
47
+ stubs: { CodeMirror: true }
45
48
  });
46
49
 
47
50
  const inputFieldTextArea = wrapper.find('[data-testid="text-area-auto-grow"]');
@@ -133,6 +133,24 @@ describe('component: LabeledSelect', () => {
133
133
  // Component is from a library and class is not going to be changed
134
134
  expect(wrapper.find('.vs__selected').text()).toBe(translation);
135
135
  });
136
+
137
+ it.each([
138
+ [['a'], 'b', 0],
139
+ [[{ value: 'a', label: 'A' }], { value: 'a', label: 'B' }, 4]
140
+ ])('should only check for a new label if options are objects', async(options: any[], newOption, checkUpdateCalled: number) => {
141
+ const spyUpdatedOption = jest.spyOn(LabeledSelect.methods, 'getUpdatedOption');
142
+
143
+ const wrapper = mount(LabeledSelect, {
144
+ propsData: {
145
+ value: 'a',
146
+ options
147
+ }
148
+ });
149
+
150
+ await wrapper.setProps({ options: [newOption] });
151
+
152
+ expect(spyUpdatedOption).toHaveBeenCalledTimes(checkUpdateCalled);
153
+ });
136
154
  });
137
155
  });
138
156
  });
@@ -1,5 +1,4 @@
1
1
  <script>
2
- import { POD } from '@shell/config/types';
3
2
  export default {
4
3
  name: 'PodsUsage',
5
4
  props: {
@@ -8,47 +7,23 @@ export default {
8
7
  required: true
9
8
  },
10
9
  },
11
- data() {
12
- return {
13
- loading: true,
14
- podsUsage: null
15
- };
16
- },
17
- methods: {
18
- async startDelayedLoading() {
19
- const id = this.row?.mgmt?.id;
20
-
21
- if (this.row?.isReady && id) {
22
- const req = await this.$store.dispatch('management/request', { url: `/k8s/clusters/${ id }/v1/counts` });
10
+ computed: {
11
+ podsUsage() {
12
+ const usedPods = this.row?.mgmt?.status?.requested?.pods;
13
+ const totalPods = this.row?.mgmt?.status?.allocatable?.pods;
23
14
 
24
- this.loading = false;
25
- const usedPods = req.data?.[0]?.counts[POD]?.summary?.count || 0;
26
- const totalPods = this.row?.mgmt?.status?.allocatable?.pods;
27
-
28
- if (totalPods) {
29
- this.podsUsage = `${ usedPods }/${ totalPods }`;
30
- } else {
31
- this.podsUsage = '—';
32
- }
33
- } else {
34
- this.loading = false;
35
- this.podsUsage = '—';
15
+ if (!this.row?.isReady || !totalPods) {
16
+ return '—';
36
17
  }
18
+
19
+ return `${ usedPods || 0 }/${ totalPods }`;
37
20
  }
38
- },
21
+ }
39
22
  };
40
23
  </script>
41
24
 
42
25
  <template>
43
- <i
44
- v-if="loading"
45
- class="icon icon-spinner icon-spin"
46
- />
47
- <p v-else>
48
- {{ podsUsage }}
26
+ <p>
27
+ <span>{{ podsUsage }}</span>
49
28
  </p>
50
29
  </template>
51
-
52
- <style lang="scss" scoped>
53
-
54
- </style>