@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.
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/translations/en-us.yaml +36 -7
- package/assets/translations/zh-hans.yaml +1 -1
- package/components/ClusterIconMenu.vue +143 -0
- package/components/CruResource.vue +10 -1
- package/components/ExplorerProjectsNamespaces.vue +11 -1
- package/components/FixedBanner.vue +17 -1
- package/components/Markdown.vue +1 -1
- package/components/Questions/__tests__/Yaml.test.ts +3 -2
- package/components/SortableTable/index.vue +3 -2
- package/components/__tests__/ProjectRow.test.ts +63 -0
- package/components/auth/RoleDetailEdit.vue +19 -2
- package/components/auth/__tests__/RoleDetailEdit.test.ts +41 -0
- package/components/auth/login/saml.vue +12 -1
- package/components/form/LabeledSelect.vue +12 -5
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/Members/MembershipEditor.vue +6 -1
- package/components/form/ResourceQuota/ProjectRow.vue +6 -2
- package/components/form/__tests__/KeyValue.test.ts +6 -3
- package/components/form/__tests__/LabeledSelect.test.ts +18 -0
- package/components/formatter/PodsUsage.vue +11 -36
- package/components/formatter/PrincipalGroupBindings.vue +8 -5
- package/components/formatter/__tests__/PodsUsage.test.ts +36 -19
- package/components/nav/Group.vue +25 -27
- package/components/nav/Header.vue +12 -5
- package/components/nav/Pinned.vue +47 -0
- package/components/nav/TopLevelMenu.vue +233 -60
- package/components/nav/Type.vue +57 -3
- package/config/home-links.js +1 -1
- package/config/product/istio.js +15 -5
- package/config/router.js +3 -9
- package/config/table-headers.js +5 -6
- package/config/uiplugins.js +1 -0
- package/core/plugin-helpers.js +3 -0
- package/core/types.ts +6 -1
- package/creators/app/files/.vscode/settings.json +0 -1
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +118 -0
- package/detail/autoscaling.horizontalpodautoscaler/index.vue +4 -4
- package/detail/provisioning.cattle.io.cluster.vue +7 -5
- package/edit/__tests__/management.cattle.io.clusterroletemplatebinding.test.ts +58 -0
- package/edit/__tests__/namespace.test.ts +5 -3
- package/edit/management.cattle.io.clusterroletemplatebinding.vue +3 -11
- package/edit/namespace.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/Basics.vue +662 -0
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +13 -8
- package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -2
- package/edit/provisioning.cattle.io.cluster/MemberRoles.vue +40 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +237 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +71 -23
- package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +52 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -142
- package/edit/provisioning.cattle.io.cluster/rke2.vue +211 -599
- package/edit/workload/storage/__tests__/Storage.test.ts +2 -2
- package/edit/workload/storage/persistentVolumeClaim/__tests__/persistentvolumeclaim.test.ts +36 -0
- package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +15 -7
- package/initialize/index.js +5 -5
- package/layouts/default.vue +6 -6
- package/layouts/home.vue +6 -2
- package/layouts/plain.vue +9 -2
- package/list/fleet.cattle.io.cluster.vue +2 -2
- package/list/management.cattle.io.feature.vue +1 -1
- package/machine-config/vmwarevsphere.vue +48 -7
- package/mixins/brand.js +0 -8
- package/mixins/child-hook.js +2 -2
- package/mixins/create-edit-view/impl.js +3 -3
- package/models/__tests__/management.cattle.io.node.ts +96 -0
- package/models/__tests__/node.ts +74 -0
- package/models/cluster/node.js +6 -5
- package/models/cluster.x-k8s.io.machinedeployment.js +2 -2
- package/models/management.cattle.io.cluster.js +22 -1
- package/models/management.cattle.io.clusterroletemplatebinding.js +3 -3
- package/models/management.cattle.io.globalrole.js +17 -2
- package/models/management.cattle.io.node.js +6 -4
- package/models/management.cattle.io.projectroletemplatebinding.js +3 -3
- package/models/management.cattle.io.roletemplate.js +17 -2
- package/package.json +2 -6
- package/pages/about.vue +2 -0
- package/pages/auth/setup.vue +5 -4
- package/pages/c/_cluster/monitoring/index.vue +8 -3
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +9 -66
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +182 -0
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +15 -32
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +8 -46
- package/pages/c/_cluster/uiplugins/index.vue +64 -64
- package/pages/diagnostic.vue +0 -39
- package/pages/home.vue +1 -1
- package/plugins/dashboard-store/normalize.js +4 -4
- package/plugins/int-number.js +5 -2
- package/plugins/positive-int-number.js +19 -0
- package/plugins/steve/__tests__/getters.spec.ts +15 -0
- package/plugins/steve/getters.js +22 -10
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +0 -8
- package/rancher-components/Form/Radio/RadioButton.test.ts +3 -7
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +2 -2
- package/store/index.js +4 -0
- package/store/prefs.js +1 -0
- package/types/shell/index.d.ts +13 -4
- package/utils/__tests__/cluster.test.ts +55 -0
- package/utils/__tests__/object.test.ts +21 -2
- package/utils/cluster.js +47 -1
- package/utils/object.js +12 -5
- package/utils/validators/formRules/__tests__/index.test.ts +13 -1
- package/utils/validators/formRules/index.ts +4 -0
- package/utils/validators/role-template.js +9 -1
- package/utils/version.js +1 -1
- package/yarn-error.log +16 -16
- package/components/ClusterProviderIconMenu.vue +0 -161
- package/content/docs/en-us/getting-started.md +0 -224
- package/content/docs/en-us/whats-new.md +0 -29
- package/content/docs/zh-hans/getting-started.md +0 -224
- package/content/docs/zh-hans/whats-new.md +0 -28
- package/pages/docs/_doc.vue +0 -345
- package/pages/docs/toc.js +0 -27
- 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 :
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
4128
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
|
146
|
+
<div
|
|
147
|
+
v-if="showBanner"
|
|
148
|
+
:id="id"
|
|
149
|
+
>
|
|
134
150
|
<div
|
|
135
151
|
v-if="!showAsDialog"
|
|
136
152
|
class="banner"
|
package/components/Markdown.vue
CHANGED
|
@@ -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:
|
|
6
|
-
Tabbed:
|
|
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
|
|
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="
|
|
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
|
|
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
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
},
|