@rancher/shell 3.0.9-rc.1 → 3.0.9-rc.3
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/_color.scss +1 -0
- package/assets/styles/base/_typography.scss +14 -5
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +1 -1
- package/assets/translations/en-us.yaml +104 -33
- package/assets/translations/zh-hans.yaml +13 -2
- package/components/ActionMenu.vue +7 -8
- package/components/ActionMenuShell.vue +23 -24
- package/components/CodeMirror.vue +4 -3
- package/components/DetailText.vue +54 -7
- package/components/Drawer/Chrome.vue +11 -4
- package/components/Drawer/DrawerCard.vue +19 -0
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
- package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
- package/components/Drawer/types.ts +1 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
- package/components/LocaleSelector.vue +1 -1
- package/components/Markdown.vue +1 -1
- package/components/PopoverCard.vue +3 -3
- package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
- package/components/Resource/Detail/Card/Scaler.vue +10 -2
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
- package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
- package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
- package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
- package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +14 -10
- package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
- package/components/Resource/Detail/Cards.vue +27 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
- package/components/Resource/Detail/Masthead/index.vue +5 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
- package/components/Resource/Detail/ResourceRow.types.ts +14 -0
- package/components/Resource/Detail/ResourceRow.vue +23 -35
- package/components/Resource/Detail/StatusRow.vue +5 -2
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +41 -6
- package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
- package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
- package/components/ResourceDetail/Masthead/index.vue +1 -0
- package/components/ResourceDetail/Masthead/latest.vue +8 -1
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/ResourceTable.vue +1 -1
- package/components/Setting.vue +1 -1
- package/components/SortableTable/index.vue +25 -0
- package/components/SortableTable/selection.js +25 -12
- package/components/SortableTable/sorting.js +1 -1
- package/components/Tabbed/Tab.vue +5 -0
- package/components/Tabbed/index.vue +40 -9
- package/components/Window/ContainerShell.vue +10 -13
- package/components/__tests__/ProjectRow.test.ts +102 -15
- package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
- package/components/fleet/FleetClusterTargets/index.vue +82 -29
- package/components/fleet/FleetClusters.vue +26 -12
- package/components/fleet/FleetGitRepoPaths.vue +2 -2
- package/components/fleet/FleetResources.vue +14 -0
- package/components/fleet/FleetValuesFrom.vue +2 -2
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
- package/components/fleet/dashboard/ResourceDetails.vue +96 -123
- package/components/form/Conditions.vue +1 -15
- package/components/form/HookOption.vue +5 -0
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/LifecycleHooks.vue +2 -6
- package/components/form/ResourceLabeledSelect.vue +12 -1
- package/components/form/ResourceQuota/Project.vue +59 -8
- package/components/form/ResourceQuota/ProjectRow.vue +116 -21
- package/components/form/ResourceQuota/shared.js +42 -18
- package/components/form/SeccompProfile.vue +113 -0
- package/components/form/Security.vue +244 -133
- package/components/form/__tests__/LabeledSelect.test.ts +1 -1
- package/components/form/__tests__/SeccompProfile.test.js +124 -0
- package/components/form/__tests__/Security.test.ts +125 -37
- package/components/formatter/Autoscaler.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +4 -1
- package/components/formatter/LinkName.vue +3 -2
- package/components/nav/Group.vue +5 -0
- package/components/nav/Header.vue +3 -3
- package/components/nav/HeaderPageActionMenu.vue +1 -1
- package/components/nav/NamespaceFilter.vue +6 -6
- package/components/nav/NotificationCenter/index.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +41 -16
- package/components/nav/TopLevelMenu.vue +45 -25
- package/components/nav/WorkspaceSwitcher.vue +1 -1
- package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
- package/components/templates/default.vue +0 -3
- package/components/templates/home.vue +0 -3
- package/components/templates/plain.vue +0 -3
- package/composables/useClickOutside.ts +1 -1
- package/config/product/explorer.js +2 -3
- package/config/table-headers.js +9 -7
- package/config/types.js +45 -9
- package/detail/__tests__/workload.test.ts +8 -16
- package/detail/catalog.cattle.io.app.vue +5 -0
- package/detail/fleet.cattle.io.cluster.vue +6 -0
- package/detail/management.cattle.io.oidcclient.vue +15 -4
- package/detail/workload/index.vue +7 -109
- package/edit/__tests__/management.cattle.io.project.test.js +137 -0
- package/edit/__tests__/projectsecret.test.ts +42 -0
- package/edit/auth/__tests__/oidc.test.ts +50 -0
- package/edit/auth/oidc.vue +68 -44
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
- package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
- package/edit/management.cattle.io.project.vue +36 -6
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +16 -3
- package/edit/projectsecret.vue +29 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
- package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
- package/edit/workload/__tests__/index.test.ts +3 -4
- package/edit/workload/index.vue +47 -28
- package/edit/workload/mixins/workload.js +66 -31
- package/initialize/install-plugins.js +0 -2
- package/list/catalog.cattle.io.clusterrepo.vue +1 -1
- package/list/projectsecret.vue +2 -2
- package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
- package/machine-config/amazonec2.vue +2 -2
- package/machine-config/vmwarevsphere.vue +58 -4
- package/mixins/__tests__/chart.test.ts +63 -0
- package/mixins/chart.js +56 -51
- package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
- package/models/__tests__/workload.test.ts +333 -0
- package/models/catalog.cattle.io.app.js +8 -0
- package/models/management.cattle.io.cluster.js +22 -30
- package/models/pod.js +14 -0
- package/models/provisioning.cattle.io.cluster.js +2 -2
- package/models/secret.js +1 -1
- package/models/workload.js +93 -27
- package/package.json +4 -4
- package/pages/__tests__/diagnostic.test.ts +71 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
- package/pages/c/_cluster/apps/charts/install.vue +4 -4
- package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
- package/pages/c/_cluster/explorer/tools/index.vue +23 -5
- package/pages/c/_cluster/fleet/index.vue +14 -8
- package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
- package/pages/c/_cluster/monitoring/alertmanagerconfig/_alertmanagerconfigid/receiver.vue +18 -5
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +41 -9
- package/pages/diagnostic.vue +17 -3
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
- package/plugins/dashboard-store/actions.js +9 -8
- package/plugins/dashboard-store/resource-class.js +97 -1
- package/plugins/steve/__tests__/revision.test.ts +84 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
- package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
- package/plugins/steve/revision.ts +26 -0
- package/plugins/steve/steve-pagination-utils.ts +6 -5
- package/plugins/steve/subscribe.js +188 -49
- package/plugins/subscribe-events.ts +2 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +2 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
- package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
- package/rancher-components/Pill/index.ts +4 -0
- package/rancher-components/RcButton/RcButton.test.ts +53 -9
- package/rancher-components/RcButton/RcButton.vue +217 -25
- package/rancher-components/RcButton/types.ts +27 -1
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
- package/rancher-components/RcDropdown/types.ts +3 -3
- package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
- package/rancher-components/RcIcon/RcIcon.vue +9 -6
- package/rancher-components/RcIcon/types.ts +13 -9
- package/rancher-components/RcItemCard/RcItemCard.test.ts +16 -6
- package/rancher-components/RcItemCard/RcItemCard.vue +13 -23
- package/rancher-components/utils/status.test.ts +10 -15
- package/rancher-components/utils/status.ts +5 -6
- package/store/__tests__/auth.test.ts +21 -5
- package/store/auth.js +6 -3
- package/store/aws.js +18 -12
- package/store/index.js +4 -8
- package/store/type-map.utils.ts +1 -1
- package/types/kube/kube-api.ts +29 -3
- package/types/rancher/steve.api.ts +40 -0
- package/types/shell/index.d.ts +262 -156
- package/types/store/pagination.types.ts +1 -0
- package/types/store/subscribe-events.types.ts +1 -0
- package/utils/__tests__/azure.test.ts +56 -0
- package/utils/__tests__/back-off.test.ts +364 -245
- package/utils/__tests__/error.test.ts +44 -0
- package/utils/__tests__/fleet.test.ts +8 -1
- package/utils/__tests__/pagination-wrapper.test.ts +167 -0
- package/utils/__tests__/version.test.ts +55 -1
- package/utils/azure.js +12 -0
- package/utils/back-off.ts +302 -69
- package/utils/dynamic-content/__tests__/index.test.ts +1 -1
- package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
- package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
- package/utils/dynamic-content/index.ts +1 -6
- package/utils/dynamic-content/new-release.ts +5 -3
- package/utils/dynamic-content/types.d.ts +0 -1
- package/utils/error.js +9 -0
- package/utils/fleet.ts +2 -2
- package/utils/inactivity.ts +2 -3
- package/utils/pagination-wrapper.ts +99 -15
- package/utils/validators/formRules/index.ts +3 -0
- package/utils/version.js +38 -0
- package/components/auth/AzureWarning.vue +0 -77
- /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
- /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import Latest from '@shell/components/ResourceDetail/Masthead/latest.vue';
|
|
3
|
+
|
|
4
|
+
jest.mock('@shell/components/Resource/Detail/TitleBar/index.vue', () => ({
|
|
5
|
+
name: 'TitleBar',
|
|
6
|
+
template: `<div data-testid="title-bar">TitleBar</div>`
|
|
7
|
+
}));
|
|
8
|
+
jest.mock('@shell/components/Resource/Detail/TitleBar/composables', () => ({ useDefaultTitleBarProps: jest.fn(() => ({})) }));
|
|
9
|
+
jest.mock('@shell/components/Resource/Detail/Metadata/index.vue', () => ({
|
|
10
|
+
name: 'Metadata',
|
|
11
|
+
template: `<div data-testid="metadata">Metadata</div>`
|
|
12
|
+
}));
|
|
13
|
+
jest.mock('@shell/components/Resource/Detail/Metadata/composables', () => ({ useDefaultMetadataForLegacyPagesProps: jest.fn(() => ({})) }));
|
|
14
|
+
jest.mock('@shell/components/Resource/Detail/composables', () => ({ useResourceDetailBannerProps: jest.fn(() => null) }));
|
|
15
|
+
jest.mock('@shell/components/Resource/Detail/Cards.vue', () => ({
|
|
16
|
+
name: 'Cards',
|
|
17
|
+
template: `<div data-testid="cards">Cards</div>`,
|
|
18
|
+
props: ['resource']
|
|
19
|
+
}));
|
|
20
|
+
jest.mock('@components/Banner', () => ({
|
|
21
|
+
Banner: {
|
|
22
|
+
name: 'Banner',
|
|
23
|
+
template: `<div data-testid="banner">Banner</div>`
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const defaultMocks = {
|
|
28
|
+
directives: { 'ui-context': () => {} },
|
|
29
|
+
global: {
|
|
30
|
+
mocks: {
|
|
31
|
+
$store: {
|
|
32
|
+
getters: { 'i18n/t': jest.fn() },
|
|
33
|
+
dispatch: jest.fn()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe('component: Masthead/latest', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should render Cards when isCustomDetailOrEdit is true', () => {
|
|
45
|
+
const props = {
|
|
46
|
+
value: { name: 'test-resource' },
|
|
47
|
+
isCustomDetailOrEdit: true
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const wrapper = mount(Latest, { props, ...defaultMocks });
|
|
51
|
+
|
|
52
|
+
expect(wrapper.find('[data-testid="cards"]').exists()).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should not render Cards when isCustomDetailOrEdit is false', () => {
|
|
56
|
+
const props = {
|
|
57
|
+
value: { name: 'test-resource' },
|
|
58
|
+
isCustomDetailOrEdit: false
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const wrapper = mount(Latest, { props, ...defaultMocks });
|
|
62
|
+
|
|
63
|
+
expect(wrapper.find('[data-testid="cards"]').exists()).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should not render Cards when isCustomDetailOrEdit is not provided (defaults to false)', () => {
|
|
67
|
+
const props = { value: { name: 'test-resource' } };
|
|
68
|
+
|
|
69
|
+
const wrapper = mount(Latest, { props, ...defaultMocks });
|
|
70
|
+
|
|
71
|
+
expect(wrapper.find('[data-testid="cards"]').exists()).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should always render TitleBar and Metadata', () => {
|
|
75
|
+
const props = {
|
|
76
|
+
value: { name: 'test-resource' },
|
|
77
|
+
isCustomDetailOrEdit: false
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const wrapper = mount(Latest, { props, ...defaultMocks });
|
|
81
|
+
|
|
82
|
+
expect(wrapper.find('[data-testid="title-bar"]').exists()).toBe(true);
|
|
83
|
+
expect(wrapper.find('[data-testid="metadata"]').exists()).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -48,6 +48,7 @@ const showLatestMasthead = computed(() => isNewDetailPageEnabled.value && isView
|
|
|
48
48
|
v-if="showLatestMasthead"
|
|
49
49
|
:value="props.value"
|
|
50
50
|
:resourceSubtype="props.resourceSubtype"
|
|
51
|
+
:isCustomDetailOrEdit="props.hasDetail || props.hasEdit"
|
|
51
52
|
/>
|
|
52
53
|
<Legacy
|
|
53
54
|
v-else
|
|
@@ -7,11 +7,13 @@ import Metadata from '@shell/components/Resource/Detail/Metadata/index.vue';
|
|
|
7
7
|
import { useDefaultMetadataForLegacyPagesProps } from '@shell/components/Resource/Detail/Metadata/composables';
|
|
8
8
|
import { useResourceDetailBannerProps } from '@shell/components/Resource/Detail/composables';
|
|
9
9
|
import { computed } from 'vue';
|
|
10
|
+
import Cards from '@shell/components/Resource/Detail/Cards.vue';
|
|
10
11
|
|
|
11
12
|
// We are disabling eslint for this script to allow the use of the Props interface
|
|
12
13
|
export interface Props {
|
|
13
14
|
value?: Object;
|
|
14
15
|
resourceSubtype?: string;
|
|
16
|
+
isCustomDetailOrEdit?: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
</script>
|
|
@@ -19,7 +21,7 @@ export interface Props {
|
|
|
19
21
|
<script lang="ts" setup>
|
|
20
22
|
import { useStore } from 'vuex';
|
|
21
23
|
|
|
22
|
-
const props = withDefaults(defineProps<Props>(), { value: () => ({}), resourceSubtype: undefined });
|
|
24
|
+
const props = withDefaults(defineProps<Props>(), { value: () => ({}), resourceSubtype: undefined, isCustomDetailOrEdit: false });
|
|
23
25
|
|
|
24
26
|
const uiCtxResource = computed(() => {
|
|
25
27
|
const {
|
|
@@ -64,6 +66,11 @@ const store = useStore();
|
|
|
64
66
|
v-bind="metadataProps"
|
|
65
67
|
class="mmt-4"
|
|
66
68
|
/>
|
|
69
|
+
<Cards
|
|
70
|
+
v-if="props.isCustomDetailOrEdit"
|
|
71
|
+
class="mb-20"
|
|
72
|
+
:resource="props.value"
|
|
73
|
+
/>
|
|
67
74
|
</div>
|
|
68
75
|
</template>
|
|
69
76
|
|
|
@@ -427,7 +427,7 @@ export default {
|
|
|
427
427
|
},
|
|
428
428
|
|
|
429
429
|
_applicableExtensionTableHooks() {
|
|
430
|
-
if (this.$store.$
|
|
430
|
+
if (this.$store.$extension?.getUIConfig) {
|
|
431
431
|
const extensionTableHooks = getApplicableExtensionEnhancements(this, ExtensionPoint.TABLE, TableLocation.RESOURCE, this.$route);
|
|
432
432
|
|
|
433
433
|
return extensionTableHooks;
|
package/components/Setting.vue
CHANGED
|
@@ -1590,6 +1590,18 @@ export default {
|
|
|
1590
1590
|
:onRowMouseEnter="onRowMouseEnter"
|
|
1591
1591
|
:onRowMouseLeave="onRowMouseLeave"
|
|
1592
1592
|
>
|
|
1593
|
+
<slot
|
|
1594
|
+
:full-colspan="fullColspan"
|
|
1595
|
+
:row="row.row"
|
|
1596
|
+
:show-sub-row="row.row.stateDescription"
|
|
1597
|
+
:sub-matches="subMatches"
|
|
1598
|
+
:keyField="keyField"
|
|
1599
|
+
:componentTestid="componentTestid"
|
|
1600
|
+
:i="i"
|
|
1601
|
+
:onRowMouseEnter="onRowMouseEnter"
|
|
1602
|
+
:onRowMouseLeave="onRowMouseLeave"
|
|
1603
|
+
name="additional-sub-row"
|
|
1604
|
+
/>
|
|
1593
1605
|
<tr
|
|
1594
1606
|
v-if="row.row.stateDescription"
|
|
1595
1607
|
:key="row.row[keyField] + '-description'"
|
|
@@ -1924,12 +1936,25 @@ export default {
|
|
|
1924
1936
|
&.main-row.has-sub-row {
|
|
1925
1937
|
border-bottom: 0;
|
|
1926
1938
|
}
|
|
1939
|
+
&.additional-sub-row.has-sub-row {
|
|
1940
|
+
border-bottom: 0;
|
|
1941
|
+
}
|
|
1927
1942
|
|
|
1928
1943
|
// if a main-row is hovered also hover it's sibling sub row. note - the reverse is handled in selection.js
|
|
1929
1944
|
&.main-row:not(.row-selected):hover + .sub-row {
|
|
1930
1945
|
background-color: var(--sortable-table-hover-bg);
|
|
1931
1946
|
}
|
|
1932
1947
|
|
|
1948
|
+
// Case with only additional-sub-row
|
|
1949
|
+
&.main-row:not(.row-selected):hover + .additional-sub-row {
|
|
1950
|
+
background-color: var(--sortable-table-hover-bg);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// Case with both additional-sub-row and sub-row
|
|
1954
|
+
&.main-row:not(.row-selected):hover + .additional-sub-row + .sub-row{
|
|
1955
|
+
background-color: var(--sortable-table-hover-bg);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1933
1958
|
&:last-of-type {
|
|
1934
1959
|
border-bottom: 0;
|
|
1935
1960
|
}
|
|
@@ -177,24 +177,32 @@ export default {
|
|
|
177
177
|
}
|
|
178
178
|
},
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
removeOrAddHover(option, e) {
|
|
181
|
+
// Hardcoded logic to not overcomplicate just adding the conditions of next and previous
|
|
181
182
|
const tr = e.target.closest('TR');
|
|
182
183
|
|
|
183
|
-
if (tr.classList.contains('sub-row')) {
|
|
184
|
-
const
|
|
184
|
+
if (tr.classList.contains('sub-row') || tr.classList.contains('additional-sub-row')) {
|
|
185
|
+
const trPreviousRow = tr.previousElementSibling;
|
|
186
|
+
const trNextRow = tr.nextElementSibling;
|
|
187
|
+
|
|
188
|
+
trPreviousRow.classList[option]('sub-row-hovered');
|
|
185
189
|
|
|
186
|
-
|
|
190
|
+
if (!trPreviousRow.classList.contains('main-row')) {
|
|
191
|
+
const trMainRow = trPreviousRow.previousElementSibling;
|
|
192
|
+
|
|
193
|
+
trMainRow.classList[option]('sub-row-hovered');
|
|
194
|
+
}
|
|
195
|
+
if (trNextRow?.classList.contains('sub-row')) {
|
|
196
|
+
trNextRow.classList[option]('sub-row-hovered');
|
|
197
|
+
}
|
|
187
198
|
}
|
|
188
199
|
},
|
|
200
|
+
onRowMouseEnter(e) {
|
|
201
|
+
this.removeOrAddHover('add', e);
|
|
202
|
+
},
|
|
189
203
|
|
|
190
204
|
onRowMouseLeave(e) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (tr.classList.contains('sub-row')) {
|
|
194
|
-
const trMainRow = tr.previousElementSibling;
|
|
195
|
-
|
|
196
|
-
trMainRow.classList.remove('sub-row-hovered');
|
|
197
|
-
}
|
|
205
|
+
this.removeOrAddHover('remove', e);
|
|
198
206
|
},
|
|
199
207
|
|
|
200
208
|
nodeForEvent(e) {
|
|
@@ -486,6 +494,11 @@ export default {
|
|
|
486
494
|
|
|
487
495
|
this.$nextTick(() => {
|
|
488
496
|
this.$emit('selection', this.selectedRows);
|
|
497
|
+
if (this.selectedRows && this.selectedRows.length) {
|
|
498
|
+
for ( let i = 0 ; i < this.selectedRows.length ; i++ ) {
|
|
499
|
+
this.updateInput(this.selectedRows[i], true, this.keyField);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
489
502
|
});
|
|
490
503
|
},
|
|
491
504
|
|
|
@@ -505,7 +518,7 @@ export default {
|
|
|
505
518
|
let tr = input.closest('tr');
|
|
506
519
|
let first = true;
|
|
507
520
|
|
|
508
|
-
while ( tr && (first || tr.classList.contains('sub-row') ) ) {
|
|
521
|
+
while ( tr && (first || tr.classList.contains('sub-row') || tr.classList.contains('additional-sub-row')) ) {
|
|
509
522
|
if (on) {
|
|
510
523
|
tr.classList.add('row-selected');
|
|
511
524
|
} else {
|
|
@@ -79,7 +79,7 @@ export default {
|
|
|
79
79
|
|
|
80
80
|
if ( markedColumn ) {
|
|
81
81
|
this._defaultSortBy = markedColumn.name;
|
|
82
|
-
descending = markedColumn.defaultSortDescending;
|
|
82
|
+
descending = markedColumn.defaultSortDescending || false;
|
|
83
83
|
} else if ( nameColumn ) {
|
|
84
84
|
// Use the name column if there is one
|
|
85
85
|
this._defaultSortBy = nameColumn.name;
|
|
@@ -15,6 +15,10 @@ export default {
|
|
|
15
15
|
default: null,
|
|
16
16
|
type: String
|
|
17
17
|
},
|
|
18
|
+
labelIcon: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: null
|
|
21
|
+
},
|
|
18
22
|
name: {
|
|
19
23
|
required: true,
|
|
20
24
|
type: String
|
|
@@ -137,6 +141,7 @@ export default {
|
|
|
137
141
|
:id="name"
|
|
138
142
|
:aria-hidden="!active"
|
|
139
143
|
role="tabpanel"
|
|
144
|
+
:aria-labelledby="`tab-${name}`"
|
|
140
145
|
>
|
|
141
146
|
<div
|
|
142
147
|
v-if="shouldShowHeader"
|
|
@@ -83,6 +83,11 @@ export default {
|
|
|
83
83
|
componentTestid: {
|
|
84
84
|
type: String,
|
|
85
85
|
default: 'tabbed'
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
removeBorders: {
|
|
89
|
+
type: Boolean,
|
|
90
|
+
default: false,
|
|
86
91
|
}
|
|
87
92
|
},
|
|
88
93
|
|
|
@@ -128,7 +133,8 @@ export default {
|
|
|
128
133
|
return {
|
|
129
134
|
tabs: [...parsedExtTabs],
|
|
130
135
|
extensionTabs: parsedExtTabs,
|
|
131
|
-
activeTabName: null
|
|
136
|
+
activeTabName: null,
|
|
137
|
+
tabRefs: {}
|
|
132
138
|
};
|
|
133
139
|
},
|
|
134
140
|
|
|
@@ -201,7 +207,7 @@ export default {
|
|
|
201
207
|
return TabLocation.OTHER;
|
|
202
208
|
}
|
|
203
209
|
},
|
|
204
|
-
|
|
210
|
+
hasErrorIcon(tab) {
|
|
205
211
|
return tab.displayAlertIcon || (tab.error && !tab.active);
|
|
206
212
|
},
|
|
207
213
|
hashChange() {
|
|
@@ -263,7 +269,10 @@ export default {
|
|
|
263
269
|
this.select(nextName);
|
|
264
270
|
|
|
265
271
|
this.$nextTick(() => {
|
|
266
|
-
this.$refs.tablist.
|
|
272
|
+
this.$refs.tablist.removeAttribute('tabindex');
|
|
273
|
+
if (this.tabRefs[nextName]) {
|
|
274
|
+
this.tabRefs[nextName].focus();
|
|
275
|
+
}
|
|
267
276
|
});
|
|
268
277
|
|
|
269
278
|
function getCyclicalIdx(currentIdx, direction, tabsLength) {
|
|
@@ -299,7 +308,8 @@ export default {
|
|
|
299
308
|
class="tabbed-container"
|
|
300
309
|
:class="{
|
|
301
310
|
'side-tabs': !!sideTabs,
|
|
302
|
-
'tabs-only': tabsOnly
|
|
311
|
+
'tabs-only': tabsOnly,
|
|
312
|
+
'remove-borders': removeBorders
|
|
303
313
|
}"
|
|
304
314
|
:data-testid="componentTestid"
|
|
305
315
|
>
|
|
@@ -308,7 +318,7 @@ export default {
|
|
|
308
318
|
ref="tablist"
|
|
309
319
|
role="tablist"
|
|
310
320
|
class="tabs"
|
|
311
|
-
:class="{'clearfix':!sideTabs, 'vertical': sideTabs, 'horizontal': !sideTabs}"
|
|
321
|
+
:class="{'clearfix':!sideTabs, 'vertical': sideTabs, 'horizontal': !sideTabs, 'remove-borders': removeBorders}"
|
|
312
322
|
:data-testid="`${componentTestid}-block`"
|
|
313
323
|
tabindex="0"
|
|
314
324
|
@keydown.right.prevent="selectNext(1)"
|
|
@@ -323,17 +333,23 @@ export default {
|
|
|
323
333
|
:key="tab.name"
|
|
324
334
|
:data-testid="tab.name"
|
|
325
335
|
:class="{tab: true, active: tab.active, disabled: tab.disabled, error: (tab.error)}"
|
|
326
|
-
role="presentation"
|
|
327
336
|
>
|
|
328
337
|
<a
|
|
338
|
+
:id="`tab-${tab.name}`"
|
|
339
|
+
:ref="(el) => { if (el) tabRefs[tab.name] = el; }"
|
|
329
340
|
:data-testid="`btn-${tab.name}`"
|
|
330
341
|
:aria-controls="tab.name"
|
|
331
342
|
:aria-selected="tab.active"
|
|
332
343
|
:aria-label="tab.labelDisplay || ''"
|
|
333
344
|
role="tab"
|
|
345
|
+
:tabindex="tab.active ? '0' : '-1'"
|
|
334
346
|
@click.prevent="select(tab.name, $event)"
|
|
335
347
|
@keyup.enter.space="select(tab.name, $event)"
|
|
336
348
|
>
|
|
349
|
+
<i
|
|
350
|
+
v-if="tab.labelIcon"
|
|
351
|
+
:class="`tab-label-icon icon ${tab.labelIcon}`"
|
|
352
|
+
/>
|
|
337
353
|
<span>
|
|
338
354
|
{{ tab.labelDisplay }}
|
|
339
355
|
</span>
|
|
@@ -342,7 +358,7 @@ export default {
|
|
|
342
358
|
class="tab-badge"
|
|
343
359
|
>{{ tab.badge }}</span>
|
|
344
360
|
<i
|
|
345
|
-
v-if="
|
|
361
|
+
v-if="hasErrorIcon(tab)"
|
|
346
362
|
v-clean-tooltip="t('validation.tab')"
|
|
347
363
|
class="conditions-alert-icon icon-error"
|
|
348
364
|
/>
|
|
@@ -442,6 +458,17 @@ export default {
|
|
|
442
458
|
display: flex;
|
|
443
459
|
flex-direction: row;
|
|
444
460
|
|
|
461
|
+
&.remove-borders {
|
|
462
|
+
border: none;
|
|
463
|
+
|
|
464
|
+
+ .tab-container {
|
|
465
|
+
border: none;
|
|
466
|
+
border-top: 1px solid var(--border);
|
|
467
|
+
padding: 0;
|
|
468
|
+
padding-top: 24px;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
445
472
|
+ .tab-container {
|
|
446
473
|
border: solid thin var(--border);
|
|
447
474
|
}
|
|
@@ -458,7 +485,7 @@ export default {
|
|
|
458
485
|
.tab {
|
|
459
486
|
position: relative;
|
|
460
487
|
float: left;
|
|
461
|
-
padding: 0
|
|
488
|
+
padding: 0 4px 0 4px;
|
|
462
489
|
cursor: pointer;
|
|
463
490
|
|
|
464
491
|
A {
|
|
@@ -491,11 +518,15 @@ export default {
|
|
|
491
518
|
}
|
|
492
519
|
|
|
493
520
|
&.error {
|
|
494
|
-
& A >
|
|
521
|
+
& A > .icon-error {
|
|
495
522
|
color: var(--error);
|
|
496
523
|
}
|
|
497
524
|
}
|
|
498
525
|
|
|
526
|
+
.tab-label-icon {
|
|
527
|
+
margin-right: 8px;
|
|
528
|
+
}
|
|
529
|
+
|
|
499
530
|
.tab-badge {
|
|
500
531
|
margin-left: 5px;
|
|
501
532
|
background-color: var(--link);
|
|
@@ -172,8 +172,11 @@ export default {
|
|
|
172
172
|
await this.$store.dispatch('cluster/find', { type: NODE, id: nodeId });
|
|
173
173
|
}
|
|
174
174
|
} catch {}
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
try {
|
|
176
|
+
await this.setupTerminal();
|
|
177
|
+
} catch (e) {
|
|
178
|
+
this.errorMsg = e;
|
|
179
|
+
}
|
|
177
180
|
await this.connect();
|
|
178
181
|
|
|
179
182
|
clearInterval(this.keepAliveTimer);
|
|
@@ -234,26 +237,20 @@ export default {
|
|
|
234
237
|
|
|
235
238
|
this.fitAddon = new addons.fit.FitAddon();
|
|
236
239
|
this.searchAddon = new addons.search.SearchAddon();
|
|
240
|
+
terminal.loadAddon(this.fitAddon);
|
|
241
|
+
terminal.loadAddon(this.searchAddon);
|
|
242
|
+
terminal.loadAddon(new addons.weblinks.WebLinksAddon());
|
|
243
|
+
terminal.open(this.$refs.xterm);
|
|
237
244
|
|
|
238
245
|
try {
|
|
239
246
|
this.webglAddon = new addons.webgl.WebglAddon();
|
|
247
|
+
terminal.loadAddon(this.webglAddon);
|
|
240
248
|
} catch (e) {
|
|
241
249
|
// Some browsers (Safari) don't support the webgl renderer, so don't use it.
|
|
242
250
|
this.webglAddon = null;
|
|
243
251
|
this.canvasAddon = new addons.canvas.CanvasAddon();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
terminal.loadAddon(this.fitAddon);
|
|
247
|
-
terminal.loadAddon(this.searchAddon);
|
|
248
|
-
terminal.loadAddon(new addons.weblinks.WebLinksAddon());
|
|
249
|
-
terminal.open(this.$refs.xterm);
|
|
250
|
-
|
|
251
|
-
if (this.webglAddon) {
|
|
252
|
-
terminal.loadAddon(this.webglAddon);
|
|
253
|
-
} else {
|
|
254
252
|
terminal.loadAddon(this.canvasAddon);
|
|
255
253
|
}
|
|
256
|
-
|
|
257
254
|
this.fit();
|
|
258
255
|
this.flush();
|
|
259
256
|
|
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
import ProjectRow from '@shell/components/form/ResourceQuota/ProjectRow.vue';
|
|
2
|
-
import { RANCHER_TYPES } from '@shell/components/form/ResourceQuota/shared';
|
|
2
|
+
import { RANCHER_TYPES, TYPES } from '@shell/components/form/ResourceQuota/shared';
|
|
3
3
|
import { shallowMount } from '@vue/test-utils';
|
|
4
4
|
|
|
5
|
-
const CONFIGMAP_STRING =
|
|
5
|
+
const CONFIGMAP_STRING = TYPES.CONFIG_MAPS;
|
|
6
6
|
|
|
7
7
|
describe('component: ProjectRow.vue', () => {
|
|
8
|
-
const
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
8
|
+
const defaultMountOptions = {
|
|
9
|
+
props: {
|
|
10
|
+
mode: 'edit',
|
|
11
|
+
types: RANCHER_TYPES,
|
|
12
|
+
type: CONFIGMAP_STRING,
|
|
13
|
+
index: 0,
|
|
14
|
+
value: {
|
|
15
|
+
spec: {
|
|
16
|
+
namespaceDefaultResourceQuota: { limit: {} },
|
|
17
|
+
resourceQuota: { limit: {} }
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
|
-
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
it('should render the correct input fields and set the correct computed values, based on the provided data', () => {
|
|
24
|
+
const wrapper = shallowMount(
|
|
25
|
+
ProjectRow,
|
|
26
|
+
{ ...defaultMountOptions }
|
|
27
|
+
);
|
|
28
|
+
|
|
24
29
|
const typeInput = wrapper.find(`[data-testid="projectrow-type-input"]`);
|
|
30
|
+
const customTypeInput = wrapper.find(`[data-testid="projectrow-custom-type-input"]`);
|
|
25
31
|
const projectQuotaInput = wrapper.find(`[data-testid="projectrow-project-quota-input"]`);
|
|
26
32
|
const namespaceQuotaInput = wrapper.find(`[data-testid="projectrow-namespace-quota-input"]`);
|
|
27
33
|
|
|
28
34
|
expect(typeInput.exists()).toBe(true);
|
|
35
|
+
expect(customTypeInput.exists()).toBe(true);
|
|
36
|
+
expect(customTypeInput.attributes().disabled).toBe('true');
|
|
29
37
|
expect(projectQuotaInput.exists()).toBe(true);
|
|
30
38
|
expect(namespaceQuotaInput.exists()).toBe(true);
|
|
31
39
|
expect(wrapper.vm.resourceQuotaLimit).toStrictEqual({});
|
|
@@ -33,6 +41,11 @@ describe('component: ProjectRow.vue', () => {
|
|
|
33
41
|
});
|
|
34
42
|
|
|
35
43
|
it('triggering "updateQuotaLimit" should trigger Vue.set with the correct data', () => {
|
|
44
|
+
const wrapper = shallowMount(
|
|
45
|
+
ProjectRow,
|
|
46
|
+
{ ...defaultMountOptions }
|
|
47
|
+
);
|
|
48
|
+
|
|
36
49
|
wrapper.vm.updateQuotaLimit('resourceQuota', CONFIGMAP_STRING, 10);
|
|
37
50
|
|
|
38
51
|
expect(wrapper.vm.value).toStrictEqual({
|
|
@@ -44,6 +57,11 @@ describe('component: ProjectRow.vue', () => {
|
|
|
44
57
|
});
|
|
45
58
|
|
|
46
59
|
it('triggering "updateType" with the same type that existed should clear limits and trigger emit', () => {
|
|
60
|
+
const wrapper = shallowMount(
|
|
61
|
+
ProjectRow,
|
|
62
|
+
{ ...defaultMountOptions }
|
|
63
|
+
);
|
|
64
|
+
|
|
47
65
|
wrapper.vm.updateType(CONFIGMAP_STRING);
|
|
48
66
|
|
|
49
67
|
expect(wrapper.vm.value).toStrictEqual({
|
|
@@ -54,6 +72,75 @@ describe('component: ProjectRow.vue', () => {
|
|
|
54
72
|
});
|
|
55
73
|
|
|
56
74
|
expect(wrapper.emitted('type-change')).toBeTruthy();
|
|
57
|
-
expect(wrapper.emitted('type-change')[0]).toStrictEqual([CONFIGMAP_STRING]);
|
|
75
|
+
expect(wrapper.emitted('type-change')[0]).toStrictEqual([{ index: 0, type: CONFIGMAP_STRING }]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should update standard resource types', async() => {
|
|
79
|
+
const wrapper = shallowMount(
|
|
80
|
+
ProjectRow,
|
|
81
|
+
{ ...defaultMountOptions }
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(wrapper.vm.isCustom).toBe(false);
|
|
85
|
+
|
|
86
|
+
await wrapper.vm.updateQuotaLimit('resourceQuota', 'limitsCpu', '100m');
|
|
87
|
+
await wrapper.vm.updateQuotaLimit('namespaceDefaultResourceQuota', 'limitsCpu', '50m');
|
|
88
|
+
|
|
89
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.limitsCpu).toBe('100m');
|
|
90
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.limitsCpu).toBe('50m');
|
|
91
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should switch to a custom resource type', async() => {
|
|
95
|
+
const wrapper = shallowMount(
|
|
96
|
+
ProjectRow,
|
|
97
|
+
{ ...defaultMountOptions }
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
await wrapper.vm.updateType(TYPES.EXTENDED);
|
|
101
|
+
|
|
102
|
+
expect(wrapper.emitted('type-change')).toHaveLength(1);
|
|
103
|
+
expect(wrapper.emitted('type-change')[0][0]).toStrictEqual({ index: 0, type: TYPES.EXTENDED });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should update custom resource types', async() => {
|
|
107
|
+
const wrapper = shallowMount(
|
|
108
|
+
ProjectRow,
|
|
109
|
+
{
|
|
110
|
+
...defaultMountOptions,
|
|
111
|
+
props: {
|
|
112
|
+
...defaultMountOptions.props,
|
|
113
|
+
type: TYPES.EXTENDED
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(wrapper.vm.isCustom).toBe(true);
|
|
119
|
+
|
|
120
|
+
const customTypeInput = wrapper.find(`[data-testid="projectrow-custom-type-input"]`);
|
|
121
|
+
|
|
122
|
+
expect(customTypeInput.attributes().disabled).toBe('false');
|
|
123
|
+
await wrapper.vm.updateCustomType('custom.resource/foo');
|
|
124
|
+
|
|
125
|
+
expect(wrapper.vm.customType).toBe('custom.resource/foo');
|
|
126
|
+
|
|
127
|
+
await wrapper.vm.updateQuotaLimit('resourceQuota', 'custom.resource/foo', 1);
|
|
128
|
+
await wrapper.vm.updateQuotaLimit('namespaceDefaultResourceQuota', 'custom.resource/foo', 2);
|
|
129
|
+
|
|
130
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended['custom.resource/foo']).toBe(1);
|
|
131
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended['custom.resource/foo']).toBe(2);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle custom resource types with periods', () => {
|
|
135
|
+
const wrapper = shallowMount(ProjectRow, {
|
|
136
|
+
...defaultMountOptions,
|
|
137
|
+
props: {
|
|
138
|
+
...defaultMountOptions.props,
|
|
139
|
+
type: 'extended.requests.nvidia.com/gpu'
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(wrapper.vm.isCustom).toBe(true);
|
|
144
|
+
expect(wrapper.vm.customType).toBe('requests.nvidia.com/gpu');
|
|
58
145
|
});
|
|
59
146
|
});
|