@rancher/shell 0.3.24 → 0.3.26

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 (115) hide show
  1. package/assets/styles/themes/_light.scss +1 -1
  2. package/assets/translations/en-us.yaml +36 -7
  3. package/assets/translations/zh-hans.yaml +1 -1
  4. package/components/ClusterIconMenu.vue +143 -0
  5. package/components/CruResource.vue +10 -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/__tests__/ProjectRow.test.ts +63 -0
  12. package/components/auth/RoleDetailEdit.vue +19 -2
  13. package/components/auth/__tests__/RoleDetailEdit.test.ts +41 -0
  14. package/components/auth/login/saml.vue +12 -1
  15. package/components/form/LabeledSelect.vue +12 -5
  16. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  17. package/components/form/Members/MembershipEditor.vue +6 -1
  18. package/components/form/ResourceQuota/ProjectRow.vue +6 -2
  19. package/components/form/__tests__/KeyValue.test.ts +6 -3
  20. package/components/form/__tests__/LabeledSelect.test.ts +18 -0
  21. package/components/formatter/PodsUsage.vue +11 -36
  22. package/components/formatter/PrincipalGroupBindings.vue +8 -5
  23. package/components/formatter/__tests__/PodsUsage.test.ts +36 -19
  24. package/components/nav/Group.vue +25 -27
  25. package/components/nav/Header.vue +12 -5
  26. package/components/nav/Pinned.vue +47 -0
  27. package/components/nav/TopLevelMenu.vue +233 -60
  28. package/components/nav/Type.vue +57 -3
  29. package/config/home-links.js +1 -1
  30. package/config/product/istio.js +15 -5
  31. package/config/router.js +3 -9
  32. package/config/table-headers.js +5 -6
  33. package/config/uiplugins.js +1 -0
  34. package/core/plugin-helpers.js +3 -0
  35. package/core/types.ts +6 -1
  36. package/creators/app/files/.vscode/settings.json +0 -1
  37. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +118 -0
  38. package/detail/autoscaling.horizontalpodautoscaler/index.vue +4 -4
  39. package/detail/provisioning.cattle.io.cluster.vue +7 -5
  40. package/edit/__tests__/management.cattle.io.clusterroletemplatebinding.test.ts +58 -0
  41. package/edit/__tests__/namespace.test.ts +5 -3
  42. package/edit/management.cattle.io.clusterroletemplatebinding.vue +3 -11
  43. package/edit/namespace.vue +8 -4
  44. package/edit/provisioning.cattle.io.cluster/Basics.vue +662 -0
  45. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +6 -0
  46. package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +13 -8
  47. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -2
  48. package/edit/provisioning.cattle.io.cluster/MemberRoles.vue +40 -0
  49. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +237 -0
  50. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +71 -23
  51. package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +52 -0
  52. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -142
  53. package/edit/provisioning.cattle.io.cluster/rke2.vue +211 -599
  54. package/edit/workload/storage/__tests__/Storage.test.ts +2 -2
  55. package/edit/workload/storage/persistentVolumeClaim/__tests__/persistentvolumeclaim.test.ts +36 -0
  56. package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +15 -7
  57. package/initialize/index.js +5 -5
  58. package/layouts/default.vue +6 -6
  59. package/layouts/home.vue +6 -2
  60. package/layouts/plain.vue +9 -2
  61. package/list/fleet.cattle.io.cluster.vue +2 -2
  62. package/list/management.cattle.io.feature.vue +1 -1
  63. package/machine-config/vmwarevsphere.vue +48 -7
  64. package/mixins/brand.js +0 -8
  65. package/mixins/child-hook.js +2 -2
  66. package/mixins/create-edit-view/impl.js +3 -3
  67. package/models/__tests__/management.cattle.io.node.ts +96 -0
  68. package/models/__tests__/node.ts +74 -0
  69. package/models/cluster/node.js +6 -5
  70. package/models/cluster.x-k8s.io.machinedeployment.js +2 -2
  71. package/models/management.cattle.io.cluster.js +22 -1
  72. package/models/management.cattle.io.clusterroletemplatebinding.js +3 -3
  73. package/models/management.cattle.io.globalrole.js +17 -2
  74. package/models/management.cattle.io.node.js +6 -4
  75. package/models/management.cattle.io.projectroletemplatebinding.js +3 -3
  76. package/models/management.cattle.io.roletemplate.js +17 -2
  77. package/package.json +2 -6
  78. package/pages/about.vue +2 -0
  79. package/pages/auth/setup.vue +5 -4
  80. package/pages/c/_cluster/monitoring/index.vue +8 -3
  81. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +9 -66
  82. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +182 -0
  83. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +15 -32
  84. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +8 -46
  85. package/pages/c/_cluster/uiplugins/index.vue +64 -64
  86. package/pages/diagnostic.vue +0 -39
  87. package/pages/home.vue +1 -1
  88. package/plugins/dashboard-store/normalize.js +4 -4
  89. package/plugins/int-number.js +5 -2
  90. package/plugins/positive-int-number.js +19 -0
  91. package/plugins/steve/__tests__/getters.spec.ts +15 -0
  92. package/plugins/steve/getters.js +22 -10
  93. package/rancher-components/Form/LabeledInput/LabeledInput.vue +0 -8
  94. package/rancher-components/Form/Radio/RadioButton.test.ts +3 -7
  95. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +2 -2
  96. package/store/index.js +4 -0
  97. package/store/prefs.js +1 -0
  98. package/types/shell/index.d.ts +13 -4
  99. package/utils/__tests__/cluster.test.ts +55 -0
  100. package/utils/__tests__/object.test.ts +21 -2
  101. package/utils/cluster.js +47 -1
  102. package/utils/object.js +12 -5
  103. package/utils/validators/formRules/__tests__/index.test.ts +13 -1
  104. package/utils/validators/formRules/index.ts +4 -0
  105. package/utils/validators/role-template.js +9 -1
  106. package/utils/version.js +1 -1
  107. package/yarn-error.log +16 -16
  108. package/components/ClusterProviderIconMenu.vue +0 -161
  109. package/content/docs/en-us/getting-started.md +0 -224
  110. package/content/docs/en-us/whats-new.md +0 -29
  111. package/content/docs/zh-hans/getting-started.md +0 -224
  112. package/content/docs/zh-hans/whats-new.md +0 -28
  113. package/pages/docs/_doc.vue +0 -345
  114. package/pages/docs/toc.js +0 -27
  115. 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};
@@ -653,6 +653,10 @@ asyncButton:
653
653
  action: Save
654
654
  success: Saved
655
655
  waiting: Saving…
656
+ editAndContinue:
657
+ action: Save and Continue
658
+ success: Saved
659
+ waiting: Saving…
656
660
  enable:
657
661
  action: Enable
658
662
  success: Enabled
@@ -760,7 +764,7 @@ backupRestoreOperator:
760
764
  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
765
  encryption: Encryption
762
766
  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.'
767
+ 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
768
  label: Encryption Config Secret
765
769
  options:
766
770
  none: Store the contents of the backup unencrypted
@@ -1137,6 +1141,7 @@ cluster:
1137
1141
  nodeRole:
1138
1142
  label: Node Role
1139
1143
  detail: Choose what roles the node will have in the cluster. The cluster needs to have at least one node with each role.
1144
+ warning: The cluster needs to have at least one node with each role to be usable.
1140
1145
  advanced:
1141
1146
  label: Advanced
1142
1147
  detail: Additional control over how the node will be registered. These values will often need to be different for each node registered.
@@ -1672,6 +1677,11 @@ cluster:
1672
1677
  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
1678
  deprecatedPsp: Pod Security Policies are deprecated as of Kubernetes v1.21, and have been removed in Kubernetes v1.25.
1674
1679
  removedPsp: Pod Security Policies have been removed in Kubernetes v1.25. Use Pod Security Admission instead.
1680
+ machinePoolError: |-
1681
+ {count, plural,
1682
+ =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. }
1683
+ 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. }
1684
+ }
1675
1685
  rkeTemplateUpgrade: Template revision {name} available for upgrade
1676
1686
 
1677
1687
  availabilityWarnings:
@@ -1752,6 +1762,7 @@ cluster:
1752
1762
  searchPlaceholder: Start typing to search
1753
1763
  noResults: No results found
1754
1764
  privateRegistry:
1765
+ header: Registry for Rancher System Container Images
1755
1766
  label: Enable cluster scoped container registry for Rancher system container images
1756
1767
  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
1768
  docsLinkRke2: "For help configuring private registry mirrors, see the RKE2 <a href=\"https://docs.rke2.io/install/containerd_registry_configuration/\" target=\"_blank\">documentation.</a>"
@@ -1834,6 +1845,9 @@ cluster:
1834
1845
  pspChange:
1835
1846
  title: Pod Security Policy deprecation
1836
1847
  body: <p>Kubernetes has removed support for Pod Security Policies (PSPs) starting with version 1.25. If your cluster has PodSecurityPolicy admission controller enabled via "kube-apiserver-arg.enable-admission-plugins" in Cluster YAML, it has to be <i>manually</i> removed before proceeding with the upgrade. Additionally, any PSPs that may be present in the cluster will no longer be available/enforced. Do you want to proceed?</p>
1848
+ editYamlMachinePool:
1849
+ title: Save Machine Configurations
1850
+ body: Machine Configurations define how machines in Pools are deployed.<br><br> They will be saved upfront to ensure valid Cluster YAML can be saved.
1837
1851
  snapshots:
1838
1852
  suffix: Snapshots per node
1839
1853
  systemService:
@@ -1864,6 +1878,8 @@ cluster:
1864
1878
  option:
1865
1879
  none: (None)
1866
1880
  default: Default - RKE2 Embedded
1881
+ secretEncryption:
1882
+ label: Encrypt Secrets
1867
1883
  cisProfile:
1868
1884
  option: (None)
1869
1885
  enableNetworkPolicy:
@@ -1872,6 +1888,7 @@ cluster:
1872
1888
  workNode:
1873
1889
  label: Worker Nodes
1874
1890
  controlPlaneConcurrency:
1891
+ header: Control Plane
1875
1892
  label: Control Plane Concurrency
1876
1893
  toolTip: "This can be either a fixed number of nodes (e.g. 1) at a time or a percentage (e.g. 10%)"
1877
1894
  workerConcurrency:
@@ -1879,8 +1896,20 @@ cluster:
1879
1896
  toolTip: "This can be either a fixed number of nodes (e.g. 1) at a time or a percentage (e.g. 10%)"
1880
1897
  drain:
1881
1898
  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."
1899
+ 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.
1900
+ deleteEmptyDir:
1901
+ 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."
1902
+ label: Delete pods using emptyDir volumes
1903
+ 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.
1904
+ force:
1905
+ label: Delete standalone pods
1906
+ 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.
1907
+ gracePeriod:
1908
+ checkboxLabel: Override pod termination grace periods
1909
+ inputLabel: Grace Period
1910
+ timeout:
1911
+ checkboxLabel: Timeout after
1912
+ inputLabel: Drain Timeout
1884
1913
  truncateHostnames: Truncate hostnames to 15 characters for NetBIOS compatibility.
1885
1914
  address:
1886
1915
  tooltip: Cluster networking values cannot be changed after the cluster is created.
@@ -4124,9 +4153,8 @@ plugins:
4124
4153
  image:
4125
4154
  name: images
4126
4155
  label: Deployment Image
4127
- cacheState:
4128
- name: cacheState
4129
- label: Cache State
4156
+ repository:
4157
+ label: Repository Name
4130
4158
  tabs:
4131
4159
  all: All
4132
4160
  available: Available
@@ -4153,7 +4181,7 @@ plugins:
4153
4181
  label: Uninstall
4154
4182
  title: "Uninstall Extension: {name}"
4155
4183
  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."
4184
+ catalog: "Are you sure that you want to uninstall this Extension Catalog Image? This will also remove any Extensions provided by this image."
4157
4185
  upgradeAvailable: A newer version of this Extension is available
4158
4186
  reload: Extensions changed - reload required
4159
4187
  safeMode:
@@ -5704,6 +5732,7 @@ validation:
5704
5732
  missingResource: You must specify a Resource for each resource grant
5705
5733
  missingApiGroup: You must specify an API Group for each resource grant
5706
5734
  missingOneResource: You must specify at least one Resource, Non-Resource URL or API Group for each resource grant
5735
+ noResourceAndNonResource: Each rule may contain Resources or Non-Resource URLs but not both
5707
5736
  service:
5708
5737
  externalName:
5709
5738
  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,13 @@ export default {
324
327
 
325
328
  async showPreviewYaml() {
326
329
  if ( this.applyHooks ) {
327
- await this.applyHooks(BEFORE_SAVE_HOOKS);
330
+ try {
331
+ await this.applyHooks(BEFORE_SAVE_HOOKS, CONTEXT_HOOK_EDIT_YAML);
332
+ } catch (e) {
333
+ console.warn('Unable to show yaml: ', e); // eslint-disable-line no-console
334
+
335
+ return;
336
+ }
328
337
  }
329
338
 
330
339
  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
  },
@@ -0,0 +1,63 @@
1
+ import ProjectRow from '@shell/components/form/ResourceQuota/ProjectRow.vue';
2
+ import { RANCHER_TYPES } from '@shell/components/form/ResourceQuota/shared';
3
+ import { shallowMount } from '@vue/test-utils';
4
+ import Vue from 'vue';
5
+
6
+ const CONFIGMAP_STRING = RANCHER_TYPES[0].value;
7
+
8
+ describe('component: ProjectRow.vue', () => {
9
+ const wrapper = shallowMount(ProjectRow,
10
+ {
11
+ propsData: {
12
+ mode: 'edit',
13
+ types: RANCHER_TYPES,
14
+ type: CONFIGMAP_STRING,
15
+ value: {
16
+ spec: {
17
+ namespaceDefaultResourceQuota: { limit: {} },
18
+ resourceQuota: { limit: {} }
19
+ }
20
+ }
21
+ }
22
+ });
23
+
24
+ it('should render the correct input fields and set the correct computed values, based on the provided data', () => {
25
+ const typeInput = wrapper.find(`[data-testid="projectrow-type-input"]`);
26
+ const projectQuotaInput = wrapper.find(`[data-testid="projectrow-project-quota-input"]`);
27
+ const namespaceQuotaInput = wrapper.find(`[data-testid="projectrow-namespace-quota-input"]`);
28
+
29
+ expect(typeInput.exists()).toBe(true);
30
+ expect(projectQuotaInput.exists()).toBe(true);
31
+ expect(namespaceQuotaInput.exists()).toBe(true);
32
+ expect(wrapper.vm.resourceQuotaLimit).toStrictEqual({});
33
+ expect(wrapper.vm.namespaceDefaultResourceQuotaLimit).toStrictEqual({});
34
+ });
35
+
36
+ it('triggering "updateQuotaLimit" should trigger Vue.set with the correct data', () => {
37
+ const vueSet = jest.spyOn(Vue, 'set');
38
+
39
+ wrapper.vm.updateQuotaLimit('resourceQuota', CONFIGMAP_STRING, 10);
40
+
41
+ expect(vueSet).toHaveBeenCalledTimes(1);
42
+ expect(wrapper.vm.value).toStrictEqual({
43
+ spec: {
44
+ namespaceDefaultResourceQuota: { limit: {} },
45
+ resourceQuota: { limit: { [`${ CONFIGMAP_STRING }`]: 10 } }
46
+ }
47
+ });
48
+ });
49
+
50
+ it('triggering "updateType" with the same type that existed should clear limits and trigger emit', () => {
51
+ wrapper.vm.updateType(CONFIGMAP_STRING);
52
+
53
+ expect(wrapper.vm.value).toStrictEqual({
54
+ spec: {
55
+ namespaceDefaultResourceQuota: { limit: {} },
56
+ resourceQuota: { limit: {} }
57
+ }
58
+ });
59
+
60
+ expect(wrapper.emitted('type-change')).toBeTruthy();
61
+ expect(wrapper.emitted('type-change')[0]).toStrictEqual([CONFIGMAP_STRING]);
62
+ });
63
+ });
@@ -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
 
@@ -146,6 +152,10 @@ export default {
146
152
  });
147
153
  }
148
154
 
155
+ if (this.value?.metadata?.name && !this.value.displayName) {
156
+ this.$set(this.value, 'displayName', this.value.metadata.name);
157
+ }
158
+
149
159
  this.$nextTick(() => {
150
160
  this.$emit('set-subtype', this.label);
151
161
  });
@@ -524,7 +534,8 @@ export default {
524
534
  :can-yaml="!isCreate"
525
535
  :mode="mode"
526
536
  :resource="value"
527
- :errors="errors"
537
+ :errors="fvUnreportedValidationErrors"
538
+ :validation-passed="fvFormIsValid"
528
539
  :cancel-event="true"
529
540
  @error="e=>errors = e"
530
541
  @finish="save"
@@ -568,6 +579,7 @@ export default {
568
579
  name-key="displayName"
569
580
  description-key="description"
570
581
  label="Name"
582
+ :rules="{ name: fvGetAndReportPathRules('displayName') }"
571
583
  />
572
584
  <div
573
585
  v-if="isRancherType"
@@ -606,6 +618,11 @@ export default {
606
618
  :label="t('rbac.roletemplate.tabs.grantResources.label')"
607
619
  :weight="1"
608
620
  >
621
+ <Error
622
+ :value="value.rules"
623
+ :rules="fvGetAndReportPathRules('rules')"
624
+ as-banner
625
+ />
609
626
  <ArrayList
610
627
  v-model="value.rules"
611
628
  label="Resources"
@@ -0,0 +1,41 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import RoleDetailEdit from '@shell/components/auth/RoleDetailEdit.vue';
3
+ import { SUBTYPE_MAPPING } from '@shell/models/management.cattle.io.roletemplate';
4
+
5
+ const role = {
6
+ apiVersion: 'management.cattle.io/v3',
7
+ kind: 'GlobalRole',
8
+ metadata: { name: 'global-role-with-inherited' },
9
+ inheritedClusterRoles: ['cluster-admin'],
10
+ rules:
11
+ [{
12
+ verbs: ['get', 'list'],
13
+ resources: ['pods'],
14
+ apiGroups: ['']
15
+ }],
16
+ subtype: SUBTYPE_MAPPING.GLOBAL.id
17
+ };
18
+
19
+ describe('component: RoleDetailEdit', () => {
20
+ it('does not have validation errors when the role has no displayName', () => {
21
+ const wrapper = mount(RoleDetailEdit, {
22
+ propsData: { value: role },
23
+ mocks: {
24
+ $fetchState: { pending: false },
25
+ $route: { name: 'anything' },
26
+ $store: {
27
+ getters: {
28
+ currentStore: () => 'store', 'i18n/t': jest.fn(), 'store/schemaFor': jest.fn()
29
+ }
30
+ }
31
+ },
32
+ stubs: {
33
+ CruResource: { template: '<div><slot></slot></div>' },
34
+ // NameNsDescription: true,
35
+ Tab: { template: '<div><slot></slot></div>' },
36
+ }
37
+ });
38
+
39
+ expect((wrapper.vm as any).fvFormIsValid).toBe(true);
40
+ });
41
+ });
@@ -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
  },