@rancher/shell 3.0.6 → 3.0.8-rc.1

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 (146) hide show
  1. package/assets/images/pl/dark/rancher-logo.svg +131 -44
  2. package/assets/images/pl/rancher-logo.svg +120 -44
  3. package/assets/images/vendor/githubapp.svg +13 -0
  4. package/assets/styles/base/_basic.scss +2 -2
  5. package/assets/styles/base/_color-classic.scss +51 -0
  6. package/assets/styles/base/_color.scss +3 -3
  7. package/assets/styles/base/_mixins.scss +1 -1
  8. package/assets/styles/base/_typography.scss +1 -1
  9. package/assets/styles/base/_variables-classic.scss +47 -0
  10. package/assets/styles/global/_button.scss +49 -17
  11. package/assets/styles/global/_form.scss +1 -1
  12. package/assets/styles/themes/_dark.scss +4 -0
  13. package/assets/styles/themes/_light.scss +3 -69
  14. package/assets/styles/themes/_modern.scss +194 -50
  15. package/assets/styles/vendor/vue-select.scss +1 -2
  16. package/assets/translations/en-us.yaml +124 -32
  17. package/assets/translations/zh-hans.yaml +0 -4
  18. package/components/ClusterIconMenu.vue +1 -1
  19. package/components/ClusterProviderIcon.vue +1 -1
  20. package/components/CodeMirror.vue +1 -1
  21. package/components/IconOrSvg.vue +40 -29
  22. package/components/Inactivity.vue +222 -106
  23. package/components/InstallHelmCharts.vue +2 -2
  24. package/components/ResourceDetail/index.vue +2 -1
  25. package/components/SortableTable/index.vue +17 -2
  26. package/components/SortableTable/sorting.js +3 -1
  27. package/components/Tabbed/index.vue +5 -5
  28. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  29. package/components/fleet/FleetSecretSelector.vue +127 -0
  30. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  31. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  32. package/components/form/FileImageSelector.vue +13 -4
  33. package/components/form/FileSelector.vue +11 -2
  34. package/components/form/ResourceLabeledSelect.vue +1 -0
  35. package/components/form/ResourceTabs/index.vue +37 -18
  36. package/components/form/SecretSelector.vue +6 -2
  37. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  38. package/components/nav/Group.vue +29 -9
  39. package/components/nav/Header.vue +7 -8
  40. package/components/nav/NamespaceFilter.vue +1 -1
  41. package/components/nav/TopLevelMenu.helper.ts +47 -20
  42. package/components/nav/TopLevelMenu.vue +44 -14
  43. package/components/nav/Type.vue +0 -5
  44. package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
  45. package/config/pagination-table-headers.js +10 -2
  46. package/config/product/auth.js +1 -0
  47. package/config/product/explorer.js +4 -3
  48. package/config/query-params.js +1 -0
  49. package/config/settings.ts +8 -1
  50. package/config/table-headers.js +9 -0
  51. package/config/types.js +2 -0
  52. package/core/plugin.ts +18 -6
  53. package/core/types.ts +8 -0
  54. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  55. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  56. package/dialog/InstallExtensionDialog.vue +71 -45
  57. package/dialog/UninstallExtensionDialog.vue +2 -1
  58. package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
  59. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  60. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  61. package/edit/auth/github-app-steps.vue +97 -0
  62. package/edit/auth/github-steps.vue +75 -0
  63. package/edit/auth/github.vue +94 -65
  64. package/edit/auth/oidc.vue +86 -16
  65. package/edit/fleet.cattle.io.helmop.vue +51 -2
  66. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  67. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  68. package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
  69. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  70. package/list/projectsecret.vue +1 -1
  71. package/machine-config/azure.vue +1 -1
  72. package/mixins/__tests__/chart.test.ts +1 -1
  73. package/mixins/chart.js +2 -2
  74. package/models/__tests__/chart.test.ts +17 -9
  75. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  76. package/models/catalog.cattle.io.app.js +1 -1
  77. package/models/chart.js +3 -1
  78. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  79. package/models/event.js +7 -0
  80. package/models/management.cattle.io.authconfig.js +1 -0
  81. package/models/provisioning.cattle.io.cluster.js +9 -0
  82. package/package.json +2 -2
  83. package/pages/auth/login.vue +5 -2
  84. package/pages/auth/verify.vue +1 -1
  85. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  86. package/pages/c/_cluster/apps/charts/chart.vue +2 -2
  87. package/pages/c/_cluster/explorer/EventsTable.vue +92 -9
  88. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  89. package/pages/c/_cluster/settings/performance.vue +13 -26
  90. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
  91. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
  92. package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
  93. package/pages/c/_cluster/uiplugins/index.vue +110 -94
  94. package/pages/home.vue +313 -12
  95. package/plugins/__tests__/subscribe.events.test.ts +194 -0
  96. package/plugins/axios.js +2 -1
  97. package/plugins/dashboard-store/actions.js +4 -1
  98. package/plugins/dashboard-store/getters.js +1 -1
  99. package/plugins/dashboard-store/resource-class.js +20 -5
  100. package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
  101. package/plugins/steve/index.js +18 -10
  102. package/plugins/steve/mutations.js +2 -2
  103. package/plugins/steve/resourceWatcher.js +2 -2
  104. package/plugins/steve/steve-pagination-utils.ts +12 -9
  105. package/plugins/steve/subscribe.js +113 -85
  106. package/plugins/subscribe-events.ts +211 -0
  107. package/rancher-components/BadgeState/BadgeState.vue +8 -6
  108. package/rancher-components/Banner/Banner.vue +2 -1
  109. package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
  110. package/rancher-components/Form/Radio/RadioButton.vue +3 -3
  111. package/scripts/extension/publish +1 -1
  112. package/store/auth.js +8 -3
  113. package/store/aws.js +8 -6
  114. package/store/features.js +1 -0
  115. package/store/index.js +21 -25
  116. package/store/prefs.js +6 -0
  117. package/types/extension-manager.ts +8 -1
  118. package/types/kube/kube-api.ts +2 -1
  119. package/types/rancher/index.d.ts +1 -0
  120. package/types/resources/settings.d.ts +52 -23
  121. package/types/shell/index.d.ts +412 -336
  122. package/types/store/subscribe-events.types.ts +70 -0
  123. package/types/store/subscribe.types.ts +6 -22
  124. package/utils/__tests__/cluster.test.ts +379 -1
  125. package/utils/cluster.js +157 -3
  126. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  127. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  128. package/utils/dynamic-content/__tests__/info.test.ts +263 -0
  129. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  130. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  131. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  132. package/utils/dynamic-content/config.ts +55 -0
  133. package/utils/dynamic-content/index.ts +273 -0
  134. package/utils/dynamic-content/info.ts +219 -0
  135. package/utils/dynamic-content/new-release.ts +126 -0
  136. package/utils/dynamic-content/support-notice.ts +169 -0
  137. package/utils/dynamic-content/types.d.ts +101 -0
  138. package/utils/dynamic-content/util.ts +122 -0
  139. package/utils/inactivity.ts +104 -0
  140. package/utils/pagination-utils.ts +105 -31
  141. package/utils/pagination-wrapper.ts +6 -8
  142. package/utils/release-notes.ts +1 -1
  143. package/utils/sort.js +5 -0
  144. package/utils/unit-tests/pagination-utils.spec.ts +283 -0
  145. package/utils/validators/formRules/__tests__/index.test.ts +7 -0
  146. package/utils/validators/formRules/index.ts +2 -2
@@ -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
 
@@ -181,6 +181,7 @@ export default {
181
181
  if (e.status === 404 || e.status === 403) {
182
182
  store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceIdNotFound', { resource: resourceType, fqid }, true)));
183
183
  }
184
+ console.debug(`Could not find '${ resourceType }' with id '${ id }''`, e); // eslint-disable-line no-console
184
185
  liveModel = {};
185
186
  notFound = fqid;
186
187
  }
@@ -480,7 +481,7 @@ export default {
480
481
  </div>
481
482
 
482
483
  <ResourceYaml
483
- v-else-if="isYaml"
484
+ v-if="isYaml"
484
485
  ref="resourceyaml"
485
486
  :value="value"
486
487
  :mode="mode"
@@ -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"
@@ -2047,8 +2053,17 @@ export default {
2047
2053
  grid-template-columns: [bulk] auto [middle] min-content [search] minmax(min-content, 350px);
2048
2054
  }
2049
2055
 
2056
+ $header-padding: 20px;
2057
+ .sub-header-row {
2058
+ padding: 0 0 $header-padding / 2 0;
2059
+ }
2060
+
2050
2061
  .fixed-header-actions {
2051
- padding: 0 0 20px 0;
2062
+ padding: 0 0 $header-padding 0;
2063
+ &.with-sub-header {
2064
+ padding: 0 0 $header-padding / 4 0;
2065
+ }
2066
+
2052
2067
  width: 100%;
2053
2068
  z-index: z-index('fixedTableHeader');
2054
2069
  background: transparent;
@@ -68,6 +68,7 @@ export default {
68
68
 
69
69
  data() {
70
70
  let sortBy = null;
71
+ let descending = false;
71
72
 
72
73
  this._defaultSortBy = this.defaultSortBy;
73
74
 
@@ -78,6 +79,7 @@ export default {
78
79
 
79
80
  if ( markedColumn ) {
80
81
  this._defaultSortBy = markedColumn.name;
82
+ descending = markedColumn.defaultSortDescending;
81
83
  } else if ( nameColumn ) {
82
84
  // Use the name column if there is one
83
85
  this._defaultSortBy = nameColumn.name;
@@ -101,7 +103,7 @@ export default {
101
103
 
102
104
  return {
103
105
  sortBy,
104
- descending: false,
106
+ descending,
105
107
  cachedRows: null,
106
108
  cacheKey: null,
107
109
  };
@@ -412,7 +412,7 @@ export default {
412
412
  }
413
413
 
414
414
  .tab.active {
415
- border-bottom: solid 2px var(--primary);
415
+ border-bottom: solid 2px var(--active, var(--primary));
416
416
  }
417
417
  }
418
418
 
@@ -450,7 +450,7 @@ export default {
450
450
 
451
451
  &.active {
452
452
  > A {
453
- color: var(--primary);
453
+ color: var(--active, var(--primary));
454
454
  text-decoration: none;
455
455
  }
456
456
  }
@@ -530,16 +530,16 @@ export default {
530
530
  border-left: solid 5px transparent;
531
531
 
532
532
  &.toggle A {
533
- color: var(--primary);
533
+ color: var(--active, var(--primary));
534
534
  }
535
535
 
536
536
  A {
537
- color: var(--primary);
537
+ color: var(--link, var(--primary));
538
538
  }
539
539
 
540
540
  &.active {
541
541
  background-color: var(--body-bg);
542
- border-left: solid 5px var(--primary);
542
+ border-left: solid 5px var(--active, var(--primary));
543
543
 
544
544
  & A {
545
545
  color: var(--input-label);
@@ -0,0 +1,117 @@
1
+ <script lang="ts" setup>
2
+ import { ref } from 'vue';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+ import { CONFIG_MAP } from '@shell/config/types';
5
+ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
6
+ import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
7
+
8
+ interface ConfigMap {
9
+ id?: string;
10
+ name: string;
11
+ namespace: string;
12
+ }
13
+
14
+ const props = defineProps({
15
+ value: {
16
+ type: Object,
17
+ required: true,
18
+ },
19
+ namespace: {
20
+ type: String,
21
+ required: true,
22
+ },
23
+ inStore: {
24
+ type: String,
25
+ default: 'management',
26
+ },
27
+ mode: {
28
+ type: String,
29
+ default: _EDIT
30
+ },
31
+ label: {
32
+ type: String,
33
+ default: '',
34
+ },
35
+ });
36
+
37
+ const emit = defineEmits(['update:value']);
38
+
39
+ const configMaps = ref<ConfigMap[]>([]);
40
+
41
+ const allConfigMapsSettings = {
42
+ updateResources: (configMapsList: ConfigMap[]) => {
43
+ const allConfigMapsInNamespace = configMapsList.filter((configMap) => configMap.namespace === props.namespace);
44
+ const mappedConfigMaps = mapConfigMaps(allConfigMapsInNamespace.sort((a, b) => a.name.localeCompare(b.name)));
45
+
46
+ configMaps.value = allConfigMapsInNamespace;
47
+
48
+ return mappedConfigMaps;
49
+ }
50
+ };
51
+
52
+ const paginateConfigMapsSetting = {
53
+ requestSettings: paginatePageOptions,
54
+ updateResources: (configMapsList: ConfigMap[]) => {
55
+ const mappedConfigMaps = mapConfigMaps(configMapsList);
56
+
57
+ configMaps.value = configMapsList;
58
+
59
+ return mappedConfigMaps;
60
+ }
61
+ };
62
+
63
+ function mapConfigMaps(configMapsList: ConfigMap[]) {
64
+ return configMapsList.reduce<{ label: string; value: string }[]>((res, c) => {
65
+ if (c.id) {
66
+ res.push({ label: c.name, value: c.name });
67
+ } else {
68
+ res.push(c as any);
69
+ }
70
+
71
+ return res;
72
+ }, []);
73
+ }
74
+
75
+ function paginatePageOptions(opts: any) {
76
+ const { opts: { filter } } = opts;
77
+
78
+ const filters = !!filter ? [PaginationParamFilter.createSingleField({
79
+ field: 'metadata.name', value: filter, exact: false, equals: true
80
+ })] : [];
81
+
82
+ filters.push(
83
+ PaginationParamFilter.createSingleField({ field: 'metadata.namespace', value: props.namespace }),
84
+ );
85
+
86
+ return {
87
+ ...opts,
88
+ filters,
89
+ groupByNamespace: false,
90
+ classify: true,
91
+ sort: [{ asc: true, field: 'metadata.name' }],
92
+ };
93
+ }
94
+
95
+ function update(value: any) {
96
+ emit('update:value', value);
97
+ }
98
+ </script>
99
+
100
+ <template>
101
+ <ResourceLabeledSelect
102
+ :key="namespace"
103
+ :value="value"
104
+ :label="label || t('fleet.configMaps.label')"
105
+ :mode="mode"
106
+ :resource-type="CONFIG_MAP"
107
+ :loading="$fetchState.pending"
108
+ :in-store="inStore"
109
+ :paginated-resource-settings="paginateConfigMapsSetting"
110
+ :all-resources-settings="allConfigMapsSettings"
111
+ :multiple="true"
112
+ @update:value="update"
113
+ />
114
+ </template>
115
+
116
+ <style lang="scss" scoped>
117
+ </style>