@rancher/shell 3.0.8-rc.2 → 3.0.8-rc.4

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 (88) hide show
  1. package/assets/brand/suse/dark/rancher-logo.svg +64 -1
  2. package/assets/brand/suse/rancher-logo.svg +1 -1
  3. package/assets/styles/global/_cards.scss +0 -3
  4. package/assets/styles/themes/_modern.scss +9 -1
  5. package/assets/styles/themes/_suse.scss +81 -24
  6. package/assets/translations/en-us.yaml +68 -3
  7. package/components/AutoscalerCard.vue +113 -0
  8. package/components/AutoscalerTab.vue +94 -0
  9. package/components/ClusterIconMenu.vue +1 -1
  10. package/components/ClusterProviderIcon.vue +1 -1
  11. package/components/IconOrSvg.vue +2 -2
  12. package/components/PopoverCard.vue +192 -0
  13. package/components/Resource/Detail/FetchLoader/composables.ts +18 -4
  14. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +1 -1
  15. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +4 -0
  16. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -19
  17. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +0 -29
  18. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +132 -150
  19. package/components/Resource/Detail/ResourcePopover/index.vue +54 -159
  20. package/components/ResourceDetail/Masthead/latest.vue +29 -0
  21. package/components/ResourceList/Masthead.vue +1 -1
  22. package/components/__tests__/AutoscalerCard.test.ts +154 -0
  23. package/components/__tests__/AutoscalerTab.test.ts +125 -0
  24. package/components/__tests__/PopoverCard.test.ts +204 -0
  25. package/components/formatter/Autoscaler.vue +97 -0
  26. package/components/formatter/InternalExternalIP.vue +195 -24
  27. package/components/formatter/__tests__/Autoscaler.test.ts +156 -0
  28. package/components/formatter/__tests__/InternalExternalIP.test.ts +133 -0
  29. package/components/nav/Group.vue +12 -3
  30. package/components/nav/TopLevelMenu.vue +2 -2
  31. package/composables/useInterval.ts +15 -0
  32. package/config/labels-annotations.js +8 -1
  33. package/config/product/manager.js +20 -9
  34. package/config/router/routes.js +4 -0
  35. package/config/settings.ts +2 -1
  36. package/config/table-headers.js +8 -0
  37. package/config/types.js +2 -0
  38. package/config/version.js +1 -1
  39. package/core/types-provisioning.ts +3 -0
  40. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  41. package/directives/ui-context.ts +8 -2
  42. package/edit/auth/github.vue +5 -0
  43. package/edit/cloudcredential.vue +1 -1
  44. package/edit/fleet.cattle.io.gitrepo.vue +0 -10
  45. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +32 -5
  46. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +35 -0
  47. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +132 -0
  48. package/edit/provisioning.cattle.io.cluster/index.vue +18 -12
  49. package/edit/provisioning.cattle.io.cluster/rke2.vue +39 -8
  50. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +107 -5
  51. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +90 -3
  52. package/initialize/install-plugins.js +3 -1
  53. package/list/provisioning.cattle.io.cluster.vue +15 -2
  54. package/machine-config/amazonec2.vue +36 -135
  55. package/machine-config/components/EC2Networking.vue +474 -0
  56. package/machine-config/components/__tests__/EC2Networking.test.ts +94 -0
  57. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +294 -0
  58. package/machine-config/digitalocean.vue +11 -0
  59. package/models/cluster/node.js +13 -6
  60. package/models/cluster.x-k8s.io.machine.js +10 -20
  61. package/models/cluster.x-k8s.io.machinedeployment.js +5 -1
  62. package/models/management.cattle.io.kontainerdriver.js +1 -0
  63. package/models/provisioning.cattle.io.cluster.js +223 -2
  64. package/package.json +2 -2
  65. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  66. package/pages/c/_cluster/manager/hostedprovider/index.vue +209 -0
  67. package/plugins/dynamic-content.js +13 -0
  68. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  69. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +8 -0
  70. package/store/features.js +1 -0
  71. package/store/notifications.ts +32 -1
  72. package/store/plugins.js +7 -3
  73. package/store/prefs.js +1 -0
  74. package/types/notifications/index.ts +24 -3
  75. package/types/shell/index.d.ts +27 -2
  76. package/utils/__tests__/object.test.ts +19 -0
  77. package/utils/autoscaler-utils.ts +7 -0
  78. package/utils/dynamic-content/__tests__/announcement.test.ts +498 -0
  79. package/utils/dynamic-content/announcement.ts +112 -0
  80. package/utils/dynamic-content/example.json +40 -0
  81. package/utils/dynamic-content/index.ts +6 -2
  82. package/utils/dynamic-content/new-release.ts +1 -1
  83. package/utils/dynamic-content/notification-handler.ts +48 -0
  84. package/utils/dynamic-content/types.d.ts +33 -1
  85. package/utils/object.js +20 -2
  86. package/utils/scroll.js +7 -0
  87. package/utils/settings.ts +15 -0
  88. package/utils/validators/machine-pool.ts +13 -3
@@ -0,0 +1,112 @@
1
+ /**
2
+ *
3
+ * The code in this file is responsible for adding 'announcement 'notifications driven off of the dynamic content metadata
4
+ *
5
+ * Announcements will be able to be shown in different places in the UI
6
+ *
7
+ */
8
+
9
+ import semver from 'semver';
10
+ import { NotificationLevel, Notification } from '@shell/types/notifications';
11
+ import { READ_ANNOUNCEMENTS } from '@shell/store/prefs';
12
+ import { Context, VersionInfo, Announcement } from './types';
13
+ import { DynamicContentAnnouncementHandlerName } from './notification-handler';
14
+
15
+ // Prefixes used in the notifications IDs created here
16
+ export const ANNOUNCEMENT_PREFIX = 'announcement-';
17
+
18
+ const ALLOWED_NOTIFICATIONS: Record<string, NotificationLevel> = {
19
+ announcement: NotificationLevel.Announcement,
20
+ info: NotificationLevel.Info,
21
+ warning: NotificationLevel.Warning,
22
+ };
23
+
24
+ /**
25
+ * Main exported function that will process the announcements
26
+ *
27
+ * @param context Context helper providing access to config, logger, store
28
+ * @param announcements Announcement information
29
+ * @param versionInfo Version information
30
+ */
31
+ export async function processAnnouncements(context: Context, announcements: Announcement[] | undefined, versionInfo: VersionInfo): Promise<void> {
32
+ if (!announcements || !announcements.length || !versionInfo?.version) {
33
+ return;
34
+ }
35
+
36
+ const { dispatch, getters, logger } = context;
37
+
38
+ // Process each announcement
39
+ await Promise.all(announcements.map(async(announcement: Announcement) => {
40
+ // Check version
41
+ if (announcement.version && !semver.satisfies(versionInfo.version, announcement.version)) {
42
+ return;
43
+ }
44
+
45
+ // Check audience (currently only admin or all, but may add more in the future)
46
+ if (announcement.audience === 'admin' && !context.isAdmin) {
47
+ return;
48
+ }
49
+
50
+ // Check type
51
+ const targetSplit = announcement.target.split('/');
52
+
53
+ if (targetSplit[0] === 'notification') {
54
+ // Show a notification
55
+ const subType = targetSplit.length === 2 ? targetSplit[1] : 'announcement';
56
+
57
+ // Because 0 is a falsy, see if we find something of type number to check for existence
58
+ if (typeof ALLOWED_NOTIFICATIONS[subType] !== 'number') {
59
+ logger.error(`Announcement notification type ${ subType } is not supported`);
60
+ } else {
61
+ logger.info(`Going to add a notification for announcement ${ announcement.target }`);
62
+
63
+ if (!announcement.id) {
64
+ logger.error(`No ID For announcement - not going to add a notification for the announcement`);
65
+
66
+ return;
67
+ }
68
+
69
+ // We should check if the notification already exists
70
+ const id = `${ ANNOUNCEMENT_PREFIX }${ announcement.id }`;
71
+ const existing = getters['notifications/item'](id);
72
+
73
+ // Check if the pref for 'read announcements' has the id
74
+ const pref = getters['prefs/get'](READ_ANNOUNCEMENTS) || '';
75
+ const prefExists = pref.split(',').includes(announcement.id);
76
+
77
+ if (existing || prefExists) {
78
+ logger.info(`Not adding announcement with ID ${ id } as it already exists or has been read previously (title: ${ announcement.title })`);
79
+
80
+ return;
81
+ }
82
+ const notification: Notification = {
83
+ id,
84
+ level: ALLOWED_NOTIFICATIONS[subType],
85
+ title: announcement.title,
86
+ message: announcement.message,
87
+ handlerName: DynamicContentAnnouncementHandlerName,
88
+ };
89
+
90
+ if (announcement.cta?.primary) {
91
+ notification.primaryAction = {
92
+ label: announcement.cta.primary.action,
93
+ target: announcement.cta.primary.link,
94
+ };
95
+ }
96
+
97
+ if (announcement.cta?.secondary) {
98
+ notification.secondaryAction = {
99
+ label: announcement.cta.secondary.action,
100
+ target: announcement.cta.secondary.link,
101
+ };
102
+ }
103
+
104
+ logger.info(`Adding announcement with ID ${ id } (title: ${ announcement.title })`);
105
+
106
+ await dispatch('notifications/add', notification);
107
+ }
108
+ } else {
109
+ logger.error(`Announcement type ${ announcement.target } is not supported`);
110
+ }
111
+ }));
112
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "version": 1,
3
+ "releases": [
4
+ {
5
+ "name": "2.12.2"
6
+ },
7
+ {
8
+ "name": "2.11.3"
9
+ },
10
+ {
11
+ "name": "2.10.3"
12
+ }
13
+ ],
14
+ "announcements": [
15
+ {
16
+ "id": "security-update",
17
+ "target": "notification/announcement",
18
+ "title": "Important Security Update",
19
+ "message": "A critical security vulnerability has been discovered in version 2.10.1. Users are strongly advised to update to version 2.12.2 immediately to ensure their systems remain secure.",
20
+ "cta": {
21
+ "primary" : {
22
+ "action": "Update Now",
23
+ "link": "https://www.suse.com/"
24
+ }
25
+ }
26
+ },
27
+ {
28
+ "id": "suse-rocks",
29
+ "target": "homepage/banner",
30
+ "title": "Important Security Update",
31
+ "message": "A critical security vulnerability has been discovered in version 2.10.1. Users are strongly advised to update to version 2.12.2 immediately to ensure their systems remain secure.",
32
+ "cta": {
33
+ "primary" : {
34
+ "action": "Update Now",
35
+ "link": "https://www.suse.com/"
36
+ }
37
+ }
38
+ }
39
+ ]
40
+ }
@@ -15,6 +15,7 @@ import { Context, DynamicContent, VersionInfo } from './types';
15
15
  import { createLogger, LOCAL_STORAGE_CONTENT_DEBUG_LOG } from './util';
16
16
  import { getConfig } from './config';
17
17
  import { SystemInfoProvider } from './info';
18
+ import { processAnnouncements } from './announcement';
18
19
 
19
20
  const FETCH_DELAY = 3 * 1000; // Short delay to let UI settle before we fetch the updates document
20
21
  const FETCH_REQUEST_TIMEOUT = 15000; // Time out the request after 15 seconds
@@ -83,7 +84,7 @@ export async function fetchAndProcessDynamicContent(dispatch: Function, getters:
83
84
  }
84
85
 
85
86
  const versionInfo: VersionInfo = {
86
- version,
87
+ version: version as semver.SemVer, // Will be defined, can not be null here
87
88
  isPrime: config.prime,
88
89
  };
89
90
 
@@ -103,7 +104,7 @@ export async function fetchAndProcessDynamicContent(dispatch: Function, getters:
103
104
  // If the cached content has a debug version then use that as an override for the current version number
104
105
  // This is only for debug and testing purposes
105
106
  if (content.settings?.debugVersion) {
106
- versionInfo.version = semver.coerce(content.settings.debugVersion);
107
+ versionInfo.version = semver.coerce(content.settings.debugVersion) || version;
107
108
  logger.debug(`Overriding version number to ${ content.settings.debugVersion }`);
108
109
  }
109
110
 
@@ -117,6 +118,9 @@ export async function fetchAndProcessDynamicContent(dispatch: Function, getters:
117
118
  // EOM, EOL notifications
118
119
  processSupportNotices(context, content.support, versionInfo);
119
120
  }
121
+
122
+ // Announcements - processed for all users
123
+ processAnnouncements(context, content.announcements, versionInfo);
120
124
  } catch (e) {
121
125
  logger.error('Error reading or processing dynamic content', e);
122
126
  }
@@ -27,7 +27,7 @@ export async function processReleaseVersion(context: Context, releaseInfo: Relea
27
27
  const versions = releaseInfo.map((v: any) => semver.coerce(v.name));
28
28
 
29
29
  // Sort the versions, so that the newest is first in the list
30
- versions.sort((a: any, b: any) => semver.gt(b, a) ? 1 : -1);
30
+ versions.sort((a: any, b: any) => semver.rcompare(a, b));
31
31
 
32
32
  // Find first newer version
33
33
  const newer = versions.find((v: any) => semver.gt(v, version));
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Notification handler for dynamic content announcements
3
+ *
4
+ * This provides custom handling for read/unread state using a single user preference
5
+ */
6
+ import { Notification, NotificationHandler } from '@shell/types/notifications';
7
+ import { READ_ANNOUNCEMENTS } from '@shell/store/prefs';
8
+ import { ANNOUNCEMENT_PREFIX } from './announcement';
9
+
10
+ // Global name for this handler that can be used when creating notifications to associate them with this handler
11
+ export const DynamicContentAnnouncementHandlerName = 'dc-announcements';
12
+
13
+ /**
14
+ * Create the dynamic content notification handler
15
+ *
16
+ * This is for announcements, where we need to manage an array of IDs of announcements that have been read, which
17
+ * is taken care of by this custom handler.
18
+ *
19
+ * When a notification is read/unread that specifies this handler, we will add or remove its ID from the list of
20
+ * read IDs that we maintain in the user preference value.
21
+ *
22
+ * This allows us to use a single user preference to track read announcements
23
+ */
24
+ export function createHandler(store: any): NotificationHandler {
25
+ return {
26
+ async onReadUpdated(notification: Notification, read: boolean) {
27
+ if (!notification.id.startsWith(ANNOUNCEMENT_PREFIX)) {
28
+ return;
29
+ }
30
+
31
+ const id = notification.id.substring(ANNOUNCEMENT_PREFIX.length);
32
+ const announcements = store.getters['notifications/all'].filter((n: any) => n.id.startsWith(ANNOUNCEMENT_PREFIX));
33
+ const pref = store.getters['prefs/get'](READ_ANNOUNCEMENTS) || '';
34
+ const values = !pref.length ? [] : pref.split(',').filter((v: string) => !announcements.includes(v));
35
+ const valuesUnique = new Set(values);
36
+
37
+ if (read) {
38
+ valuesUnique.add(id);
39
+ } else {
40
+ valuesUnique.delete(id);
41
+ }
42
+
43
+ const newValues = Array.from(valuesUnique).sort();
44
+
45
+ await store.dispatch('prefs/set', { key: READ_ANNOUNCEMENTS, value: newValues.join(',') });
46
+ }
47
+ };
48
+ }
@@ -56,7 +56,7 @@ export type Context = {
56
56
  * Version information
57
57
  */
58
58
  export type VersionInfo = {
59
- version: SemVer | null;
59
+ version: SemVer;
60
60
  isPrime: boolean;
61
61
  };
62
62
 
@@ -90,6 +90,37 @@ export type SupportInfo = {
90
90
  }
91
91
  };
92
92
 
93
+ /**
94
+ * Call to action for an announcement
95
+ */
96
+ export type CallToAction = {
97
+ action: string;
98
+ link: string;
99
+ };
100
+
101
+ /**
102
+ * Announcements to be shown in the notification center or on the home page
103
+ *
104
+ * Target determines where the notification will be shown, supported values:
105
+ *
106
+ * - `notification/announcement` - Shown with `Announcement` level in the Notification Center
107
+ * - `notification/info` - Shown with `Info` level in the Notification Center
108
+ * - `notification/warning` - Shown with `Warning` level in the Notification Center
109
+ *
110
+ */
111
+ export type Announcement = {
112
+ id: string; // Unique id for this announcement
113
+ title: string; // Title to be shown
114
+ message: string; // Message/Body for the announcement
115
+ target: string; // Where the announcement should be shown
116
+ version?: string; // Version or semver expression for when to show this announcement
117
+ audience?: 'admin' | 'all'; // Audience - show for just Admins or for all users
118
+ cta?: {
119
+ primary: CallToAction, // Must have a primary call to action, if we have a cta field
120
+ secondary?: CallToAction,
121
+ }
122
+ };
123
+
93
124
  /**
94
125
  * Main type for the metadata that is retrieved from the dynamic content endpoint
95
126
  */
@@ -97,5 +128,6 @@ export type DynamicContent = {
97
128
  version: string;
98
129
  releases: ReleaseInfo[],
99
130
  support: SupportInfo,
131
+ announcements: Announcement[],
100
132
  settings?: Partial<SettingsInfo>,
101
133
  };
package/utils/object.js CHANGED
@@ -205,7 +205,7 @@ export function definedKeys(obj) {
205
205
  return compact(flattenDeep(keys));
206
206
  }
207
207
 
208
- export function diff(from, to) {
208
+ export function diff(from, to, preventNull = false) {
209
209
  from = from || {};
210
210
  to = to || {};
211
211
 
@@ -234,7 +234,25 @@ export function diff(from, to) {
234
234
  const missing = difference(fromKeys, toKeys);
235
235
 
236
236
  for ( const k of missing ) {
237
- set(out, k, null);
237
+ // we need to gate this in order to prevent unforseen problems with addonConfig
238
+ if (preventNull) {
239
+ // keys that come from "definedKeys" method are strings with "" chars inside... We need to clean them up
240
+ // so that we can access the value of the obj property
241
+ let key = k;
242
+
243
+ if (!k.includes('.')) {
244
+ key = k.replaceAll('"', '');
245
+ }
246
+
247
+ // // if value exists in "from" but is missing in "to", let's add it, otherwise we just set it null as we did before
248
+ if (from[key] !== undefined && from[key] !== null) {
249
+ set(out, key, from[key]);
250
+ } else {
251
+ set(out, key, null);
252
+ }
253
+ } else {
254
+ set(out, k, null);
255
+ }
238
256
  }
239
257
 
240
258
  return out;
@@ -0,0 +1,7 @@
1
+ export function scrollToBottom() {
2
+ const scrollable = document.getElementsByTagName('main')[0];
3
+
4
+ if (scrollable) {
5
+ scrollable.scrollTop = scrollable.scrollHeight;
6
+ }
7
+ }
package/utils/settings.ts CHANGED
@@ -3,6 +3,7 @@ import { Store } from 'vuex';
3
3
  import { DEFAULT_PERF_SETTING, PerfSettings, SETTING } from '@shell/config/settings';
4
4
  import { pluralize } from '@shell/utils/string';
5
5
  import { _MULTI } from '@shell/plugins/dashboard-store/actions';
6
+ import { ClusterProvisionerContext } from '@shell/core/types';
6
7
 
7
8
  export const fetchOrCreateSetting = async(store: Store<any>, id: string, val: string, save = true): Promise<any> => {
8
9
  let setting;
@@ -109,3 +110,17 @@ export const getPerformanceSetting = (rootGetters: Record<string, (arg0: string,
109
110
 
110
111
  return Object.assign(safeDefaults, perfSetting || {});
111
112
  };
113
+
114
+ export const isProviderEnabled = (context: ClusterProvisionerContext, provider: string): boolean => {
115
+ const providerTypesJSON = context.getters['management/byId'](MANAGEMENT.SETTING, SETTING.KEV2_OPERATORS )?.value;
116
+ const providerTypes = providerTypesJSON ? JSON.parse(providerTypesJSON) : [];
117
+
118
+ for ( let i = 0; i < providerTypes.length; i++) {
119
+ if ( providerTypes[i].name === provider) {
120
+ return providerTypes[i].active;
121
+ }
122
+ }
123
+
124
+ // We want to have providers enabled by default unless they are turned off by a setting
125
+ return true;
126
+ };
@@ -1,6 +1,8 @@
1
1
  const FIELDS = {
2
- NAME: 'pool.name',
3
- QUANTITY: 'pool.quantity'
2
+ NAME: 'pool.name',
3
+ QUANTITY: 'pool.quantity',
4
+ AUTOSCALER_MIN: 'pool.autoscalingMinSize',
5
+ AUTOSCALER_MAX: 'pool.autoscalingMaxSize'
4
6
  };
5
7
 
6
8
  const RULESETS = [
@@ -11,7 +13,15 @@ const RULESETS = [
11
13
  {
12
14
  path: FIELDS.NAME,
13
15
  rules: ['required'],
14
- }
16
+ },
17
+ {
18
+ path: FIELDS.AUTOSCALER_MIN,
19
+ rules: ['isPositive', 'isAutoscalerMaxGreaterThanMin'],
20
+ },
21
+ {
22
+ path: FIELDS.AUTOSCALER_MAX,
23
+ rules: ['isPositive', 'isAutoscalerMaxGreaterThanMin'],
24
+ },
15
25
  ];
16
26
 
17
27
  export const MACHINE_POOL_VALIDATION = {