@rancher/shell 3.0.9-rc.5 → 3.0.9

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.
Files changed (172) hide show
  1. package/assets/images/providers/oci-open-containers.svg +22 -0
  2. package/assets/images/providers/traefik.png +0 -0
  3. package/assets/styles/themes/_dark.scss +2 -0
  4. package/assets/styles/themes/_light.scss +2 -0
  5. package/assets/styles/themes/_modern.scss +6 -0
  6. package/assets/translations/en-us.yaml +129 -25
  7. package/components/CruResource.vue +3 -1
  8. package/components/ExplorerProjectsNamespaces.vue +12 -12
  9. package/components/IconOrSvg.vue +61 -42
  10. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  11. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  12. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  13. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  14. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  15. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  16. package/components/Resource/Detail/ResourceRow.vue +2 -2
  17. package/components/ResourceList/index.vue +7 -4
  18. package/components/SortableTable/index.vue +2 -2
  19. package/components/Window/ContainerLogs.vue +48 -37
  20. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  21. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  22. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  23. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  24. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  25. package/components/fleet/GitRepoTargetTab.vue +77 -0
  26. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  27. package/components/fleet/HelmOpChartTab.vue +158 -0
  28. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  29. package/components/fleet/HelmOpTargetTab.vue +84 -0
  30. package/components/fleet/HelmOpValuesTab.vue +147 -0
  31. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  32. package/components/form/BannerSettings.vue +2 -2
  33. package/components/form/NodeScheduling.vue +81 -7
  34. package/components/form/NotificationSettings.vue +2 -2
  35. package/components/form/PodAffinity.vue +1 -36
  36. package/components/form/ResourceLabeledSelect.vue +8 -4
  37. package/components/form/ResourceQuota/Namespace.vue +30 -9
  38. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  39. package/components/form/ResourceQuota/Project.vue +140 -82
  40. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  41. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  42. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  43. package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
  44. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  45. package/components/form/SchedulingCustomization.vue +14 -6
  46. package/components/form/SelectOrCreateAuthSecret.vue +107 -18
  47. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  48. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  49. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  50. package/components/formatter/ClusterLink.vue +8 -0
  51. package/components/formatter/SecretOrigin.vue +79 -0
  52. package/config/labels-annotations.js +7 -6
  53. package/config/pagination-table-headers.js +6 -4
  54. package/config/product/explorer.js +1 -11
  55. package/config/product/manager.js +0 -1
  56. package/config/query-params.js +3 -0
  57. package/config/settings.ts +15 -2
  58. package/config/table-headers.js +21 -17
  59. package/config/types.js +23 -8
  60. package/detail/fleet.cattle.io.cluster.vue +1 -1
  61. package/detail/workload/index.vue +11 -16
  62. package/dialog/DeactivateDriverDialog.vue +1 -1
  63. package/dialog/FeatureFlagListDialog.vue +1 -1
  64. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  65. package/dialog/ScalePoolDownDialog.vue +2 -2
  66. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  67. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  68. package/edit/__tests__/management.cattle.io.project.test.js +56 -128
  69. package/edit/auth/oidc.vue +1 -1
  70. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  71. package/edit/fleet.cattle.io.gitrepo.vue +153 -283
  72. package/edit/fleet.cattle.io.helmop.vue +190 -332
  73. package/edit/management.cattle.io.project.vue +5 -42
  74. package/edit/management.cattle.io.setting.vue +6 -0
  75. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
  76. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
  77. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
  78. package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
  79. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
  80. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
  81. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
  82. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
  83. package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
  84. package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
  85. package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
  86. package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
  87. package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
  88. package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
  89. package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
  90. package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
  91. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  92. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  93. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  94. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  95. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
  96. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +114 -0
  97. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  98. package/edit/provisioning.cattle.io.cluster/rke2.vue +167 -69
  99. package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
  100. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  101. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +70 -7
  102. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +343 -0
  103. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  104. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  105. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  106. package/edit/secret/index.vue +1 -1
  107. package/edit/token.vue +68 -29
  108. package/edit/workload/__tests__/index.test.ts +2 -37
  109. package/edit/workload/index.vue +6 -2
  110. package/edit/workload/mixins/workload.js +0 -32
  111. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  112. package/list/management.cattle.io.setting.vue +13 -0
  113. package/list/provisioning.cattle.io.cluster.vue +50 -1
  114. package/list/secret.vue +4 -9
  115. package/list/service.vue +6 -8
  116. package/machine-config/amazonec2.vue +11 -4
  117. package/machine-config/components/EC2Networking.vue +46 -30
  118. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  119. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  120. package/machine-config/digitalocean.vue +3 -3
  121. package/models/__tests__/chart.test.ts +2 -2
  122. package/models/__tests__/namespace.test.ts +11 -0
  123. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  124. package/models/__tests__/workload.test.ts +42 -1
  125. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  126. package/models/chart.js +3 -3
  127. package/models/ext.cattle.io.token.js +48 -0
  128. package/models/kontainerdriver.js +2 -2
  129. package/models/namespace.js +7 -1
  130. package/models/nodedriver.js +2 -2
  131. package/models/provisioning.cattle.io.cluster.js +28 -7
  132. package/models/secret.js +0 -17
  133. package/models/service.js +44 -1
  134. package/models/token.js +4 -0
  135. package/models/workload.js +12 -6
  136. package/package.json +1 -1
  137. package/pages/account/index.vue +96 -67
  138. package/pages/auth/setup.vue +5 -14
  139. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +45 -18
  140. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  141. package/pages/c/_cluster/apps/charts/index.vue +82 -3
  142. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  143. package/pages/c/_cluster/explorer/tools/index.vue +1 -1
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  146. package/pages/c/_cluster/settings/index.vue +3 -1
  147. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  148. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  149. package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
  150. package/plugins/dashboard-store/actions.js +3 -8
  151. package/plugins/dashboard-store/getters.js +7 -5
  152. package/plugins/dashboard-store/mutations.js +4 -1
  153. package/plugins/dashboard-store/resource-class.js +3 -3
  154. package/plugins/steve/__tests__/steve-class.test.ts +102 -141
  155. package/plugins/steve/steve-class.js +12 -3
  156. package/plugins/steve/steve-pagination-utils.ts +6 -2
  157. package/rancher-components/RcIcon/types.ts +2 -0
  158. package/rancher-components/RcItemCard/RcItemCard.vue +72 -20
  159. package/store/prefs.js +3 -0
  160. package/types/aws-sdk.d.ts +121 -0
  161. package/types/resources/node.ts +15 -0
  162. package/types/shell/index.d.ts +537 -506
  163. package/types/store/pagination.types.ts +5 -5
  164. package/utils/__tests__/array.test.ts +1 -29
  165. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  166. package/utils/array.ts +0 -11
  167. package/utils/aws.ts +21 -0
  168. package/utils/cluster.js +22 -2
  169. package/utils/selector-typed.ts +1 -1
  170. package/utils/svg-filter.js +4 -3
  171. package/components/__tests__/ProjectRow.test.ts +0 -206
  172. package/components/form/ResourceQuota/ProjectRow.vue +0 -277
@@ -0,0 +1,109 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import StatusCard from '@shell/components/Resource/Detail/Card/StatusCard/index.vue';
3
+ import StatusBar from '@shell/components/Resource/Detail/StatusBar.vue';
4
+ import StatusRow from '@shell/components/Resource/Detail/StatusRow.vue';
5
+ import Scaler from '@shell/components/Resource/Detail/Card/Scaler.vue';
6
+
7
+ describe('component: StatusCard', () => {
8
+ const mockResource = (stateDisplay: string, stateSimpleColor: string) => ({
9
+ stateDisplay,
10
+ stateSimpleColor,
11
+ });
12
+
13
+ const defaultMocks = {
14
+ $store: {
15
+ getters: { 'i18n/t': (key: string) => key },
16
+ dispatch: jest.fn(),
17
+ },
18
+ };
19
+
20
+ const mountCard = (props: Record<string, unknown> = {}) => {
21
+ return mount(StatusCard, {
22
+ props: { title: 'Pods', ...props },
23
+ global: {
24
+ mocks: defaultMocks,
25
+ stubs: {
26
+ StatusBar: true,
27
+ StatusRow: true,
28
+ Scaler: true,
29
+ },
30
+ },
31
+ });
32
+ };
33
+
34
+ describe('with resources', () => {
35
+ it('should render StatusBar and StatusRows when resources are present', () => {
36
+ const resources = [
37
+ mockResource('Running', 'text-success'),
38
+ mockResource('Running', 'text-success'),
39
+ mockResource('Error', 'text-error'),
40
+ ];
41
+
42
+ const wrapper = mountCard({ resources });
43
+
44
+ expect(wrapper.findComponent(StatusBar).exists()).toBe(true);
45
+ expect(wrapper.findAllComponents(StatusRow)).toHaveLength(2);
46
+ });
47
+
48
+ it('should not render noResourcesMessage when resources are present', () => {
49
+ const resources = [mockResource('Running', 'text-success')];
50
+
51
+ const wrapper = mountCard({ resources, noResourcesMessage: 'No pods' });
52
+
53
+ expect(wrapper.find('.text-deemphasized').exists()).toBe(false);
54
+ });
55
+ });
56
+
57
+ describe('with empty resources', () => {
58
+ it('should not render StatusBar or StatusRows', () => {
59
+ const wrapper = mountCard({ resources: [], noResourcesMessage: 'No pods' });
60
+
61
+ expect(wrapper.findComponent(StatusBar).exists()).toBe(false);
62
+ expect(wrapper.findAllComponents(StatusRow)).toHaveLength(0);
63
+ });
64
+
65
+ it('should render noResourcesMessage when provided', () => {
66
+ const wrapper = mountCard({ resources: [], noResourcesMessage: 'There are no pods currently present.' });
67
+
68
+ const emptyDiv = wrapper.find('.text-deemphasized');
69
+
70
+ expect(emptyDiv.exists()).toBe(true);
71
+ expect(emptyDiv.text()).toBe('There are no pods currently present.');
72
+ });
73
+
74
+ it('should not render empty-state div when noResourcesMessage is not provided', () => {
75
+ const wrapper = mountCard({ resources: [] });
76
+
77
+ expect(wrapper.find('.text-deemphasized').exists()).toBe(false);
78
+ });
79
+ });
80
+
81
+ describe('with undefined resources', () => {
82
+ it('should not render StatusBar, StatusRows, or noResourcesMessage', () => {
83
+ const wrapper = mountCard({ noResourcesMessage: 'No pods' });
84
+
85
+ expect(wrapper.findComponent(StatusBar).exists()).toBe(false);
86
+ expect(wrapper.findAllComponents(StatusRow)).toHaveLength(0);
87
+ expect(wrapper.find('.text-deemphasized').exists()).toBe(true);
88
+ expect(wrapper.find('.text-deemphasized').text()).toBe('No pods');
89
+ });
90
+ });
91
+
92
+ describe('scaling', () => {
93
+ it('should render Scaler when showScaling is true', () => {
94
+ const resources = [mockResource('Running', 'text-success')];
95
+
96
+ const wrapper = mountCard({ resources, showScaling: true });
97
+
98
+ expect(wrapper.findComponent(Scaler).exists()).toBe(true);
99
+ });
100
+
101
+ it('should not render Scaler when showScaling is false', () => {
102
+ const resources = [mockResource('Running', 'text-success')];
103
+
104
+ const wrapper = mountCard({ resources, showScaling: false });
105
+
106
+ expect(wrapper.findComponent(Scaler).exists()).toBe(false);
107
+ });
108
+ });
109
+ });
@@ -13,6 +13,7 @@ export interface Props {
13
13
  title: string;
14
14
  resources?: any[];
15
15
  showScaling?: boolean;
16
+ noResourcesMessage?: string;
16
17
  }
17
18
  </script>
18
19
 
@@ -20,7 +21,11 @@ export interface Props {
20
21
  const store = useStore();
21
22
  const i18n = useI18n(store);
22
23
 
23
- const props = withDefaults(defineProps<Props>(), { resources: undefined, showScaling: false });
24
+ const props = withDefaults(defineProps<Props>(), {
25
+ resources: undefined,
26
+ showScaling: false,
27
+ noResourcesMessage: undefined
28
+ });
24
29
  const emit = defineEmits(['decrease', 'increase']);
25
30
 
26
31
  const segmentAccumulator = computed(() => {
@@ -99,9 +104,15 @@ const rows = computed(() => {
99
104
  @decrease="(newValue) => emit('decrease', newValue)"
100
105
  />
101
106
  </template>
102
- <StatusBar :segments="segments" />
103
- <VerticalGap />
104
- <div class="pod-distribution">
107
+ <StatusBar
108
+ v-if="rows.length > 0"
109
+ :segments="segments"
110
+ />
111
+ <VerticalGap v-if="rows.length > 0" />
112
+ <div
113
+ v-if="rows.length > 0"
114
+ class="pod-distribution"
115
+ >
105
116
  <StatusRow
106
117
  v-for="(row, i) in rows"
107
118
  :key="i"
@@ -111,6 +122,12 @@ const rows = computed(() => {
111
122
  :percent="row.percent"
112
123
  />
113
124
  </div>
125
+ <div
126
+ v-else-if="props.noResourcesMessage"
127
+ class="text-deemphasized"
128
+ >
129
+ {{ props.noResourcesMessage }}
130
+ </div>
114
131
  </Card>
115
132
  </template>
116
133
 
@@ -6,7 +6,12 @@ import { NAME as FLEET_NAME } from '@shell/config/product/fleet';
6
6
 
7
7
  const mockStore = {
8
8
  getters: {
9
- productId: 'PRODUCT_ID', clusterId: 'CLUSTER_ID', 'type-map/optionsFor': jest.fn(), currentCluster: 'CLUSTER_ID'
9
+ productId: 'PRODUCT_ID',
10
+ clusterId: 'CLUSTER_ID',
11
+ 'type-map/optionsFor': jest.fn(),
12
+ currentCluster: 'CLUSTER_ID',
13
+ currentStore: () => 'cluster',
14
+ 'cluster/canList': () => true,
10
15
  }
11
16
  };
12
17
  const mockRoute = { params: { cluster: 'CLUSTER', namespace: 'NAMESPACE' } };
@@ -32,7 +37,8 @@ describe('composables: IdentifyingFields', () => {
32
37
  expect(result).toBeUndefined();
33
38
  });
34
39
 
35
- it('should return a valid namespace row', () => {
40
+ it('should return a valid namespace row with ResourcePopover when user canList namespaces', () => {
41
+ mockStore.getters['cluster/canList'] = () => true;
36
42
  const resource = { namespace: 'NAMESPACE' };
37
43
  const result = useNamespace(resource);
38
44
 
@@ -42,6 +48,17 @@ describe('composables: IdentifyingFields', () => {
42
48
  expect(result?.value.label).toStrictEqual('component.resource.detail.metadata.identifyingInformation.namespace');
43
49
  expect(result?.value.valueDataTestid).toStrictEqual('masthead-subheader-namespace');
44
50
  });
51
+
52
+ it('should return a plain text namespace row when user cannot canList namespaces', () => {
53
+ mockStore.getters['cluster/canList'] = () => false;
54
+ const resource = { namespace: 'NAMESPACE' };
55
+ const result = useNamespace(resource);
56
+
57
+ expect(result?.value.valueOverride).toBeUndefined();
58
+ expect(result?.value.value).toStrictEqual(resource.namespace);
59
+ expect(result?.value.label).toStrictEqual('component.resource.detail.metadata.identifyingInformation.namespace');
60
+ expect(result?.value.valueDataTestid).toStrictEqual('masthead-subheader-namespace');
61
+ });
45
62
  });
46
63
 
47
64
  describe('useWorkspace', () => {
@@ -24,18 +24,26 @@ export const useNamespace = (resource: any): ComputedRef<Row> | undefined => {
24
24
  }
25
25
 
26
26
  return computed(() => {
27
- return {
28
- label: i18n.t('component.resource.detail.metadata.identifyingInformation.namespace'),
29
- value: resourceValue.namespace,
30
- valueDataTestid: 'masthead-subheader-namespace',
31
- valueOverride: {
32
- component: markRaw(defineAsyncComponent(() => import('@shell/components/Resource/Detail/ResourcePopover/index.vue'))),
33
- props: {
34
- type: NAMESPACE,
35
- id: resourceValue.namespace,
36
- detailLocation: resourceValue.namespaceLocation
37
- }
27
+ const currentStore = store.getters['currentStore'](NAMESPACE);
28
+ const canList = store.getters[`${ currentStore }/canList`](NAMESPACE);
29
+
30
+ const label = i18n.t('component.resource.detail.metadata.identifyingInformation.namespace');
31
+ const value = resourceValue.namespace;
32
+ const valueDataTestid = 'masthead-subheader-namespace';
33
+ const valueOverride = canList ? {
34
+ component: markRaw(defineAsyncComponent(() => import('@shell/components/Resource/Detail/ResourcePopover/index.vue'))),
35
+ props: {
36
+ type: NAMESPACE,
37
+ id: resourceValue.namespace,
38
+ detailLocation: resourceValue.namespaceLocation
38
39
  }
40
+ } : undefined;
41
+
42
+ return {
43
+ label,
44
+ value,
45
+ valueDataTestid,
46
+ valueOverride,
39
47
  };
40
48
  });
41
49
  };
@@ -79,6 +79,18 @@ describe('component: ResourcePopover/index.vue', () => {
79
79
  expect(wrapper.find('.display').exists()).toBe(false);
80
80
  });
81
81
 
82
+ it('should show plain text and no PopoverCard when fetch fails', async() => {
83
+ mockClusterFind.mockRejectedValue(new Error('Not found'));
84
+ const wrapper = createWrapper(undefined, undefined, PopoverCardStub);
85
+
86
+ await wrapper.vm.$nextTick();
87
+ await wrapper.vm.$nextTick();
88
+ await wrapper.vm.$nextTick();
89
+
90
+ expect(wrapper.findComponent(PopoverCard).exists()).toBe(false);
91
+ expect(wrapper.text()).toBe('test-ns/test-pod');
92
+ });
93
+
82
94
  it('should fetch data using the default store', async() => {
83
95
  const wrapper = createWrapper();
84
96
 
@@ -58,6 +58,7 @@ const actionInvoked = () => {
58
58
 
59
59
  <template>
60
60
  <PopoverCard
61
+ v-if="!fetch.error"
61
62
  class="resource-popover"
62
63
  :card-title="nameDisplay"
63
64
  fallback-focus="[data-testid='resource-popover-action-menu']"
@@ -104,6 +105,7 @@ const actionInvoked = () => {
104
105
  />
105
106
  </template>
106
107
  </PopoverCard>
108
+ <span v-else>{{ props.id }}</span>
107
109
  </template>
108
110
 
109
111
  <style lang="scss" scoped>
@@ -47,7 +47,7 @@ const displayCounts = computed(() => {
47
47
  </SubtleLink>
48
48
  <span
49
49
  v-else
50
- class="text-muted"
50
+ class="text-deemphasized"
51
51
  >
52
52
  {{ label }}
53
53
  </span>
@@ -55,7 +55,7 @@ const displayCounts = computed(() => {
55
55
  <div class="right">
56
56
  <div
57
57
  v-if="!counts || counts.length == 0"
58
- class="text-muted"
58
+ class="text-deemphasized"
59
59
  >
60
60
  0
61
61
  </div>
@@ -87,11 +87,14 @@ export default {
87
87
  },
88
88
 
89
89
  beforeMount() {
90
- const inStore = this.$store.getters['currentStore'](this.resource);
91
- const canList = this.$store.getters[`${ inStore }/canList`](this.resource);
90
+ if (!this.hasListComponent) {
91
+ // If a list doesn't have a list component confirm via schema that the resource is listable
92
+ const inStore = this.$store.getters['currentStore'](this.resource);
93
+ const canList = this.$store.getters[`${ inStore }/canList`](this.resource);
92
94
 
93
- if (!canList) {
94
- this.$store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceListNotListable', { resource: this.schema.id }, true)));
95
+ if (!canList) {
96
+ this.$store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceListNotListable', { resource: this.schema?.id || this.resource || 'unknown' }, true)));
97
+ }
95
98
  }
96
99
  },
97
100
 
@@ -2099,13 +2099,13 @@ export default {
2099
2099
 
2100
2100
  $header-padding: 20px;
2101
2101
  .sub-header-row {
2102
- padding: 0 0 $header-padding / 2 0;
2102
+ padding: 0 0 calc($header-padding / 2) 0;
2103
2103
  }
2104
2104
 
2105
2105
  .fixed-header-actions {
2106
2106
  padding: 0 0 $header-padding 0;
2107
2107
  &.with-sub-header {
2108
- padding: 0 0 $header-padding / 4 0;
2108
+ padding: 0 0 calc($header-padding / 4) 0;
2109
2109
  }
2110
2110
 
2111
2111
  width: 100%;
@@ -13,7 +13,6 @@ import LogItem from '@shell/components/LogItem';
13
13
  import ContainerLogsActions from '@shell/components/Window/ContainerLogsActions.vue';
14
14
  import { shallowRef } from 'vue';
15
15
  import { useStore } from 'vuex';
16
- import { debounce } from 'lodash';
17
16
  import { useRuntimeFlag } from '@shell/composables/useRuntimeFlag';
18
17
 
19
18
  import { escapeRegex } from '@shell/utils/string';
@@ -110,12 +109,6 @@ export default {
110
109
  required: true,
111
110
  },
112
111
 
113
- // The height of the window
114
- height: {
115
- type: Number,
116
- required: true,
117
- },
118
-
119
112
  // The pod to connect to
120
113
  pod: {
121
114
  type: Object,
@@ -147,7 +140,6 @@ export default {
147
140
  socket: null,
148
141
  isOpen: false,
149
142
  isFollowing: true,
150
- scrollThreshold: 80,
151
143
  timestamps: this.$store.getters['prefs/get'](LOGS_TIME),
152
144
  wrap: this.$store.getters['prefs/get'](LOGS_WRAP),
153
145
  previous: false,
@@ -290,18 +282,6 @@ export default {
290
282
  container() {
291
283
  this.connect();
292
284
  },
293
-
294
- lines: {
295
- handler() {
296
- if (this.isFollowing) {
297
- this.$nextTick(() => {
298
- this.follow();
299
- });
300
- }
301
- },
302
- deep: true
303
- }
304
-
305
285
  },
306
286
 
307
287
  beforeUnmount() {
@@ -315,6 +295,35 @@ export default {
315
295
  },
316
296
 
317
297
  methods: {
298
+ onScrollToBottom() {
299
+ this.startFollowing();
300
+ },
301
+
302
+ onMouseWheel(ev) {
303
+ // On scroll up stop following
304
+ if (ev.deltaY < 0) {
305
+ this.stopFollowing();
306
+ }
307
+ },
308
+
309
+ onMouseDown() {
310
+ this.stopFollowing();
311
+ },
312
+
313
+ onMouseUp() {
314
+ const virtualList = this.$refs.virtualList;
315
+
316
+ if (!virtualList) {
317
+ return;
318
+ }
319
+
320
+ const isVirtualListScrolledToBottom = virtualList.getOffset() + virtualList.getClientSize() >= virtualList.getScrollSize();
321
+
322
+ if (isVirtualListScrolledToBottom) {
323
+ this.startFollowing();
324
+ }
325
+ },
326
+
318
327
  openContainerMenu() {
319
328
  this.isContainerMenuOpen = true;
320
329
  },
@@ -451,22 +460,21 @@ export default {
451
460
  if (maxLines && this.lines.length > maxLines) {
452
461
  this.lines = this.lines.slice(-maxLines);
453
462
  }
463
+
464
+ this.$nextTick(() => {
465
+ this.follow();
466
+ });
454
467
  }
455
468
  },
456
469
 
457
- updateFollowing: debounce(function() {
458
- const virtualList = this.$refs.virtualList;
459
-
460
- if (virtualList) {
461
- const scrollSize = virtualList.getScrollSize();
462
- const clientSize = virtualList.getClientSize();
463
- const offset = virtualList.getOffset();
464
-
465
- const distanceFromBottom = scrollSize - clientSize - offset;
470
+ stopFollowing() {
471
+ this.isFollowing = false;
472
+ },
466
473
 
467
- this.isFollowing = distanceFromBottom <= this.scrollThreshold;
468
- }
469
- }, 100),
474
+ startFollowing() {
475
+ this.isFollowing = true;
476
+ this.follow();
477
+ },
470
478
 
471
479
  parseRange(range) {
472
480
  range = `${ range }`.trim().toLowerCase();
@@ -537,9 +545,8 @@ export default {
537
545
  follow() {
538
546
  const virtualList = this.$refs.virtualList;
539
547
 
540
- if (virtualList) {
541
- virtualList.scrollToBottom();
542
- this.isFollowing = true;
548
+ if (virtualList && this.isFollowing) {
549
+ virtualList.scrollToOffset(virtualList.getScrollSize());
543
550
  }
544
551
  },
545
552
 
@@ -607,7 +614,7 @@ export default {
607
614
  :aria-label="t('wm.containerLogs.follow')"
608
615
  :aria-disabled="isFollowing"
609
616
  :disabled="isFollowing"
610
- @click="follow"
617
+ @click="startFollowing"
611
618
  >
612
619
  <t
613
620
  class="wm-btn-large"
@@ -762,7 +769,11 @@ export default {
762
769
  :data-component="logItem"
763
770
  direction="vertical"
764
771
  :keeps="200"
765
- @scroll="updateFollowing"
772
+ :bottom-threshold="200"
773
+ @tobottom="onScrollToBottom"
774
+ @wheel.passive="onMouseWheel"
775
+ @mousedown="onMouseDown"
776
+ @mouseup="onMouseUp"
766
777
  />
767
778
  <template v-if="!filtered.length">
768
779
  <div v-if="search">
@@ -67,13 +67,13 @@ export default {
67
67
  flex-direction: column;
68
68
  }
69
69
  .targets-list-list {
70
- overflow-y: scroll;
70
+ overflow-y: auto;
71
71
  }
72
72
  .link-main{
73
73
  word-spacing: 22px;
74
+ line-height: 17px; // To fit the icon size and make sure it doesnt resize
74
75
  }
75
76
  .link-icon {
76
- position: absolute;
77
77
  margin-left: -14px; // Remove the space of the icon to make it float to accomodate the underline
78
78
  display: none; // Make the icon disappear by default
79
79
  }
@@ -406,7 +406,10 @@ export default {
406
406
  :placeholder="t('fleet.clusterTargets.clusters.byName.placeholder')"
407
407
  @update:value="selectClusters"
408
408
  />
409
- <div class="mmt-6">
409
+ <div
410
+ v-if="!isView || (clusterSelectors && clusterSelectors.length > 0)"
411
+ class="mmt-6"
412
+ >
410
413
  <h4 class="m-0">
411
414
  {{ t('fleet.clusterTargets.clusters.byLabel.title') }}
412
415
  </h4>
@@ -427,6 +430,7 @@ export default {
427
430
  @update:value="updateMatchExpressions(i, $event, selector.key)"
428
431
  />
429
432
  <RcButton
433
+ v-if="!isView"
430
434
  size="small"
431
435
  variant="link"
432
436
  @click="removeMatchExpressions(selector.key)"
@@ -435,6 +439,7 @@ export default {
435
439
  </RcButton>
436
440
  </div>
437
441
  <RcButton
442
+ v-if="!isView"
438
443
  size="small"
439
444
  variant="secondary"
440
445
  class="mmt-4"