@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
@@ -0,0 +1,169 @@
1
+ /**
2
+ *
3
+ * The code in this file is responsible for adding Support notifications driven off of the dynamic content metadata
4
+ *
5
+ * This covers these cases:
6
+ *
7
+ * 1. Current release has reached End of Maintenance (EOL)
8
+ * 2. Current release has reached End of Maintenance (EOM)
9
+ * 3. Current release is approaching End of Maintenance (EOL)
10
+ * 4. Current release is approaching End of Maintenance (EOM)
11
+ *
12
+ * Note that we process in the order to that shown above, and stop at the first one that is active - so reaching EOL will show a notification
13
+ * and we won't look at the others.
14
+ *
15
+ */
16
+
17
+ import semver from 'semver';
18
+ import day from 'dayjs';
19
+ import { NotificationLevel } from '@shell/types/notifications';
20
+ import { READ_SUPPORT_NOTICE, READ_UPCOMING_SUPPORT_NOTICE } from '@shell/store/prefs';
21
+ import { removeMatchingNotifications } from './util';
22
+ import { Context, VersionInfo, UpcomingSupportInfo, SupportInfo } from './types';
23
+ import { UPDATE_DATE_FORMAT } from './index';
24
+
25
+ // Number of days ahead of upcoming EOM or EOL that we will notify the user
26
+ const DEFAULT_UPCOMING_WINDOW = 30;
27
+
28
+ // Prefixes used in the notifications IDs created here
29
+ const SUPPORT_NOTICE_PREFIX = 'support-notice-';
30
+ const UPCOMING_SUPPORT_NOTICE_PREFIX = 'upcoming-support-notice-';
31
+
32
+ // Prefixes used in the value of the user preference to track which notifications the user has read
33
+ const PREFIX = {
34
+ EOM: 'eom',
35
+ EOL: 'eol',
36
+ };
37
+
38
+ // Internal type used with convenience functions
39
+ type Config = {
40
+ prefValuePrefix?: string;
41
+ pref: any;
42
+ notificationPrefix: string;
43
+ titleKey: string;
44
+ messageKey: string;
45
+ };
46
+
47
+ /**
48
+ * Main exported function that will process the support (stateInfo)
49
+ *
50
+ * @param context Context helper providing access to config, logger, store
51
+ * @param statusInfo Support information
52
+ * @param versionInfo Version information
53
+ */
54
+
55
+ export async function processSupportNotices(context: Context, statusInfo: SupportInfo | undefined, versionInfo: VersionInfo): Promise<void> {
56
+ if (!statusInfo || !versionInfo?.version) {
57
+ return;
58
+ }
59
+
60
+ const { version } = versionInfo;
61
+ const { logger } = context;
62
+
63
+ // TODO: ****************************************************************************************
64
+ // TODO: Check if the user is an admin if we are Prime - we only notify admins of EOM and EOL
65
+ // TODO: ****************************************************************************************
66
+
67
+ const status = statusInfo.status || {};
68
+ const majorMinor = `${ semver.major(version) }.${ semver.minor(version) }`;
69
+
70
+ // Check if this version is EOL - we warn of EOL
71
+ // If a version is EOL, then is has passed EOM, so we don't need to check that
72
+ if (status.eol && semver.satisfies(version, status.eol)) {
73
+ logger.info(`This version (${ version }) is End of Life`);
74
+
75
+ return await checkAndAddNotification(context, {
76
+ prefValuePrefix: PREFIX.EOL,
77
+ pref: READ_SUPPORT_NOTICE,
78
+ notificationPrefix: SUPPORT_NOTICE_PREFIX,
79
+ titleKey: 'dynamicContent.eol.title',
80
+ messageKey: 'dynamicContent.eol.message',
81
+ }, majorMinor);
82
+ }
83
+
84
+ if (status.eom && semver.satisfies(version, status.eom)) {
85
+ logger.info(`This version (${ version }) is End of Maintenance`);
86
+
87
+ return await checkAndAddNotification(context, {
88
+ prefValuePrefix: PREFIX.EOM,
89
+ pref: READ_SUPPORT_NOTICE,
90
+ notificationPrefix: SUPPORT_NOTICE_PREFIX,
91
+ titleKey: 'dynamicContent.eom.title',
92
+ messageKey: 'dynamicContent.eom.message',
93
+ }, majorMinor);
94
+ }
95
+
96
+ // Now check for upcoming EOL or EOM
97
+
98
+ // Upcoming EOL
99
+ if (statusInfo.upcoming?.eol && semver.satisfies(version, statusInfo.upcoming.eol.version)) {
100
+ if (await checkAndAddUpcomingNotification(context, statusInfo.upcoming.eol, {
101
+ prefValuePrefix: PREFIX.EOL,
102
+ pref: READ_UPCOMING_SUPPORT_NOTICE,
103
+ notificationPrefix: UPCOMING_SUPPORT_NOTICE_PREFIX,
104
+ titleKey: 'dynamicContent.upcomingEol.title',
105
+ messageKey: 'dynamicContent.upcomingEol.message',
106
+ }, majorMinor)) {
107
+ return;
108
+ }
109
+ }
110
+
111
+ // Upcoming EOM
112
+ if (statusInfo.upcoming?.eom && semver.satisfies(version, statusInfo.upcoming.eom.version)) {
113
+ await checkAndAddUpcomingNotification(context, statusInfo.upcoming.eom, {
114
+ prefValuePrefix: PREFIX.EOM,
115
+ pref: READ_UPCOMING_SUPPORT_NOTICE,
116
+ notificationPrefix: UPCOMING_SUPPORT_NOTICE_PREFIX,
117
+ titleKey: 'dynamicContent.upcomingEom.title',
118
+ messageKey: 'dynamicContent.upcomingEom.message',
119
+ }, majorMinor);
120
+ }
121
+ }
122
+
123
+ async function checkAndAddUpcomingNotification(context: Context, info: UpcomingSupportInfo, config: Config, majorMinor: string): Promise<boolean> {
124
+ const now = day(day().format(UPDATE_DATE_FORMAT));
125
+ const upcomingDate = day(day(info.date).format(UPDATE_DATE_FORMAT));
126
+ const distance = upcomingDate.diff(now, 'day');
127
+ const noticeWindow = info.noticeDays || DEFAULT_UPCOMING_WINDOW;
128
+
129
+ // If we've passed the upcoming date, then ignore, as this should have been covered by the eom status
130
+ if (distance > 0 && distance < noticeWindow) {
131
+ await checkAndAddNotification(context, config, majorMinor, distance);
132
+
133
+ return true;
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Check if a notification already exists or has already been read and if not, add it
141
+ *
142
+ * @param context Context helper providing access to config, logger, store
143
+ * @param config Configuration for the notification
144
+ * @param majorMinor Major.Minor version number
145
+ * @param distance Number of days until the event occurs (for upcoming support events)
146
+ */
147
+ async function checkAndAddNotification(context: Context, config: Config, majorMinor: string, distance?: number) {
148
+ const { dispatch, getters, logger } = context;
149
+ const t = getters['i18n/t'];
150
+ const lastReadNotice = getters['prefs/get'](config.pref) || '';
151
+ const prefValue = config.prefValuePrefix ? `${ config.prefValuePrefix }-${ majorMinor }` : majorMinor;
152
+
153
+ if (!await removeMatchingNotifications(context, config.notificationPrefix, prefValue) && lastReadNotice !== prefValue) {
154
+ const notification = {
155
+ id: `${ config.notificationPrefix }${ prefValue }`,
156
+ level: NotificationLevel.Warning,
157
+ title: t(config.titleKey, { version: majorMinor, days: distance }),
158
+ message: t(config.messageKey, { version: majorMinor, days: distance }),
159
+ preference: {
160
+ key: config.pref,
161
+ value: prefValue
162
+ },
163
+ };
164
+
165
+ logger.info(`Adding support notification for ${ majorMinor } (${ config.notificationPrefix }${ config.prefValuePrefix })`);
166
+
167
+ await dispatch('notifications/add', notification);
168
+ }
169
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Shared types for Dynamic Content
3
+ */
4
+
5
+ import { SemVer } from 'semver';
6
+
7
+ /**
8
+ * Logger interface for dynamic content
9
+ */
10
+ export interface Logger {
11
+ error: Function,
12
+ info: Function,
13
+ debug: Function,
14
+ }
15
+
16
+ /**
17
+ * Distribution type
18
+ */
19
+ export type Distribution = 'community' | 'prime';
20
+
21
+ /**
22
+ * Dynamic Content configuration
23
+ */
24
+ export type Configuration = {
25
+ enabled: boolean;
26
+ debug: boolean;
27
+ log: boolean;
28
+ endpoint: string;
29
+ prime: boolean;
30
+ distribution: Distribution;
31
+ };
32
+
33
+ /**
34
+ * Settings configuration that can be supplied in the dynamic content package
35
+ */
36
+ export type SettingsInfo = {
37
+ releaseNotesUrl: string; // URL format to use when generating release note links for new releases
38
+ suseExtensions: string[]; // Names of extra SUSE UI extensions on top of the list built-in
39
+ debugVersion?: string;
40
+ };
41
+
42
+ /**
43
+ * Common context passed through various functions
44
+ */
45
+ export type Context = {
46
+ dispatch: Function,
47
+ getters: any,
48
+ axios: any,
49
+ logger: Logger,
50
+ isAdmin: boolean,
51
+ config: Configuration,
52
+ settings: SettingsInfo,
53
+ };
54
+
55
+ /**
56
+ * Version information
57
+ */
58
+ export type VersionInfo = {
59
+ version: SemVer | null;
60
+ isPrime: boolean;
61
+ };
62
+
63
+ /**
64
+ * Information for a new release
65
+ */
66
+ export type ReleaseInfo = {
67
+ name: string;
68
+ };
69
+
70
+ /**
71
+ * Information for an upcoming release
72
+ */
73
+ export type UpcomingSupportInfo = {
74
+ version: string, // Version number: semver (e.g. '<= 2.12')
75
+ date: Date, // Date when the eom/eol takes place (note, only the day part is used, time is ignored)
76
+ noticeDays?: number, // Number of days in advance to notify the user (if not specified, the default is used)
77
+ };
78
+
79
+ /**
80
+ * Support information covering current EOM/EOL and upcoming EOM/EOL information
81
+ */
82
+ export type SupportInfo = {
83
+ status: {
84
+ eom: string,
85
+ eol: string,
86
+ },
87
+ upcoming: {
88
+ eom: UpcomingSupportInfo,
89
+ eol: UpcomingSupportInfo,
90
+ }
91
+ };
92
+
93
+ /**
94
+ * Main type for the metadata that is retrieved from the dynamic content endpoint
95
+ */
96
+ export type DynamicContent = {
97
+ version: string;
98
+ releases: ReleaseInfo[],
99
+ support: SupportInfo,
100
+ settings?: Partial<SettingsInfo>,
101
+ };
@@ -0,0 +1,122 @@
1
+ /**
2
+ *
3
+ * The code in this file provides utility functions for dynamic content
4
+ *
5
+ * First up is a helper to remove notifications that match a given prefix
6
+ * Second up is a basic logging helper than will log to the console but can also log to local storage
7
+ * so that we have a persistent log of what the dynamic content code has been doing to help with debugging
8
+ *
9
+ */
10
+
11
+ import { randomStr } from '@shell/utils/string';
12
+ import { Configuration, Context } from './types';
13
+ import { Notification } from '@shell/types/notifications';
14
+
15
+ const MAX_LOG_MESSAGES = 50;
16
+
17
+ export const LOCAL_STORAGE_CONTENT_DEBUG_LOG = 'rancher-updates-debug-log';
18
+
19
+ /**
20
+ * Remove all notifications that have the given prefix, except the one that has the specified current id
21
+ *
22
+ * Returns whether a notification with the current id was found
23
+ *
24
+ * @param dispatch Store dispatcher
25
+ * @param getters Store getters
26
+ * @param prefix Prefix to look for and remove notifications whose IDs start with this prefix
27
+ * @param currentId Current ID of notification to keep, if present
28
+ * @returns If a notification with the current ID was found
29
+ */
30
+ export async function removeMatchingNotifications(context: Context, prefix: string, currentId: string): Promise<boolean> {
31
+ const { dispatch, getters, logger } = context;
32
+ const id = `${ prefix }${ currentId }`;
33
+ let found = false;
34
+ let removed = 0;
35
+ const all = getters['notifications/all'] || [];
36
+ const ids = all.map((n: Notification) => n.id);
37
+
38
+ for (let i = 0; i < ids.length; i++) {
39
+ const notificationId = ids[i];
40
+
41
+ if (notificationId.startsWith(prefix)) {
42
+ if (notificationId === id) {
43
+ found = true;
44
+ } else {
45
+ await dispatch('notifications/remove', notificationId);
46
+ removed++;
47
+
48
+ logger.debug(`Remove matching notifications ${ prefix }, ${ currentId } - removed notification ${ notificationId }`);
49
+ }
50
+ }
51
+ }
52
+
53
+ if (found) {
54
+ logger.debug(`Remove matching notifications ${ prefix }, ${ currentId } - found an existing notification (removed ${ removed })`);
55
+ } else {
56
+ logger.debug(`Remove matching notifications ${ prefix }, ${ currentId } - did not find an existing notification (removed ${ removed })`);
57
+ }
58
+
59
+ return found;
60
+ }
61
+
62
+ /**
63
+ * Create a logger interface that can be used to log messages and errors appropriately
64
+ * @param config Configuration to help us determine where and when to log
65
+ * @returns Logger interface with methods to log for error, info and debug
66
+ */
67
+ export function createLogger(config: Configuration) {
68
+ return {
69
+ error: (message: string, ...args: any[]) => log(config, 'error', message, ...args),
70
+ info: (message: string, ...args: any[]) => log(config, 'info', message, ...args),
71
+ debug: (message: string, ...args: any[]) => log(config, 'debug', message, ...args),
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Actual logging function that logs appropriately
77
+ * @param config Configuration to help us determine where and when to log
78
+ * @param level Log level (error, info, debug)
79
+ * @param message Log message
80
+ * @param arg Optional argument to be logged
81
+ */
82
+ function log(config: Configuration, level: string, message: string, ...args: any[]) {
83
+ // Log to the console when appropriate
84
+ if (level === 'error') {
85
+ console.error(message, ...args); // eslint-disable-line no-console
86
+ } else if (level === 'info' && config.log) {
87
+ console.info(message, ...args); // eslint-disable-line no-console
88
+ } else if (level === 'debug' && config.debug) {
89
+ console.debug(message, ...args); // eslint-disable-line no-console
90
+ }
91
+
92
+ // Only log to local storage if the configuration says we should
93
+ if (config.log) {
94
+ // Add the log message to the log we keep in local storage
95
+ try {
96
+ let data = [];
97
+
98
+ // If we can't parse the data in local storage, then we will reset to an emptry array
99
+ try {
100
+ data = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_CONTENT_DEBUG_LOG) || '[]');
101
+ } catch {}
102
+
103
+ const item = {
104
+ level,
105
+ message,
106
+ timestamp: new Date().toISOString(),
107
+ args,
108
+ uuid: randomStr(),
109
+ };
110
+
111
+ data.unshift(item);
112
+
113
+ // Limit the number of log messages
114
+ window.localStorage.setItem(LOCAL_STORAGE_CONTENT_DEBUG_LOG, JSON.stringify(data.slice(0, MAX_LOG_MESSAGES)));
115
+
116
+ // Send an event so the UI can update if necessary
117
+ const event = new CustomEvent('dynamicContentLog', { detail: item });
118
+
119
+ window.dispatchEvent(event);
120
+ } catch {}
121
+ }
122
+ }
@@ -0,0 +1,104 @@
1
+ import { EXT } from '@shell/config/types';
2
+ import { RancherKubeMetadata } from '@shell/types/kube/kube-api';
3
+
4
+ interface UserActivityResponse {
5
+ metadata: RancherKubeMetadata,
6
+ status: {
7
+ expiresAt: string
8
+ }
9
+ }
10
+
11
+ interface ParsedInactivitySetting {
12
+ expiresAt: string | undefined,
13
+ sessionTokenName: string | undefined,
14
+ courtesyTimer: number | undefined,
15
+ courtesyCountdown: number | undefined,
16
+ showModalAfter: number | undefined
17
+ }
18
+
19
+ interface SpecData {
20
+ tokenId: string;
21
+ seenAt?: string;
22
+ }
23
+
24
+ export class Inactivity {
25
+ private sessionTokenName: string | undefined = undefined;
26
+
27
+ public getSessionTokenName(): string | undefined {
28
+ return this.sessionTokenName;
29
+ }
30
+
31
+ public setSessionTokenName(tokenName: string): void {
32
+ this.sessionTokenName = tokenName;
33
+ }
34
+
35
+ public async getUserActivity(store: any, sessionTokenName: string, force = true): Promise<UserActivityResponse> {
36
+ try {
37
+ const updatedData = await store.dispatch('management/find', {
38
+ type: EXT.USER_ACTIVITY,
39
+ id: sessionTokenName,
40
+ opt: {
41
+ force, watch: false, logoutOnError: false
42
+ }
43
+ });
44
+
45
+ return updatedData;
46
+ } catch (e: any) {
47
+ if (e._status === 401) {
48
+ return store.dispatch('auth/logout', { sessionIdle: true });
49
+ }
50
+
51
+ console.error(`Could not GET UserActivity for session token ${ sessionTokenName }`, e); // eslint-disable-line no-console
52
+ throw new Error(e);
53
+ }
54
+ }
55
+
56
+ public async updateUserActivity(userActivityResource: any, sessionTokenName: string, seenAt: string): Promise<UserActivityResponse> {
57
+ const spec: SpecData = { tokenId: sessionTokenName };
58
+
59
+ if (seenAt) {
60
+ spec.seenAt = seenAt;
61
+ }
62
+
63
+ userActivityResource.spec = spec;
64
+
65
+ try {
66
+ const savedData = await userActivityResource.save({ force: true });
67
+
68
+ return savedData;
69
+ } catch (e: any) {
70
+ console.error(`Could not update (POST) UserActivity for session token ${ sessionTokenName }`, e); // eslint-disable-line no-console
71
+ throw new Error(e);
72
+ }
73
+ }
74
+
75
+ public parseTTLData(userActivityData: UserActivityResponse): ParsedInactivitySetting {
76
+ const currDate = Date.now();
77
+ const endDate = new Date(userActivityData.status?.expiresAt).getTime();
78
+
79
+ // let's give this a 3 second buffer so that we can make sure the logout happens by the frontend
80
+ const thresholdSeconds = Math.floor((endDate - currDate) / 1000) - 3;
81
+
82
+ // Amount of time the user sees the inactivity warning
83
+ const courtesyTimerVal = Math.floor(thresholdSeconds * 0.2); // the modal is shown for 10% of the total time to display
84
+ const courtesyTimer = Math.min(courtesyTimerVal, 60 * 5); // Never show the modal more than 5 minutes
85
+
86
+ const courtesyCountdown = courtesyTimer;
87
+
88
+ // Amount of time before the user sees the inactivity warning
89
+ // Note - time before warning is shown + time warning is shown = settings threshold (total amount of time)
90
+ const showModalAfter = thresholdSeconds - courtesyTimer;
91
+
92
+ return {
93
+ expiresAt: userActivityData.status?.expiresAt,
94
+ sessionTokenName: userActivityData.metadata?.name,
95
+ courtesyTimer,
96
+ courtesyCountdown,
97
+ showModalAfter
98
+ };
99
+ }
100
+ }
101
+
102
+ const instance = new Inactivity();
103
+
104
+ export default instance;
@@ -1,4 +1,6 @@
1
- import { PaginationFeature, PaginationSettings, PaginationSettingsStore } from '@shell/types/resources/settings';
1
+ import {
2
+ PaginationFeature, PaginationFeatureHomePageClusterConfig, PaginationFeatureName, PaginationSettings, PaginationSettingsFeatures, PaginationSettingsStore, PaginationSettingsStores
3
+ } from '@shell/types/resources/settings';
2
4
  import {
3
5
  NAMESPACE_FILTER_ALL_USER as ALL_USER,
4
6
  NAMESPACE_FILTER_ALL as ALL,
@@ -16,8 +18,21 @@ import { STEVE_CACHE } from '@shell/store/features';
16
18
  import { getPerformanceSetting } from '@shell/utils/settings';
17
19
  import { PAGINATION_SETTINGS_STORE_DEFAULTS } from '@shell/plugins/steve/steve-pagination-utils';
18
20
  import { MANAGEMENT } from '@shell/config/types';
21
+ import { VuexStore } from '@shell/types/store/vuex';
22
+ import { ServerSidePaginationExtensionConfig } from '@shell/core/types';
23
+ import { EXT_IDS } from '@shell/core/plugin';
24
+ import { ExtensionManager } from '@shell/types/extension-manager';
19
25
  import { DEFAULT_PERF_SETTING } from '@shell/config/settings';
20
26
 
27
+ const homePageClusterFeature: PaginationFeature<PaginationFeatureHomePageClusterConfig> = {
28
+ version: 1,
29
+ enabled: true,
30
+ configuration: {
31
+ threshold: 500, results: 250, pagesPerRow: 25
32
+ }
33
+ };
34
+ const PAGINATION_SETTINGS_FEATURE_DEFAULTS: PaginationSettingsFeatures = { homePageCluster: homePageClusterFeature };
35
+
21
36
  /**
22
37
  * Helper functions for server side pagination
23
38
  */
@@ -39,9 +54,9 @@ class PaginationUtils {
39
54
  return perf.serverPagination;
40
55
  }
41
56
 
42
- public getStoreSettings(ctx: any): PaginationSettingsStore
43
- public getStoreSettings(serverPagination: PaginationSettings): PaginationSettingsStore
44
- public getStoreSettings(arg: any | PaginationSettings): PaginationSettingsStore {
57
+ public getStoreSettings(ctx: any): PaginationSettingsStores
58
+ public getStoreSettings(serverPagination: PaginationSettings): PaginationSettingsStores
59
+ public getStoreSettings(arg: any | PaginationSettings): PaginationSettingsStores {
45
60
  const serverPagination: PaginationSettings = arg?.rootGetters !== undefined ? this.getSettings(arg) : arg;
46
61
 
47
62
  // Ensure we use the current default store settings if
@@ -55,7 +70,7 @@ class PaginationUtils {
55
70
  return serverPagination?.stores || this.getStoreDefault();
56
71
  }
57
72
 
58
- public getStoreDefault(): PaginationSettingsStore {
73
+ public getStoreDefault(): PaginationSettingsStores {
59
74
  return PAGINATION_SETTINGS_STORE_DEFAULTS;
60
75
  }
61
76
 
@@ -82,28 +97,17 @@ class PaginationUtils {
82
97
  }
83
98
 
84
99
  /**
85
- * Is pagination enabled at a global level or for a specific resource
100
+ * Helper - check if a specific resource in a specific store is enabled given the provided settings
86
101
  */
87
- isEnabled({ rootGetters }: any, enabledFor: PaginationResourceContext) {
88
- // Cache must be enabled to support pagination api
89
- if (!this.isSteveCacheEnabled({ rootGetters })) {
90
- return false;
91
- }
92
-
93
- const settings = this.getSettings({ rootGetters });
94
-
95
- // No setting, not enabled
96
- if (!settings) {
97
- return false;
98
- }
99
-
100
- // Missing required params, not enabled
101
- if (!enabledFor) {
102
- return false;
103
- }
104
-
105
- const storeSettings = this.getStoreSettings(settings)?.[enabledFor.store];
106
-
102
+ private isEnabledInStore({
103
+ ctx: { rootGetters },
104
+ storeSettings,
105
+ enabledFor
106
+ }: {
107
+ ctx: Partial<VuexStore>,
108
+ storeSettings: PaginationSettingsStore,
109
+ enabledFor: PaginationResourceContext
110
+ }): boolean {
107
111
  // No pagination setting for target store, not enabled
108
112
  if (!storeSettings) {
109
113
  return false;
@@ -130,16 +134,19 @@ class PaginationUtils {
130
134
  !rootGetters['type-map/configuredPaginationHeaders'](enabledFor.resource.id) &&
131
135
  !rootGetters['type-map/hasCustomList'](enabledFor.resource.id);
132
136
 
133
- if (storeSettings.resources.enableSome.generic && isGeneric) {
137
+ // Store says generic resource with no custom pagination settings are supported
138
+ if (storeSettings.resources.enableSome?.generic && isGeneric) {
134
139
  return true;
135
140
  }
136
141
 
137
- if (storeSettings.resources.enableSome.enabled.find((setting) => {
142
+ // Store says some specific resources are enabled
143
+ if (storeSettings.resources.enableSome?.enabled?.find((setting) => {
138
144
  if (typeof setting === 'string') {
139
145
  return setting === enabledFor.resource?.id;
140
146
  }
141
147
 
142
148
  if (setting.resource === enabledFor.resource?.id) {
149
+ // Store says only specific usages of this resource are enabled
143
150
  if (!!setting.context) {
144
151
  return enabledFor.resource?.context ? setting.context.includes(enabledFor.resource.context) : false;
145
152
  }
@@ -155,6 +162,69 @@ class PaginationUtils {
155
162
  return false;
156
163
  }
157
164
 
165
+ /**
166
+ * Is pagination enabled at a global level or for a specific resource
167
+ */
168
+ isEnabled({ rootGetters, $plugin }: any, enabledFor: PaginationResourceContext) {
169
+ // Cache must be enabled to support pagination api
170
+ if (!this.isSteveCacheEnabled({ rootGetters })) {
171
+ return false;
172
+ }
173
+
174
+ const settings = this.getSettings({ rootGetters });
175
+
176
+ // No setting, not enabled
177
+ if (!settings) {
178
+ return false;
179
+ }
180
+
181
+ // Missing required params, not enabled
182
+ if (!enabledFor) {
183
+ return false;
184
+ }
185
+
186
+ // Does an extension say this type is enabled?
187
+ const plugin = $plugin as ExtensionManager;
188
+ const paginationExtensionPoints = plugin.getAll()[EXT_IDS.SERVER_SIDE_PAGINATION_RESOURCES];
189
+
190
+ if (paginationExtensionPoints) {
191
+ const allowed = Object.entries(paginationExtensionPoints).find(([_, settingsFn]) => {
192
+ if (!settingsFn) {
193
+ return false;
194
+ }
195
+
196
+ const settings: ServerSidePaginationExtensionConfig = settingsFn();
197
+ const allowed = Object.entries(settings).find(([store, settings]) => {
198
+ if (store !== enabledFor.store) {
199
+ return false;
200
+ }
201
+
202
+ return this.isEnabledInStore({
203
+ ctx: { rootGetters },
204
+ storeSettings: settings,
205
+ enabledFor
206
+ });
207
+ });
208
+
209
+ if (allowed) {
210
+ return true;
211
+ }
212
+ });
213
+
214
+ if (allowed) {
215
+ return true;
216
+ }
217
+ }
218
+
219
+ const storeSettings = this.getStoreSettings(settings)?.[enabledFor.store];
220
+
221
+ return this.isEnabledInStore({
222
+ ctx: { rootGetters },
223
+ storeSettings,
224
+ enabledFor
225
+ });
226
+ }
227
+
158
228
  listAutoRefreshToggleEnabled({ rootGetters }: any): boolean {
159
229
  return this.isFeatureEnabled({ rootGetters }, 'listAutoRefreshToggle');
160
230
  }
@@ -163,15 +233,19 @@ class PaginationUtils {
163
233
  return this.isFeatureEnabled({ rootGetters }, 'listManualRefresh');
164
234
  }
165
235
 
166
- private isFeatureEnabled({ rootGetters }: any, featureName: PaginationFeature): boolean {
236
+ getFeature<Config = any>({ rootGetters }: any, featureName: PaginationFeatureName): PaginationFeature<Config> | undefined {
167
237
  // Cache must be enabled to support pagination api
168
238
  if (!this.isSteveCacheEnabled({ rootGetters })) {
169
- return false;
239
+ return undefined;
170
240
  }
171
241
 
172
242
  const settings = this.getSettings({ rootGetters });
173
243
 
174
- return !!settings.features?.[featureName]?.enabled;
244
+ return settings.features?.[featureName] || PAGINATION_SETTINGS_FEATURE_DEFAULTS[featureName];
245
+ }
246
+
247
+ private isFeatureEnabled({ rootGetters }: any, featureName: PaginationFeatureName): boolean {
248
+ return !!this.getFeature({ rootGetters }, featureName)?.enabled;
175
249
  }
176
250
 
177
251
  resourceChangesDebounceMs({ rootGetters }: any): number | undefined {