@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.4
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/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +144 -41
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/CruResource.vue +161 -14
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +10 -6
- package/components/GrowlManager.vue +4 -0
- package/components/MgmtNodeList.vue +184 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
- package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +7 -1
- package/components/ResourceList/index.vue +82 -1
- package/components/RichTranslation.vue +5 -2
- package/components/Setting.vue +1 -0
- package/components/SubtleLink.vue +31 -6
- package/components/Tabbed/Tab.vue +29 -3
- package/components/Tabbed/index.vue +25 -3
- package/components/TableOfContents/TableOfContents.vue +109 -0
- package/components/TableOfContents/composables.ts +258 -0
- package/components/Window/ContainerShell.vue +21 -11
- package/components/Window/__tests__/ContainerShell.test.ts +107 -37
- package/components/Wizard.vue +9 -4
- package/components/fleet/AppCoChartGrid.vue +401 -0
- package/components/fleet/AppCoEmptyState.vue +127 -0
- package/components/fleet/AppCoPageHeader.vue +119 -0
- package/components/fleet/AppCoVersionSelect.vue +70 -0
- package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +189 -146
- package/components/fleet/FleetIntro.vue +7 -3
- package/components/fleet/FleetNoWorkspaces.vue +7 -3
- package/components/fleet/FleetSecretSelector.vue +5 -3
- package/components/fleet/FleetValuesFrom.vue +8 -2
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpResourcesSection.vue +82 -0
- package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
- package/components/fleet/HelmOpTargetTab.vue +64 -60
- package/components/fleet/HelmOpValuesTab.vue +129 -105
- package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
- package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
- package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
- package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
- package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
- package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
- package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
- package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
- package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
- package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
- package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
- package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
- package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
- package/components/fleet/dashboard/Empty.vue +8 -4
- package/components/fleet/dashboard/ResourceCard.vue +28 -0
- package/components/fleet/dashboard/ResourceDetails.vue +28 -0
- package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
- package/components/form/ArrayList.vue +61 -4
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +39 -1
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +4 -0
- package/components/nav/TopLevelMenu.vue +7 -2
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
- package/components/templates/default.vue +9 -4
- package/components/templates/home.vue +9 -4
- package/components/templates/plain.vue +9 -4
- package/composables/useHelmOpResources.test.ts +56 -0
- package/composables/useHelmOpResources.ts +32 -0
- package/composables/useStateColor.test.ts +325 -0
- package/composables/useStateColor.ts +128 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +1 -0
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +2 -0
- package/config/router/index.js +16 -0
- package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
- package/config/router/navigation-guards/authentication.js +10 -4
- package/config/router/routes.js +20 -6
- package/config/settings.ts +0 -2
- package/config/table-headers.js +3 -4
- package/config/types.js +9 -0
- package/core/plugin-products-base.ts +3 -3
- package/core/plugin-types.ts +83 -30
- package/core/plugin.ts +3 -0
- package/core/types-provisioning.ts +34 -1
- package/core/types.ts +15 -2
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
- package/detail/__tests__/workload.test.ts +3 -152
- package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +30 -4
- package/detail/workload/index.vue +12 -55
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
- package/edit/auth/__tests__/azuread.test.ts +34 -9
- package/edit/auth/__tests__/github.test.ts +234 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -6
- package/edit/auth/__tests__/saml.test.ts +196 -0
- package/edit/auth/azuread.vue +128 -95
- package/edit/auth/github.vue +72 -13
- package/edit/auth/ldap/__tests__/index.test.ts +206 -0
- package/edit/auth/ldap/config.vue +8 -0
- package/edit/auth/ldap/index.vue +75 -1
- package/edit/auth/oidc.vue +119 -73
- package/edit/auth/saml.vue +76 -12
- package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
- package/edit/fleet.cattle.io.helmop.vue +491 -136
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/list/group.principal.vue +5 -4
- package/list/harvesterhci.io.management.cluster.vue +8 -9
- package/list/management.cattle.io.user.vue +12 -9
- package/list/provisioning.cattle.io.cluster.vue +16 -10
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +94 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/chart.js +11 -2
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +21 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
- package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
- package/models/__tests__/fleet-application.test.ts +175 -0
- package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
- package/models/__tests__/management.cattle.io.node.ts +22 -0
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
- package/models/__tests__/workload.test.ts +401 -26
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/compliance.cattle.io.clusterscan.js +39 -4
- package/models/fleet-application.js +4 -0
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +18 -2
- package/models/management.cattle.io.node.js +44 -3
- package/models/namespace.js +1 -1
- package/models/pod.js +33 -1
- package/models/provisioning.cattle.io.cluster.js +5 -5
- package/models/workload.js +108 -13
- package/models/workload.service.js +5 -0
- package/package.json +14 -13
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +11 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/chart.vue +2 -1
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +122 -116
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
- package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
- package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
- package/pages/c/_cluster/fleet/application/create.vue +187 -136
- package/pages/c/_cluster/fleet/application/index.vue +5 -3
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
- package/pages/c/_cluster/fleet/index.vue +2 -2
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/index.vue +15 -0
- package/pages/fail-whale.vue +16 -11
- package/pages/home.vue +16 -46
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
- package/plugins/dashboard-store/resource-class.js +62 -7
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +103 -0
- package/rancher-components/RcButton/RcButton.vue +94 -15
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
- package/rancher-components/RcSection/RcSection.vue +28 -3
- package/scripts/extension/helm/package/Dockerfile +1 -1
- package/scripts/test-plugins-build.sh +2 -1
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/catalog.js +57 -0
- package/store/plugins.js +7 -4
- package/types/components/buttonGroup.ts +5 -0
- package/types/shell/index.d.ts +104 -70
- package/utils/__tests__/auth.test.ts +273 -0
- package/utils/__tests__/computed.test.ts +193 -0
- package/utils/__tests__/cspAdaptor.test.ts +163 -0
- package/utils/__tests__/dom.test.ts +81 -0
- package/utils/__tests__/duration.test.ts +37 -1
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/position.test.ts +237 -0
- package/utils/__tests__/provider.test.ts +51 -1
- package/utils/__tests__/queue.test.ts +232 -0
- package/utils/__tests__/release-notes.test.ts +221 -0
- package/utils/__tests__/router.test.js +254 -1
- package/utils/__tests__/select.test.ts +208 -0
- package/utils/__tests__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +126 -6
- package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
- package/utils/crypto/__tests__/index.test.ts +144 -0
- package/utils/duration.ts +104 -0
- package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
- package/utils/dynamic-content/info.ts +2 -1
- package/utils/error.js +13 -0
- package/utils/fleet-appco.ts +323 -0
- package/utils/object.js +22 -2
- package/utils/provider.ts +12 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/xccdf.ts +39 -42
- package/vue.config.js +1 -1
- package/pages/support/index.vue +0 -264
- package/utils/duration.js +0 -43
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { reactive } from 'vue';
|
|
2
|
+
import { useStore } from 'vuex';
|
|
3
|
+
import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
|
|
4
|
+
import type { StateColor } from '@shell/utils/style';
|
|
5
|
+
import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
|
|
6
|
+
import { PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
7
|
+
|
|
8
|
+
export interface StateSummaryEntry {
|
|
9
|
+
type: string;
|
|
10
|
+
summary: { property: string; counts: Record<string, { total: number; namespace: Record<string, number> }> }[] | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const stateColorCache = reactive<Record<string, StateColor>>({});
|
|
14
|
+
const pendingResolves = new Set<string>();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Composable that maps Kubernetes resource state names to dashboard color tokens.
|
|
18
|
+
*
|
|
19
|
+
* This scenario is to forcefully resolve state colors based on properties like error and transitioning.
|
|
20
|
+
* It is to be used when you only have the state name and don't have the actual state object
|
|
21
|
+
*
|
|
22
|
+
* Using a request of only one resource per unknown state, it will fetch the data to retrieve the state object.
|
|
23
|
+
* For normal cases where the object is already available, this composable is not needed.
|
|
24
|
+
*
|
|
25
|
+
* @returns `toStateColor` — synchronous lookup that triggers background API
|
|
26
|
+
* resolution for unknown states when a resource type is provided.
|
|
27
|
+
*/
|
|
28
|
+
export function useStateColor() {
|
|
29
|
+
const store = useStore();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the cached {@link StateColor} for a given state name.
|
|
33
|
+
* Falls back to `colorForState` as a temporary color and triggers an async
|
|
34
|
+
* resolve via the Steve API when `type` is provided. Once resolved, the
|
|
35
|
+
* reactive cache updates and the UI re-renders with the correct color.
|
|
36
|
+
*
|
|
37
|
+
* @param state - The resource state name (e.g. `'active'`, `'error'`). Case-insensitive.
|
|
38
|
+
* @param type - The Steve resource type (e.g. `'apps.deployment'`). Used to
|
|
39
|
+
* fetch a resource via the API when the state is not cached.
|
|
40
|
+
* @returns The resolved color token.
|
|
41
|
+
*/
|
|
42
|
+
function toStateColor(state: string, type: string): StateColor {
|
|
43
|
+
const stateLower = (state || '').toLowerCase();
|
|
44
|
+
const key = `${ type }:${ stateLower }`;
|
|
45
|
+
|
|
46
|
+
if (stateColorCache[key]) {
|
|
47
|
+
return stateColorCache[key];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!pendingResolves.has(key)) {
|
|
51
|
+
pendingResolves.add(key);
|
|
52
|
+
resolveStateColor(key, state, type);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const rawColor = colorForState(stateLower, false, false).replace('text-', '');
|
|
56
|
+
|
|
57
|
+
return (rawColor === 'darker' ? 'disabled' : rawColor) as StateColor;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function resolveStateColor(key: string, originalName: string, type: string): Promise<void> {
|
|
61
|
+
try {
|
|
62
|
+
const schema = store.getters['cluster/schemaFor'](type);
|
|
63
|
+
const params = stevePaginationUtils.createParamsForPagination({
|
|
64
|
+
schema,
|
|
65
|
+
opt: {
|
|
66
|
+
pagination: {
|
|
67
|
+
page: 1,
|
|
68
|
+
pageSize: 1,
|
|
69
|
+
sort: [],
|
|
70
|
+
filters: [PaginationParamFilter.createSingleField({ field: 'metadata.state.name', value: originalName })],
|
|
71
|
+
projectsOrNamespaces: [],
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const url = `${ store.getters['cluster/urlFor'](type) }?${ params }`;
|
|
76
|
+
const res = await store.dispatch('cluster/request', { url });
|
|
77
|
+
const resource = res?.data?.[0];
|
|
78
|
+
|
|
79
|
+
if (resource?.metadata?.state) {
|
|
80
|
+
const { error: isError, transitioning, name } = resource.metadata.state;
|
|
81
|
+
const rawColor = colorForState(name, isError, transitioning).replace('text-', '');
|
|
82
|
+
|
|
83
|
+
stateColorCache[key] = (rawColor === 'darker' ? 'disabled' : rawColor) as StateColor;
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Keep the colorForState fallback
|
|
87
|
+
} finally {
|
|
88
|
+
pendingResolves.delete(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function resolveStateColors(entries: StateSummaryEntry[]): Promise<void> {
|
|
93
|
+
const toResolve: Array<{ key: string; originalName: string; type: string }> = [];
|
|
94
|
+
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (!entry.summary) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const s of entry.summary) {
|
|
101
|
+
if (s.property === 'metadata.state.name') {
|
|
102
|
+
for (const stateName of Object.keys(s.counts)) {
|
|
103
|
+
const key = stateName.toLowerCase();
|
|
104
|
+
|
|
105
|
+
const cacheKey = `${ entry.type }:${ key }`;
|
|
106
|
+
|
|
107
|
+
if (!stateColorCache[cacheKey] && !pendingResolves.has(cacheKey)) {
|
|
108
|
+
pendingResolves.add(cacheKey);
|
|
109
|
+
toResolve.push({
|
|
110
|
+
key: cacheKey, originalName: stateName, type: entry.type
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const concurrency = 10;
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < toResolve.length; i += concurrency) {
|
|
121
|
+
await Promise.all(
|
|
122
|
+
toResolve.slice(i, i + concurrency).map((r) => resolveStateColor(r.key, r.originalName, r.type))
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { toStateColor, resolveStateColors };
|
|
128
|
+
}
|
package/config/home-links.js
CHANGED
|
@@ -19,6 +19,7 @@ export const NODE_ARCHITECTURE = 'kubernetes.io/arch';
|
|
|
19
19
|
export const IMPORTED_CLUSTER_VERSION_MANAGEMENT = 'rancher.io/imported-cluster-version-management';
|
|
20
20
|
export const UI_PROJECT_SECRET = 'management.cattle.io/project-scoped-secret';
|
|
21
21
|
export const UI_PROJECT_SECRET_COPY = 'management.cattle.io/project-scoped-secret-copy';
|
|
22
|
+
export const SERVICE_LINKS = 'ui.rancher/service-links';
|
|
22
23
|
|
|
23
24
|
export const KUBERNETES = {
|
|
24
25
|
SERVICE_ACCOUNT_UID: 'kubernetes.io/service-account.uid',
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
SNAPSHOT,
|
|
11
11
|
VIRTUAL_TYPES,
|
|
12
12
|
CAPI,
|
|
13
|
+
WORKLOAD_DASHBOARD,
|
|
13
14
|
} from '@shell/config/types';
|
|
14
15
|
|
|
15
16
|
import {
|
|
@@ -103,6 +104,7 @@ export function init(store) {
|
|
|
103
104
|
CONFIG_MAP
|
|
104
105
|
], 'storage');
|
|
105
106
|
basicType([
|
|
107
|
+
WORKLOAD_DASHBOARD,
|
|
106
108
|
WORKLOAD,
|
|
107
109
|
WORKLOAD_TYPES.DEPLOYMENT,
|
|
108
110
|
WORKLOAD_TYPES.DAEMON_SET,
|
|
@@ -112,10 +114,6 @@ export function init(store) {
|
|
|
112
114
|
POD,
|
|
113
115
|
], 'workload');
|
|
114
116
|
|
|
115
|
-
setGroupDefaultType('workload', () => {
|
|
116
|
-
return store.getters['features/get'](STEVE_CACHE) ? WORKLOAD_TYPES.DEPLOYMENT : undefined;
|
|
117
|
-
});
|
|
118
|
-
|
|
119
117
|
weightGroup('cluster', 99, true);
|
|
120
118
|
weightGroup('workload', 98, true);
|
|
121
119
|
weightGroup('serviceDiscovery', 96, true);
|
|
@@ -586,6 +584,21 @@ export function init(store) {
|
|
|
586
584
|
overview: true,
|
|
587
585
|
});
|
|
588
586
|
|
|
587
|
+
// Workload Dashboard - overview page using the Resource Summary API
|
|
588
|
+
virtualType({
|
|
589
|
+
label: store.getters['i18n/t'](`typeLabel.${ WORKLOAD }`, { count: 2 }),
|
|
590
|
+
group: 'Root',
|
|
591
|
+
namespaced: true,
|
|
592
|
+
name: WORKLOAD_DASHBOARD,
|
|
593
|
+
weight: 100,
|
|
594
|
+
icon: 'folder',
|
|
595
|
+
ifHaveSubTypes: Object.values(WORKLOAD_TYPES),
|
|
596
|
+
ifFeature: STEVE_CACHE,
|
|
597
|
+
route: { name: 'c-cluster-explorer-workload-dashboard' },
|
|
598
|
+
exact: true,
|
|
599
|
+
overview: true,
|
|
600
|
+
});
|
|
601
|
+
|
|
589
602
|
virtualType({
|
|
590
603
|
labelKey: 'members.clusterAndProject',
|
|
591
604
|
group: 'cluster',
|
|
@@ -130,10 +130,12 @@ export function init(store) {
|
|
|
130
130
|
|
|
131
131
|
basicType([
|
|
132
132
|
HOSTED_PROVIDER,
|
|
133
|
+
CAPI.CAPI_PROVIDER,
|
|
133
134
|
'rke-kontainer-providers',
|
|
134
135
|
'rke-node-providers',
|
|
135
136
|
], 'providers');
|
|
136
137
|
|
|
138
|
+
weightType(CAPI.CAPI_PROVIDER, 4, true);
|
|
137
139
|
weightType(CAPI.MACHINE_DEPLOYMENT, 4, true);
|
|
138
140
|
weightType(CAPI.MACHINE_SET, 3, true);
|
|
139
141
|
weightType(CAPI.MACHINE, 2, true);
|
package/config/router/index.js
CHANGED
|
@@ -9,6 +9,22 @@ export const routerOptions = {
|
|
|
9
9
|
base: process.env.routerBase || '/',
|
|
10
10
|
routes: Routes,
|
|
11
11
|
fallback: false,
|
|
12
|
+
scrollBehavior(to, from, savedPosition) {
|
|
13
|
+
// Returning the savedPosition will result in a native-like behavior when
|
|
14
|
+
// navigating with back/forward buttons
|
|
15
|
+
if (savedPosition) {
|
|
16
|
+
return savedPosition;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Handle the "skip to main content" link
|
|
20
|
+
if (to.hash === '#main-content') {
|
|
21
|
+
const el = document.getElementById('main-content');
|
|
22
|
+
|
|
23
|
+
el?.focus();
|
|
24
|
+
|
|
25
|
+
return { el: to.hash };
|
|
26
|
+
}
|
|
27
|
+
},
|
|
12
28
|
};
|
|
13
29
|
|
|
14
30
|
export function extendRouter(config, context) {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { authenticate } from '@shell/config/router/navigation-guards/authentication';
|
|
2
|
+
|
|
3
|
+
jest.mock('@shell/utils/router', () => ({ routeRequiresAuthentication: () => true }));
|
|
4
|
+
|
|
5
|
+
const isLoggedInMock = jest.fn();
|
|
6
|
+
const findMeMock = jest.fn();
|
|
7
|
+
const notLoggedInMock = jest.fn();
|
|
8
|
+
const noAuthMock = jest.fn();
|
|
9
|
+
|
|
10
|
+
jest.mock('@shell/utils/auth', () => {
|
|
11
|
+
const actual = jest.requireActual('@shell/utils/auth');
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
isLoggedIn: (...args: any[]) => isLoggedInMock(...args),
|
|
16
|
+
findMe: (...args: any[]) => findMeMock(...args),
|
|
17
|
+
notLoggedIn: (...args: any[]) => notLoggedInMock(...args),
|
|
18
|
+
noAuth: (...args: any[]) => noAuthMock(...args),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function makeStore({ user, fromHeader }: { user: any, fromHeader: string }) {
|
|
23
|
+
const getters: Record<string, any> = {
|
|
24
|
+
'auth/enabled': true,
|
|
25
|
+
'auth/loggedIn': false,
|
|
26
|
+
'auth/user': user,
|
|
27
|
+
'auth/fromHeader': fromHeader,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
dispatch: jest.fn().mockResolvedValue(undefined),
|
|
32
|
+
commit: jest.fn(),
|
|
33
|
+
getters,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const to = { name: 'c-cluster-explorer', query: {} };
|
|
38
|
+
|
|
39
|
+
describe('navigation-guards/authentication: mustChangePassword', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
isLoggedInMock.mockReset().mockResolvedValue(undefined);
|
|
42
|
+
findMeMock.mockReset();
|
|
43
|
+
notLoggedInMock.mockReset();
|
|
44
|
+
noAuthMock.mockReset();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('redirects local users with mustChangePassword to auth-setup', async() => {
|
|
48
|
+
const store = makeStore({
|
|
49
|
+
user: { mustChangePassword: true, principalIds: ['local://user-1'] },
|
|
50
|
+
fromHeader: 'true',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
findMeMock.mockResolvedValue({ id: 'local://user-1', provider: 'local' });
|
|
54
|
+
|
|
55
|
+
const next = jest.fn();
|
|
56
|
+
|
|
57
|
+
await authenticate(to as any, {} as any, next, { store } as any);
|
|
58
|
+
|
|
59
|
+
expect(next).toHaveBeenCalledWith({ name: 'auth-setup' });
|
|
60
|
+
expect(isLoggedInMock).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('lets SSO users with mustChangePassword reach the requested route', async() => {
|
|
64
|
+
const me = { id: 'oidc_user://user@example.com', provider: 'genericoidc' };
|
|
65
|
+
const store = makeStore({
|
|
66
|
+
user: { mustChangePassword: true, principalIds: ['local://user-1', me.id] },
|
|
67
|
+
fromHeader: 'true',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
findMeMock.mockResolvedValue(me);
|
|
71
|
+
|
|
72
|
+
const next = jest.fn();
|
|
73
|
+
|
|
74
|
+
await authenticate(to as any, {} as any, next, { store } as any);
|
|
75
|
+
|
|
76
|
+
expect(next).toHaveBeenCalledWith();
|
|
77
|
+
expect(next).not.toHaveBeenCalledWith({ name: 'auth-setup' });
|
|
78
|
+
expect(isLoggedInMock).toHaveBeenCalledTimes(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('does not redirect when mustChangePassword is false, regardless of provider', async() => {
|
|
82
|
+
const store = makeStore({
|
|
83
|
+
user: { mustChangePassword: false, principalIds: ['local://user-1'] },
|
|
84
|
+
fromHeader: 'true',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
findMeMock.mockResolvedValue({ id: 'local://user-1', provider: 'local' });
|
|
88
|
+
|
|
89
|
+
const next = jest.fn();
|
|
90
|
+
|
|
91
|
+
await authenticate(to as any, {} as any, next, { store } as any);
|
|
92
|
+
|
|
93
|
+
expect(next).toHaveBeenCalledWith();
|
|
94
|
+
expect(isLoggedInMock).toHaveBeenCalledTimes(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('redirects local users with mustChangePassword to auth-setup for older-style fromHeader', async() => {
|
|
98
|
+
const store = makeStore({
|
|
99
|
+
user: { mustChangePassword: true, principalIds: ['local://user-1'] },
|
|
100
|
+
fromHeader: 'unknown',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
findMeMock.mockResolvedValue({ id: 'local://user-1', provider: 'local' });
|
|
104
|
+
|
|
105
|
+
const next = jest.fn();
|
|
106
|
+
|
|
107
|
+
await authenticate(to as any, {} as any, next, { store } as any);
|
|
108
|
+
|
|
109
|
+
expect(next).toHaveBeenCalledWith({ name: 'auth-setup' });
|
|
110
|
+
expect(isLoggedInMock).not.toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('lets SSO users with mustChangePassword through for older-style fromHeader', async() => {
|
|
114
|
+
const me = { id: 'saml_user://user', provider: 'saml' };
|
|
115
|
+
const store = makeStore({
|
|
116
|
+
user: { mustChangePassword: true, principalIds: ['local://user-1', me.id] },
|
|
117
|
+
fromHeader: 'unknown',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
findMeMock.mockResolvedValue(me);
|
|
121
|
+
|
|
122
|
+
const next = jest.fn();
|
|
123
|
+
|
|
124
|
+
await authenticate(to as any, {} as any, next, { store } as any);
|
|
125
|
+
|
|
126
|
+
expect(next).toHaveBeenCalledWith();
|
|
127
|
+
expect(next).not.toHaveBeenCalledWith({ name: 'auth-setup' });
|
|
128
|
+
expect(isLoggedInMock).toHaveBeenCalledTimes(1);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -61,19 +61,21 @@ export async function authenticate(to, from, next, { store }) {
|
|
|
61
61
|
await store.dispatch('auth/getUser');
|
|
62
62
|
const user = store.getters['auth/user'] || {};
|
|
63
63
|
|
|
64
|
-
if (user?.mustChangePassword) {
|
|
65
|
-
return next({ name: 'auth-setup' });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
64
|
// In newer versions the API calls return the auth state instead of having to make a new call all the time.
|
|
69
65
|
const fromHeader = store.getters['auth/fromHeader'];
|
|
70
66
|
|
|
67
|
+
const mustChangePasswordFor = (me) => user?.mustChangePassword && me?.provider === 'local';
|
|
68
|
+
|
|
71
69
|
if ( fromHeader === 'none' ) {
|
|
72
70
|
noAuth(store);
|
|
73
71
|
handleOidcRedirectToCallbackUrl();
|
|
74
72
|
} else if ( fromHeader === 'true' ) {
|
|
75
73
|
const me = await findMe(store);
|
|
76
74
|
|
|
75
|
+
if (mustChangePasswordFor(me)) {
|
|
76
|
+
return next({ name: 'auth-setup' });
|
|
77
|
+
}
|
|
78
|
+
|
|
77
79
|
await isLoggedIn(store, getUserObject(user, me));
|
|
78
80
|
handleOidcRedirectToCallbackUrl();
|
|
79
81
|
} else if ( fromHeader === 'false' ) {
|
|
@@ -85,6 +87,10 @@ export async function authenticate(to, from, next, { store }) {
|
|
|
85
87
|
try {
|
|
86
88
|
const me = await findMe(store);
|
|
87
89
|
|
|
90
|
+
if (mustChangePasswordFor(me)) {
|
|
91
|
+
return next({ name: 'auth-setup' });
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
await isLoggedIn(store, getUserObject(user, me));
|
|
89
95
|
handleOidcRedirectToCallbackUrl();
|
|
90
96
|
} catch (e) {
|
package/config/router/routes.js
CHANGED
|
@@ -58,12 +58,7 @@ export default [
|
|
|
58
58
|
path: '/home',
|
|
59
59
|
component: () => interopDefault(import('@shell/pages/home.vue')),
|
|
60
60
|
name: 'home'
|
|
61
|
-
}
|
|
62
|
-
{
|
|
63
|
-
path: '/support',
|
|
64
|
-
component: () => interopDefault(import('@shell/pages/support/index.vue')),
|
|
65
|
-
name: 'support'
|
|
66
|
-
},
|
|
61
|
+
}
|
|
67
62
|
]
|
|
68
63
|
},
|
|
69
64
|
{
|
|
@@ -238,6 +233,21 @@ export default [
|
|
|
238
233
|
path: '/c/:cluster/fleet/application/create',
|
|
239
234
|
component: () => interopDefault(import('@shell/pages/c/_cluster/fleet/application/create.vue')),
|
|
240
235
|
name: 'c-cluster-fleet-application-create',
|
|
236
|
+
}, {
|
|
237
|
+
path: '/c/:cluster/fleet/application/suse-app-collection/credentials',
|
|
238
|
+
component: () => interopDefault(import('@shell/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue')),
|
|
239
|
+
name: 'c-cluster-fleet-application-appco-credentials',
|
|
240
|
+
meta: { disableWorkspaceSwitcher: true },
|
|
241
|
+
}, {
|
|
242
|
+
path: '/c/:cluster/fleet/application/suse-app-collection/charts',
|
|
243
|
+
component: () => interopDefault(import('@shell/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue')),
|
|
244
|
+
name: 'c-cluster-fleet-application-appco-charts',
|
|
245
|
+
meta: { disableWorkspaceSwitcher: true },
|
|
246
|
+
}, {
|
|
247
|
+
path: '/c/:cluster/fleet/application/suse-app-collection/chart',
|
|
248
|
+
component: () => interopDefault(import('@shell/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue')),
|
|
249
|
+
name: 'c-cluster-fleet-application-appco-chart',
|
|
250
|
+
meta: { disableWorkspaceSwitcher: true },
|
|
241
251
|
}, {
|
|
242
252
|
path: '/c/:cluster/fleet/application/:resource/create',
|
|
243
253
|
component: () => interopDefault(import('@shell/pages/c/_cluster/fleet/application/_resource/create.vue')),
|
|
@@ -349,6 +359,10 @@ export default [
|
|
|
349
359
|
path: '/c/:cluster/explorer/explorer-utils',
|
|
350
360
|
component: () => interopDefault(import('@shell/pages/c/_cluster/explorer/explorer-utils.js')),
|
|
351
361
|
name: 'c-cluster-explorer-explorer-utils'
|
|
362
|
+
}, {
|
|
363
|
+
path: '/c/:cluster/explorer/workload-dashboard',
|
|
364
|
+
component: () => interopDefault(import('@shell/pages/c/_cluster/explorer/workload-dashboard/index.vue')),
|
|
365
|
+
name: 'c-cluster-explorer-workload-dashboard'
|
|
352
366
|
}, {
|
|
353
367
|
path: '/c/:cluster/explorer/tools',
|
|
354
368
|
component: () => interopDefault(import('@shell/pages/c/_cluster/explorer/tools/index.vue')),
|
package/config/settings.ts
CHANGED
|
@@ -63,7 +63,6 @@ export const SETTING = {
|
|
|
63
63
|
AUTH_USER_INFO_RESYNC_CRON: 'auth-user-info-resync-cron',
|
|
64
64
|
AUTH_LOCAL_VALIDATE_DESC: 'auth-password-requirements-description',
|
|
65
65
|
PASSWORD_MIN_LENGTH: 'password-min-length', // CATTLE_PASSWORD_MIN_LENGTH
|
|
66
|
-
UI_INDEX: 'ui-index',
|
|
67
66
|
UI_DASHBOARD_INDEX: 'ui-dashboard-index',
|
|
68
67
|
UI_DASHBOARD_HARVESTER_LEGACY_PLUGIN: 'ui-dashboard-harvester-legacy-plugin',
|
|
69
68
|
UI_OFFLINE_PREFERRED: 'ui-offline-preferred',
|
|
@@ -164,7 +163,6 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
|
|
|
164
163
|
[SETTING.SERVER_URL]: { kind: 'url', canReset: true },
|
|
165
164
|
[SETTING.RKE_METADATA_CONFIG]: { kind: 'json' },
|
|
166
165
|
[SETTING.SYSTEM_DEFAULT_REGISTRY]: {},
|
|
167
|
-
[SETTING.UI_INDEX]: {},
|
|
168
166
|
[SETTING.UI_DASHBOARD_INDEX]: {},
|
|
169
167
|
[SETTING.UI_OFFLINE_PREFERRED]: {
|
|
170
168
|
kind: 'enum',
|
package/config/table-headers.js
CHANGED
|
@@ -793,11 +793,10 @@ export const FLEET_SUMMARY = {
|
|
|
793
793
|
export const FLEET_APPLICATION_TYPE = {
|
|
794
794
|
name: 'applicationType',
|
|
795
795
|
labelKey: 'fleet.tableHeaders.applicationType',
|
|
796
|
-
value: '
|
|
797
|
-
sort: '
|
|
796
|
+
value: 'applicationType',
|
|
797
|
+
sort: 'applicationType',
|
|
798
798
|
search: false,
|
|
799
|
-
|
|
800
|
-
width: 100,
|
|
799
|
+
width: 200,
|
|
801
800
|
};
|
|
802
801
|
|
|
803
802
|
export const FLEET_APPLICATION_SOURCE = {
|
package/config/types.js
CHANGED
|
@@ -81,6 +81,7 @@ export const RBAC = {
|
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
export const WORKLOAD = 'workload';
|
|
84
|
+
export const WORKLOAD_DASHBOARD = 'workload-dashboard';
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
87
|
* Rancher Workload types
|
|
@@ -279,6 +280,7 @@ export const CAPI = {
|
|
|
279
280
|
MACHINE: 'cluster.x-k8s.io.machine',
|
|
280
281
|
RANCHER_CLUSTER: 'provisioning.cattle.io.cluster',
|
|
281
282
|
MACHINE_CONFIG_GROUP: 'rke-machine-config.cattle.io',
|
|
283
|
+
CAPI_PROVIDER: 'turtles-capi.cattle.io.capiprovider'
|
|
282
284
|
};
|
|
283
285
|
|
|
284
286
|
// --------------------------------------
|
|
@@ -295,6 +297,7 @@ export const FLEET = {
|
|
|
295
297
|
DASHBOARD: 'fleet.cattle.io.dashboard',
|
|
296
298
|
GIT_REPO: 'fleet.cattle.io.gitrepo',
|
|
297
299
|
HELM_OP: 'fleet.cattle.io.helmop',
|
|
300
|
+
SUSE_APP_COLLECTION: 'suse-application-collection',
|
|
298
301
|
WORKSPACE: 'management.cattle.io.fleetworkspace',
|
|
299
302
|
TOKEN: 'fleet.cattle.io.clusterregistrationtoken',
|
|
300
303
|
BUNDLE_NAMESPACE_MAPPING: 'fleet.cattle.io.bundlenamespacemapping',
|
|
@@ -420,6 +423,12 @@ export const CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME = 'clusterrepo-appco-auth-';
|
|
|
420
423
|
*/
|
|
421
424
|
export const CLUSTER_REPO_AUTH_GENERATE_NAME = 'clusterrepo-auth-';
|
|
422
425
|
|
|
426
|
+
/**
|
|
427
|
+
* The `generateName` prefix used when creating Helm Op authentication secrets
|
|
428
|
+
* for standard Helm sources.
|
|
429
|
+
*/
|
|
430
|
+
export const AUTH_GENERATE_NAME = 'auth-';
|
|
431
|
+
|
|
423
432
|
export const ZERO_TIME = '0001-01-01T00:00:00Z';
|
|
424
433
|
|
|
425
434
|
export const DEFAULT_GRAFANA_STORAGE_SIZE = '10Gi';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IExtension } from '@shell/core/types';
|
|
2
2
|
import {
|
|
3
3
|
ProductChild, ProductMetadata,
|
|
4
|
-
|
|
4
|
+
ResourcePageConfiguration, CustomPageConfiguration,
|
|
5
5
|
ProductChildCustomPage, VueRouteComponent,
|
|
6
6
|
OverviewPageRoutingMetadata
|
|
7
7
|
} from '@shell/core/plugin-types';
|
|
@@ -343,7 +343,7 @@ export abstract class BasePluginProduct {
|
|
|
343
343
|
this.registeredPageNames.add(finalName);
|
|
344
344
|
this.pageIdMap.set(item.name, finalName);
|
|
345
345
|
|
|
346
|
-
const virtualTypeConfig:
|
|
346
|
+
const virtualTypeConfig: CustomPageConfiguration = {
|
|
347
347
|
label: item.label,
|
|
348
348
|
labelKey: item.labelKey,
|
|
349
349
|
namespaced: false,
|
|
@@ -377,7 +377,7 @@ export abstract class BasePluginProduct {
|
|
|
377
377
|
|
|
378
378
|
const route = pluginProductsHelpers.generateConfigureTypeRoute(parentName, item, { extendProduct: !this.isNewProduct });
|
|
379
379
|
|
|
380
|
-
const configureTypeConfig:
|
|
380
|
+
const configureTypeConfig: ResourcePageConfiguration = {
|
|
381
381
|
isCreatable: true,
|
|
382
382
|
isEditable: true,
|
|
383
383
|
isRemovable: true,
|