@rancher/shell 0.3.1 → 0.3.3

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 (49) hide show
  1. package/assets/styles/global/_gauges.scss +1 -1
  2. package/assets/styles/global/_layout.scss +4 -0
  3. package/assets/styles/themes/_dark.scss +1 -0
  4. package/assets/translations/en-us.yaml +6 -1
  5. package/assets/translations/zh-hans.yaml +175 -44
  6. package/components/ActionMenu.vue +28 -7
  7. package/components/DetailTop.vue +14 -1
  8. package/components/ExtensionPanel.vue +42 -0
  9. package/components/IconOrSvg.vue +31 -2
  10. package/components/ResourceDetail/Masthead.vue +16 -3
  11. package/components/ResourceList/index.vue +15 -2
  12. package/components/ResourceTable.vue +3 -1
  13. package/components/SortableTable/THead.vue +6 -9
  14. package/components/SortableTable/filtering.js +1 -1
  15. package/components/SortableTable/selection.js +15 -3
  16. package/components/Tabbed/Tab.vue +1 -1
  17. package/components/form/InputWithSelect.vue +1 -0
  18. package/components/form/ResourceTabs/index.vue +23 -0
  19. package/components/nav/Header.vue +69 -5
  20. package/config/harvester-manager-types.js +1 -0
  21. package/config/product/backup.js +1 -1
  22. package/config/query-params.js +1 -0
  23. package/config/uiplugins.js +3 -3
  24. package/core/plugin-helpers.js +171 -0
  25. package/core/plugin.ts +61 -1
  26. package/core/plugins.js +33 -0
  27. package/core/types.ts +128 -2
  28. package/edit/catalog.cattle.io.clusterrepo.vue +3 -0
  29. package/edit/fleet.cattle.io.gitrepo.vue +12 -3
  30. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +14 -2
  31. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -0
  32. package/edit/provisioning.cattle.io.cluster/import.vue +1 -1
  33. package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -18
  34. package/package.json +2 -1
  35. package/pages/c/_cluster/apps/charts/index.vue +17 -0
  36. package/pages/c/_cluster/explorer/index.vue +39 -0
  37. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +2 -0
  38. package/pages/c/_cluster/uiplugins/InstallDialog.vue +3 -0
  39. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +17 -3
  40. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +2 -0
  41. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +1 -0
  42. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +2 -0
  43. package/pages/c/_cluster/uiplugins/index.vue +18 -4
  44. package/plugins/dashboard-store/resource-class.js +16 -1
  45. package/rancher-components/components/Banner/Banner.vue +1 -0
  46. package/store/action-menu.js +4 -3
  47. package/store/prefs.js +19 -12
  48. package/store/type-map.js +26 -0
  49. package/types/shell/index.d.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -138,6 +138,7 @@
138
138
  "vue-server-renderer": "2.6.14",
139
139
  "vue-shortkey": "3.1.7",
140
140
  "vue-template-compiler": "2.6.14",
141
+ "vue-virtual-scroll-list": "^2.3.4",
141
142
  "vue2-transitions": "0.3.0",
142
143
  "vuedraggable": "2.24.3",
143
144
  "vuex": "3.6.2",
@@ -21,6 +21,7 @@ import { CATALOG } from '@shell/config/labels-annotations';
21
21
  import { isUIPlugin } from '@shell/config/uiplugins';
22
22
 
23
23
  export default {
24
+ name: 'Charts',
24
25
  components: {
25
26
  AsyncButton,
26
27
  Banner,
@@ -53,6 +54,7 @@ export default {
53
54
  searchQuery: null,
54
55
  showDeprecated: null,
55
56
  showHidden: null,
57
+ isPspLegacy: false,
56
58
  chartOptions: [
57
59
  {
58
60
  label: 'Browse',
@@ -239,6 +241,14 @@ export default {
239
241
  }
240
242
  },
241
243
 
244
+ created() {
245
+ const release = this.currentCluster?.status?.version.gitVersion || '';
246
+ const isRKE2 = release.includes('rke2');
247
+ const version = release.match(/\d+/g);
248
+
249
+ this.isPspLegacy = version?.length ? isRKE2 && (+version[0] === 1 && +version[1] < 25) : false;
250
+ },
251
+
242
252
  methods: {
243
253
  colorForChart(chart) {
244
254
  const repos = this.repoOptions;
@@ -363,6 +373,13 @@ export default {
363
373
  @clicked="(row) => selectChart(row)"
364
374
  />
365
375
  </div>
376
+
377
+ <Banner
378
+ v-if="isPspLegacy"
379
+ color="warning"
380
+ :label="t('catalog.chart.banner.legacy')"
381
+ />
382
+
366
383
  <TypeDescription resource="chart" />
367
384
  <div class="left-right-split">
368
385
  <Select
@@ -40,6 +40,9 @@ import { isEmpty } from '@shell/utils/object';
40
40
  import ConfigBadge from './ConfigBadge';
41
41
  import EventsTable from './EventsTable';
42
42
  import { fetchClusterResources } from './explorer-utils';
43
+ import SimpleBox from '@shell/components/SimpleBox';
44
+ import { ExtensionPoint, CardLocation } from '@shell/core/types';
45
+ import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
43
46
 
44
47
  export const RESOURCES = [NAMESPACE, INGRESS, PV, WORKLOAD_TYPES.DEPLOYMENT, WORKLOAD_TYPES.STATEFUL_SET, WORKLOAD_TYPES.JOB, WORKLOAD_TYPES.DAEMON_SET, SERVICE];
45
48
 
@@ -71,6 +74,7 @@ export default {
71
74
  EmberPage,
72
75
  ConfigBadge,
73
76
  EventsTable,
77
+ SimpleBox,
74
78
  },
75
79
 
76
80
  mixins: [metricPoller],
@@ -126,6 +130,7 @@ export default {
126
130
  ETCD_METRICS_SUMMARY_URL,
127
131
  clusterCounts,
128
132
  selectedTab: 'cluster-events',
133
+ extensionCards: getApplicableExtensionEnhancements(this, ExtensionPoint.CARD, CardLocation.CLUSTER_DASHBOARD_CARD, this.$route),
129
134
  };
130
135
  },
131
136
 
@@ -492,6 +497,27 @@ export default {
492
497
  />
493
498
  </div>
494
499
 
500
+ <!-- extension cards -->
501
+ <div
502
+ v-if="extensionCards.length"
503
+ class="extension-card-container mt-20"
504
+ >
505
+ <SimpleBox
506
+ v-for="item, i in extensionCards"
507
+ :key="`extensionCards${i}`"
508
+ class="extension-card"
509
+ :style="item.style"
510
+ >
511
+ <h3>
512
+ {{ item.label }}
513
+ </h3>
514
+ <component
515
+ :is="item.component"
516
+ :resource="currentCluster"
517
+ />
518
+ </SimpleBox>
519
+ </div>
520
+
495
521
  <h3
496
522
  v-if="!hasV1Monitoring && hasStats"
497
523
  class="mt-40"
@@ -631,6 +657,19 @@ export default {
631
657
  </template>
632
658
 
633
659
  <style lang="scss" scoped>
660
+ .extension-card-container {
661
+ display: grid;
662
+ grid-template-columns: repeat(auto-fit, minmax(calc((100%/3) - 40px), 1fr));
663
+ grid-column-gap: 15px;
664
+ grid-row-gap: 20px;
665
+ }
666
+
667
+ @media only screen and (max-width: map-get($breakpoints, "--viewport-9")) {
668
+ .extension-card-container {
669
+ grid-template-columns: 1fr !important;
670
+ }
671
+ }
672
+
634
673
  .cluster-dashboard-glance {
635
674
  border-top: 1px solid var(--border);
636
675
  border-bottom: 1px solid var(--border);
@@ -181,12 +181,14 @@ export default {
181
181
  <div class="dialog-buttons mt-20">
182
182
  <button
183
183
  class="btn role-secondary"
184
+ data-testid="dev-install-ext-modal-cancel-btn"
184
185
  @click="closeDialog()"
185
186
  >
186
187
  {{ t('generic.cancel') }}
187
188
  </button>
188
189
  <AsyncButton
189
190
  mode="load"
191
+ data-testid="dev-install-ext-modal-install-btn"
190
192
  @click="loadPlugin"
191
193
  />
192
194
  </div>
@@ -243,6 +243,7 @@ export default {
243
243
  label-key="plugins.install.version"
244
244
  :options="versionOptions"
245
245
  class="version-selector mt-10"
246
+ data-testid="install-ext-modal-select-version"
246
247
  />
247
248
  <div v-else>
248
249
  {{ t('plugins.install.version') }} {{ version }}
@@ -252,12 +253,14 @@ export default {
252
253
  <button
253
254
  :disabled="busy"
254
255
  class="btn role-secondary"
256
+ data-testid="install-ext-modal-cancel-btn"
255
257
  @click="closeDialog(false)"
256
258
  >
257
259
  {{ t('generic.cancel') }}
258
260
  </button>
259
261
  <AsyncButton
260
262
  :mode="buttonMode"
263
+ data-testid="install-ext-modal-install-btn"
261
264
  @click="install"
262
265
  />
263
266
  </div>
@@ -113,10 +113,12 @@ export default {
113
113
  <div
114
114
  v-if="showSlideIn"
115
115
  class="glass"
116
+ data-testid="extension-details-bg"
116
117
  @click="hide()"
117
118
  />
118
119
  <div
119
120
  class="slideIn"
121
+ data-testid="extension-details"
120
122
  :class="{'hide': false, 'slideIn__show': showSlideIn}"
121
123
  >
122
124
  <div
@@ -142,8 +144,11 @@ export default {
142
144
  >
143
145
  </div>
144
146
  <div class="plugin-title">
145
- <h2 class="slideIn__header">
146
- {{ info.name }}
147
+ <h2
148
+ class="slideIn__header"
149
+ data-testid="extension-details-title"
150
+ >
151
+ {{ info.label }}
147
152
  </h2>
148
153
  <p class="plugin-description">
149
154
  {{ info.description }}
@@ -153,6 +158,7 @@ export default {
153
158
  <div class="slideIn__header__buttons">
154
159
  <div
155
160
  class="slideIn__header__button"
161
+ data-testid="extension-details-close"
156
162
  @click="showSlideIn = false"
157
163
  >
158
164
  <i class="icon icon-close" />
@@ -224,8 +230,11 @@ export default {
224
230
  </div>
225
231
  <div v-if="!info.versions.length">
226
232
  <h3>
227
- {{ t('plugins.version', { version: info.displayVersion }) }}
233
+ {{ t('plugins.info.versions') }}
228
234
  </h3>
235
+ <div class="version-link version-active version-builtin">
236
+ {{ info.displayVersion }}
237
+ </div>
229
238
  </div>
230
239
  </div>
231
240
  </div>
@@ -236,6 +245,7 @@ export default {
236
245
  position: fixed;
237
246
  top: 0;
238
247
  left: 0;
248
+ z-index: 1;
239
249
 
240
250
  $slideout-width: 35%;
241
251
  $title-height: 50px;
@@ -345,6 +355,10 @@ export default {
345
355
  color: var(--link-text);
346
356
  background: var(--link);
347
357
  }
358
+
359
+ &.version-builtin {
360
+ display: inline-block;
361
+ }
348
362
  }
349
363
 
350
364
  &__header {
@@ -107,6 +107,7 @@ export default {
107
107
  name="confirm-uiplugins-remove"
108
108
  :title="t('plugins.setup.remove.title')"
109
109
  mode="disable"
110
+ data-testid="disable-ext-modal"
110
111
  @okay="doRemove"
111
112
  >
112
113
  <template>
@@ -121,6 +122,7 @@ export default {
121
122
  v-model="removeRepo"
122
123
  :primary="true"
123
124
  label-key="plugins.setup.remove.registry.title"
125
+ data-testid="disable-ext-modal-remove-repo"
124
126
  />
125
127
  <div class="checkbox-info">
126
128
  {{ t('plugins.setup.remove.registry.prompt') }}
@@ -214,6 +214,7 @@ export default {
214
214
  :manual="true"
215
215
  :current-phase="buttonState"
216
216
  class="enable-plugin-support"
217
+ data-testid="extension-enable-operator"
217
218
  @click="enable"
218
219
  />
219
220
  </div>
@@ -84,12 +84,14 @@ export default {
84
84
  <button
85
85
  :disabled="busy"
86
86
  class="btn role-secondary"
87
+ data-testid="uninstall-ext-modal-cancel-btn"
87
88
  @click="closeDialog(false)"
88
89
  >
89
90
  {{ t('generic.cancel') }}
90
91
  </button>
91
92
  <AsyncButton
92
93
  mode="uninstall"
94
+ data-testid="uninstall-ext-modal-uninstall-btn"
93
95
  @click="uninstall()"
94
96
  />
95
97
  </div>
@@ -221,10 +221,12 @@ export default {
221
221
  const chart = all.find(c => c.name === p.name);
222
222
 
223
223
  if (!chart) {
224
- // A pluign is loaded, but there is no chart, so add an item so that it shows up
224
+ // A plugin is loaded, but there is no chart, so add an item so that it shows up
225
+ const rancher = typeof p.metadata?.rancher === 'object' ? p.metadata.rancher : {};
226
+ const label = rancher[UI_PLUGIN_CHART_ANNOTATIONS.DISPLAY_NAME] || p.name;
225
227
  const item = {
226
228
  name: p.name,
227
- label: p.name,
229
+ label,
228
230
  description: p.metadata?.description,
229
231
  icon: p.metadata?.icon,
230
232
  id: p.id,
@@ -492,10 +494,13 @@ export default {
492
494
  <template>
493
495
  <div class="plugins">
494
496
  <div class="plugin-header">
495
- <h2>{{ t('plugins.title') }}</h2>
497
+ <h2 data-testid="extensions-page-title">
498
+ {{ t('plugins.title') }}
499
+ </h2>
496
500
  <div
497
501
  v-if="reloadRequired"
498
502
  class="plugin-reload-banner mr-20"
503
+ data-testid="extension-reload-banner"
499
504
  >
500
505
  <i class="icon icon-checkmark mr-10" />
501
506
  <span>
@@ -503,6 +508,7 @@ export default {
503
508
  </span>
504
509
  <button
505
510
  class="ml-10 btn btn-sm role-primary"
511
+ data-testid="extension-reload-banner-reload-btn"
506
512
  @click="reload()"
507
513
  >
508
514
  {{ t('generic.reload') }}
@@ -514,6 +520,7 @@ export default {
514
520
  aria-haspopup="true"
515
521
  type="button"
516
522
  class="btn actions role-secondary"
523
+ data-testid="extensions-page-menu"
517
524
  @click="setMenu"
518
525
  >
519
526
  <i class="icon icon-actions" />
@@ -555,15 +562,18 @@ export default {
555
562
  <Tabbed
556
563
  ref="tabs"
557
564
  :tabs-only="true"
565
+ data-testid="extension-tabs"
558
566
  @changed="filterChanged"
559
567
  >
560
568
  <Tab
561
569
  name="installed"
570
+ data-testid="extension-tab-installed"
562
571
  label-key="plugins.tabs.installed"
563
572
  :weight="20"
564
573
  />
565
574
  <Tab
566
575
  name="available"
576
+ data-testid="extension-tab-available"
567
577
  label-key="plugins.tabs.available"
568
578
  :weight="19"
569
579
  />
@@ -606,6 +616,7 @@ export default {
606
616
  v-for="plugin in list"
607
617
  :key="plugin.name"
608
618
  class="plugin"
619
+ :data-testid="`extension-card-${plugin.name}`"
609
620
  @click="showPluginDetail(plugin)"
610
621
  >
611
622
  <div
@@ -709,6 +720,7 @@ export default {
709
720
  <button
710
721
  v-if="!plugin.builtin"
711
722
  class="btn role-secondary"
723
+ :data-testid="`extension-card-uninstall-btn-${plugin.name}`"
712
724
  @click="showUninstallDialog(plugin, $event)"
713
725
  >
714
726
  {{ t('plugins.uninstall.label') }}
@@ -716,6 +728,7 @@ export default {
716
728
  <button
717
729
  v-if="plugin.upgrade"
718
730
  class="btn role-secondary"
731
+ :data-testid="`extension-card-update-btn-${plugin.name}`"
719
732
  @click="showInstallDialog(plugin, 'update', $event)"
720
733
  >
721
734
  {{ t('plugins.update.label') }}
@@ -723,6 +736,7 @@ export default {
723
736
  <button
724
737
  v-if="!plugin.upgrade && plugin.versions.length > 1"
725
738
  class="btn role-secondary"
739
+ :data-testid="`extension-card-rollback-btn-${plugin.name}`"
726
740
  @click="showInstallDialog(plugin, 'rollback', $event)"
727
741
  >
728
742
  {{ t('plugins.rollback.label') }}
@@ -734,6 +748,7 @@ export default {
734
748
  >
735
749
  <button
736
750
  class="btn role-secondary"
751
+ :data-testid="`extension-card-install-btn-${plugin.name}`"
737
752
  @click="showInstallDialog(plugin, 'install', $event)"
738
753
  >
739
754
  {{ t('plugins.install.label') }}
@@ -867,7 +882,6 @@ export default {
867
882
  width: 40px;
868
883
  -o-object-fit: contain;
869
884
  object-fit: contain;
870
- position: relative;
871
885
  top: 2px;
872
886
  left: 2px;
873
887
  }
@@ -37,6 +37,9 @@ import Vue from 'vue';
37
37
 
38
38
  import { normalizeType } from './normalize';
39
39
 
40
+ import { ExtensionPoint, ActionLocation } from '@shell/core/types';
41
+ import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
42
+
40
43
  const STRING_LIKE_TYPES = [
41
44
  'string',
42
45
  'date',
@@ -851,7 +854,11 @@ export default class Resource {
851
854
 
852
855
  // You can add custom actions by overriding your own availableActions (and probably reading super._availableActions)
853
856
  get _availableActions() {
854
- const all = [
857
+ // get menu actions available by plugins configuration
858
+ const currentRoute = this.currentRouter().app._route;
859
+ const extensionMenuActions = getApplicableExtensionEnhancements(this.$rootState, ExtensionPoint.ACTION, ActionLocation.TABLE, currentRoute, this);
860
+
861
+ let all = [
855
862
  { divider: true },
856
863
  {
857
864
  action: this.canUpdate ? 'goToEdit' : 'goToViewConfig',
@@ -899,6 +906,13 @@ export default class Resource {
899
906
  },
900
907
  ];
901
908
 
909
+ // Extension actions get added to the end, so add a divider if there are any
910
+ if (extensionMenuActions.length) {
911
+ // Add a divider first
912
+ all.push({ divider: true });
913
+ all = all.concat(extensionMenuActions);
914
+ }
915
+
902
916
  return all;
903
917
  }
904
918
 
@@ -1051,6 +1065,7 @@ export default class Resource {
1051
1065
  async _save(opt = {}) {
1052
1066
  delete this.__rehydrate;
1053
1067
  delete this.__clone;
1068
+
1054
1069
  const forNew = !this.id;
1055
1070
 
1056
1071
  const errors = await this.validationErrors(this, opt.ignoreFields);
@@ -121,6 +121,7 @@ $icon-size: 24px;
121
121
  margin: 15px 0;
122
122
  position: relative;
123
123
  width: 100%;
124
+ color: var(--body-text);
124
125
 
125
126
  &__icon {
126
127
  width: $icon-size * 2;
@@ -22,9 +22,10 @@ export const state = function() {
22
22
  };
23
23
 
24
24
  export const getters = {
25
- showing: state => state.show,
26
- elem: state => state.elem,
27
- event: state => state.event,
25
+ showing: state => state.show,
26
+ elem: state => state.elem,
27
+ event: state => state.event,
28
+ resources: state => state.resources,
28
29
 
29
30
  options(state) {
30
31
  let selected = state.resources;
package/store/prefs.js CHANGED
@@ -136,12 +136,13 @@ export const state = function() {
136
136
  return {
137
137
  cookiesLoaded: false,
138
138
  data: {},
139
+ definitions,
139
140
  };
140
141
  };
141
142
 
142
143
  export const getters = {
143
144
  get: state => (key) => {
144
- const definition = definitions[key];
145
+ const definition = state.definitions[key];
145
146
 
146
147
  if (!definition) {
147
148
  throw new Error(`Unknown preference: ${ key }`);
@@ -159,7 +160,7 @@ export const getters = {
159
160
  },
160
161
 
161
162
  defaultValue: state => (key) => {
162
- const definition = definitions[key];
163
+ const definition = state.definitions[key];
163
164
 
164
165
  if (!definition) {
165
166
  throw new Error(`Unknown preference: ${ key }`);
@@ -169,7 +170,7 @@ export const getters = {
169
170
  },
170
171
 
171
172
  options: state => (key) => {
172
- const definition = definitions[key];
173
+ const definition = state.definitions[key];
173
174
 
174
175
  if (!definition) {
175
176
  throw new Error(`Unknown preference: ${ key }`);
@@ -252,19 +253,25 @@ export const mutations = {
252
253
  },
253
254
 
254
255
  reset(state) {
255
- for (const key in definitions) {
256
- if ( definitions[key]?.asCookie ) {
256
+ for (const key in state.definitions) {
257
+ if ( state.definitions[key]?.asCookie ) {
257
258
  continue;
258
259
  }
259
260
  delete state.data[key];
260
261
  }
261
- }
262
+ },
263
+
264
+ setDefinition(state, { name, definition = {} }) {
265
+ state.definitions[name] = definition;
266
+ },
262
267
  };
263
268
 
264
269
  export const actions = {
265
- async set({ dispatch, commit, rootGetters }, opt) {
270
+ async set({
271
+ dispatch, commit, rootGetters, state
272
+ }, opt) {
266
273
  let { key, value } = opt; // eslint-disable-line prefer-const
267
- const definition = definitions[key];
274
+ const definition = state.definitions[key];
268
275
  let server;
269
276
 
270
277
  if ( opt.val ) {
@@ -326,8 +333,8 @@ export const actions = {
326
333
  return;
327
334
  }
328
335
 
329
- for (const key in definitions) {
330
- const definition = definitions[key];
336
+ for (const key in state.definitions) {
337
+ const definition = state.definitions[key];
331
338
 
332
339
  if ( !definition.asCookie ) {
333
340
  continue;
@@ -441,8 +448,8 @@ export const actions = {
441
448
  prefsBeforeLogin = {};
442
449
  }
443
450
 
444
- for (const key in definitions) {
445
- const definition = definitions[key];
451
+ for (const key in state.definitions) {
452
+ const definition = state.definitions[key];
446
453
  let value = clone(server.data[key]);
447
454
 
448
455
  if (value === undefined && definition.inheritFrom) {
package/store/type-map.js CHANGED
@@ -148,6 +148,8 @@ import { sortBy } from '@shell/utils/sort';
148
148
  import { haveV1Monitoring, haveV2Monitoring } from '@shell/utils/monitoring';
149
149
  import { NEU_VECTOR_NAMESPACE } from '@shell/config/product/neuvector';
150
150
 
151
+ import { ExtensionPoint, TableColumnLocation } from '@shell/core/types';
152
+
151
153
  export const NAMESPACED = 'namespaced';
152
154
  export const CLUSTER_LEVEL = 'cluster';
153
155
  export const BOTH = 'both';
@@ -222,6 +224,30 @@ export function DSL(store, product, module = 'type-map') {
222
224
  },
223
225
 
224
226
  headers(type, headers) {
227
+ const extensionCols = store.$plugin.getUIConfig(ExtensionPoint.TABLE_COL, TableColumnLocation.RESOURCE);
228
+
229
+ // Try and insert the columns before the Age column, if that is the last column
230
+ let insertPosition = headers.length;
231
+
232
+ if (headers.length > 0) {
233
+ const lastColumn = headers[headers.length - 1];
234
+
235
+ if (lastColumn?.name === AGE.name) {
236
+ insertPosition--;
237
+ }
238
+ }
239
+
240
+ // adding extension defined cols to the correct header config
241
+ extensionCols.forEach((col) => {
242
+ if (col.locationConfig.resource) {
243
+ col.locationConfig.resource.forEach((resource) => {
244
+ if (resource && type === resource) {
245
+ headers.splice(insertPosition, 0, col);
246
+ }
247
+ });
248
+ }
249
+ });
250
+
225
251
  headers.forEach((header) => {
226
252
  // If on the client, then use the value getter if there is one
227
253
  if (header.getValue) {
@@ -47,6 +47,7 @@ export const MODE: "mode";
47
47
  export const _CREATE: "create";
48
48
  export const _VIEW: "view";
49
49
  export const _EDIT: "edit";
50
+ export const _LIST: "list";
50
51
  export const _CLONE: "clone";
51
52
  export const _STAGE: "stage";
52
53
  export const _IMPORT: "import";