@rancher/shell 3.0.7 → 3.0.8-rc.2

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 (123) hide show
  1. package/assets/images/vendor/githubapp.svg +13 -0
  2. package/assets/styles/base/_typography.scss +1 -1
  3. package/assets/styles/global/_layout.scss +21 -35
  4. package/assets/styles/themes/_modern.scss +5 -5
  5. package/assets/translations/en-us.yaml +102 -17
  6. package/assets/translations/zh-hans.yaml +0 -4
  7. package/components/EmberPage.vue +1 -1
  8. package/components/Inactivity.vue +222 -106
  9. package/components/InstallHelmCharts.vue +2 -2
  10. package/components/Resource/Detail/CopyToClipboard.vue +1 -1
  11. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
  12. package/components/Resource/Detail/TitleBar/index.vue +10 -6
  13. package/components/ResourceDetail/index.vue +4 -1
  14. package/components/SortableTable/index.vue +18 -2
  15. package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
  16. package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
  17. package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
  18. package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
  19. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  20. package/components/fleet/FleetSecretSelector.vue +127 -0
  21. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  22. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  23. package/components/form/FileImageSelector.vue +13 -4
  24. package/components/form/FileSelector.vue +11 -2
  25. package/components/form/ResourceLabeledSelect.vue +1 -0
  26. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  27. package/components/nav/Header.vue +34 -13
  28. package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
  29. package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
  30. package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
  31. package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
  32. package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
  33. package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
  34. package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
  35. package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
  36. package/components/nav/WindowManager/constants.ts +23 -0
  37. package/components/nav/WindowManager/index.vue +61 -575
  38. package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
  39. package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
  40. package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
  41. package/components/templates/default.vue +4 -40
  42. package/components/templates/home.vue +31 -5
  43. package/config/product/auth.js +1 -0
  44. package/config/query-params.js +1 -0
  45. package/config/settings.ts +8 -1
  46. package/config/store.js +4 -2
  47. package/config/types.js +2 -0
  48. package/detail/pod.vue +1 -0
  49. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  50. package/directives/ui-context.ts +97 -0
  51. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  52. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  53. package/edit/auth/github-app-steps.vue +97 -0
  54. package/edit/auth/github-steps.vue +75 -0
  55. package/edit/auth/github.vue +94 -65
  56. package/edit/fleet.cattle.io.helmop.vue +51 -2
  57. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  58. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  59. package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
  60. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  61. package/initialize/install-directives.js +2 -0
  62. package/list/projectsecret.vue +1 -1
  63. package/machine-config/azure.vue +1 -1
  64. package/mixins/chart.js +1 -1
  65. package/models/__tests__/chart.test.ts +17 -9
  66. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  67. package/models/catalog.cattle.io.app.js +1 -1
  68. package/models/chart.js +3 -1
  69. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  70. package/models/management.cattle.io.authconfig.js +1 -0
  71. package/package.json +2 -2
  72. package/pages/auth/login.vue +5 -2
  73. package/pages/auth/verify.vue +1 -1
  74. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  75. package/pages/c/_cluster/apps/charts/chart.vue +2 -2
  76. package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
  77. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  78. package/pages/c/_cluster/settings/performance.vue +12 -25
  79. package/pages/home.vue +313 -12
  80. package/plugins/axios.js +2 -1
  81. package/plugins/dashboard-store/actions.js +1 -1
  82. package/plugins/dashboard-store/resource-class.js +17 -2
  83. package/plugins/steve/steve-pagination-utils.ts +2 -2
  84. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
  85. package/scripts/extension/publish +1 -1
  86. package/store/auth.js +8 -3
  87. package/store/aws.js +8 -6
  88. package/store/features.js +1 -0
  89. package/store/index.js +9 -3
  90. package/store/prefs.js +6 -0
  91. package/store/ui-context.ts +86 -0
  92. package/store/wm.ts +244 -0
  93. package/types/kube/kube-api.ts +2 -1
  94. package/types/rancher/index.d.ts +1 -0
  95. package/types/resources/settings.d.ts +29 -7
  96. package/types/shell/index.d.ts +59 -0
  97. package/types/window-manager.ts +22 -0
  98. package/utils/__tests__/cluster.test.ts +379 -1
  99. package/utils/cluster.js +157 -3
  100. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  101. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  102. package/utils/dynamic-content/__tests__/info.test.ts +263 -0
  103. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  104. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  105. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  106. package/utils/dynamic-content/config.ts +55 -0
  107. package/utils/dynamic-content/index.ts +273 -0
  108. package/utils/dynamic-content/info.ts +219 -0
  109. package/utils/dynamic-content/new-release.ts +126 -0
  110. package/utils/dynamic-content/support-notice.ts +169 -0
  111. package/utils/dynamic-content/types.d.ts +101 -0
  112. package/utils/dynamic-content/util.ts +122 -0
  113. package/utils/dynamic-importer.js +2 -2
  114. package/utils/inactivity.ts +104 -0
  115. package/utils/pagination-utils.ts +19 -4
  116. package/utils/release-notes.ts +1 -1
  117. package/assets/images/icons/document.svg +0 -3
  118. package/store/wm.js +0 -95
  119. /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
  120. /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
  121. /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
  122. /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
  123. /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
@@ -3,11 +3,14 @@ import ModalWithCard from '@shell/components/ModalWithCard';
3
3
  import { Banner } from '@components/Banner';
4
4
  import PercentageBar from '@shell/components/PercentageBar.vue';
5
5
  import throttle from 'lodash/throttle';
6
- import { MANAGEMENT } from '@shell/config/types';
7
- import { DEFAULT_PERF_SETTING, SETTING } from '@shell/config/settings';
6
+ import Inactivity from '@shell/utils/inactivity';
7
+ import { MANAGEMENT, EXT, NORMAN } from '@shell/config/types';
8
+ import { SETTING } from '@shell/config/settings';
8
9
 
9
10
  let globalId;
10
11
 
12
+ const MODAL_VISIBILITY_CHECK_DELAY_SECONDS = 10;
13
+
11
14
  export default {
12
15
  name: 'Inactivity',
13
16
  components: {
@@ -15,64 +18,151 @@ export default {
15
18
  },
16
19
  data() {
17
20
  return {
18
- enabled: null,
19
- isOpen: false,
20
- isInactive: false,
21
- showModalAfter: null,
22
- inactivityTimeoutId: null,
23
- courtesyTimer: null,
24
- courtesyTimerId: null,
25
- courtesyCountdown: null,
26
- trackInactivity: throttle(this._trackInactivity, 1000),
27
- id: null,
21
+ sessionTokenName: null,
22
+ tokens: [],
23
+ isUserActive: false,
24
+ userActivityIsoDate: '',
25
+ modalVisibilityCheckRan: false,
26
+ isOpen: false,
27
+ showModalAfter: null,
28
+ expiresAt: null,
29
+ inactivityTimeoutId: null,
30
+ courtesyTimer: null,
31
+ courtesyTimerId: null,
32
+ courtesyCountdown: null,
33
+ trackInactivity: throttle(this._trackInactivity, 1000),
34
+ id: null
28
35
  };
29
36
  },
30
- async mounted() {
31
- // Info: normally, this is done in the fetch hook but for some reasons while awaiting for things that will take a while, it won't be ready by the time mounted() is called, pending for investigation.
32
- let settings;
33
-
34
- try {
35
- const settingsString = await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.UI_PERFORMANCE });
37
+ beforeUnmount() {
38
+ this.removeEventListeners();
39
+ this.clearAllTimeouts();
40
+ },
41
+ computed: {
42
+ timerPercentageLeft() {
43
+ return Math.floor((this.courtesyCountdown / this.courtesyTimer ) * 100);
44
+ },
45
+ colorStops() {
46
+ return {
47
+ 0: '--info', 30: '--info', 70: '--info'
48
+ };
49
+ },
50
+ userSessionTtlIdleSetting() {
51
+ return this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.AUTH_USER_SESSION_IDLE_TTL_MINUTES);
52
+ },
53
+ userSessionTtlSetting() {
54
+ return this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.AUTH_USER_SESSION_TTL_MINUTES);
55
+ },
56
+ userActivityResource() {
57
+ return this.$store.getters['management/byId'](EXT.USER_ACTIVITY, this.sessionTokenName);
58
+ },
59
+ ttlIdleValue() {
60
+ return parseInt(this.userSessionTtlIdleSetting?.value || 0);
61
+ },
62
+ ttlValue() {
63
+ return parseInt(this.userSessionTtlSetting?.value || 0);
64
+ },
65
+ isFeatureEnabled() {
66
+ return this.ttlIdleValue < this.ttlValue;
67
+ },
68
+ userActivityExpiresAt() {
69
+ return this.userActivityResource?.status?.expiresAt || '';
70
+ },
71
+ watcherData() {
72
+ return {
73
+ userActivityExpiresAt: this.userActivityExpiresAt,
74
+ sessionTokenName: this.sessionTokenName,
75
+ isFeatureEnabled: this.isFeatureEnabled
76
+ };
77
+ }
78
+ },
36
79
 
37
- settings = settingsString?.value ? JSON.parse(settingsString.value) : DEFAULT_PERF_SETTING;
38
- } catch { }
80
+ watch: {
81
+ // every time the Idle setting changes, we need to fetch the updated userActivity
82
+ async ttlIdleValue() {
83
+ await Inactivity.getUserActivity(this.$store, this.sessionTokenName);
84
+ },
85
+ watcherData: {
86
+ async handler(neu, old) {
87
+ if (!old?.isFeatureEnabled && neu?.isFeatureEnabled) {
88
+ const tokenName = Inactivity.getSessionTokenName();
39
89
 
40
- if (!settings || !settings?.inactivity || !settings?.inactivity.enabled) {
41
- return;
42
- }
90
+ if (tokenName) {
91
+ this.sessionTokenName = tokenName;
92
+ }
43
93
 
44
- this.enabled = settings?.inactivity?.enabled || false;
94
+ await this.initializeInactivityData();
95
+ }
45
96
 
46
- // Total amount of time before the user's session is lost
47
- const thresholdToSeconds = settings?.inactivity?.threshold * 60;
97
+ const currDate = Date.now();
98
+ const endDate = new Date(neu.userActivityExpiresAt || '0001-01-01 00:00:00 +0000 UTC').getTime();
48
99
 
49
- // Amount of time the user sees the inactivity warning
50
- this.courtesyTimer = Math.floor(thresholdToSeconds * 0.1);
51
- this.courtesyTimer = Math.min(this.courtesyTimer, 60 * 5); // Never show the modal more than 5 minutes
52
- // Amount of time before the user sees the inactivity warning
53
- // Note - time before warning is shown + time warning is shown = settings threshold (total amount of time)
54
- this.showModalAfter = thresholdToSeconds - this.courtesyTimer;
100
+ if (endDate > currDate && neu?.sessionTokenName && neu?.isFeatureEnabled) {
101
+ // feature is considered as enabled
102
+ // make sure we always clean up first so that we don't get duplicate timers running
103
+ this.stopInactivity();
55
104
 
56
- console.debug(`Inactivity modal will show after ${ this.showModalAfter / 60 }(m) and be shown for ${ this.courtesyTimer / 60 }(m)`); // eslint-disable-line no-console
105
+ // resets inactivity data and timers, starting the inactivity again with the proper data
106
+ this.resetInactivityDataAndTimers(this.userActivityResource);
57
107
 
58
- this.courtesyCountdown = this.courtesyTimer;
108
+ // add event listeners for UI interaction
109
+ this.addIdleListeners();
110
+ }
59
111
 
60
- if (settings?.inactivity.enabled) {
61
- this.trackInactivity();
62
- this.addIdleListeners();
112
+ if (!neu?.isFeatureEnabled) {
113
+ this.stopInactivity();
114
+ }
115
+ },
116
+ immediate: true,
117
+ deep: true
63
118
  }
64
119
  },
65
- beforeUnmount() {
66
- this.removeEventListener();
67
- this.clearAllTimeouts();
68
- },
120
+
69
121
  methods: {
122
+ async initializeInactivityData() {
123
+ const canListUserAct = this.$store.getters[`management/canList`](EXT.USER_ACTIVITY);
124
+ const canListTokens = this.$store.getters[`rancher/canList`](NORMAN.TOKEN);
125
+
126
+ if (canListUserAct && canListTokens) {
127
+ const tokens = await this.$store.dispatch('rancher/findAll', { type: NORMAN.TOKEN, opt: { watch: false } });
128
+
129
+ this.tokens = tokens;
130
+
131
+ // handle the fetching/storage of session token name
132
+ if (!this.sessionTokenName) {
133
+ const sessionToken = this.tokens.find((token) => {
134
+ return token.description === 'UI session' && token.current;
135
+ });
136
+
137
+ if (sessionToken?.name) {
138
+ this.sessionTokenName = sessionToken.name;
139
+ Inactivity.setSessionTokenName(sessionToken.name);
140
+ }
141
+ }
142
+
143
+ // get the latest userActivity data so that get reactivity on all this logic
144
+ const userActivityData = await Inactivity.getUserActivity(this.$store, this.sessionTokenName, false);
145
+
146
+ const expiresAt = userActivityData?.status?.expiresAt;
147
+ const currDate = Date.now();
148
+ const endDate = new Date(expiresAt).getTime();
149
+
150
+ // If expiresAt isn't initialised yet '0001-01-01 00:00:00 +0000 UTC' || '', or just passed the 'now' date
151
+ // We need to update/initialise the UserActivity resource
152
+ if ((currDate > endDate) || !expiresAt) {
153
+ const updatedData = await Inactivity.updateUserActivity(this.userActivityResource, this.sessionTokenName, new Date().toISOString());
154
+
155
+ this.expiresAt = updatedData?.status?.expiresAt;
156
+ } else if (expiresAt) {
157
+ this.expiresAt = expiresAt;
158
+ }
159
+ }
160
+ },
70
161
  _trackInactivity() {
71
- if (this.isInactive || this.isOpen || !this.showModalAfter) {
162
+ if (this.isOpen || !this.showModalAfter) {
72
163
  return;
73
164
  }
74
165
 
75
- this.clearAllTimeouts();
76
166
  const endTime = Date.now() + this.showModalAfter * 1000;
77
167
 
78
168
  this.id = endTime;
@@ -89,6 +179,18 @@ export default {
89
179
  this.isOpen = true;
90
180
  this.startCountdown();
91
181
  } else {
182
+ // When we have X seconds to go until we display the modal, check for activity on the backend flag
183
+ // it may have come from another tab in the same browser
184
+ if (now >= endTime - (MODAL_VISIBILITY_CHECK_DELAY_SECONDS * 1000) && !this.modalVisibilityCheckRan) {
185
+ this.modalVisibilityCheckRan = true;
186
+
187
+ if (this.isUserActive) {
188
+ this.resetUserActivity();
189
+ } else {
190
+ this.checkBackendInactivity(this.expiresAt);
191
+ }
192
+ }
193
+
92
194
  this.inactivityTimeoutId = setTimeout(checkInactivityTimer, 1000);
93
195
  }
94
196
  };
@@ -98,13 +200,16 @@ export default {
98
200
  startCountdown() {
99
201
  const endTime = Date.now() + (this.courtesyCountdown * 1000);
100
202
 
101
- const checkCountdown = () => {
203
+ const checkCountdown = async() => {
102
204
  const now = Date.now();
103
205
 
104
206
  if (now >= endTime) {
105
- this.isInactive = true;
106
- this.unsubscribe();
107
207
  this.clearAllTimeouts();
208
+ const isUserActive = await this.checkBackendInactivity(this.expiresAt);
209
+
210
+ if (!isUserActive) {
211
+ return this.$store.dispatch('auth/logout', { sessionIdle: true });
212
+ }
108
213
  } else {
109
214
  this.courtesyCountdown = Math.floor((endTime - now) / 1000);
110
215
  this.courtesyTimerId = setTimeout(checkCountdown, 1000);
@@ -113,63 +218,85 @@ export default {
113
218
 
114
219
  checkCountdown();
115
220
  },
221
+ async checkBackendInactivity(currExpiresAt) {
222
+ let isUserActive = false;
223
+ const userActivityData = await Inactivity.getUserActivity(this.$store, this.sessionTokenName);
224
+
225
+ // this means that something updated the backend expiresAt, which means we must now reset the timers and adjust for new data
226
+ if (userActivityData?.status?.expiresAt && (userActivityData?.status?.expiresAt !== currExpiresAt)) {
227
+ isUserActive = true;
228
+ this.resetInactivityDataAndTimers(userActivityData);
229
+ }
230
+
231
+ return isUserActive;
232
+ },
233
+ setUserAsActive() {
234
+ this.isUserActive = true;
235
+ this.userActivityIsoDate = new Date().toISOString();
236
+ },
116
237
  addIdleListeners() {
117
- document.addEventListener('mousemove', this.trackInactivity);
118
- document.addEventListener('mousedown', this.trackInactivity);
119
- document.addEventListener('keypress', this.trackInactivity);
120
- document.addEventListener('touchmove', this.trackInactivity);
121
- document.addEventListener('visibilitychange', this.trackInactivity);
122
- },
123
- removeEventListener() {
124
- document.removeEventListener('mousemove', this.trackInactivity);
125
- document.removeEventListener('mousedown', this.trackInactivity);
126
- document.removeEventListener('keypress', this.trackInactivity);
127
- document.removeEventListener('touchmove', this.trackInactivity);
128
- document.removeEventListener('visibilitychange', this.trackInactivity);
129
- },
130
-
131
- resume() {
132
- this.isInactive = false;
238
+ document.addEventListener('mousemove', this.setUserAsActive);
239
+ document.addEventListener('mousedown', this.setUserAsActive);
240
+ document.addEventListener('keypress', this.setUserAsActive);
241
+ document.addEventListener('touchmove', this.setUserAsActive);
242
+ document.addEventListener('visibilitychange', this.setUserAsActive);
243
+ },
244
+ removeEventListeners() {
245
+ document.removeEventListener('mousemove', this.setUserAsActive);
246
+ document.removeEventListener('mousedown', this.setUserAsActive);
247
+ document.removeEventListener('keypress', this.setUserAsActive);
248
+ document.removeEventListener('touchmove', this.setUserAsActive);
249
+ document.removeEventListener('visibilitychange', this.setUserAsActive);
250
+ },
251
+ async resetUserActivity(useCurrDate = false) {
252
+ let seenAt;
253
+
254
+ if (useCurrDate) {
255
+ seenAt = new Date().toISOString();
256
+ } else {
257
+ seenAt = this.userActivityIsoDate;
258
+ }
259
+
260
+ const userActivityData = await Inactivity.updateUserActivity(this.userActivityResource, this.sessionTokenName, seenAt);
261
+
262
+ this.resetInactivityDataAndTimers(userActivityData);
263
+ },
264
+ stopInactivity() {
265
+ this.modalVisibilityCheckRan = false;
133
266
  this.isOpen = false;
134
- this.courtesyCountdown = this.courtesyTimer;
267
+ this.isUserActive = false;
268
+ this.userActivityIsoDate = '';
269
+
270
+ this.removeEventListeners();
135
271
  this.clearAllTimeouts();
136
272
  },
273
+ resetInactivityDataAndTimers(userActivityData) {
274
+ const data = Inactivity.parseTTLData(userActivityData);
137
275
 
138
- refresh() {
139
- window.location.reload();
140
- },
276
+ this.modalVisibilityCheckRan = false;
277
+ this.isOpen = false;
278
+ this.isUserActive = false;
279
+ this.userActivityIsoDate = '';
141
280
 
142
- unsubscribe() {
143
- console.debug('Unsubscribing from all websocket events'); // eslint-disable-line no-console
144
- this.$store.dispatch('unsubscribe');
281
+ this.courtesyTimer = data.courtesyTimer;
282
+ this.courtesyCountdown = data.courtesyCountdown;
283
+ this.showModalAfter = data.showModalAfter;
284
+ this.sessionTokenName = data.sessionTokenName;
285
+ this.expiresAt = data.expiresAt;
286
+
287
+ const shownAfter = Math.round(((data.showModalAfter || 0) / 60) * 100) / 100;
288
+ const shownFor = Math.round(((data.courtesyTimer || 0) / 60) * 100) / 100;
289
+
290
+ console.debug(`UI inactivity modal (backend-based) will show after ${ shownAfter }(m) and be shown for ${ shownFor }(m)`); // eslint-disable-line no-console
291
+
292
+ this.clearAllTimeouts();
293
+ this.trackInactivity();
145
294
  },
146
295
  clearAllTimeouts() {
147
296
  clearTimeout(this.inactivityTimeoutId);
148
297
  clearTimeout(this.courtesyTimerId);
149
298
  }
150
-
151
299
  },
152
- computed: {
153
- isInactiveTexts() {
154
- return this.isInactive ? {
155
- title: this.t('inactivity.titleExpired'),
156
- banner: this.t('inactivity.bannerExpired'),
157
- content: this.t('inactivity.contentExpired'),
158
- } : {
159
- title: this.t('inactivity.title'),
160
- banner: this.t('inactivity.banner'),
161
- content: this.t('inactivity.content'),
162
- };
163
- },
164
- timerPercentageLeft() {
165
- return Math.floor((this.courtesyCountdown / this.courtesyTimer ) * 100);
166
- },
167
- colorStops() {
168
- return {
169
- 0: '--info', 30: '--info', 70: '--info'
170
- };
171
- },
172
- }
173
300
  };
174
301
  </script>
175
302
 
@@ -179,24 +306,22 @@ export default {
179
306
  ref="inactivityModal"
180
307
  name="inactivityModal"
181
308
  save-text="Continue"
182
- @finish="resume"
183
309
  >
184
310
  <template #title>
185
- {{ isInactiveTexts.title }}
311
+ {{ t('inactivity.title') }}
186
312
  </template>
187
313
  <span>{{ courtesyCountdown }}</span>
188
314
 
189
315
  <template #content>
190
316
  <Banner color="info">
191
- {{ isInactiveTexts.banner }}
317
+ {{ t('inactivity.banner') }}
192
318
  </Banner>
193
319
 
194
320
  <p>
195
- {{ isInactiveTexts.content }}
321
+ {{ t('inactivity.content') }}
196
322
  </p>
197
323
 
198
324
  <PercentageBar
199
- v-if="!isInactive"
200
325
  class="mt-20"
201
326
  :modelValue="timerPercentageLeft"
202
327
  :color-stops="colorStops"
@@ -208,20 +333,11 @@ export default {
208
333
  >
209
334
  <div class="card-actions">
210
335
  <button
211
- v-if="!isInactive"
212
336
  class="btn role-tertiary bg-primary"
213
- @click.prevent="resume"
337
+ @click.prevent="resetUserActivity(true)"
214
338
  >
215
339
  <t k="inactivity.cta" />
216
340
  </button>
217
-
218
- <button
219
- v-if="isInactive"
220
- class="btn role-tertiary bg-primary"
221
- @click.prevent="refresh"
222
- >
223
- <t k="inactivity.ctaExpired" />
224
- </button>
225
341
  </div>
226
342
  </template>
227
343
  </ModalWithCard>
@@ -117,7 +117,7 @@ export default {
117
117
 
118
118
  // server url and project ids are used in global values
119
119
  try {
120
- this.serverUrlSetting = await this.$store.dispatch(`${ this.store }/find`, {
120
+ this.serverUrlSetting = await this.$store.dispatch(`management/find`, {
121
121
  type: MANAGEMENT.SETTING,
122
122
  id: SETTING.SERVER_URL,
123
123
  });
@@ -125,7 +125,7 @@ export default {
125
125
  this.$store.dispatch('growl/fromError', { err: e });
126
126
  }
127
127
 
128
- await this.$store.dispatch(`${ this.store }/findAll`, { type: MANAGEMENT.PROJECT });
128
+ await this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.PROJECT });
129
129
  }
130
130
  },
131
131
 
@@ -5,7 +5,7 @@ import { ref } from 'vue';
5
5
  import { useStore } from 'vuex';
6
6
 
7
7
  export interface Props {
8
- value: string;
8
+ value: string;
9
9
  }
10
10
 
11
11
  const props = defineProps<Props>();
@@ -3,8 +3,6 @@ import TitleBar from '@shell/components/Resource/Detail/TitleBar/index.vue';
3
3
  import ActionMenu from '@shell/components/ActionMenuShell.vue';
4
4
  import { createStore } from 'vuex';
5
5
 
6
- jest.mock(`@shell/assets/images/icons/document.svg`, () => `@shell/assets/images/icons/document.svg`);
7
-
8
6
  describe('component: TitleBar/index', () => {
9
7
  const resourceTypeLabel = 'RESOURCE_TYPE_LABEL';
10
8
  const resourceTo = 'RESOURCE_TO';
@@ -32,8 +32,6 @@ export interface TitleBarProps {
32
32
  actionMenuResource?: any;
33
33
  onShowConfiguration?: (returnFocusSelector: string) => void;
34
34
  }
35
-
36
- const showConfigurationIcon = require(`@shell/assets/images/icons/document.svg`);
37
35
  </script>
38
36
 
39
37
  <script setup lang="ts">
@@ -84,6 +82,7 @@ watch(
84
82
  </span>
85
83
  <BadgeState
86
84
  v-if="badge"
85
+ v-ui-context="{ store: store, icon: 'icon-folder', hookable: true, value: resource, tag: '__details-state', description: 'Details' }"
87
86
  class="badge-state"
88
87
  :color="badge.color"
89
88
  :label="badge.label"
@@ -99,11 +98,10 @@ watch(
99
98
  :aria-label="i18n.t('component.resource.detail.titleBar.ariaLabel.showConfiguration', { resource: resourceName })"
100
99
  @click="() => emit('show-configuration', showConfigurationReturnFocusSelector)"
101
100
  >
102
- <img
103
- :src="showConfigurationIcon"
104
- class="mmr-3"
101
+ <i
102
+ class="icon icon-document"
105
103
  aria-hidden="true"
106
- >
104
+ />
107
105
  {{ i18n.t('component.resource.detail.titleBar.showConfiguration') }}
108
106
  </RcButton>
109
107
  <ActionMenu
@@ -139,6 +137,12 @@ watch(
139
137
  position: relative;
140
138
  }
141
139
 
140
+ .icon-document {
141
+ width: 15px;
142
+ font-size: 16px;
143
+ margin-right: 10px;
144
+ }
145
+
142
146
  .show-configuration {
143
147
  margin-left: 16px;
144
148
  }
@@ -430,6 +430,7 @@ export default {
430
430
  :is="showComponent"
431
431
  v-else-if="isFullPageOverride"
432
432
  v-model:value="value"
433
+ v-ui-context="{ icon: 'icon-folder', value: value.name, tag: value.kind?.toLowerCase(), description: value.kind }"
433
434
  v-bind="$data"
434
435
  :done-params="doneParams"
435
436
  :done-route="doneRoute"
@@ -446,6 +447,7 @@ export default {
446
447
  <div v-else>
447
448
  <Masthead
448
449
  v-if="showMasthead"
450
+ v-ui-context="{ icon: 'icon-folder', value: liveModel.name, tag: liveModel.kind?.toLowerCase(), description: liveModel.kind }"
449
451
  :resource="resourceType"
450
452
  :value="liveModel"
451
453
  :mode="mode"
@@ -481,7 +483,7 @@ export default {
481
483
  </div>
482
484
 
483
485
  <ResourceYaml
484
- v-else-if="isYaml"
486
+ v-if="isYaml"
485
487
  ref="resourceyaml"
486
488
  :value="value"
487
489
  :mode="mode"
@@ -499,6 +501,7 @@ export default {
499
501
  v-else
500
502
  ref="comp"
501
503
  v-model:value="value"
504
+ v-ui-context="{ icon: 'icon-folder', value: value.name, tag: value.kind?.toLowerCase(), description: value.kind }"
502
505
  v-bind="$data"
503
506
  :done-params="doneParams"
504
507
  :done-route="doneRoute"
@@ -1090,7 +1090,7 @@ export default {
1090
1090
  <div
1091
1091
  v-if="showHeaderRow"
1092
1092
  class="fixed-header-actions"
1093
- :class="{button: !!$slots['header-button'], 'advanced-filtering': hasAdvancedFiltering}"
1093
+ :class="{button: !!$slots['header-button'], 'with-sub-header': !!$slots['sub-header-row'], 'advanced-filtering': hasAdvancedFiltering}"
1094
1094
  >
1095
1095
  <div
1096
1096
  :class="bulkActionsClass"
@@ -1296,6 +1296,12 @@ export default {
1296
1296
  <slot name="header-button" />
1297
1297
  </div>
1298
1298
  </div>
1299
+ <div
1300
+ v-if="!!$slots['sub-header-row']"
1301
+ class="sub-header-row"
1302
+ >
1303
+ <slot name="sub-header-row" />
1304
+ </div>
1299
1305
  </div>
1300
1306
  <table
1301
1307
  ref="table"
@@ -1468,6 +1474,7 @@ export default {
1468
1474
  <td
1469
1475
  v-show="!hasAdvancedFiltering || (hasAdvancedFiltering && col.col.isColVisible)"
1470
1476
  :key="col.col.name"
1477
+ v-ui-context="col.col.name === 'state' ? { icon: 'icon-folder', hookable: true, value: row.row, tag: '__sortable-table-row', description: 'Row' } : undefined"
1471
1478
  :data-title="col.col.label"
1472
1479
  :data-testid="`sortable-cell-${ i }-${ j }`"
1473
1480
  :align="col.col.align || 'left'"
@@ -2047,8 +2054,17 @@ export default {
2047
2054
  grid-template-columns: [bulk] auto [middle] min-content [search] minmax(min-content, 350px);
2048
2055
  }
2049
2056
 
2057
+ $header-padding: 20px;
2058
+ .sub-header-row {
2059
+ padding: 0 0 $header-padding / 2 0;
2060
+ }
2061
+
2050
2062
  .fixed-header-actions {
2051
- padding: 0 0 20px 0;
2063
+ padding: 0 0 $header-padding 0;
2064
+ &.with-sub-header {
2065
+ padding: 0 0 $header-padding / 4 0;
2066
+ }
2067
+
2052
2068
  width: 100%;
2053
2069
  z-index: z-index('fixedTableHeader');
2054
2070
  background: transparent;
@@ -10,7 +10,7 @@ import AsyncButton from '@shell/components/AsyncButton';
10
10
  import Select from '@shell/components/form/Select';
11
11
  import VirtualList from 'vue3-virtual-scroll-list';
12
12
  import LogItem from '@shell/components/LogItem';
13
- import ContainerLogsActions from '@shell/components/nav/WindowManager/ContainerLogsActions.vue';
13
+ import ContainerLogsActions from '@shell/components/Window/ContainerLogsActions.vue';
14
14
  import { shallowRef } from 'vue';
15
15
  import { useStore } from 'vuex';
16
16
  import { debounce } from 'lodash';
@@ -49,6 +49,7 @@ defineEmits([
49
49
  <rc-dropdown-item-select
50
50
  :model-value="range"
51
51
  :options="rangeOptions"
52
+ :label="t('wm.containerLogs.range.label')"
52
53
  @select="$emit('toggleRange', $event)"
53
54
  />
54
55
  <rc-dropdown-item-checkbox
@@ -1,6 +1,6 @@
1
1
  import { nextTick } from 'vue';
2
2
  import { shallowMount } from '@vue/test-utils';
3
- import ContainerLogs from '@shell/components/nav/WindowManager/ContainerLogs.vue';
3
+ import ContainerLogs from '@shell/components/Window/ContainerLogs.vue';
4
4
  import { base64Encode } from '@shell/utils/crypto';
5
5
  import { Buffer } from 'buffer';
6
6
  import { addEventListener } from '@shell/utils/socket';
@@ -1,9 +1,9 @@
1
1
  import { flushPromises, mount, Wrapper } from '@vue/test-utils';
2
- import ContainerShell from '@shell/components/nav/WindowManager/ContainerShell.vue';
2
+ import ContainerShell from '@shell/components/Window/ContainerShell.vue';
3
3
  import Socket, {
4
4
  addEventListener, EVENT_CONNECTED, EVENT_CONNECTING, EVENT_DISCONNECTED, EVENT_MESSAGE, EVENT_CONNECT_ERROR
5
5
  } from '@shell/utils/socket';
6
- import Window from '@shell/components/nav/WindowManager/Window.vue';
6
+ import Window from '@shell/components/Window/Window.vue';
7
7
 
8
8
  jest.mock('@shell/utils/socket');
9
9
  jest.mock('@shell/utils/crypto', () => {