@rancher/shell 0.3.24 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/translations/en-us.yaml +29 -7
- package/assets/translations/zh-hans.yaml +1 -1
- package/components/ClusterIconMenu.vue +143 -0
- package/components/CruResource.vue +7 -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/auth/RoleDetailEdit.vue +15 -2
- 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/__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 +194 -598
- 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/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
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
|
|
4
4
|
import AsyncButton from '@shell/components/AsyncButton';
|
|
5
|
-
import { CATALOG
|
|
6
|
-
import {
|
|
7
|
-
import { allHash } from '@shell/utils/promise';
|
|
5
|
+
import { CATALOG } from '@shell/config/types';
|
|
6
|
+
import { UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
|
|
8
7
|
|
|
9
8
|
export default {
|
|
10
9
|
components: { AsyncButton },
|
|
@@ -33,7 +32,7 @@ export default {
|
|
|
33
32
|
this.$emit('update', plugin.name, 'uninstall');
|
|
34
33
|
|
|
35
34
|
// Delete the CR if this is a developer plugin (there is no Helm App, so need to remove the CRD ourselves)
|
|
36
|
-
if (plugin.uiplugin?.isDeveloper
|
|
35
|
+
if (plugin.uiplugin?.isDeveloper) {
|
|
37
36
|
// Delete the custom resource
|
|
38
37
|
await plugin.uiplugin.remove();
|
|
39
38
|
}
|
|
@@ -41,28 +40,13 @@ export default {
|
|
|
41
40
|
// Find the app for this plugin
|
|
42
41
|
const apps = await this.$store.dispatch('management/findAll', { type: CATALOG.APP });
|
|
43
42
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
// Find the related apps from the deployed helm repository
|
|
47
|
-
const charts = this.allCharts.filter((chart) => chart.repoName === plugin.repo?.metadata?.name);
|
|
48
|
-
|
|
49
|
-
return charts.some((chart) => chart.chartName === app.metadata.name);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (app.namespace === UI_PLUGIN_NAMESPACE && app.name === plugin.name) {
|
|
53
|
-
return app;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return false;
|
|
43
|
+
const pluginApp = apps.find((app) => {
|
|
44
|
+
return app.namespace === UI_PLUGIN_NAMESPACE && app.name === plugin.name;
|
|
57
45
|
});
|
|
58
46
|
|
|
59
|
-
if (
|
|
60
|
-
await this.removePluginImageResources(plugin.uiplugin);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (pluginApps.length) {
|
|
47
|
+
if (pluginApp) {
|
|
64
48
|
try {
|
|
65
|
-
|
|
49
|
+
await pluginApp.remove();
|
|
66
50
|
} catch (e) {
|
|
67
51
|
this.$store.dispatch('growl/error', {
|
|
68
52
|
title: this.t('plugins.error.generic'),
|
|
@@ -75,28 +59,6 @@ export default {
|
|
|
75
59
|
}
|
|
76
60
|
|
|
77
61
|
this.closeDialog(plugin);
|
|
78
|
-
},
|
|
79
|
-
async removePluginImageResources(plugin) {
|
|
80
|
-
const selector = `${ UI_PLUGIN_LABELS.CATALOG_IMAGE }=${ plugin.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE] }`;
|
|
81
|
-
const namespace = UI_PLUGIN_NAMESPACE;
|
|
82
|
-
|
|
83
|
-
if (selector) {
|
|
84
|
-
const hash = await allHash({
|
|
85
|
-
deployment: this.$store.dispatch('management/findMatching', {
|
|
86
|
-
type: WORKLOAD_TYPES.DEPLOYMENT, selector, namespace
|
|
87
|
-
}),
|
|
88
|
-
service: this.$store.dispatch('management/findMatching', {
|
|
89
|
-
type: SERVICE, selector, namespace
|
|
90
|
-
}),
|
|
91
|
-
repo: this.$store.dispatch('management/findMatching', { type: CATALOG.CLUSTER_REPO, selector })
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
for (const resource of Object.keys(hash)) {
|
|
95
|
-
if (hash[resource]) {
|
|
96
|
-
hash[resource].forEach((r) => r.remove());
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
62
|
}
|
|
101
63
|
}
|
|
102
64
|
};
|
|
@@ -118,7 +80,7 @@ export default {
|
|
|
118
80
|
<div class="mt-10 dialog-panel">
|
|
119
81
|
<div class="dialog-info">
|
|
120
82
|
<p>
|
|
121
|
-
{{
|
|
83
|
+
{{ t('plugins.uninstall.prompt') }}
|
|
122
84
|
</p>
|
|
123
85
|
</div>
|
|
124
86
|
<div class="dialog-buttons">
|
|
@@ -19,6 +19,7 @@ import { BadgeState } from '@components/BadgeState';
|
|
|
19
19
|
import UninstallDialog from './UninstallDialog.vue';
|
|
20
20
|
import InstallDialog from './InstallDialog.vue';
|
|
21
21
|
import CatalogLoadDialog from './CatalogList/CatalogLoadDialog.vue';
|
|
22
|
+
import CatalogUninstallDialog from './CatalogList/CatalogUninstallDialog.vue';
|
|
22
23
|
import DeveloperInstallDialog from './DeveloperInstallDialog.vue';
|
|
23
24
|
import PluginInfoPanel from './PluginInfoPanel.vue';
|
|
24
25
|
import SetupUIPlugins from './SetupUIPlugins';
|
|
@@ -35,7 +36,6 @@ import {
|
|
|
35
36
|
isChartVersionHigher,
|
|
36
37
|
UI_PLUGIN_NAMESPACE,
|
|
37
38
|
UI_PLUGIN_CHART_ANNOTATIONS,
|
|
38
|
-
UI_PLUGIN_LABELS,
|
|
39
39
|
UI_PLUGINS_REPO_URL,
|
|
40
40
|
UI_PLUGINS_PARTNERS_REPO_URL
|
|
41
41
|
} from '@shell/config/uiplugins';
|
|
@@ -58,6 +58,7 @@ export default {
|
|
|
58
58
|
CatalogList,
|
|
59
59
|
Banner,
|
|
60
60
|
CatalogLoadDialog,
|
|
61
|
+
CatalogUninstallDialog,
|
|
61
62
|
InstallDialog,
|
|
62
63
|
LazyImage,
|
|
63
64
|
PluginInfoPanel,
|
|
@@ -318,7 +319,11 @@ export default {
|
|
|
318
319
|
builtin: !!p.builtin,
|
|
319
320
|
};
|
|
320
321
|
|
|
321
|
-
|
|
322
|
+
// Built-in plugins can chose to be hidden - used where we implement as extensions
|
|
323
|
+
// but don't want to shows them individually on the extensions page
|
|
324
|
+
if (!(item.builtin && rancher[UI_PLUGIN_CHART_ANNOTATIONS.HIDDEN_BUILTIN])) {
|
|
325
|
+
all.push(item);
|
|
326
|
+
}
|
|
322
327
|
}
|
|
323
328
|
});
|
|
324
329
|
|
|
@@ -326,11 +331,6 @@ export default {
|
|
|
326
331
|
this.plugins.forEach((p) => {
|
|
327
332
|
const chart = all.find((c) => c.name === p.name);
|
|
328
333
|
|
|
329
|
-
// Plugin is a container image, do not add to charts
|
|
330
|
-
if (p.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE]) {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
334
|
if (chart) {
|
|
335
335
|
chart.installed = true;
|
|
336
336
|
chart.uiplugin = p;
|
|
@@ -409,10 +409,6 @@ export default {
|
|
|
409
409
|
|
|
410
410
|
// Sort by name
|
|
411
411
|
return sortBy(all, 'name', false);
|
|
412
|
-
},
|
|
413
|
-
|
|
414
|
-
pluginsFromCatalogImage() {
|
|
415
|
-
return this.plugins.filter((p) => p.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE]);
|
|
416
412
|
}
|
|
417
413
|
},
|
|
418
414
|
|
|
@@ -464,15 +460,11 @@ export default {
|
|
|
464
460
|
|
|
465
461
|
neu.forEach((plugin) => {
|
|
466
462
|
const existing = installed.find((p) => !p.removed && p.name === plugin.name && p.version === plugin.version);
|
|
467
|
-
const isCustomImage = plugin.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE];
|
|
468
463
|
|
|
469
464
|
if (!existing && plugin.isCached) {
|
|
470
|
-
if (!this.uiErrors[plugin.name]
|
|
465
|
+
if (!this.uiErrors[plugin.name]) {
|
|
471
466
|
changes++;
|
|
472
467
|
}
|
|
473
|
-
if (isCustomImage) {
|
|
474
|
-
this.refreshCharts(true);
|
|
475
|
-
}
|
|
476
468
|
|
|
477
469
|
this.updatePluginInstallStatus(plugin.name, false);
|
|
478
470
|
}
|
|
@@ -481,7 +473,7 @@ export default {
|
|
|
481
473
|
if (changes > 0) {
|
|
482
474
|
Vue.set(this, 'reloadRequired', true);
|
|
483
475
|
}
|
|
484
|
-
}
|
|
476
|
+
}
|
|
485
477
|
},
|
|
486
478
|
|
|
487
479
|
// Forget the types when we leave the page
|
|
@@ -550,6 +542,10 @@ export default {
|
|
|
550
542
|
this.$refs.catalogLoadDialog.showDialog();
|
|
551
543
|
},
|
|
552
544
|
|
|
545
|
+
showCatalogUninstallDialog(ev) {
|
|
546
|
+
this.$refs.catalogUninstallDialog.showDialog(ev);
|
|
547
|
+
},
|
|
548
|
+
|
|
553
549
|
showInstallDialog(plugin, mode, ev) {
|
|
554
550
|
ev.target?.blur();
|
|
555
551
|
ev.preventDefault();
|
|
@@ -667,50 +663,49 @@ export default {
|
|
|
667
663
|
{{ t('plugins.title') }}
|
|
668
664
|
</h2>
|
|
669
665
|
</template>
|
|
670
|
-
<div
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
<i class="icon icon-checkmark mr-10" />
|
|
676
|
-
<span>
|
|
677
|
-
{{ t('plugins.reload') }}
|
|
678
|
-
</span>
|
|
679
|
-
<button
|
|
680
|
-
class="ml-10 btn btn-sm role-primary"
|
|
681
|
-
data-testid="extension-reload-banner-reload-btn"
|
|
682
|
-
@click="reload()"
|
|
683
|
-
>
|
|
684
|
-
{{ t('generic.reload') }}
|
|
685
|
-
</button>
|
|
686
|
-
</div>
|
|
687
|
-
<div
|
|
688
|
-
v-if="hasService && hasMenuActions"
|
|
689
|
-
class="actions-container"
|
|
690
|
-
>
|
|
691
|
-
<button
|
|
692
|
-
ref="actions"
|
|
693
|
-
aria-haspopup="true"
|
|
694
|
-
type="button"
|
|
695
|
-
class="btn role-multi-action actions"
|
|
696
|
-
data-testid="extensions-page-menu"
|
|
697
|
-
@click="setMenu"
|
|
666
|
+
<div class="actions-container">
|
|
667
|
+
<div
|
|
668
|
+
v-if="reloadRequired"
|
|
669
|
+
class="plugin-reload-banner mr-20"
|
|
670
|
+
data-testid="extension-reload-banner"
|
|
698
671
|
>
|
|
699
|
-
<i class="icon icon-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
672
|
+
<i class="icon icon-checkmark mr-10" />
|
|
673
|
+
<span>
|
|
674
|
+
{{ t('plugins.reload') }}
|
|
675
|
+
</span>
|
|
676
|
+
<button
|
|
677
|
+
class="ml-10 btn btn-sm role-primary"
|
|
678
|
+
data-testid="extension-reload-banner-reload-btn"
|
|
679
|
+
@click="reload()"
|
|
680
|
+
>
|
|
681
|
+
{{ t('generic.reload') }}
|
|
682
|
+
</button>
|
|
683
|
+
</div>
|
|
684
|
+
<div v-if="hasService && hasMenuActions">
|
|
685
|
+
<button
|
|
686
|
+
ref="actions"
|
|
687
|
+
aria-haspopup="true"
|
|
688
|
+
type="button"
|
|
689
|
+
class="btn role-multi-action actions"
|
|
690
|
+
data-testid="extensions-page-menu"
|
|
691
|
+
@click="setMenu"
|
|
692
|
+
>
|
|
693
|
+
<i class="icon icon-actions" />
|
|
694
|
+
</button>
|
|
695
|
+
<ActionMenu
|
|
696
|
+
:custom-actions="menuActions"
|
|
697
|
+
:open="menuOpen"
|
|
698
|
+
:use-custom-target-element="true"
|
|
699
|
+
:custom-target-element="menuTargetElement"
|
|
700
|
+
:custom-target-event="menuTargetEvent"
|
|
701
|
+
@close="setMenu(false)"
|
|
702
|
+
@devLoad="showDeveloperLoadDialog"
|
|
703
|
+
@removePluginSupport="removePluginSupport"
|
|
704
|
+
@manageRepos="manageRepos"
|
|
705
|
+
@addRancherRepos="showAddExtensionReposDialog"
|
|
706
|
+
@manageExtensionView="manageExtensionView"
|
|
707
|
+
/>
|
|
708
|
+
</div>
|
|
714
709
|
</div>
|
|
715
710
|
</div>
|
|
716
711
|
|
|
@@ -737,9 +732,8 @@ export default {
|
|
|
737
732
|
<div v-else>
|
|
738
733
|
<template v-if="showCatalogList">
|
|
739
734
|
<CatalogList
|
|
740
|
-
:plugins="pluginsFromCatalogImage"
|
|
741
735
|
@showCatalogLoadDialog="showCatalogLoadDialog"
|
|
742
|
-
@
|
|
736
|
+
@showCatalogUninstallDialog="showCatalogUninstallDialog($event)"
|
|
743
737
|
/>
|
|
744
738
|
</template>
|
|
745
739
|
<template v-else>
|
|
@@ -814,8 +808,8 @@ export default {
|
|
|
814
808
|
/>
|
|
815
809
|
<template v-else>
|
|
816
810
|
<div
|
|
817
|
-
v-for="plugin in list"
|
|
818
|
-
:key="plugin.name"
|
|
811
|
+
v-for="(plugin, i) in list"
|
|
812
|
+
:key="plugin.name + i"
|
|
819
813
|
class="plugin"
|
|
820
814
|
:data-testid="`extension-card-${plugin.name}`"
|
|
821
815
|
@click="showPluginDetail(plugin)"
|
|
@@ -999,6 +993,12 @@ export default {
|
|
|
999
993
|
<CatalogLoadDialog
|
|
1000
994
|
ref="catalogLoadDialog"
|
|
1001
995
|
@closed="didInstall"
|
|
996
|
+
@refresh="() => reloadRequired = true"
|
|
997
|
+
/>
|
|
998
|
+
<CatalogUninstallDialog
|
|
999
|
+
ref="catalogUninstallDialog"
|
|
1000
|
+
@closed="didUninstall"
|
|
1001
|
+
@refresh="() => reloadRequired = true"
|
|
1002
1002
|
/>
|
|
1003
1003
|
<DeveloperInstallDialog
|
|
1004
1004
|
ref="developerInstallDialog"
|
package/pages/diagnostic.vue
CHANGED
|
@@ -120,9 +120,6 @@ export default {
|
|
|
120
120
|
systemInformation.jsMemory.value += `, ${ this.t('about.diagnostic.systemInformation.memUsedJsHeapSize', { usedJSHeapSize: window?.performance?.memory?.usedJSHeapSize }) }`;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// scroll logs container to the bottom
|
|
124
|
-
this.scrollLogsToBottom();
|
|
125
|
-
|
|
126
123
|
return {
|
|
127
124
|
systemInformation,
|
|
128
125
|
topFifteenForResponseTime: null,
|
|
@@ -130,16 +127,9 @@ export default {
|
|
|
130
127
|
finalCounts: null,
|
|
131
128
|
includeResponseTimes: true,
|
|
132
129
|
storeMapping: this.$store?._modules?.root?.state,
|
|
133
|
-
latestLogs: console.logs // eslint-disable-line no-console
|
|
134
130
|
};
|
|
135
131
|
},
|
|
136
132
|
|
|
137
|
-
watch: {
|
|
138
|
-
latestLogs() {
|
|
139
|
-
this.scrollLogsToBottom();
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
|
|
143
133
|
computed: {
|
|
144
134
|
clusterCount() {
|
|
145
135
|
return this.finalCounts?.length;
|
|
@@ -147,14 +137,6 @@ export default {
|
|
|
147
137
|
},
|
|
148
138
|
|
|
149
139
|
methods: {
|
|
150
|
-
scrollLogsToBottom() {
|
|
151
|
-
this.$nextTick(() => {
|
|
152
|
-
const logsContainer = document.querySelector('.logs-container');
|
|
153
|
-
|
|
154
|
-
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
155
|
-
});
|
|
156
|
-
},
|
|
157
|
-
|
|
158
140
|
generateKey(data) {
|
|
159
141
|
const randomize = Math.random() * 10000;
|
|
160
142
|
|
|
@@ -166,7 +148,6 @@ export default {
|
|
|
166
148
|
const fileName = 'rancher-diagnostic-data.json';
|
|
167
149
|
const data = {
|
|
168
150
|
systemInformation: this.systemInformation,
|
|
169
|
-
logs: this.latestLogs,
|
|
170
151
|
storeMapping: this.parseStoreData(this.storeMapping),
|
|
171
152
|
resourceCounts: this.finalCounts,
|
|
172
153
|
responseTimes: this.responseTimes
|
|
@@ -411,26 +392,6 @@ export default {
|
|
|
411
392
|
</div>
|
|
412
393
|
</div>
|
|
413
394
|
|
|
414
|
-
<!-- Logs -->
|
|
415
|
-
<div class="mb-40">
|
|
416
|
-
<h2 class="mb-20">
|
|
417
|
-
{{ t('about.diagnostic.logs.subtitle') }}
|
|
418
|
-
</h2>
|
|
419
|
-
<ul class="logs-container">
|
|
420
|
-
<li
|
|
421
|
-
v-for="logEntry in latestLogs"
|
|
422
|
-
:key="generateKey(logEntry.timestamp)"
|
|
423
|
-
:class="logEntry.type"
|
|
424
|
-
>
|
|
425
|
-
<span class="log-entry-type">{{ logEntry.type }} :: </span>
|
|
426
|
-
<span
|
|
427
|
-
v-for="(arg, i) in logEntry.data"
|
|
428
|
-
:key="i"
|
|
429
|
-
>{{ arg }}</span>
|
|
430
|
-
</li>
|
|
431
|
-
</ul>
|
|
432
|
-
</div>
|
|
433
|
-
|
|
434
395
|
<PromptModal />
|
|
435
396
|
</div>
|
|
436
397
|
</template>
|
package/pages/home.vue
CHANGED
|
@@ -192,7 +192,7 @@ export default {
|
|
|
192
192
|
label: this.t('tableHeaders.pods'),
|
|
193
193
|
name: 'pods',
|
|
194
194
|
value: '',
|
|
195
|
-
sort: ['status.allocatable.pods', 'status.
|
|
195
|
+
sort: ['status.allocatable.pods', 'status.requested.pods'],
|
|
196
196
|
formatter: 'PodsUsage',
|
|
197
197
|
delayLoading: true
|
|
198
198
|
},
|
|
@@ -20,10 +20,10 @@ export function normalizeType(type) {
|
|
|
20
20
|
// Detect and resolve conflicts from a 409 response.
|
|
21
21
|
// If they are resolved, return a false-y value
|
|
22
22
|
// Else they can't be resolved, return an array of errors to show to the user.
|
|
23
|
-
export function handleConflict(initialValueJSON, value, liveValue, rootGetters, store) {
|
|
24
|
-
const orig = store.dispatch(
|
|
25
|
-
const user = store.dispatch(
|
|
26
|
-
const cur = store.dispatch(
|
|
23
|
+
export async function handleConflict(initialValueJSON, value, liveValue, rootGetters, store, storeNamespace) {
|
|
24
|
+
const orig = await store.dispatch(`${ storeNamespace }/cleanForDiff`, initialValueJSON, { root: true });
|
|
25
|
+
const user = await store.dispatch(`${ storeNamespace }/cleanForDiff`, value.toJSON(), { root: true });
|
|
26
|
+
const cur = await store.dispatch(`${ storeNamespace }/cleanForDiff`, liveValue.toJSON(), { root: true });
|
|
27
27
|
|
|
28
28
|
const bgChange = changeset(orig, cur);
|
|
29
29
|
const userChange = changeset(orig, user);
|
package/plugins/int-number.js
CHANGED
|
@@ -5,9 +5,12 @@ export default Vue.directive('intNumber', {
|
|
|
5
5
|
el.addEventListener('keypress', (e) => {
|
|
6
6
|
e = e || window.event;
|
|
7
7
|
const charcode = typeof e.charCode === 'number' ? e.charCode : e.keyCode;
|
|
8
|
-
const
|
|
8
|
+
const inputChar = String.fromCharCode(charcode);
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Allow digits, minus sign at the beginning, and Ctrl key combinations
|
|
11
|
+
const re = /^-?\d*$/;
|
|
12
|
+
|
|
13
|
+
if (!re.test(inputChar) && charcode > 9 && !e.ctrlKey) {
|
|
11
14
|
if (e.preventDefault) {
|
|
12
15
|
e.preventDefault();
|
|
13
16
|
} else {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Vue from 'vue';
|
|
2
|
+
|
|
3
|
+
export default Vue.directive('positiveIntNumber', {
|
|
4
|
+
inserted(el) {
|
|
5
|
+
el.addEventListener('keypress', (e) => {
|
|
6
|
+
e = e || window.event;
|
|
7
|
+
const charcode = typeof e.charCode === 'number' ? e.charCode : e.keyCode;
|
|
8
|
+
const re = /^\d+$/; // Use regex to match positive numbers
|
|
9
|
+
|
|
10
|
+
if (!re.test(String.fromCharCode(charcode)) && charcode > 9 && !e.ctrlKey) {
|
|
11
|
+
if (e.preventDefault) {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
} else {
|
|
14
|
+
e.returnValue = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
@@ -68,9 +68,15 @@ describe('steve: getters', () => {
|
|
|
68
68
|
it('returns a string with a single filter statement applied if a single filter statement is applied', () => {
|
|
69
69
|
expect(urlOptionsGetter('foo', { filter: { bar: 'baz' } })).toBe('foo?bar=baz');
|
|
70
70
|
});
|
|
71
|
+
it('returns a string with a single filter statement applied and formatted for steve if a single filter statement is applied and the url starts with "/v1"', () => {
|
|
72
|
+
expect(urlOptionsGetter('/v1/foo', { filter: { bar: 'baz' } })).toBe('/v1/foo?filter=bar=baz&exclude=metadata.managedFields');
|
|
73
|
+
});
|
|
71
74
|
it('returns a string with a multiple filter statements applied if a single filter statement is applied', () => {
|
|
72
75
|
expect(urlOptionsGetter('foo', { filter: { bar: 'baz', far: 'faz' } })).toBe('foo?bar=baz&far=faz');
|
|
73
76
|
});
|
|
77
|
+
it('returns a string with a multiple filter statements applied and formatted for steve if a single filter statement is applied and the url starts with "/v1"', () => {
|
|
78
|
+
expect(urlOptionsGetter('/v1/foo', { filter: { bar: 'baz', far: 'faz' } })).toBe('/v1/foo?filter=bar=baz&far=faz&exclude=metadata.managedFields');
|
|
79
|
+
});
|
|
74
80
|
it('returns a string with an exclude statement for "bar" and "metadata.managedFields" if excludeFields is a single element array with the string "bar" and the url starts with "/v1/"', () => {
|
|
75
81
|
expect(urlOptionsGetter('/v1/foo', { excludeFields: ['bar'] })).toBe('/v1/foo?exclude=bar&exclude=metadata.managedFields');
|
|
76
82
|
});
|
|
@@ -86,8 +92,17 @@ describe('steve: getters', () => {
|
|
|
86
92
|
it('returns a string with a sorting criteria if the sort option is provided', () => {
|
|
87
93
|
expect(urlOptionsGetter('foo', { sortBy: 'bar' })).toBe('foo?sort=bar');
|
|
88
94
|
});
|
|
95
|
+
it('returns a string with a sorting criteria formatted for steve if the sort option is provided and the url starts with "/v1"', () => {
|
|
96
|
+
expect(urlOptionsGetter('/v1/foo', { sortBy: 'bar' })).toBe('/v1/foo?exclude=metadata.managedFields&sort=bar');
|
|
97
|
+
});
|
|
89
98
|
it('returns a string with a sorting criteria if the sort option is provided and an order if sortOrder is provided', () => {
|
|
90
99
|
expect(urlOptionsGetter('foo', { sortBy: 'bar', sortOrder: 'baz' })).toBe('foo?sort=bar&order=baz');
|
|
91
100
|
});
|
|
101
|
+
it('returns a string with a sorting criteria formatted for steve if the sort option is provided and an order if sortOrder is provided and the url starts with "/v1"', () => {
|
|
102
|
+
expect(urlOptionsGetter('/v1/foo', { sortBy: 'bar', sortOrder: 'baz' })).toBe('/v1/foo?exclude=metadata.managedFields&sort=bar');
|
|
103
|
+
});
|
|
104
|
+
it('returns a string with a sorting criteria formatted for steve if the sort option is provided and an order if sortOrder is "desc" and the url starts with "/v1"', () => {
|
|
105
|
+
expect(urlOptionsGetter('/v1/foo', { sortBy: 'bar', sortOrder: 'desc' })).toBe('/v1/foo?exclude=metadata.managedFields&sort=-bar');
|
|
106
|
+
});
|
|
92
107
|
});
|
|
93
108
|
});
|
package/plugins/steve/getters.js
CHANGED
|
@@ -31,8 +31,8 @@ export default {
|
|
|
31
31
|
const isSteve = parsedUrl.path.startsWith('/v1');
|
|
32
32
|
|
|
33
33
|
// Filter
|
|
34
|
-
// Steve's filter options work differently nowadays (https://github.com/rancher/steve#filter) #9341
|
|
35
34
|
if ( opt.filter ) {
|
|
35
|
+
url += `${ (url.includes('?') ? '&' : '?') }`;
|
|
36
36
|
const keys = Object.keys(opt.filter);
|
|
37
37
|
|
|
38
38
|
keys.forEach((key) => {
|
|
@@ -42,9 +42,18 @@ export default {
|
|
|
42
42
|
vals = [vals];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Steve's filter options now support more complex filtering not yet implemented here #9341
|
|
46
|
+
if (isSteve) {
|
|
47
|
+
url += `${ (url.includes('filter=') ? '&' : 'filter=') }`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const filterStrings = vals.map((val) => {
|
|
51
|
+
return `${ encodeURI(key) }=${ encodeURI(val) }`;
|
|
47
52
|
});
|
|
53
|
+
const urlEnding = url.charAt(url.length - 1);
|
|
54
|
+
const nextStringConnector = ['&', '?', '='].includes(urlEnding) ? '' : '&';
|
|
55
|
+
|
|
56
|
+
url += `${ nextStringConnector }${ filterStrings.join('&') }`;
|
|
48
57
|
});
|
|
49
58
|
}
|
|
50
59
|
|
|
@@ -82,18 +91,21 @@ export default {
|
|
|
82
91
|
// End: Limit
|
|
83
92
|
|
|
84
93
|
// Sort
|
|
85
|
-
// Steve's sort options
|
|
94
|
+
// Steve's sort options supports multi-column sorting and column specific sort orders, not implemented yet #9341
|
|
86
95
|
const sortBy = opt.sortBy;
|
|
96
|
+
const orderBy = opt.sortOrder;
|
|
87
97
|
|
|
88
98
|
if ( sortBy ) {
|
|
89
|
-
|
|
99
|
+
if (isSteve) {
|
|
100
|
+
url += `${ url.includes('?') ? '&' : '?' }sort=${ (orderBy === 'desc' ? '-' : '') + encodeURI(sortBy) }`;
|
|
101
|
+
} else {
|
|
102
|
+
url += `${ url.includes('?') ? '&' : '?' }sort=${ encodeURI(sortBy) }`;
|
|
103
|
+
if ( orderBy ) {
|
|
104
|
+
url += `${ url.includes('?') ? '&' : '?' }order=${ encodeURI(orderBy) }`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
90
107
|
}
|
|
91
108
|
|
|
92
|
-
const orderBy = opt.sortOrder;
|
|
93
|
-
|
|
94
|
-
if ( orderBy ) {
|
|
95
|
-
url += `${ url.includes('?') ? '&' : '?' }order=${ encodeURIComponent(orderBy) }`;
|
|
96
|
-
}
|
|
97
109
|
// End: Sort
|
|
98
110
|
|
|
99
111
|
return url;
|
|
@@ -206,13 +206,6 @@ export default (
|
|
|
206
206
|
}
|
|
207
207
|
},
|
|
208
208
|
|
|
209
|
-
/**
|
|
210
|
-
* Emit on input change
|
|
211
|
-
*/
|
|
212
|
-
onChange(event: Event): void {
|
|
213
|
-
this.$emit('change', event);
|
|
214
|
-
},
|
|
215
|
-
|
|
216
209
|
/**
|
|
217
210
|
* Emit on input with delay. Note: Arrow function is avoided due context
|
|
218
211
|
* binding.
|
|
@@ -306,7 +299,6 @@ export default (
|
|
|
306
299
|
@input="onInput($event.target.value)"
|
|
307
300
|
@focus="onFocus"
|
|
308
301
|
@blur="onBlur"
|
|
309
|
-
@change="onChange"
|
|
310
302
|
>
|
|
311
303
|
</slot>
|
|
312
304
|
|
|
@@ -4,7 +4,7 @@ import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
|
|
|
4
4
|
|
|
5
5
|
describe('radioButton.vue', () => {
|
|
6
6
|
it('renders label slot contents', () => {
|
|
7
|
-
const wrapper = shallowMount(RadioButton, { slots: { label: 'Test Label' }
|
|
7
|
+
const wrapper = shallowMount(RadioButton, { slots: { label: 'Test Label' } });
|
|
8
8
|
|
|
9
9
|
expect(wrapper.find('.radio-label').text()).toBe('Test Label');
|
|
10
10
|
});
|
|
@@ -14,9 +14,7 @@ describe('radioButton.vue', () => {
|
|
|
14
14
|
RadioButton,
|
|
15
15
|
{
|
|
16
16
|
directives: { cleanHtmlDirective },
|
|
17
|
-
propsData: {
|
|
18
|
-
label: 'Test Label', val: {}, value: {}
|
|
19
|
-
}
|
|
17
|
+
propsData: { label: 'Test Label' }
|
|
20
18
|
});
|
|
21
19
|
|
|
22
20
|
expect(wrapper.find('.radio-label').text()).toBe('Test Label');
|
|
@@ -25,9 +23,7 @@ describe('radioButton.vue', () => {
|
|
|
25
23
|
it('renders slot contents when both slot and label prop are provided', () => {
|
|
26
24
|
const wrapper = shallowMount(RadioButton, {
|
|
27
25
|
slots: { label: 'Test Label - Slot' },
|
|
28
|
-
propsData: {
|
|
29
|
-
label: 'Test Label - Props', val: {}, value: {}
|
|
30
|
-
},
|
|
26
|
+
propsData: { label: 'Test Label - Props' },
|
|
31
27
|
});
|
|
32
28
|
|
|
33
29
|
expect(wrapper.find('.radio-label').text()).toBe('Test Label - Slot');
|
package/store/index.js
CHANGED
|
@@ -568,6 +568,10 @@ export const getters = {
|
|
|
568
568
|
return getters['isSingleProduct'] && cluster.isHarvester && !getters['isRancherInHarvester'];
|
|
569
569
|
},
|
|
570
570
|
|
|
571
|
+
showTopLevelMenu(getters) {
|
|
572
|
+
return getters['isRancherInHarvester'] || getters['isMultiCluster'] || !getters['isSingleProduct'];
|
|
573
|
+
},
|
|
574
|
+
|
|
571
575
|
targetRoute(state) {
|
|
572
576
|
return state.targetRoute;
|
|
573
577
|
},
|
package/store/prefs.js
CHANGED
|
@@ -54,6 +54,7 @@ export const NAMESPACE_FILTERS = create('ns-by-cluster', {}, { parseJSON });
|
|
|
54
54
|
export const WORKSPACE = create('workspace', '');
|
|
55
55
|
export const EXPANDED_GROUPS = create('open-groups', ['cluster', 'policy', 'rbac', 'serviceDiscovery', 'storage', 'workload'], { parseJSON });
|
|
56
56
|
export const FAVORITE_TYPES = create('fav-type', [], { parseJSON });
|
|
57
|
+
export const PINNED_CLUSTERS = create('pinned-clusters', [], { parseJSON });
|
|
57
58
|
export const GROUP_RESOURCES = create('group-by', 'namespace');
|
|
58
59
|
export const DIFF = create('diff', 'unified', { options: ['unified', 'split'] });
|
|
59
60
|
export const THEME = create('theme', 'auto', {
|