@rancher/shell 0.1.3 → 0.1.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 (131) hide show
  1. package/assets/brand/suse/dark/rancher-logo.svg +1 -148
  2. package/assets/brand/suse/rancher-logo.svg +1 -130
  3. package/assets/images/featured/img1.jpg +0 -0
  4. package/assets/images/featured.jpg +0 -0
  5. package/assets/images/generic-plugin.svg +7 -0
  6. package/assets/styles/themes/_dark.scss +3 -0
  7. package/assets/styles/themes/_light.scss +3 -0
  8. package/assets/styles/themes/_suse.scss +1 -1
  9. package/assets/translations/en-us.yaml +183 -45
  10. package/assets/translations/zh-hans.yaml +21 -24
  11. package/components/AsyncButton.vue +17 -2
  12. package/components/ButtonDropdown.vue +4 -0
  13. package/components/Carousel.vue +291 -0
  14. package/components/CommunityLinks.vue +69 -18
  15. package/components/CruResource.vue +11 -3
  16. package/components/Dialog.vue +102 -0
  17. package/components/ExplorerMembers.vue +2 -4
  18. package/components/ExplorerProjectsNamespaces.vue +6 -7
  19. package/components/IconMessage.vue +9 -1
  20. package/components/LocaleSelector.vue +62 -29
  21. package/components/ResourceTable.vue +7 -2
  22. package/components/SimpleBox.vue +6 -4
  23. package/components/SortableTable/index.vue +11 -21
  24. package/components/Tabbed/Tab.vue +5 -0
  25. package/components/Tabbed/index.vue +29 -2
  26. package/components/auth/Principal.vue +1 -0
  27. package/components/fleet/FleetBundles.vue +8 -3
  28. package/components/fleet/FleetSummary.vue +6 -0
  29. package/components/form/KeyValue.vue +80 -58
  30. package/components/form/NameNsDescription.vue +10 -4
  31. package/components/form/ResourceTabs/index.vue +5 -1
  32. package/components/formatter/ClusterLink.vue +3 -7
  33. package/components/nav/NamespaceFilter.vue +3 -3
  34. package/components/nav/TopLevelMenu.vue +10 -28
  35. package/config/footer.js +13 -14
  36. package/config/labels-annotations.js +2 -1
  37. package/config/product/explorer.js +5 -4
  38. package/config/product/legacy.js +0 -47
  39. package/config/product/multi-cluster-apps.js +0 -12
  40. package/config/product/settings.js +12 -1
  41. package/config/product/uiplugins.js +17 -0
  42. package/config/settings.js +21 -2
  43. package/config/types.js +5 -1
  44. package/config/uiplugins.js +60 -0
  45. package/content/docs/en-us/getting-started.md +1 -26
  46. package/core/plugins.js +12 -0
  47. package/detail/provisioning.cattle.io.cluster.vue +3 -3
  48. package/detail/workload/index.vue +2 -2
  49. package/dialog/DiagnosticTimingsDialog.vue +116 -0
  50. package/dialog/RotateCertificatesDialog.vue +9 -3
  51. package/edit/auth/azuread.vue +28 -9
  52. package/edit/networking.k8s.io.ingress/index.vue +2 -2
  53. package/edit/persistentvolume/index.vue +3 -0
  54. package/edit/pod.vue +27 -0
  55. package/edit/provisioning.cattle.io.cluster/rke2.vue +76 -5
  56. package/edit/service.vue +7 -5
  57. package/edit/workload/__tests__/Upgrading.test.ts +1 -0
  58. package/edit/workload/index.vue +13 -1
  59. package/edit/workload/mixins/workload.js +13 -13
  60. package/edit/workload/storage/ContainerMountPaths.vue +240 -0
  61. package/edit/workload/storage/Mount.vue +1 -0
  62. package/edit/workload/storage/awsElasticBlockStore.vue +20 -1
  63. package/edit/workload/storage/azureDisk.vue +22 -2
  64. package/edit/workload/storage/azureFile.vue +20 -2
  65. package/edit/workload/storage/csi/index.vue +23 -1
  66. package/edit/workload/storage/gcePersistentDisk.vue +20 -2
  67. package/edit/workload/storage/index.vue +23 -49
  68. package/edit/workload/storage/vsphereVolume.vue +11 -1
  69. package/layouts/default.vue +14 -8
  70. package/layouts/home.vue +9 -4
  71. package/layouts/plain.vue +10 -5
  72. package/list/management.cattle.io.setting.vue +3 -3
  73. package/list/provisioning.cattle.io.cluster.vue +1 -1
  74. package/machine-config/harvester.vue +5 -3
  75. package/models/catalog.cattle.io.uiplugin.js +34 -0
  76. package/models/cluster/node.js +25 -2
  77. package/models/fleet.cattle.io.bundle.js +1 -1
  78. package/models/harvesterhci.io.management.cluster.js +11 -5
  79. package/models/provisioning.cattle.io.cluster.js +12 -6
  80. package/models/workload.js +5 -3
  81. package/nuxt.config.js +69 -25
  82. package/package.json +108 -109
  83. package/pages/auth/login.vue +1 -1
  84. package/pages/c/_cluster/apps/charts/index.vue +46 -1
  85. package/pages/c/_cluster/apps/charts/install.vue +10 -9
  86. package/pages/c/_cluster/explorer/index.vue +72 -9
  87. package/pages/c/_cluster/explorer/tools/index.vue +12 -5
  88. package/pages/c/_cluster/mcapps/index.vue +1 -1
  89. package/pages/c/_cluster/settings/brand.vue +0 -40
  90. package/pages/c/_cluster/settings/links.vue +200 -0
  91. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +232 -0
  92. package/pages/c/_cluster/uiplugins/InstallDialog.vue +242 -0
  93. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +284 -0
  94. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +130 -0
  95. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +253 -0
  96. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +115 -0
  97. package/pages/c/_cluster/uiplugins/index.vue +694 -0
  98. package/pages/diagnostic.vue +185 -101
  99. package/pages/docs/_doc.vue +3 -1
  100. package/pages/home.vue +21 -56
  101. package/pages/prefs.vue +108 -88
  102. package/pages/safeMode.vue +17 -0
  103. package/pages/support/index.vue +23 -15
  104. package/pkg/dynamic-importer.lib.js +4 -0
  105. package/plugins/dashboard-store/resource-class.js +2 -2
  106. package/plugins/formatters.js +15 -0
  107. package/plugins/plugin.js +56 -4
  108. package/plugins/steve/mutations.js +1 -1
  109. package/plugins/steve/subscribe.js +94 -72
  110. package/plugins/steve/web-worker.steve-sub-worker.js +24 -15
  111. package/promptRemove/management.cattle.io.globalrole.vue +47 -0
  112. package/promptRemove/management.cattle.io.roletemplate.vue +47 -0
  113. package/promptRemove/mixin/roleDeletionCheck.js +97 -0
  114. package/scripts/publish-shell.sh +1 -1
  115. package/scripts/sync-shell-deps +37 -0
  116. package/store/catalog.js +9 -8
  117. package/store/i18n.js +10 -1
  118. package/store/prefs.js +16 -0
  119. package/store/type-map.js +32 -5
  120. package/store/uiplugins.ts +15 -61
  121. package/utils/__tests__/object.test.ts +0 -24
  122. package/utils/__tests__/selector.test.ts +1 -1
  123. package/utils/dynamic-importer.js +4 -0
  124. package/utils/grafana.js +2 -6
  125. package/utils/socket.js +41 -20
  126. package/utils/string.js +1 -7
  127. package/utils/validators/formRules/__tests__/index.test.ts +108 -0
  128. package/utils/validators/formRules/index.ts +9 -1
  129. package/yarn-error.log +195 -0
  130. package/pages/plugins.vue +0 -387
  131. package/server/verdaccio-middleware.js +0 -56
package/store/prefs.js CHANGED
@@ -15,6 +15,7 @@ export const create = function(name, def, opt = {}) {
15
15
  const asCookie = opt.asCookie === true;
16
16
  const asUserPreference = opt.asUserPreference !== false;
17
17
  const options = opt.options;
18
+ const inheritFrom = opt.inheritFrom;
18
19
 
19
20
  definitions[name] = {
20
21
  def,
@@ -22,6 +23,7 @@ export const create = function(name, def, opt = {}) {
22
23
  parseJSON,
23
24
  asCookie,
24
25
  asUserPreference,
26
+ inheritFrom, // if value is not defined on server, we can default it to another pref
25
27
  mangleRead: opt.mangleRead, // Alter the value read from the API (to match old Rancher expectations)
26
28
  mangleWrite: opt.mangleWrite, // Alter the value written back to the API (ditto)
27
29
  };
@@ -72,6 +74,7 @@ export const HIDE_REPOS = create('hide-repos', [], { parseJSON });
72
74
  export const HIDE_DESC = create('hide-desc', [], { parseJSON });
73
75
  export const HIDE_SENSITIVE = create('hide-sensitive', true, { options: [true, false], parseJSON });
74
76
  export const SHOW_PRE_RELEASE = create('show-pre-release', false, { options: [false, true], parseJSON });
77
+ export const SHOW_CHART_MODE = create('chartMode', 'featured', { parseJSON });
75
78
 
76
79
  export const DATE_FORMAT = create('date-format', 'ddd, MMM D YYYY', {
77
80
  options: [
@@ -91,12 +94,18 @@ export const TIME_FORMAT = create('time-format', 'h:mm:ss a', {
91
94
  });
92
95
 
93
96
  export const TIME_ZONE = create('time-zone', 'local');
97
+ // DEV will be deprecated on v2.7.0, but is needed so that we can grab the value for the new settings that derived from it
98
+ // such as: VIEW_IN_API, ALL_NAMESPACES, THEME_SHORTCUT
94
99
  export const DEV = create('dev', false, { parseJSON });
100
+ export const VIEW_IN_API = create('view-in-api', false, { parseJSON, inheritFrom: DEV });
101
+ export const ALL_NAMESPACES = create('all-namespaces', false, { parseJSON, inheritFrom: DEV });
102
+ export const THEME_SHORTCUT = create('theme-shortcut', false, { parseJSON, inheritFrom: DEV });
95
103
  export const LAST_VISITED = create('last-visited', 'home', { parseJSON });
96
104
  export const SEEN_WHATS_NEW = create('seen-whatsnew', '', { parseJSON });
97
105
  export const READ_WHATS_NEW = create('read-whatsnew', '', { parseJSON });
98
106
  export const AFTER_LOGIN_ROUTE = create('after-login-route', 'home', { parseJSON } );
99
107
  export const HIDE_HOME_PAGE_CARDS = create('home-page-cards', {}, { parseJSON } );
108
+ export const PLUGIN_DEVELOPER = create('plugin-developer', false, { parseJSON, inheritFrom: DEV }); // Is the user a plugin developer?
100
109
 
101
110
  export const _RKE1 = 'rke1';
102
111
  export const _RKE2 = 'rke2';
@@ -105,6 +114,9 @@ export const PROVISIONER = create('provisioner', _RKE1, { options: [_RKE1, _RKE2
105
114
  // Promo for Cluster Tools feature on Cluster Dashboard page
106
115
  export const CLUSTER_TOOLS_TIP = create('hide-cluster-tools-tip', false, { parseJSON });
107
116
 
117
+ // Promo for Pod Security Policies (PSPs) being deprecated on kube version 1.25 on Cluster Dashboard page
118
+ export const PSP_DEPRECATION_BANNER = create('hide-psp-deprecation-banner', false, { parseJSON });
119
+
108
120
  // Maximum number of clusters to show in the slide-in menu
109
121
  export const MENU_MAX_CLUSTERS = create('menu-max-clusters', 4, { options: [2, 3, 4, 5, 6, 7, 8, 9, 10], parseJSON });
110
122
 
@@ -431,6 +443,10 @@ export const actions = {
431
443
  const definition = definitions[key];
432
444
  let value = clone(server.data[key]);
433
445
 
446
+ if (value === undefined && definition.inheritFrom) {
447
+ value = clone(server.data[definition.inheritFrom]);
448
+ }
449
+
434
450
  if ( value === undefined || key === ignoreKey) {
435
451
  continue;
436
452
  }
package/store/type-map.js CHANGED
@@ -122,7 +122,7 @@
122
122
  // )
123
123
  import { AGE, NAME, NAMESPACE as NAMESPACE_COL, STATE } from '@shell/config/table-headers';
124
124
  import { COUNT, SCHEMA, MANAGEMENT, NAMESPACE } from '@shell/config/types';
125
- import { DEV, EXPANDED_GROUPS, FAVORITE_TYPES } from '@shell/store/prefs';
125
+ import { VIEW_IN_API, EXPANDED_GROUPS, FAVORITE_TYPES } from '@shell/store/prefs';
126
126
  import {
127
127
  addObject, findBy, insertAt, isArray, removeObject, filterBy
128
128
  } from '@shell/utils/array';
@@ -131,7 +131,7 @@ import {
131
131
  ensureRegex, escapeHtml, escapeRegex, ucFirst, pluralize
132
132
  } from '@shell/utils/string';
133
133
  import {
134
- importList, importDetail, importEdit, listProducts, loadProduct, importCustomPromptRemove, resolveList, resolveEdit, resolveWindowComponent, importWindowComponent, resolveDetail, importDialog
134
+ importChart, importList, importDetail, importEdit, listProducts, loadProduct, importCustomPromptRemove, resolveList, resolveEdit, resolveWindowComponent, importWindowComponent, resolveChart, resolveDetail, importDialog
135
135
  } from '@shell/utils/dynamic-importer';
136
136
 
137
137
  import { NAME as EXPLORER } from '@shell/config/product/explorer';
@@ -168,6 +168,7 @@ export const IF_HAVE = {
168
168
  NOT_V1_ISTIO: 'not-v1-istio',
169
169
  MULTI_CLUSTER: 'multi-cluster',
170
170
  NEUVECTOR_NAMESPACE: 'neuvector-namespace',
171
+ ADMIN: 'admin-user',
171
172
  };
172
173
 
173
174
  export function DSL(store, product, module = 'type-map') {
@@ -360,6 +361,7 @@ export const state = function() {
360
361
  groupLabel: {},
361
362
  ignore: {},
362
363
  list: {},
364
+ chart: {},
363
365
  detail: {},
364
366
  edit: {},
365
367
  componentFor: {},
@@ -802,7 +804,7 @@ export const getters = {
802
804
  const module = findBy(state.products, 'name', product).inStore;
803
805
  const schemas = rootGetters[`${ module }/all`](SCHEMA);
804
806
  const counts = rootGetters[`${ module }/all`](COUNT)?.[0]?.counts || {};
805
- const isDev = rootGetters['prefs/get'](DEV);
807
+ const isDev = rootGetters['prefs/get'](VIEW_IN_API);
806
808
  const isBasic = mode === BASIC;
807
809
 
808
810
  const out = {};
@@ -1055,6 +1057,14 @@ export const getters = {
1055
1057
  };
1056
1058
  },
1057
1059
 
1060
+ hasCustomChart(state, getters, rootState) {
1061
+ return (rawType) => {
1062
+ const key = getters.componentFor(rawType);
1063
+
1064
+ return hasCustom(state, rootState, 'chart', key, key => resolveChart(key));
1065
+ };
1066
+ },
1067
+
1058
1068
  hasCustomDetail(state, getters, rootState) {
1059
1069
  return (rawType, subType) => {
1060
1070
  const key = getters.componentFor(rawType, subType);
@@ -1123,6 +1133,12 @@ export const getters = {
1123
1133
  };
1124
1134
  },
1125
1135
 
1136
+ importChart(state, getters, rootState) {
1137
+ return (rawType) => {
1138
+ return loadExtension(rootState, 'chart', getters.componentFor(rawType), importChart);
1139
+ };
1140
+ },
1141
+
1126
1142
  importDetail(state, getters, rootState) {
1127
1143
  return (rawType, subType) => {
1128
1144
  return loadExtension(rootState, 'detail', getters.componentFor(rawType, subType), importDetail);
@@ -1215,7 +1231,7 @@ export const getters = {
1215
1231
  activeProducts(state, getters, rootState, rootGetters) {
1216
1232
  const knownTypes = {};
1217
1233
  const knownGroups = {};
1218
- const isDev = rootGetters['prefs/get'](DEV);
1234
+ const isDev = rootGetters['prefs/get'](VIEW_IN_API);
1219
1235
 
1220
1236
  if ( state.schemaGeneration < 0 ) {
1221
1237
  // This does nothing, but makes activeProducts depend on schemaGeneration
@@ -1544,7 +1560,7 @@ export const mutations = {
1544
1560
  let obj = { ...options, match };
1545
1561
 
1546
1562
  if ( idx >= 0 ) {
1547
- obj = Object.assign(obj, state.typeOptions[idx]);
1563
+ obj = Object.assign(state.typeOptions[idx], obj);
1548
1564
  state.typeOptions.splice(idx, 1, obj);
1549
1565
  } else {
1550
1566
  const obj = Object.assign({}, options, { match });
@@ -1732,11 +1748,22 @@ function ifHave(getters, option) {
1732
1748
  case IF_HAVE.NEUVECTOR_NAMESPACE: {
1733
1749
  return getters[`cluster/all`](NAMESPACE).find(n => n.metadata.name === NEU_VECTOR_NAMESPACE);
1734
1750
  }
1751
+ case IF_HAVE.ADMIN: {
1752
+ return isAdminUser(getters);
1753
+ }
1735
1754
  default:
1736
1755
  return false;
1737
1756
  }
1738
1757
  }
1739
1758
 
1759
+ // Could list a larger set of resources that typically only an admin user would have
1760
+ export function isAdminUser(getters) {
1761
+ const canEditSettings = (getters['management/schemaFor'](MANAGEMENT.SETTING)?.resourceMethods || []).includes('PUT');
1762
+ const canEditFeatureFlags = (getters['management/schemaFor'](MANAGEMENT.FEATURE)?.resourceMethods || []).includes('PUT');
1763
+
1764
+ return canEditSettings && canEditFeatureFlags;
1765
+ }
1766
+
1740
1767
  // Is V1 Istio installed?
1741
1768
  function isV1Istio(getters) {
1742
1769
  const cluster = getters['currentCluster'];
@@ -3,20 +3,22 @@
3
3
 
4
4
  // import { addObject, removeObject } from '@shell/utils/array';
5
5
 
6
- import { allHash } from '@shell/utils/promise';
7
6
  import { Plugin } from '@shell/core/plugin';
8
7
 
9
8
  interface UIPluginState {
10
9
  plugins: Plugin[],
11
- catalog: any[],
12
- catalogs: string[],
10
+ errors: any,
11
+ }
12
+
13
+ interface LoadError {
14
+ name: string,
15
+ error: boolean,
13
16
  }
14
17
 
15
18
  export const state = function(): UIPluginState {
16
19
  return {
17
20
  plugins: [],
18
- catalog: [],
19
- catalogs: [''],
21
+ errors: {},
20
22
  };
21
23
  };
22
24
 
@@ -25,16 +27,16 @@ export const getters = {
25
27
  return state.plugins;
26
28
  },
27
29
 
28
- catalog: (state: any) => {
29
- return state.catalog;
30
- },
31
-
32
- catalogs: (state: any) => {
33
- return state.catalogs;
30
+ errors: (state: any) => {
31
+ return state.errors;
34
32
  },
35
33
  };
36
34
 
37
35
  export const mutations = {
36
+ setError(state: UIPluginState, error: LoadError) {
37
+ state.errors[error.name] = error.error;
38
+ },
39
+
38
40
  addPlugin(state: UIPluginState, plugin: Plugin) {
39
41
  // TODO: Duplicates?
40
42
  state.plugins.push(plugin);
@@ -47,59 +49,11 @@ export const mutations = {
47
49
  state.plugins.splice(index, 1);
48
50
  }
49
51
  },
50
-
51
- setCatalog(state: UIPluginState, catalog: any) {
52
- state.catalog = catalog;
53
- },
54
-
55
- addCatalog(state: UIPluginState, catalog: string) {
56
- state.catalogs.push(catalog);
57
- }
58
52
  };
59
53
 
60
54
  export const actions = {
61
- addCatalog( { commit, dispatch }: any, url: string ) {
62
- commit('addCatalog', url);
63
- },
64
-
65
- // This is just for PoC - we wouldn't get the catalog from Verdaccio
66
- // This fetches the catalog each time
67
- async loadCatalogs( { getters, commit, dispatch }: any) {
68
- const packages: any[] = [];
69
- const catalogHash = {} as any;
70
- const catalogs = getters['catalogs'];
71
-
72
- catalogs.forEach((url: string) => {
73
- const base = url;
74
-
75
- if (!url) {
76
- url = '/verdaccio/data/packages';
77
- } else {
78
- url = `/uiplugins-catalog/?${ url }`;
79
- }
80
-
81
- try {
82
- catalogHash[base] = dispatch('rancher/request', { url }, { root: true });
83
- } catch (err) {
84
- // Ignore errors... or all plugins fail
85
- console.warn('Unable to fetch catalog: ', url, err); // eslint-disable-line no-console
86
- }
87
- });
88
-
89
- const res = await allHash(catalogHash);
90
-
91
- Object.keys(res as any).forEach((r: string) => {
92
- const v: any = (res as any)[r];
93
-
94
- v.forEach((p: any) => {
95
- p.location = r;
96
- packages.push(p);
97
- });
98
- });
99
-
100
- const uiPackages = packages.filter((pkg: any) => pkg.rancher);
101
-
102
- commit('setCatalog', uiPackages);
55
+ setError( { commit }: any, error: LoadError ) {
56
+ commit('setError', error);
103
57
  },
104
58
 
105
59
  addPlugin({ commit }: any, plugin: Plugin) {
@@ -38,18 +38,6 @@ describe('fx: get', () => {
38
38
  expect(result).toBeUndefined();
39
39
  expect(() => result).not.toThrow();
40
40
  });
41
-
42
- it.each([
43
- 'key2.nonsense',
44
- 'non.sense',
45
- ])('should catch error and return undefined', (path) => {
46
- const obj = { key1: 'value', key2: { bat: 42, 'with.dots': 43 } };
47
-
48
- const result = get(obj, path);
49
-
50
- expect(result).toBeUndefined();
51
- expect(() => result).not.toThrow();
52
- });
53
41
  });
54
42
 
55
43
  describe('fx: getter', () => {
@@ -95,18 +83,6 @@ describe('fx: getter', () => {
95
83
  expect(result).toBeUndefined();
96
84
  expect(() => result).not.toThrow();
97
85
  });
98
-
99
- it.each([
100
- 'key2.nonsense',
101
- 'non.sense',
102
- ])('should catch error and return undefined', (path) => {
103
- const obj = { key1: 'value', key2: { bat: 42, 'with.dots': 43 } };
104
-
105
- const result = getter(path)(obj);
106
-
107
- expect(result).toBeUndefined();
108
- expect(() => result).not.toThrow();
109
- });
110
86
  });
111
87
  });
112
88
 
@@ -171,7 +171,7 @@ describe('fx: parse', () => {
171
171
  ['!some.prefix/key-bar_baz '],
172
172
  ['! some.prefix/key-bar_baz '],
173
173
  [' ! some.prefix/key-bar_baz '],
174
- ])('should parse expression %p to selector %p', (expression) => {
174
+ ])('should parse expression %p to selector %p using prefixes', (expression) => {
175
175
  const expected = {
176
176
  key: 'some.prefix/key-bar_baz',
177
177
  operator: 'DoesNotExist',
@@ -107,6 +107,10 @@ export function resolveList(key) {
107
107
  return require.resolve(`@shell/list/${ key }`);
108
108
  }
109
109
 
110
+ export function resolveChart(key) {
111
+ return require.resolve(`@shell/chart/${ key }`);
112
+ }
113
+
110
114
  export function resolveEdit(key) {
111
115
  return require.resolve(`@shell/edit/${ key }`);
112
116
  }
package/utils/grafana.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { haveV2Monitoring } from '@shell/utils/monitoring';
1
2
  import { parse as parseUrl, addParam } from '@shell/utils/url';
2
- import { MONITORING } from '@shell/config/types';
3
3
 
4
4
  export function computeDashboardUrl(embedUrl, clusterId, params) {
5
5
  const url = parseUrl(embedUrl);
@@ -21,7 +21,7 @@ export function computeDashboardUrl(embedUrl, clusterId, params) {
21
21
  }
22
22
 
23
23
  export async function dashboardExists(store, clusterId, embedUrl, storeName = 'cluster') {
24
- if (!isMonitoringInstalled(store.getters, storeName)) {
24
+ if ( !haveV2Monitoring(store.getters) ) {
25
25
  return false;
26
26
  }
27
27
 
@@ -81,7 +81,3 @@ export async function failedProposals(dispatch, clusterId) {
81
81
 
82
82
  return response.data.result[0]?.values?.[0]?.[1] || 0;
83
83
  }
84
-
85
- function isMonitoringInstalled(getters, storeName = 'cluster') {
86
- return !!getters[`${ storeName }/schemaFor`](MONITORING.SERVICEMONITOR);
87
- }
package/utils/socket.js CHANGED
@@ -31,6 +31,8 @@ export default class Socket extends EventTarget {
31
31
  hasBeenOpen = false;
32
32
  hasReconnected = false;
33
33
  protocol = null;
34
+ maxTries = null;
35
+ tries = 0;
34
36
 
35
37
  // "Private"
36
38
  socket = null;
@@ -38,17 +40,19 @@ export default class Socket extends EventTarget {
38
40
  framesReceived = 0;
39
41
  frameTimer;
40
42
  reconnectTimer;
41
- tries = 0;
42
43
  disconnectCbs = [];
43
44
  disconnectedAt = 0;
44
45
  closingId = 0;
45
46
 
46
- constructor(url, autoReconnect = true, frameTimeout = null, protocol = null) {
47
+ constructor(url, autoReconnect = true, frameTimeout = null, protocol = null, maxTries = null) {
47
48
  super();
48
49
 
49
50
  this.setUrl(url);
50
51
  this.autoReconnect = autoReconnect;
51
52
  this.protocol = protocol;
53
+ // maxTries = null === never stop trying to reconnect
54
+ // allow maxTries to be defined on individual sockets bc not all will clearly warn the user that we've stopped trying
55
+ this.maxTries = maxTries;
52
56
 
53
57
  if ( frameTimeout !== null ) {
54
58
  this.frameTimeout = frameTimeout;
@@ -57,10 +61,10 @@ export default class Socket extends EventTarget {
57
61
 
58
62
  setUrl(url) {
59
63
  if ( !url.match(/wss?:\/\//) ) {
60
- url = window.location.origin.replace(/^http/, 'ws') + url;
64
+ url = self.location.origin.replace(/^http/, 'ws') + url;
61
65
  }
62
66
 
63
- if ( window.location.protocol === 'https:' && url.startsWith(INSECURE) ) {
67
+ if ( self.location.protocol === 'https:' && url.startsWith(INSECURE) ) {
64
68
  url = SECURE + url.substr(INSECURE.length);
65
69
  }
66
70
 
@@ -74,6 +78,10 @@ export default class Socket extends EventTarget {
74
78
  return;
75
79
  }
76
80
 
81
+ if (this.state !== STATE_RECONNECTING) {
82
+ this.state = STATE_CONNECTING;
83
+ }
84
+
77
85
  Object.assign(this.metadata, metadata);
78
86
 
79
87
  const id = sockId++;
@@ -83,6 +91,8 @@ export default class Socket extends EventTarget {
83
91
 
84
92
  let socket;
85
93
 
94
+ this.tries++;
95
+
86
96
  if ( this.protocol ) {
87
97
  socket = new WebSocket(url, this.protocol);
88
98
  } else {
@@ -209,11 +219,11 @@ export default class Socket extends EventTarget {
209
219
  this._log('opened');
210
220
  const now = (new Date()).getTime();
211
221
 
212
- const at = this.disconnectedAt;
213
- let after = 0;
222
+ const atTime = this.disconnectedAt;
223
+ let afterMilliseconds = 0;
214
224
 
215
- if ( at ) {
216
- after = now - at;
225
+ if ( atTime ) {
226
+ afterMilliseconds = now - atTime;
217
227
  }
218
228
 
219
229
  if ( this.hasBeenOpen ) {
@@ -225,7 +235,8 @@ export default class Socket extends EventTarget {
225
235
  this.framesReceived = 0;
226
236
  this.disconnectedAt = 0;
227
237
 
228
- this.dispatchEvent(new CustomEvent(EVENT_CONNECTED, { detail: { tries: this.tries, after } }));
238
+ this.dispatchEvent(new CustomEvent(EVENT_CONNECTED, { detail: { tries: this.tries, afterMilliseconds } }));
239
+ this.tries = 0;
229
240
  this._resetWatchdog();
230
241
  clearTimeout(this.reconnectTimer);
231
242
  }
@@ -291,18 +302,26 @@ export default class Socket extends EventTarget {
291
302
  this.dispatchEvent(e);
292
303
  warningShown = true;
293
304
  } else if ( this.autoReconnect ) {
294
- if (this.tries === 0) {
295
- const e = new CustomEvent(EVENT_DISCONNECT_ERROR);
305
+ this.state = STATE_RECONNECTING;
306
+
307
+ if (this.maxTries && this.tries > 1 && this.tries <= this.maxTries) {
308
+ // dispatch an event which will trigger a growl from steve-plugin sockets warning users that we've lost connection and are attemping to reconnect
309
+ const e = new CustomEvent(EVENT_CONNECT_ERROR);
296
310
 
297
311
  this.dispatchEvent(e);
298
312
  }
299
- this.state = STATE_RECONNECTING;
300
- this.tries++;
301
- const delay = Math.max(1000, Math.min(1000 * this.tries, 30000));
302
313
 
303
- this.reconnectTimer = setTimeout(() => {
304
- this.connect();
305
- }, delay);
314
+ if (this.maxTries && this.tries > this.maxTries) {
315
+ this.state = STATE_DISCONNECTED;
316
+ // dispatch an event which will trigger a growl from steve-plugin sockets warning users that we've given up trying to reconnect
317
+ this.dispatchEvent(new CustomEvent(EVENT_DISCONNECT_ERROR));
318
+ } else {
319
+ const delay = Math.max(1000, Math.min(1000 * this.tries, 30000));
320
+
321
+ this.reconnectTimer = setTimeout(() => {
322
+ this.connect();
323
+ }, delay);
324
+ }
306
325
  } else {
307
326
  this.state = STATE_DISCONNECTED;
308
327
  }
@@ -315,10 +334,12 @@ export default class Socket extends EventTarget {
315
334
  }
316
335
 
317
336
  _log(...args) {
318
- args.unshift('Socket');
337
+ const message = JSON.parse(JSON.stringify([...args]));
338
+
339
+ message.unshift('Socket');
319
340
 
320
- args.push(`(state=${ this.state }, id=${ this.socket ? this.socket.sockId : 0 })`);
341
+ message.push(`(state=${ this.state }, id=${ this.socket ? this.socket.sockId : 0 })`);
321
342
 
322
- console.log(args.join(' ')); // eslint-disable-line no-console
343
+ console.log(message.join(' ')); // eslint-disable-line no-console
323
344
  }
324
345
  }
package/utils/string.js CHANGED
@@ -70,13 +70,7 @@ export function random32(count) {
70
70
  const out = [];
71
71
  let i;
72
72
 
73
- if ( process.server ) {
74
- const crypto = require('crypto');
75
-
76
- for ( i = 0 ; i < count ; i++ ) {
77
- out[i] = crypto.randomBytes(4).readUInt32BE(0, true);
78
- }
79
- } else if (window.crypto && window.crypto.getRandomValues) {
73
+ if (window.crypto && window.crypto.getRandomValues) {
80
74
  const tmp = new Uint32Array(count);
81
75
 
82
76
  window.crypto.getRandomValues(tmp);
@@ -906,6 +906,114 @@ describe('formRules', () => {
906
906
  expect(formRuleResult).toStrictEqual(expectedResult);
907
907
  });
908
908
 
909
+ // this rule is pretty much identical to the standard hostname, but also allows for wildcards
910
+ it('"wildcardHostname" : returns undefined when value is valid hostname', () => {
911
+ const testValue = 'www.url.com';
912
+ const formRuleResult = formRules.wildcardHostname(testValue);
913
+
914
+ expect(formRuleResult).toBeUndefined();
915
+ });
916
+
917
+ it('"wildcardHostname" : returns expected message when value starts with a dot', () => {
918
+ const testValue = '.hostname';
919
+ const formRuleResult = formRules.wildcardHostname(testValue);
920
+ const expectedResult = JSON.stringify({ message: 'validation.dns.hostname.startDot', key: 'testDisplayKey' });
921
+
922
+ expect(formRuleResult).toStrictEqual(expectedResult);
923
+ });
924
+
925
+ it('"wildcardHostname" : returns expected message when value starts is too long for a hostname', () => {
926
+ const testValue = 'There.are.many.variations.of.passages.of.Lorem.Ipsum.available.but.the.majority.have.suffered.alteration.in.some.form.by.injected.humour.or.randomised.words.which.dont.look.even.slightly.believable.If.you.are.going.to.use.a.passage.of.Lorem.Ipsum.you.need';
927
+ const formRuleResult = formRules.wildcardHostname(testValue);
928
+ const expectedResult = JSON.stringify({
929
+ message: 'validation.dns.hostname.tooLong', key: 'testDisplayKey', max: 253
930
+ });
931
+
932
+ expect(formRuleResult).toStrictEqual(expectedResult);
933
+ });
934
+
935
+ it('"wildcardHostname" : returns expected message when value contains invalid characters', () => {
936
+ const testValue = 'www.host*name.com';
937
+ const formRuleResult = formRules.wildcardHostname(testValue);
938
+ const expectedResult = JSON.stringify({
939
+ message: 'validation.chars', key: 'testDisplayKey', count: 1, chars: '"*"'
940
+ });
941
+
942
+ expect(formRuleResult).toStrictEqual(expectedResult);
943
+ });
944
+
945
+ it('"wildcardHostname" : returns expected message when value contains a space character', () => {
946
+ const testValue = 'www.host name.com';
947
+ const formRuleResult = formRules.wildcardHostname(testValue);
948
+ const expectedResult = JSON.stringify({
949
+ message: 'validation.chars', key: 'testDisplayKey', count: 1, chars: 'Space'
950
+ });
951
+
952
+ expect(formRuleResult).toStrictEqual(expectedResult);
953
+ });
954
+
955
+ it('"wildcardHostname" : returns expected message when hostname label starts with a dash', () => {
956
+ const testValue = 'www.-hostname.com';
957
+ const formRuleResult = formRules.wildcardHostname(testValue);
958
+ const expectedResult = JSON.stringify({ message: 'validation.dns.hostname.startHyphen', key: 'testDisplayKey' });
959
+
960
+ expect(formRuleResult).toStrictEqual(expectedResult);
961
+ });
962
+
963
+ it('"wildcardHostname" : returns expected message when hostname label ends with a dash', () => {
964
+ const testValue = 'www.hostname-.com';
965
+ const formRuleResult = formRules.wildcardHostname(testValue);
966
+ const expectedResult = JSON.stringify({ message: 'validation.dns.hostname.endHyphen', key: 'testDisplayKey' });
967
+
968
+ expect(formRuleResult).toStrictEqual(expectedResult);
969
+ });
970
+
971
+ it('"wildcardHostname" : returns expected message when hostname label contains a double-dash at the third character position', () => {
972
+ const testValue = 'www.ho--stname.com';
973
+ const formRuleResult = formRules.wildcardHostname(testValue);
974
+ const expectedResult = JSON.stringify({ message: 'validation.dns.doubleHyphen', key: 'testDisplayKey' });
975
+
976
+ expect(formRuleResult).toStrictEqual(expectedResult);
977
+ });
978
+
979
+ it('"wildcardHostname" : returns expected message when hostname label is too long', () => {
980
+ const testValue = 'www.0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.com';
981
+ const formRuleResult = formRules.wildcardHostname(testValue);
982
+ const expectedResult = JSON.stringify({
983
+ message: 'validation.dns.hostname.tooLongLabel', key: 'testDisplayKey', max: 63
984
+ });
985
+
986
+ expect(formRuleResult).toStrictEqual(expectedResult);
987
+ });
988
+
989
+ it('"wildcardHostname" : returns expected message when wildcard character is not the first part', () => {
990
+ const testValue = 'www.*.hostname.com';
991
+ const formRuleResult = formRules.wildcardHostname(testValue);
992
+ const expectedResult = JSON.stringify({
993
+ message: 'validation.chars', key: 'testDisplayKey', count: 1, chars: '"*"'
994
+ });
995
+
996
+ expect(formRuleResult).toStrictEqual(expectedResult);
997
+ });
998
+
999
+ it('"wildcardHostname" : returns expected message when wildcard character is at the beginning but not its own part', () => {
1000
+ const testValue = '*hostname.com';
1001
+ const formRuleResult = formRules.wildcardHostname(testValue);
1002
+
1003
+ const expectedResult = JSON.stringify({
1004
+ message: 'validation.chars', key: 'testDisplayKey', count: 1, chars: '"*"'
1005
+ });
1006
+
1007
+ expect(formRuleResult).toStrictEqual(expectedResult);
1008
+ });
1009
+
1010
+ it('"wildcardHostname" : returns valid when wildcard character is the first part', () => {
1011
+ const testValue = '*.hostname.com';
1012
+ const formRuleResult = formRules.wildcardHostname(testValue);
1013
+
1014
+ expect(formRuleResult).toBeUndefined();
1015
+ });
1016
+
909
1017
  it('"absolutePath" : return expected message when path doesn\'t begin with a "/"', () => {
910
1018
  const formRuleResult = formRules.absolutePath('absolute_path');
911
1019
  const expectedResult = JSON.stringify({ message: 'validation.path', key: 'testDisplayKey' });
@@ -204,6 +204,13 @@ export default function(t: (key: string, options?: any) => string, opt: {display
204
204
  }
205
205
  };
206
206
 
207
+ const wildcardHostname: Validator = (val: string) => {
208
+ // allow wildcard in first part of hostname
209
+ val = val ? val.replace(/^\*\./, '') : val;
210
+
211
+ return hostname(val);
212
+ };
213
+
207
214
  const externalName: Validator = (val: string) => {
208
215
  if (isEmpty(val)) {
209
216
  return t('validation.service.externalName.none');
@@ -442,6 +449,7 @@ export default function(t: (key: string, options?: any) => string, opt: {display
442
449
  hostname,
443
450
  testRule,
444
451
  subDomain,
445
- absolutePath
452
+ absolutePath,
453
+ wildcardHostname,
446
454
  };
447
455
  }