@rancher/shell 3.0.2-rc.2 → 3.0.2-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/base/_basic.scss +7 -8
- package/assets/styles/global/_button.scss +10 -0
- package/assets/styles/global/_form.scss +2 -1
- package/assets/styles/global/_tooltip.scss +2 -2
- package/assets/styles/themes/_dark.scss +15 -3
- package/assets/styles/themes/_light.scss +7 -2
- package/assets/styles/vendor/vue-select.scss +4 -0
- package/assets/translations/en-us.yaml +66 -9
- package/assets/translations/zh-hans.yaml +2 -3
- package/components/AppModal.vue +50 -0
- package/components/BannerGraphic.vue +0 -42
- package/components/ButtonMultiAction.vue +1 -1
- package/components/Carousel.vue +88 -74
- package/components/CommunityLinks.vue +6 -1
- package/components/CopyToClipboardText.vue +3 -0
- package/components/Dialog.vue +20 -1
- package/components/GrowlManager.vue +9 -2
- package/components/LocaleSelector.vue +8 -1
- package/components/PaginatedResourceTable.vue +4 -7
- package/components/ProgressBarMulti.vue +14 -0
- package/components/PromptChangePassword.vue +3 -0
- package/components/Questions/Reference.vue +57 -28
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/SelectIconGrid.vue +12 -1
- package/components/SideNav.vue +12 -38
- package/components/SortableTable/index.vue +1 -0
- package/components/Tabbed/index.vue +9 -1
- package/components/YamlEditor.vue +1 -0
- package/components/__tests__/Carousel.test.ts +56 -27
- package/components/auth/Principal.vue +5 -3
- package/components/fleet/FleetClusters.vue +82 -1
- package/components/fleet/FleetRepos.vue +13 -30
- package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
- package/components/form/ChangePassword.vue +2 -0
- package/components/form/ColorInput.vue +24 -1
- package/components/form/FileSelector.vue +2 -0
- package/components/form/KeyValue.vue +230 -160
- package/components/form/LabeledSelect.vue +2 -2
- package/components/form/PlusMinus.vue +14 -2
- package/components/form/ResourceLabeledSelect.vue +13 -53
- package/components/form/ResourceSelector.vue +1 -0
- package/components/form/ResourceTabs/index.vue +79 -36
- package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +192 -0
- package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +104 -0
- package/components/form/SSHKnownHosts/index.vue +101 -0
- package/components/form/SecretSelector.vue +2 -2
- package/components/form/Select.vue +1 -1
- package/components/form/SelectOrCreateAuthSecret.vue +43 -11
- package/components/form/__tests__/KeyValue.test.ts +1 -1
- package/components/form/__tests__/SSHKnownHosts.test.ts +59 -0
- package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +6 -7
- package/components/formatter/WorkloadHealthScale.vue +7 -0
- package/components/nav/Group.vue +30 -4
- package/components/nav/Header.vue +82 -114
- package/components/nav/HeaderPageActionMenu.vue +27 -131
- package/components/nav/NamespaceFilter.vue +1 -1
- package/components/nav/Type.vue +15 -0
- package/composables/focusTrap.ts +68 -0
- package/config/home-links.js +21 -13
- package/config/labels-annotations.js +2 -0
- package/config/page-actions.js +1 -0
- package/config/pagination-table-headers.js +15 -1
- package/config/product/explorer.js +7 -17
- package/config/table-headers.js +6 -0
- package/config/version.js +5 -1
- package/core/plugin.ts +41 -1
- package/core/plugins.js +125 -72
- package/core/types-provisioning.ts +91 -2
- package/core/types.ts +55 -0
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
- package/detail/catalog.cattle.io.app.vue +1 -1
- package/detail/fleet.cattle.io.cluster.vue +3 -3
- package/detail/namespace.vue +13 -19
- package/detail/networking.k8s.io.ingress.vue +13 -53
- package/detail/provisioning.cattle.io.cluster.vue +12 -1
- package/detail/secret.vue +25 -0
- package/detail/workload/index.vue +3 -3
- package/dialog/AddCustomBadgeDialog.vue +5 -1
- package/edit/auth/ldap/__tests__/config.test.ts +18 -0
- package/edit/auth/ldap/config.vue +24 -0
- package/edit/auth/saml.vue +8 -6
- package/edit/fleet.cattle.io.gitrepo.vue +34 -23
- package/edit/logging-flow/index.vue +4 -19
- package/edit/networking.k8s.io.ingress/index.vue +18 -65
- package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
- package/edit/provisioning.cattle.io.cluster/index.vue +27 -8
- package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
- package/edit/secret/index.vue +1 -1
- package/edit/secret/ssh.vue +21 -3
- package/edit/service.vue +1 -2
- package/list/networking.k8s.io.ingress.vue +1 -1
- package/list/node.vue +15 -8
- package/list/persistentvolume.vue +12 -4
- package/list/provisioning.cattle.io.cluster.vue +1 -0
- package/list/service.vue +1 -1
- package/list/workload.vue +4 -0
- package/mixins/chart.js +4 -1
- package/models/catalog.cattle.io.app.js +3 -1
- package/models/catalog.cattle.io.clusterrepo.js +56 -7
- package/models/fleet.cattle.io.bundle.js +0 -11
- package/models/fleet.cattle.io.cluster.js +17 -1
- package/models/fleet.cattle.io.gitrepo.js +88 -52
- package/models/provisioning.cattle.io.cluster.js +36 -1
- package/models/secret.js +5 -0
- package/models/service.js +1 -0
- package/models/workload.js +19 -1
- package/package.json +5 -4
- package/pages/account/index.vue +4 -0
- package/pages/c/_cluster/apps/charts/index.vue +4 -0
- package/pages/c/_cluster/explorer/ConfigBadge.vue +4 -2
- package/pages/c/_cluster/explorer/index.vue +13 -6
- package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
- package/pages/c/_cluster/fleet/index.vue +75 -89
- package/pages/c/_cluster/settings/links.vue +2 -2
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +3 -1
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +3 -0
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +7 -1
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +3 -1
- package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +10 -7
- package/pages/c/_cluster/uiplugins/InstallDialog.vue +7 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +181 -106
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +2 -0
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +9 -1
- package/pages/c/_cluster/uiplugins/index.vue +50 -12
- package/pages/diagnostic.vue +17 -15
- package/pages/home.vue +32 -6
- package/plugins/clean-html.js +50 -0
- package/plugins/dashboard-store/resource-class.js +4 -0
- package/plugins/plugin.js +54 -49
- package/plugins/steve/mutations.js +1 -1
- package/plugins/steve/steve-class.js +8 -0
- package/plugins/steve/steve-pagination-utils.ts +3 -1
- package/rancher-components/Accordion/Accordion.vue +4 -4
- package/rancher-components/BadgeState/BadgeState.vue +7 -0
- package/rancher-components/Card/Card.vue +12 -0
- package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +19 -1
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
- package/rancher-components/RcButton/RcButton.vue +90 -0
- package/rancher-components/RcButton/index.ts +2 -0
- package/rancher-components/RcButton/types.ts +17 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +122 -0
- package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
- package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +42 -0
- package/rancher-components/RcDropdown/index.ts +4 -0
- package/rancher-components/RcDropdown/types.ts +22 -0
- package/rancher-components/RcDropdown/useDropdownCollection.ts +46 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +110 -0
- package/scripts/test-plugins-build.sh +2 -0
- package/scripts/typegen.sh +2 -0
- package/store/catalog.js +1 -1
- package/tsconfig.json +2 -1
- package/types/components/paginatedResourceTable.ts +25 -0
- package/types/components/resourceLabeledSelect.ts +48 -0
- package/types/resources/fleet.d.ts +17 -0
- package/types/shell/index.d.ts +61 -0
- package/utils/auth.js +5 -1
- package/utils/cluster.js +106 -0
- package/utils/fleet.ts +35 -3
- package/utils/ingress.ts +64 -0
- package/utils/uiplugins.ts +56 -44
- package/utils/validators/cron-schedule.js +7 -2
- package/utils/validators/formRules/__tests__/index.test.ts +53 -17
- package/utils/validators/formRules/index.ts +20 -5
- package/vue.config.js +1 -1
- package/components/RelatedWorkloadsTable.vue +0 -50
package/pages/home.vue
CHANGED
|
@@ -4,7 +4,7 @@ import { mapPref, AFTER_LOGIN_ROUTE, READ_WHATS_NEW, HIDE_HOME_PAGE_CARDS } from
|
|
|
4
4
|
import { Banner } from '@components/Banner';
|
|
5
5
|
import BannerGraphic from '@shell/components/BannerGraphic.vue';
|
|
6
6
|
import IndentedPanel from '@shell/components/IndentedPanel.vue';
|
|
7
|
-
import PaginatedResourceTable
|
|
7
|
+
import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
|
|
8
8
|
import { BadgeState } from '@components/BadgeState';
|
|
9
9
|
import CommunityLinks from '@shell/components/CommunityLinks.vue';
|
|
10
10
|
import SingleClusterInfo from '@shell/components/SingleClusterInfo.vue';
|
|
@@ -23,11 +23,12 @@ import { filterHiddenLocalCluster, filterOnlyKubernetesClusters, paginationFilte
|
|
|
23
23
|
import TabTitle from '@shell/components/TabTitle.vue';
|
|
24
24
|
import { ActionFindPageArgs } from '@shell/types/store/dashboard-store.types';
|
|
25
25
|
|
|
26
|
-
import { RESET_CARDS_ACTION, SET_LOGIN_ACTION } from '@shell/config/page-actions';
|
|
26
|
+
import { RESET_CARDS_ACTION, SET_LOGIN_ACTION, SHOW_HIDE_BANNER_ACTION } from '@shell/config/page-actions';
|
|
27
27
|
import { STEVE_NAME_COL, STEVE_STATE_COL } from '@shell/config/pagination-table-headers';
|
|
28
28
|
import { PaginationParamFilter, FilterArgs, PaginationFilterField, PaginationArgs } from '@shell/types/store/pagination.types';
|
|
29
29
|
import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
|
|
30
30
|
import { sameContents } from '@shell/utils/array';
|
|
31
|
+
import { PagTableFetchPageSecondaryResourcesOpts, PagTableFetchSecondaryResourcesOpts, PagTableFetchSecondaryResourcesReturns } from '@shell/types/components/paginatedResourceTable';
|
|
31
32
|
|
|
32
33
|
export default defineComponent({
|
|
33
34
|
name: 'Home',
|
|
@@ -56,6 +57,10 @@ export default defineComponent({
|
|
|
56
57
|
action: SET_LOGIN_ACTION
|
|
57
58
|
},
|
|
58
59
|
{ separator: true },
|
|
60
|
+
{
|
|
61
|
+
labelKey: 'nav.header.showHideBanner',
|
|
62
|
+
action: SHOW_HIDE_BANNER_ACTION
|
|
63
|
+
},
|
|
59
64
|
{
|
|
60
65
|
labelKey: 'nav.header.restoreCards',
|
|
61
66
|
action: RESET_CARDS_ACTION
|
|
@@ -238,9 +243,9 @@ export default defineComponent({
|
|
|
238
243
|
|
|
239
244
|
methods: {
|
|
240
245
|
/**
|
|
241
|
-
* Of type
|
|
246
|
+
* Of type PagTableFetchSecondaryResources
|
|
242
247
|
*/
|
|
243
|
-
fetchSecondaryResources(opts:
|
|
248
|
+
fetchSecondaryResources(opts: PagTableFetchSecondaryResourcesOpts): PagTableFetchSecondaryResourcesReturns {
|
|
244
249
|
if (opts.canPaginate) {
|
|
245
250
|
return Promise.resolve({});
|
|
246
251
|
}
|
|
@@ -271,7 +276,7 @@ export default defineComponent({
|
|
|
271
276
|
|
|
272
277
|
async fetchPageSecondaryResources({
|
|
273
278
|
canPaginate, force, page, pagResult
|
|
274
|
-
}:
|
|
279
|
+
}: PagTableFetchPageSecondaryResourcesOpts) {
|
|
275
280
|
if (!canPaginate || !page?.length) {
|
|
276
281
|
this.clusterCount = 0;
|
|
277
282
|
|
|
@@ -367,6 +372,10 @@ export default defineComponent({
|
|
|
367
372
|
this.resetCards();
|
|
368
373
|
break;
|
|
369
374
|
|
|
375
|
+
case SHOW_HIDE_BANNER_ACTION:
|
|
376
|
+
this.toggleBanner();
|
|
377
|
+
break;
|
|
378
|
+
|
|
370
379
|
case SET_LOGIN_ACTION:
|
|
371
380
|
this.afterLoginRoute = 'home';
|
|
372
381
|
break;
|
|
@@ -406,10 +415,27 @@ export default defineComponent({
|
|
|
406
415
|
},
|
|
407
416
|
|
|
408
417
|
async resetCards() {
|
|
409
|
-
|
|
418
|
+
const value = this.$store.getters['prefs/get'](HIDE_HOME_PAGE_CARDS) || {};
|
|
419
|
+
|
|
420
|
+
delete value.setLoginPage;
|
|
421
|
+
|
|
422
|
+
await this.$store.dispatch('prefs/set', { key: HIDE_HOME_PAGE_CARDS, value });
|
|
423
|
+
|
|
410
424
|
await this.$store.dispatch('prefs/set', { key: READ_WHATS_NEW, value: '' });
|
|
411
425
|
},
|
|
412
426
|
|
|
427
|
+
async toggleBanner() {
|
|
428
|
+
const value = this.$store.getters['prefs/get'](HIDE_HOME_PAGE_CARDS) || {};
|
|
429
|
+
|
|
430
|
+
if (value.welcomeBanner) {
|
|
431
|
+
delete value.welcomeBanner;
|
|
432
|
+
} else {
|
|
433
|
+
value.welcomeBanner = true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
await this.$store.dispatch('prefs/set', { key: HIDE_HOME_PAGE_CARDS, value });
|
|
437
|
+
},
|
|
438
|
+
|
|
413
439
|
async closeSetLoginBanner(retry = 0) {
|
|
414
440
|
let value = this.$store.getters['prefs/get'](HIDE_HOME_PAGE_CARDS);
|
|
415
441
|
|
package/plugins/clean-html.js
CHANGED
|
@@ -30,6 +30,8 @@ const ALLOWED_TAGS = [
|
|
|
30
30
|
'blockquote'
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
+
let linkInterceptors = [];
|
|
34
|
+
|
|
33
35
|
// Allow 'A' tags to keep the target=_blank attribute if they have it
|
|
34
36
|
DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
|
|
35
37
|
if (node.tagName === 'A' && data.attrName === 'target' && data.attrValue === '_blank') {
|
|
@@ -46,8 +48,56 @@ DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
|
|
46
48
|
|
|
47
49
|
node.setAttribute('rel', combined.join(' '));
|
|
48
50
|
}
|
|
51
|
+
|
|
52
|
+
if (node.tagName === 'A' && linkInterceptors.length) {
|
|
53
|
+
let link = node.href;
|
|
54
|
+
|
|
55
|
+
// Allow each interceptor to modify the link href
|
|
56
|
+
link = processLink(link);
|
|
57
|
+
|
|
58
|
+
// If the link is different from the original update the href
|
|
59
|
+
if (link !== node.href) {
|
|
60
|
+
node.href = link;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
49
63
|
});
|
|
50
64
|
|
|
51
65
|
export const purifyHTML = (value, options = { ALLOWED_TAGS }) => {
|
|
52
66
|
return DOMPurify.sanitize(value, options);
|
|
53
67
|
};
|
|
68
|
+
|
|
69
|
+
// Link Interceptors are typically used to allow different doc links to be used
|
|
70
|
+
|
|
71
|
+
export function addLinkInterceptor(fn, name) {
|
|
72
|
+
// Check the arg is not undefined and is a function
|
|
73
|
+
if (fn && typeof fn === 'function') {
|
|
74
|
+
linkInterceptors.push(fn);
|
|
75
|
+
} else {
|
|
76
|
+
if (name) {
|
|
77
|
+
console.error(`Invalid link interceptor function for ${ name }`); // eslint-disable-line no-console
|
|
78
|
+
} else {
|
|
79
|
+
console.error('Invalid link interceptor function'); // eslint-disable-line no-console
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function removeLinkInterceptor(fn) {
|
|
85
|
+
linkInterceptors = linkInterceptors.filter((item) => item !== fn);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Process a link through all of the link interceptors
|
|
90
|
+
*/
|
|
91
|
+
export function processLink(link) {
|
|
92
|
+
// Allow each interceptor to modify the link href
|
|
93
|
+
for (let i = 0; i < linkInterceptors.length; i++) {
|
|
94
|
+
const updated = linkInterceptors[i](link);
|
|
95
|
+
|
|
96
|
+
// If a value if returned, use that in place of the original value
|
|
97
|
+
if (updated) {
|
|
98
|
+
link = updated;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return link;
|
|
103
|
+
}
|
package/plugins/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// This plugin loads any UI
|
|
1
|
+
// This plugin loads any UI Extensions at app load time
|
|
2
2
|
import { allHashSettled } from '@shell/utils/promise';
|
|
3
3
|
import { shouldNotLoadPlugin, UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
|
|
4
4
|
import { getKubeVersionData, getVersionData } from '@shell/config/version';
|
|
@@ -11,14 +11,14 @@ export default async function(context) {
|
|
|
11
11
|
|
|
12
12
|
const hash = {};
|
|
13
13
|
|
|
14
|
-
// Provide a mechanism to load the UI without the
|
|
14
|
+
// Provide a mechanism to load the UI without the extensions loaded - in case there is a problem
|
|
15
15
|
let loadPlugins = true;
|
|
16
16
|
|
|
17
17
|
const queryKeys = Object.keys(context.route?.query || {}).map((q) => q.toLowerCase());
|
|
18
18
|
|
|
19
19
|
if (queryKeys.includes('safemode')) {
|
|
20
20
|
loadPlugins = false;
|
|
21
|
-
console.warn('Safe Mode -
|
|
21
|
+
console.warn('Safe Mode - extensions will not be loaded'); // eslint-disable-line no-console
|
|
22
22
|
setTimeout(() => {
|
|
23
23
|
context.store.dispatch('growl/success', {
|
|
24
24
|
title: context.store.getters['i18n/t']('plugins.safeMode.title'),
|
|
@@ -27,62 +27,67 @@ export default async function(context) {
|
|
|
27
27
|
}, 1000);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const fetches = { versions: versions.fetch(context) };
|
|
31
|
+
|
|
32
|
+
// If we are loading extensions then add the API fetch for the list of extensions to the fetches we will make
|
|
30
33
|
if (loadPlugins) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
headers: { accept: 'application/json' },
|
|
39
|
-
redirectUnauthorized: false,
|
|
40
|
-
})
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
if (res.plugins.status === 'rejected') {
|
|
44
|
-
throw new Error(res.reason);
|
|
45
|
-
}
|
|
34
|
+
fetches.plugins = context.store.dispatch('management/request', {
|
|
35
|
+
url: `${ UI_PLUGIN_BASE_URL }`,
|
|
36
|
+
method: 'GET',
|
|
37
|
+
headers: { accept: 'application/json' },
|
|
38
|
+
redirectUnauthorized: false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!shouldNotLoad) {
|
|
57
|
-
hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
|
|
58
|
-
} else {
|
|
59
|
-
context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
} catch (e) {
|
|
63
|
-
if (e?.code === 404) {
|
|
64
|
-
// Not found, so extensions operator probably not installed
|
|
65
|
-
console.log('Could not load UI Extensions list (Extensions Operator may not be installed)'); // eslint-disable-line no-console
|
|
66
|
-
} else {
|
|
67
|
-
console.error('Could not load UI Extensions list', e); // eslint-disable-line no-console
|
|
68
|
-
}
|
|
42
|
+
// Fetch list of installed extensions from the extensions endpoint if needed and the version information
|
|
43
|
+
try {
|
|
44
|
+
const res = await allHashSettled(fetches);
|
|
45
|
+
|
|
46
|
+
// Initialize the built-in extensions now - this is now done here so that built-in extensions get the same, correct environment data (version etc)
|
|
47
|
+
context.$plugin.loadBuiltinExtensions();
|
|
48
|
+
|
|
49
|
+
if (res.plugins?.status === 'rejected') {
|
|
50
|
+
throw new Error(res.reason);
|
|
69
51
|
}
|
|
70
52
|
|
|
71
|
-
|
|
72
|
-
const
|
|
53
|
+
const kubeVersion = getKubeVersionData()?.gitVersion;
|
|
54
|
+
const rancherVersion = getVersionData().Version;
|
|
73
55
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const res = pluginLoads[name];
|
|
56
|
+
const plugins = res.plugins?.value || {};
|
|
57
|
+
const entries = plugins.entries || plugins.Entries || {};
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
Object.values(entries).forEach((plugin) => {
|
|
60
|
+
const shouldNotLoad = shouldNotLoadPlugin(plugin, { rancherVersion, kubeVersion }, context.store.getters['uiplugins/plugins'] || []); // Error key string or boolean
|
|
80
61
|
|
|
81
|
-
|
|
82
|
-
|
|
62
|
+
if (!shouldNotLoad) {
|
|
63
|
+
hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
|
|
64
|
+
} else {
|
|
65
|
+
context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
|
|
83
66
|
}
|
|
84
67
|
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (e?.code === 404) {
|
|
70
|
+
// Not found, so extensions operator probably not installed
|
|
71
|
+
console.log('Could not load UI Extensions list (Extensions Operator may not be installed)'); // eslint-disable-line no-console
|
|
72
|
+
} else {
|
|
73
|
+
console.error('Could not load UI Extensions list', e); // eslint-disable-line no-console
|
|
74
|
+
}
|
|
85
75
|
}
|
|
86
76
|
|
|
77
|
+
// Load all of the extensions
|
|
78
|
+
const pluginLoads = await allHashSettled(hash);
|
|
79
|
+
|
|
80
|
+
// Some extensions may have failed to load - store this
|
|
81
|
+
Object.keys(pluginLoads).forEach((name) => {
|
|
82
|
+
const res = pluginLoads[name];
|
|
83
|
+
|
|
84
|
+
if (res?.status === 'rejected') {
|
|
85
|
+
console.error(`Failed to load extension: ${ name }. `, res.reason || 'Unknown reason'); // eslint-disable-line no-console
|
|
86
|
+
|
|
87
|
+
// Record error in the uiplugins store, so that we can show this to the user
|
|
88
|
+
context.store.dispatch('uiplugins/setError', { name, error: 'plugins.error.load' }); // i18n-uses plugins.error.load
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
87
92
|
return true;
|
|
88
93
|
}
|
|
@@ -31,7 +31,7 @@ function registerNamespace(state, namespace) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* update the podsByNamespace cache with new or changed pods
|
|
34
|
+
* update the podsByNamespace cache with new or changed pods.
|
|
35
35
|
*/
|
|
36
36
|
function updatePodsByNamespaceCache(state, ctx, pods, loadAll) {
|
|
37
37
|
if (loadAll) {
|
|
@@ -2,6 +2,7 @@ import { DESCRIPTION } from '@shell/config/labels-annotations';
|
|
|
2
2
|
import HybridModel from './hybrid-class';
|
|
3
3
|
import { NEVER_ADD } from '@shell/utils/create-yaml';
|
|
4
4
|
import { deleteProperty } from '@shell/utils/object';
|
|
5
|
+
import { EXT_IDS } from '@shell/core/plugin';
|
|
5
6
|
|
|
6
7
|
// Some fields that are removed for YAML (NEVER_ADD) are required via API
|
|
7
8
|
const STEVE_ADD = [
|
|
@@ -41,6 +42,13 @@ export default class SteveModel extends HybridModel {
|
|
|
41
42
|
this._description = value;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Get all model extensions for this model
|
|
47
|
+
*/
|
|
48
|
+
get modelExtensions() {
|
|
49
|
+
return this.$plugin.getDynamic(EXT_IDS.MODEL_EXTENSION, this.type) || [];
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
cleanForSave(data, forNew) {
|
|
45
53
|
const val = super.cleanForSave(data);
|
|
46
54
|
|
|
@@ -157,6 +157,7 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
157
157
|
{ field: '_type' },
|
|
158
158
|
{ field: 'reason' },
|
|
159
159
|
{ field: 'involvedObject.kind' },
|
|
160
|
+
// { field: 'involvedObject.uid' }, // Pending API Support - https://github.com/rancher/rancher/issues/48603
|
|
160
161
|
{ field: 'message' },
|
|
161
162
|
],
|
|
162
163
|
[CATALOG.CLUSTER_REPO]: [
|
|
@@ -431,7 +432,8 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
431
432
|
// Check if the API supports filtering by this field
|
|
432
433
|
this.validateField(validateFields, schema, field.field);
|
|
433
434
|
|
|
434
|
-
const
|
|
435
|
+
const value = encodeURIComponent(field.value);
|
|
436
|
+
const exactPartial = field.exact ? `'${ value }'` : value;
|
|
435
437
|
|
|
436
438
|
return `${ this.convertArrayPath(field.field) }${ field.equals ? '=' : '!=' }${ exactPartial }`;
|
|
437
439
|
}
|
|
@@ -47,12 +47,12 @@ export default defineComponent({
|
|
|
47
47
|
data-testid="accordion-chevron"
|
|
48
48
|
/>
|
|
49
49
|
<slot name="header">
|
|
50
|
-
<
|
|
50
|
+
<h2
|
|
51
51
|
data-testid="accordion-title-slot-content"
|
|
52
52
|
class="mb-0"
|
|
53
53
|
>
|
|
54
54
|
{{ titleKey ? t(titleKey) : title }}
|
|
55
|
-
</
|
|
55
|
+
</h2>
|
|
56
56
|
</slot>
|
|
57
57
|
</div>
|
|
58
58
|
<div
|
|
@@ -70,7 +70,7 @@ export default defineComponent({
|
|
|
70
70
|
border: 1px solid var(--border)
|
|
71
71
|
}
|
|
72
72
|
.accordion-header {
|
|
73
|
-
padding:
|
|
73
|
+
padding: 16px 16px 16px 11px;
|
|
74
74
|
display: flex;
|
|
75
75
|
align-items: center;
|
|
76
76
|
&>*{
|
|
@@ -81,6 +81,6 @@ export default defineComponent({
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
.accordion-body {
|
|
84
|
-
padding:
|
|
84
|
+
padding: 0px 16px 16px;
|
|
85
85
|
}
|
|
86
86
|
</style>
|
|
@@ -94,6 +94,13 @@ export default defineComponent({
|
|
|
94
94
|
background: transparent;
|
|
95
95
|
border-color: var(--success);
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
// Added badge-disabled instead of bg-disabled since bg-disabled is used in other places with !important styling, an investigation is needed to make the naming consistent
|
|
99
|
+
&.badge-disabled {
|
|
100
|
+
color: var(--badge-state-disabled-text);
|
|
101
|
+
background-color: var( --badge-state-disabled-bg);
|
|
102
|
+
border: 1px solid var(--badge-state-disabled-border);
|
|
103
|
+
}
|
|
97
104
|
}
|
|
98
105
|
</style>
|
|
99
106
|
<style lang="scss">
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { defineComponent, PropType } from 'vue';
|
|
3
|
+
import { useBasicSetupFocusTrap } from '@shell/composables/focusTrap';
|
|
3
4
|
|
|
4
5
|
export default defineComponent({
|
|
6
|
+
|
|
5
7
|
name: 'Card',
|
|
6
8
|
props: {
|
|
7
9
|
/**
|
|
@@ -50,12 +52,22 @@ export default defineComponent({
|
|
|
50
52
|
type: Boolean,
|
|
51
53
|
default: false,
|
|
52
54
|
},
|
|
55
|
+
triggerFocusTrap: {
|
|
56
|
+
type: Boolean,
|
|
57
|
+
default: false,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
setup(props) {
|
|
61
|
+
if (props.triggerFocusTrap) {
|
|
62
|
+
useBasicSetupFocusTrap('#focus-trap-card-container-element');
|
|
63
|
+
}
|
|
53
64
|
}
|
|
54
65
|
});
|
|
55
66
|
</script>
|
|
56
67
|
|
|
57
68
|
<template>
|
|
58
69
|
<div
|
|
70
|
+
id="focus-trap-card-container-element"
|
|
59
71
|
class="card-container"
|
|
60
72
|
:class="{'highlight-border': showHighlightBorder, 'card-sticky': sticky}"
|
|
61
73
|
data-testid="card"
|
|
@@ -264,13 +264,15 @@ export default defineComponent({
|
|
|
264
264
|
<template v-else-if="label">{{ label }}</template>
|
|
265
265
|
<i
|
|
266
266
|
v-if="tooltipKey"
|
|
267
|
-
v-clean-tooltip="t(tooltipKey)"
|
|
267
|
+
v-clean-tooltip="{content: t(tooltipKey), triggers: ['hover', 'touch', 'focus']}"
|
|
268
268
|
class="checkbox-info icon icon-info icon-lg"
|
|
269
|
+
:tabindex="isDisabled ? -1 : 0"
|
|
269
270
|
/>
|
|
270
271
|
<i
|
|
271
272
|
v-else-if="tooltip"
|
|
272
|
-
v-clean-tooltip="tooltip"
|
|
273
|
+
v-clean-tooltip="{content: tooltip, triggers: ['hover', 'touch', 'focus']}"
|
|
273
274
|
class="checkbox-info icon icon-info icon-lg"
|
|
275
|
+
:tabindex="isDisabled ? -1 : 0"
|
|
274
276
|
/>
|
|
275
277
|
</slot>
|
|
276
278
|
</span>
|
|
@@ -329,6 +331,11 @@ $fontColor: var(--input-label);
|
|
|
329
331
|
.checkbox-info {
|
|
330
332
|
line-height: normal;
|
|
331
333
|
margin-left: 2px;
|
|
334
|
+
|
|
335
|
+
&:focus-visible {
|
|
336
|
+
@include focus-outline;
|
|
337
|
+
outline-offset: 2px;
|
|
338
|
+
}
|
|
332
339
|
}
|
|
333
340
|
|
|
334
341
|
.checkbox-custom {
|
|
@@ -20,7 +20,7 @@ describe('component: LabeledInput', () => {
|
|
|
20
20
|
expect(wrapper.emitted('update:value')![0][0]).toBe(value);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it('using
|
|
23
|
+
it('using type "multiline" should emit input value correctly', () => {
|
|
24
24
|
const value = 'any-string';
|
|
25
25
|
const delay = 1;
|
|
26
26
|
const wrapper = mount(LabeledInput, {
|
|
@@ -37,4 +37,21 @@ describe('component: LabeledInput', () => {
|
|
|
37
37
|
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
|
38
38
|
expect(wrapper.emitted('update:value')![0][0]).toBe(value);
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
describe('using type "chron"', () => {
|
|
42
|
+
it.each([
|
|
43
|
+
['0 * * * *', 'Every hour, every day'],
|
|
44
|
+
['@daily', 'At 12:00 AM, every day'],
|
|
45
|
+
['You must fail! Go!', '%generic.invalidCron%'],
|
|
46
|
+
])('passing value %p should display hint %p', (value, hint) => {
|
|
47
|
+
const wrapper = mount(LabeledInput, {
|
|
48
|
+
propsData: { value, type: 'cron' },
|
|
49
|
+
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const subLabel = wrapper.find('[data-testid="sub-label"]');
|
|
53
|
+
|
|
54
|
+
expect(subLabel.text()).toBe(hint);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
40
57
|
});
|
|
@@ -179,14 +179,28 @@ export default defineComponent({
|
|
|
179
179
|
if (this.type !== 'cron' || !this.value) {
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
|
+
|
|
183
|
+
// TODO - #13202: This is required due use of 2 libraries and 3 different libraries through the code.
|
|
184
|
+
const predefined = [
|
|
185
|
+
'@yearly',
|
|
186
|
+
'@annually',
|
|
187
|
+
'@monthly',
|
|
188
|
+
'@weekly',
|
|
189
|
+
'@daily',
|
|
190
|
+
'@midnight',
|
|
191
|
+
'@hourly'
|
|
192
|
+
];
|
|
193
|
+
const isPredefined = predefined.includes(this.value as string);
|
|
194
|
+
|
|
182
195
|
// refer https://github.com/GuillaumeRochat/cron-validator#readme
|
|
183
|
-
if (!isValidCron(this.value as string, {
|
|
196
|
+
if (!isPredefined && !isValidCron(this.value as string, {
|
|
184
197
|
alias: true,
|
|
185
198
|
allowBlankDay: true,
|
|
186
199
|
allowSevenAsSunday: true,
|
|
187
200
|
})) {
|
|
188
201
|
return this.t('generic.invalidCron');
|
|
189
202
|
}
|
|
203
|
+
|
|
190
204
|
try {
|
|
191
205
|
const hint = cronstrue.toString(this.value as string || '', { verbose: true });
|
|
192
206
|
|
|
@@ -350,6 +364,7 @@ export default defineComponent({
|
|
|
350
364
|
<input
|
|
351
365
|
v-else
|
|
352
366
|
ref="value"
|
|
367
|
+
role="textbox"
|
|
353
368
|
:class="{ 'no-label': !hasLabel }"
|
|
354
369
|
v-bind="$attrs"
|
|
355
370
|
:maxlength="_maxlength"
|
|
@@ -382,9 +397,12 @@ export default defineComponent({
|
|
|
382
397
|
<div
|
|
383
398
|
v-if="cronHint || subLabel"
|
|
384
399
|
class="sub-label"
|
|
400
|
+
data-testid="sub-label"
|
|
385
401
|
>
|
|
386
402
|
<div
|
|
387
403
|
v-if="cronHint"
|
|
404
|
+
role="alert"
|
|
405
|
+
:aria-label="cronHint"
|
|
388
406
|
>
|
|
389
407
|
{{ cronHint }}
|
|
390
408
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { defineComponent } from 'vue';
|
|
2
|
+
import { defineComponent, onMounted, onBeforeUnmount, useTemplateRef } from 'vue';
|
|
3
3
|
|
|
4
4
|
type StateType = boolean | 'true' | 'false' | undefined;
|
|
5
5
|
|
|
@@ -33,6 +33,29 @@ export default defineComponent({
|
|
|
33
33
|
|
|
34
34
|
emits: ['update:value'],
|
|
35
35
|
|
|
36
|
+
setup() {
|
|
37
|
+
const switchChrome = useTemplateRef<HTMLElement>('switchChrome');
|
|
38
|
+
const focus = () => {
|
|
39
|
+
switchChrome.value?.classList.add('focus');
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const blur = () => {
|
|
43
|
+
switchChrome.value?.classList.remove('focus');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const switchInput = useTemplateRef<HTMLInputElement>('switchInput');
|
|
47
|
+
|
|
48
|
+
onMounted(() => {
|
|
49
|
+
switchInput.value?.addEventListener('focus', focus);
|
|
50
|
+
switchInput.value?.addEventListener('blur', blur);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
onBeforeUnmount(() => {
|
|
54
|
+
switchInput.value?.removeEventListener('focus', focus);
|
|
55
|
+
switchInput.value?.removeEventListener('blur', blur);
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
|
|
36
59
|
data() {
|
|
37
60
|
return { state: false as StateType };
|
|
38
61
|
},
|
|
@@ -64,11 +87,18 @@ export default defineComponent({
|
|
|
64
87
|
>{{ offLabel }}</span>
|
|
65
88
|
<label class="switch hand">
|
|
66
89
|
<input
|
|
90
|
+
ref="switchInput"
|
|
67
91
|
type="checkbox"
|
|
92
|
+
role="switch"
|
|
68
93
|
:checked="state"
|
|
94
|
+
:aria-label="onLabel"
|
|
69
95
|
@input="toggle(null)"
|
|
96
|
+
@keydown.enter="toggle(null)"
|
|
70
97
|
>
|
|
71
|
-
<span
|
|
98
|
+
<span
|
|
99
|
+
ref="switchChrome"
|
|
100
|
+
class="slider round"
|
|
101
|
+
/>
|
|
72
102
|
</label>
|
|
73
103
|
<span
|
|
74
104
|
class="label no-select hand"
|
|
@@ -118,6 +148,13 @@ $toggle-height: 16px;
|
|
|
118
148
|
background-color: var(--checkbox-disabled-bg);
|
|
119
149
|
-webkit-transition: .4s;
|
|
120
150
|
transition: .4s;
|
|
151
|
+
|
|
152
|
+
&.focus {
|
|
153
|
+
@include focus-outline;
|
|
154
|
+
outline-offset: 2px;
|
|
155
|
+
-webkit-transition: 0s;
|
|
156
|
+
transition: 0s;
|
|
157
|
+
}
|
|
121
158
|
}
|
|
122
159
|
|
|
123
160
|
.slider:before {
|