@rancher/shell 3.0.8-rc.12 → 3.0.8-rc.13

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 (58) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +90 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +98 -0
  6. package/apis/intf/system.ts +34 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +54 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +33 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/components/CruResource.vue +8 -1
  18. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  19. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  20. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
  21. package/components/ModalManager.vue +11 -1
  22. package/components/ResourceDetail/index.vue +3 -0
  23. package/components/ResourceTable.vue +53 -20
  24. package/components/SlideInPanelManager.vue +16 -11
  25. package/components/SortableTable/index.vue +19 -1
  26. package/components/Tabbed/index.vue +37 -2
  27. package/components/form/ResourceTabs/composable.ts +2 -2
  28. package/composables/cruResource.ts +27 -0
  29. package/composables/focusTrap.ts +3 -1
  30. package/composables/resourceDetail.ts +15 -0
  31. package/core/__tests__/extension-manager-impl.test.js +437 -0
  32. package/core/extension-manager-impl.js +1 -22
  33. package/core/plugin.ts +9 -1
  34. package/core/types.ts +35 -0
  35. package/detail/provisioning.cattle.io.cluster.vue +2 -0
  36. package/edit/workload/index.vue +1 -1
  37. package/initialize/install-plugins.js +4 -5
  38. package/package.json +1 -1
  39. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  40. package/pages/c/_cluster/fleet/index.vue +4 -7
  41. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
  42. package/plugins/steve/steve-pagination-utils.ts +108 -43
  43. package/scripts/publish-shell.sh +25 -0
  44. package/store/__tests__/type-map.test.ts +164 -2
  45. package/store/notifications.ts +2 -0
  46. package/store/type-map.js +10 -1
  47. package/types/internal-api/shell/modal.d.ts +6 -6
  48. package/types/notifications/index.ts +126 -15
  49. package/types/rancher/index.d.ts +9 -0
  50. package/types/vue-shim.d.ts +5 -4
  51. package/composables/useExtensionManager.ts +0 -17
  52. package/core/__test__/extension-manager-impl.test.js +0 -236
  53. package/core/plugins.js +0 -38
  54. package/plugins/internal-api/index.ts +0 -37
  55. package/plugins/internal-api/shared/base-api.ts +0 -13
  56. package/plugins/internal-api/shell/shell.api.ts +0 -108
  57. package/types/internal-api/shell/growl.d.ts +0 -25
  58. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -37,20 +37,35 @@ interface Namespace extends ModelNamespace {
37
37
  }
38
38
  }
39
39
 
40
+ interface NamespaceProjectFilterResult {
41
+ /**
42
+ * True if the ns should be filtered IN. False if filtered OUT.
43
+ */
44
+ [nsName: string]: boolean;
45
+ }
46
+
47
+ /**
48
+ * Helper class, contains namespace / project filter specific functions
49
+ */
40
50
  class NamespaceProjectFilters {
41
51
  /**
42
52
  * User needs all resources.... except if there's some settings which should remove resources in specific circumstances
43
53
  */
44
54
  protected handlePrefAndSettingFilter(args: {
45
55
  allNamespaces: Namespace[],
56
+ /**
57
+ * Reserved / Obscure namespaces are ones used to support clusters and users. By default these are hidden
58
+ */
46
59
  showReservedRancherNamespaces: boolean,
60
+ /**
61
+ * Has product config disabled system projects and namespaces
62
+ */
47
63
  productHidesSystemNamespaces: boolean,
48
- }): PaginationParamFilter[] {
64
+ }): NamespaceProjectFilterResult {
49
65
  const { allNamespaces, showReservedRancherNamespaces, productHidesSystemNamespaces } = args;
50
66
 
51
- // These are AND'd together
52
67
  // Not ns 1 AND ns 2
53
- const filterNamespaces = allNamespaces.reduce((res, ns) => {
68
+ return allNamespaces.reduce((res, ns) => {
54
69
  // Links to ns.isObscure and covers things like `c-`, `user-`, etc (see OBSCURE_NAMESPACE_PREFIX)
55
70
  const hideObscure = showReservedRancherNamespaces ? false : ns.isObscure;
56
71
 
@@ -58,23 +73,11 @@ class NamespaceProjectFilters {
58
73
  const hideSystem = productHidesSystemNamespaces ? ns.isSystem : false;
59
74
 
60
75
  if (hideObscure || hideSystem) {
61
- res.push(ns.name);
76
+ res[ns.name] = false;
62
77
  }
63
78
 
64
79
  return res;
65
- }, [] as String[]);
66
-
67
- if (filterNamespaces.length) {
68
- return [new PaginationParamFilter({
69
- fields: [{
70
- value: filterNamespaces.join(','),
71
- equality: PaginationFilterEquality.NOT_IN,
72
- field: 'metadata.namespace',
73
- }],
74
- })];
75
- }
76
-
77
- return [];
80
+ }, {} as NamespaceProjectFilterResult);
78
81
  }
79
82
 
80
83
  /**
@@ -88,31 +91,88 @@ class NamespaceProjectFilters {
88
91
  allNamespaces: Namespace[],
89
92
  isAllSystem: boolean,
90
93
  isAllUser: boolean,
91
- }) {
92
- const { allNamespaces, isAllSystem } = args;
94
+ }): NamespaceProjectFilterResult {
95
+ const { allNamespaces, isAllSystem, isAllUser } = args;
93
96
  const allSystem = allNamespaces.filter((ns) => ns.isSystem);
94
97
 
95
- // > Neither of these use projectsOrNamespaces to avoid scenarios where the local cluster provides a namespace which has
96
- // > a matching project... which could lead to results in the user project resource being included in the system filter
97
- if (isAllSystem) {
98
- // return resources in system ns 1 OR in system ns 2 ...
99
- // &filter=metadata.namespace=system ns 1,metadata.namespace=system ns 2
100
- return [PaginationParamFilter.createMultipleFields(
101
- allSystem.map(
102
- (ns) => new PaginationFilterField({ field: 'metadata.namespace', value: ns.name })
103
- )
104
- )];
105
- } else { // if isAllUser
106
- // return resources not in system ns 1 AND not in system ns 2 ...
107
- // &filter=metadata.namespace!=system ns 1&filter=metadata.namespace!=system ns 2
108
- return allSystem.map((ns) => PaginationParamFilter.createSingleField({
109
- field: 'metadata.namespace', value: ns.name, equals: false
98
+ return allSystem.reduce((res, ns) => {
99
+ if (isAllSystem) {
100
+ // We want to filter IN system namespaces
101
+ res[ns.name] = true;
102
+ }
103
+
104
+ if (isAllUser) {
105
+ // We want to filter OUT system namespaces
106
+ res[ns.name] = false;
107
+ }
108
+
109
+ return res;
110
+ }, {} as NamespaceProjectFilterResult);
111
+ }
112
+
113
+ /**
114
+ * Combine result `b` into `a` and return result
115
+ */
116
+ protected combineNsProjectFilterResults(a: NamespaceProjectFilterResult, b: NamespaceProjectFilterResult): NamespaceProjectFilterResult {
117
+ // Start with `a`
118
+ const res = { ...a };
119
+
120
+ // Merge entries from `b` into `a` if they don't exist in `a`. This maintains a hierarchy
121
+ // 1. if something has been excluded in `a` ignore requests to include given `b`
122
+ // 2. if something has been included in `a` ignore requests to exclude given `b`
123
+ Object.entries(b).forEach(([ns, include]) => {
124
+ if (res[ns] === undefined) {
125
+ res[ns] = include;
126
+ }
127
+ });
128
+
129
+ return res;
130
+ }
131
+
132
+ /**
133
+ * Convert @NamespaceProjectFilterResult into @PaginationParamFilter
134
+ */
135
+ protected createFiltersFromNamespaceProjectFilterResult(filterResult: NamespaceProjectFilterResult): PaginationParamFilter[] {
136
+ const inList: string[] = [];
137
+ const outList: string[] = [];
138
+
139
+ Object.entries(filterResult).forEach(([ns, include]) => {
140
+ if (include) {
141
+ inList.push(ns);
142
+ } else {
143
+ outList.push(ns);
144
+ }
145
+ });
146
+
147
+ const res: PaginationParamFilter[] = [];
148
+
149
+ // There's no point having both IN and OUT lists together, so prefer the IN list
150
+ if (inList.length) {
151
+ res.push(new PaginationParamFilter({
152
+ fields: [{
153
+ value: inList.join(','),
154
+ equality: PaginationFilterEquality.IN,
155
+ field: 'metadata.namespace',
156
+ }],
157
+ }));
158
+ } else if (outList.length) {
159
+ res.push(new PaginationParamFilter({
160
+ fields: [{
161
+ value: outList.join(','),
162
+ equality: PaginationFilterEquality.NOT_IN,
163
+ field: 'metadata.namespace',
164
+ }],
110
165
  }));
111
166
  }
167
+
168
+ return res;
112
169
  }
113
170
 
114
171
  /**
115
172
  * User needs resources in a set of projects or namespaces
173
+ *
174
+ * Mainly deals with the projectornamespaces filter, also ensures namespace in local cluster matching target project's aren't included
175
+ *
116
176
  */
117
177
  protected handleSelectionFilter(neu: string[], isLocalCluster: boolean) {
118
178
  // User has one or more projects or namespaces. We can pass this straight through to projectsornamespaces
@@ -124,12 +184,12 @@ class NamespaceProjectFilters {
124
184
  ];
125
185
 
126
186
  if (isLocalCluster) {
127
- // > As per `handleSystemOrUserFilter` above, we need to be careful of the local cluster where there's namespaces related to projects with the same id
128
- // > In this case
187
+ // We need to be careful of the local cluster where there's namespaces related to projects with the same id
188
+ // In this case
129
189
  // - We're including resources in the project and it's related namespace (via projectsornamespaces)
130
190
  // - We're also then excluding resources in the related namespace (via below `filter`)
131
191
 
132
- // Exclude resources NOT in projects namespace 1 AND not in projects namespace 2
192
+ // Exclude resources NOT in project's backing namespace 1 AND not in project's backing namespace 2
133
193
  // &filter=metadata.namespace!=pn1&filter=metadata.namespace!=pn2
134
194
  return {
135
195
  projectsOrNamespaces,
@@ -352,24 +412,29 @@ class StevePaginationUtils extends NamespaceProjectFilters {
352
412
  let projectsOrNamespaces: PaginationParamProjectOrNamespace[] = [];
353
413
  // used to return resources in / not in namespaces
354
414
  // &filter=metadata.namespace=abc
355
- let filters: PaginationParamFilter[] = [];
415
+ const filters: PaginationParamFilter[] = [];
416
+ let nsProjectFilterResults = {};
356
417
 
357
418
  if (!showReservedRancherNamespaces || productHidesSystemNamespaces) {
358
- // We need to hide reserved namespaces ('c-', 'user-', etc) OR system namespaces
359
- filters = this.handlePrefAndSettingFilter({
419
+ // We need to hide reserved namespaces ('c-', 'user-', etc) OR system namespaces (given product may hide them)
420
+ nsProjectFilterResults = this.combineNsProjectFilterResults(nsProjectFilterResults, this.handlePrefAndSettingFilter({
360
421
  allNamespaces, showReservedRancherNamespaces, productHidesSystemNamespaces
361
- });
422
+ }));
362
423
  }
363
424
 
364
425
  const isAllSystem = selection[0] === NAMESPACE_FILTER_ALL_SYSTEM;
365
426
  const isAllUser = selection[0] === NAMESPACE_FILTER_ALL_USER;
366
427
 
367
428
  if (selection.length === 1 && (isAllSystem || isAllUser)) {
368
- // Filter by resources either in or not in system namespaces
369
- filters.push(...this.handleSystemOrUserFilter({
429
+ // Filter by resources either in or not in system namespaces (given user selection)
430
+ nsProjectFilterResults = this.combineNsProjectFilterResults(nsProjectFilterResults, this.handleSystemOrUserFilter({
370
431
  allNamespaces, isAllSystem, isAllUser
371
432
  }));
433
+
434
+ filters.push(...this.createFiltersFromNamespaceProjectFilterResult(nsProjectFilterResults));
372
435
  } else {
436
+ filters.push(...this.createFiltersFromNamespaceProjectFilterResult(nsProjectFilterResults));
437
+
373
438
  // User has one or more projects or namespaces
374
439
  const res = this.handleSelectionFilter(selection, isLocalCluster);
375
440
 
@@ -8,6 +8,7 @@ SHELL_DIR=$BASE_DIR/shell/
8
8
  CREATORS_DIR=$BASE_DIR/creators/extension
9
9
  FORCE_PUBLISH_TO_NPM="false"
10
10
  DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
11
+ DUMMY_VERSION="99.99.99"
11
12
 
12
13
  # if TAG doesn't exist, we can exit as it's needed for any type of publish.
13
14
  if [ -z "$TAG" ]; then
@@ -84,6 +85,14 @@ function publish() {
84
85
  fi
85
86
  }
86
87
 
88
+ update_version_in_package_json() {
89
+ local package_json_path="$1"
90
+ local version="$2"
91
+
92
+ sed -i.bak -e "s/\"version\": \"[0-9]*.[0-9]*.[0-9]*\(-alpha\.[0-9]*\|-release[0-9]*.[0-9]*.[0-9]*\|-rc\.[0-9]*\)\{0,1\}\",/\"version\": \"${version}\",/g" "$package_json_path"
93
+ rm "${package_json_path}.bak"
94
+ }
95
+
87
96
  echo "TAG ${TAG}"
88
97
 
89
98
  # let's get the package name and version from the tag
@@ -102,10 +111,26 @@ fi
102
111
  case $PKG_NAME in
103
112
  "shell")
104
113
  echo "Publishing only Shell pkg via tagged release"
114
+
115
+ # with the changes in https://github.com/rancher/dashboard/pull/16166/files#diff-d954ab41ef46f7fdbaaf6d8c2bc715ad2cc823a829317b6ff93a3c94a92811f1
116
+ # with NPM 11.3 --dry--run now does additional checks, one of them is the version number
117
+ # so for dry runs we need to provide a valid version number here (not something published before)
118
+ if [ ${DRY_RUN} == "true" ]; then
119
+ update_version_in_package_json "${SHELL_DIR}/package.json" "${DUMMY_VERSION}"
120
+ fi
121
+
105
122
  publish "Shell" ${SHELL_DIR} ${PKG_V}
106
123
  ;;
107
124
  "creators")
108
125
  echo "Publishing only Creators pkg via tagged release"
126
+
127
+ # with the changes in https://github.com/rancher/dashboard/pull/16166/files#diff-d954ab41ef46f7fdbaaf6d8c2bc715ad2cc823a829317b6ff93a3c94a92811f1
128
+ # with NPM 11.3 --dry--run now does additional checks, one of them is the version number
129
+ # so for dry runs we need to provide a valid version number here (not something published before)
130
+ if [ ${DRY_RUN} == "true" ]; then
131
+ update_version_in_package_json "${CREATORS_DIR}/package.json" "${DUMMY_VERSION}"
132
+ fi
133
+
109
134
  publish "Extension creator" ${CREATORS_DIR} ${PKG_V}
110
135
  ;;
111
136
  *)
@@ -1116,7 +1116,169 @@ describe('type-map', () => {
1116
1116
  });
1117
1117
  });
1118
1118
  });
1119
+
1120
+ describe('activeProducts', () => {
1121
+ // Basic schemas for product filtering tests
1122
+ const productSchemas = {
1123
+ myType: {
1124
+ id: 'mytype',
1125
+ _id: 'mytype',
1126
+ type: SCHEMA,
1127
+ _group: 'mygroup',
1128
+ },
1129
+ anotherType: {
1130
+ id: 'anothertype',
1131
+ _id: 'anothertype',
1132
+ type: SCHEMA,
1133
+ _group: 'anothergroup',
1134
+ },
1135
+ };
1136
+
1137
+ const createProductState = (products) => ({
1138
+ products,
1139
+ schemaGeneration: 1,
1140
+ });
1141
+
1142
+ const createProductRootGetters = (moduleSchemas = [], moduleName = 'cluster') => ({
1143
+ 'prefs/get': () => false,
1144
+ [`${ moduleName }/all`]: (resource) => {
1145
+ if (resource === SCHEMA) {
1146
+ return moduleSchemas;
1147
+ }
1148
+
1149
+ return [];
1150
+ },
1151
+ });
1152
+
1153
+ describe('ifHaveType', () => {
1154
+ it('should show product when matching type exists', () => {
1155
+ const state = createProductState([{
1156
+ name: 'test-product',
1157
+ inStore: 'cluster',
1158
+ ifHaveType: 'mytype',
1159
+ }]);
1160
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1161
+
1162
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1163
+
1164
+ expect(active).toHaveLength(1);
1165
+ expect(active[0].name).toBe('test-product');
1166
+ });
1167
+
1168
+ it('should hide product when matching type does not exist', () => {
1169
+ const state = createProductState([{
1170
+ name: 'test-product',
1171
+ inStore: 'cluster',
1172
+ ifHaveType: 'missingtype',
1173
+ }]);
1174
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1175
+
1176
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1177
+
1178
+ expect(active).toHaveLength(0);
1179
+ });
1180
+ });
1181
+
1182
+ describe('ifNotHaveType', () => {
1183
+ it('should show product when matching type does NOT exist', () => {
1184
+ const state = createProductState([{
1185
+ name: 'test-product',
1186
+ inStore: 'cluster',
1187
+ ifNotHaveType: 'missingtype',
1188
+ }]);
1189
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1190
+
1191
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1192
+
1193
+ expect(active).toHaveLength(1);
1194
+ expect(active[0].name).toBe('test-product');
1195
+ });
1196
+
1197
+ it('should hide product when matching type exists', () => {
1198
+ const state = createProductState([{
1199
+ name: 'test-product',
1200
+ inStore: 'cluster',
1201
+ ifNotHaveType: 'mytype',
1202
+ }]);
1203
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1204
+
1205
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1206
+
1207
+ expect(active).toHaveLength(0);
1208
+ });
1209
+
1210
+ it('should support regex pattern in ifNotHaveType', () => {
1211
+ const state = createProductState([{
1212
+ name: 'test-product',
1213
+ inStore: 'cluster',
1214
+ ifNotHaveType: 'my.*',
1215
+ }]);
1216
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1217
+
1218
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1219
+
1220
+ expect(active).toHaveLength(0);
1221
+ });
1222
+
1223
+ it('should show product when regex pattern does not match any type', () => {
1224
+ const state = createProductState([{
1225
+ name: 'test-product',
1226
+ inStore: 'cluster',
1227
+ ifNotHaveType: 'nomatch.*',
1228
+ }]);
1229
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1230
+
1231
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1232
+
1233
+ expect(active).toHaveLength(1);
1234
+ expect(active[0].name).toBe('test-product');
1235
+ });
1236
+ });
1237
+
1238
+ describe('combined ifHaveType and ifNotHaveType', () => {
1239
+ it('should show product when ifHaveType matches and ifNotHaveType does not match', () => {
1240
+ const state = createProductState([{
1241
+ name: 'test-product',
1242
+ inStore: 'cluster',
1243
+ ifHaveType: 'mytype',
1244
+ ifNotHaveType: 'missingtype',
1245
+ }]);
1246
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1247
+
1248
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1249
+
1250
+ expect(active).toHaveLength(1);
1251
+ expect(active[0].name).toBe('test-product');
1252
+ });
1253
+
1254
+ it('should hide product when ifHaveType matches but ifNotHaveType also matches', () => {
1255
+ const state = createProductState([{
1256
+ name: 'test-product',
1257
+ inStore: 'cluster',
1258
+ ifHaveType: 'mytype',
1259
+ ifNotHaveType: 'anothertype',
1260
+ }]);
1261
+ const rootGetters = createProductRootGetters([productSchemas.myType, productSchemas.anotherType]);
1262
+
1263
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1264
+
1265
+ expect(active).toHaveLength(0);
1266
+ });
1267
+
1268
+ it('should hide product when ifHaveType does not match', () => {
1269
+ const state = createProductState([{
1270
+ name: 'test-product',
1271
+ inStore: 'cluster',
1272
+ ifHaveType: 'missingtype',
1273
+ ifNotHaveType: 'anothermissingtype',
1274
+ }]);
1275
+ const rootGetters = createProductRootGetters([productSchemas.myType]);
1276
+
1277
+ const active = getters.activeProducts(state, {}, {}, rootGetters);
1278
+
1279
+ expect(active).toHaveLength(0);
1280
+ });
1281
+ });
1282
+ });
1119
1283
  });
1120
1284
  });
1121
-
1122
- // getTree - Remove ignored schemas, not-applicable to ns filter
@@ -299,6 +299,8 @@ export const actions = {
299
299
 
300
300
  // Show a growl for the notification if necessary
301
301
  dispatch('growl/notification', notification, { root: true });
302
+
303
+ return notification.id;
302
304
  },
303
305
 
304
306
  async fromGrowl( { commit, getters }: any, notification: Notification) {
package/store/type-map.js CHANGED
@@ -35,6 +35,7 @@
35
35
  // ifHave, -- Show this product only if the given capability is available
36
36
  // ifHaveGroup, -- Show this product only if the given group exists in the store [inStore]
37
37
  // ifHaveType, -- Show this product only if the given type exists in the store [inStore], This can also be specified as an object { type: TYPE, store: 'management' } if the type isn't in the current [inStore]
38
+ // ifNotHaveType, -- Hide this product if the given type exists in the store [inStore] (opposite of ifHaveType)
38
39
  // ifHaveVerb, -- In combination with ifHaveTYpe, show it only if the type also has this collectionMethod
39
40
  // inStore, -- Which store to look at for if* above and the left-nav, defaults to "cluster"
40
41
  // rootProduct, -- Optional root (parent) product - if set, used to optimize navigation when product changes stays within root product
@@ -238,7 +239,7 @@ export function DSL(store, product, module = 'type-map') {
238
239
  };
239
240
 
240
241
  // Convert strings to regex's - we do this once here for efficiency
241
- for ( const k of ['ifHaveGroup', 'ifHaveType'] ) {
242
+ for ( const k of ['ifHaveGroup', 'ifHaveType', 'ifNotHaveType'] ) {
242
243
  if ( opt[k] ) {
243
244
  if (Array.isArray(opt[k])) {
244
245
  opt[k] = opt[k].map((r) => regexToString(ensureRegex(r)));
@@ -1419,6 +1420,14 @@ export const getters = {
1419
1420
  }
1420
1421
  }
1421
1422
 
1423
+ if ( p.ifNotHaveType ) {
1424
+ const haveIds = knownTypes[module].filter((t) => t.match(stringToRegex(p.ifNotHaveType)) );
1425
+
1426
+ if ( haveIds.length ) {
1427
+ return false;
1428
+ }
1429
+ }
1430
+
1422
1431
  if ( p.ifHaveGroup && !knownGroups[module].find((t) => t.match(stringToRegex(p.ifHaveGroup)) ) ) {
1423
1432
  return false;
1424
1433
  }
@@ -14,7 +14,7 @@ export interface ModalConfig {
14
14
  *
15
15
  * this.$shell.modal({
16
16
  * component: MyCustomModal,
17
- * componentProps: { title: 'Hello Modal' }
17
+ * props: { title: 'Hello Modal' }
18
18
  * });
19
19
  * ```
20
20
  */
@@ -25,10 +25,10 @@ export interface ModalConfig {
25
25
  *
26
26
  * Example:
27
27
  * ```ts
28
- * componentProps: { title: 'Hello Modal', isVisible: true }
28
+ * props: { title: 'Hello Modal', isVisible: true }
29
29
  * ```
30
30
  */
31
- componentProps?: Record<string, any>;
31
+ props?: Record<string, any>;
32
32
 
33
33
  /**
34
34
  * Optional array of resources that the modal component might need.
@@ -47,11 +47,11 @@ export interface ModalConfig {
47
47
  *
48
48
  * Examples:
49
49
  * ```ts
50
- * modalWidth: '800px' // Width in pixels
51
- * modalWidth: '75%' // Width as a percentage
50
+ * width: '800px' // Width in pixels
51
+ * width: '75%' // Width as a percentage
52
52
  * ```
53
53
  */
54
- modalWidth?: string;
54
+ width?: string;
55
55
 
56
56
  /**
57
57
  * Determines if clicking outside the modal will close it. Defaults to `true`.