@rancher/shell 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/assets/images/providers/ovhcloudmks.svg +122 -0
  2. package/assets/images/providers/ovhcloudpubliccloud.svg +122 -0
  3. package/assets/styles/global/_layout.scss +99 -0
  4. package/assets/translations/en-us.yaml +30 -5
  5. package/assets/translations/zh-hans.yaml +1 -1
  6. package/babel.config.js +7 -1
  7. package/chart/monitoring/alerting/index.vue +7 -21
  8. package/chart/monitoring/grafana/index.vue +55 -0
  9. package/chart/monitoring/index.vue +51 -17
  10. package/chart/monitoring/prometheus/index.vue +37 -43
  11. package/chart/rancher-backup/index.vue +2 -1
  12. package/cloud-credential/azure.vue +4 -17
  13. package/components/Certificates.vue +164 -0
  14. package/components/CodeMirror.vue +19 -21
  15. package/components/CruResource.vue +1 -0
  16. package/components/EtcdInfoBanner.vue +1 -1
  17. package/components/ExplorerProjectsNamespaces.vue +25 -1
  18. package/components/IconOrSvg.vue +1 -1
  19. package/components/LandingPagePreference.vue +1 -4
  20. package/components/Questions/index.vue +1 -1
  21. package/components/ResourceDetail/Masthead.vue +16 -3
  22. package/components/ResourceTable.vue +14 -2
  23. package/components/ResourceYaml.vue +5 -0
  24. package/components/SideNav.vue +1 -1
  25. package/components/SingleClusterInfo.vue +1 -4
  26. package/components/Tabbed/index.vue +12 -0
  27. package/components/fleet/FleetRepos.vue +62 -27
  28. package/components/fleet/FleetResources.vue +6 -1
  29. package/components/form/ArrayListSelect.vue +10 -0
  30. package/components/form/KeyValue.vue +4 -0
  31. package/components/form/LabeledSelect.vue +4 -0
  32. package/components/formatter/Checked.vue +11 -3
  33. package/components/formatter/FleetClusterSummaryGraph.vue +27 -0
  34. package/components/formatter/FleetSummaryGraph.vue +23 -11
  35. package/components/formatter/LiveDuration.vue +1 -1
  36. package/components/formatter/PercentageBar.vue +1 -1
  37. package/components/formatter/__tests__/Checked.test.ts +19 -0
  38. package/components/nav/Group.vue +2 -2
  39. package/components/nav/Header.vue +0 -1
  40. package/components/nav/TopLevelMenu.vue +36 -6
  41. package/components/nav/Type.vue +1 -3
  42. package/components/nav/WindowManager/ContainerLogs.vue +101 -3
  43. package/components/nav/WindowManager/ContainerShell.vue +6 -1
  44. package/components/nav/WindowManager/__tests__/ContainerLogs.test.ts +186 -0
  45. package/components/nav/WindowManager/index.vue +11 -10
  46. package/components/nav/__tests__/TopLevelMenu.test.ts +33 -0
  47. package/components/nav/__tests__/Type.test.ts +1 -1
  48. package/components/nuxt/nuxt-child.js +14 -78
  49. package/components/nuxt/nuxt.js +1 -1
  50. package/{layouts → components/templates}/blank.vue +1 -1
  51. package/{layouts → components/templates}/default.vue +8 -98
  52. package/{layouts → components/templates}/error.vue +10 -19
  53. package/{layouts → components/templates}/home.vue +4 -1
  54. package/{layouts → components/templates}/plain.vue +4 -1
  55. package/{layouts → components/templates}/standalone.vue +1 -1
  56. package/{layouts → components/templates}/unauthenticated.vue +1 -1
  57. package/composables/useCompactInput.ts +20 -0
  58. package/composables/useLabeledFormElement.ts +138 -0
  59. package/config/harvester-manager-types.js +2 -0
  60. package/config/private-label.js +22 -0
  61. package/config/product/explorer.js +3 -0
  62. package/config/product/fleet.js +6 -1
  63. package/config/product/manager.js +8 -2
  64. package/config/query-params.js +1 -0
  65. package/config/router.js +385 -364
  66. package/config/settings.ts +1 -0
  67. package/config/store.js +1 -1
  68. package/config/system-namespaces.js +3 -0
  69. package/config/table-headers.js +47 -0
  70. package/core/plugin-routes.ts +56 -114
  71. package/core/plugin.ts +16 -10
  72. package/core/plugins-loader.js +7 -9
  73. package/core/plugins.js +0 -3
  74. package/creators/app/files/.gitlab-ci.yml +1 -1
  75. package/detail/fleet.cattle.io.cluster.vue +11 -1
  76. package/detail/provisioning.cattle.io.cluster.vue +4 -3
  77. package/dialog/ScaleMachineDownDialog.vue +34 -17
  78. package/edit/__tests__/service.test.ts +89 -0
  79. package/edit/auth/googleoauth.vue +1 -5
  80. package/edit/catalog.cattle.io.clusterrepo.vue +18 -0
  81. package/edit/cloudcredential.vue +2 -0
  82. package/edit/configmap.vue +2 -1
  83. package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.spec.ts +1 -1
  84. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +15 -7
  85. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +112 -0
  86. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +473 -0
  87. package/edit/provisioning.cattle.io.cluster/__tests__/{CustomCommand.tests.ts → CustomCommand.test.ts} +4 -0
  88. package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +1 -1
  89. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +73 -0
  90. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +7 -1
  91. package/edit/provisioning.cattle.io.cluster/__tests__/utils/cluster.ts +386 -0
  92. package/edit/provisioning.cattle.io.cluster/import.vue +2 -2
  93. package/edit/provisioning.cattle.io.cluster/index.vue +92 -36
  94. package/edit/provisioning.cattle.io.cluster/rke2.vue +171 -583
  95. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +137 -0
  96. package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +157 -0
  97. package/edit/provisioning.cattle.io.cluster/{Basics.vue → tabs/Basics.vue} +94 -19
  98. package/edit/provisioning.cattle.io.cluster/{MachinePool.vue → tabs/MachinePool.vue} +1 -0
  99. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +135 -0
  100. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +189 -0
  101. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +144 -0
  102. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/index.vue +76 -0
  103. package/edit/service.vue +12 -0
  104. package/edit/workload/mixins/workload.js +1 -1
  105. package/initialize/App.js +25 -71
  106. package/initialize/client.js +21 -162
  107. package/initialize/index.js +27 -123
  108. package/list/management.cattle.io.feature.vue +1 -7
  109. package/list/node.vue +1 -0
  110. package/machine-config/__tests__/vmwarevsphere.test.ts +100 -21
  111. package/machine-config/vmwarevsphere.vue +73 -51
  112. package/middleware/authenticated.js +10 -17
  113. package/mixins/auth-config.js +2 -7
  114. package/mixins/brand.js +29 -41
  115. package/mixins/labeled-form-element.ts +6 -1
  116. package/models/__tests__/management.cattle.io.node.ts +85 -0
  117. package/models/__tests__/management.cattle.io.nodepool.ts +83 -0
  118. package/models/__tests__/namespace.test.ts +49 -9
  119. package/models/__tests__/workload.test.ts +91 -0
  120. package/models/cluster/node.js +4 -4
  121. package/models/cluster.x-k8s.io.machinedeployment.js +14 -0
  122. package/models/fleet.cattle.io.cluster.js +4 -0
  123. package/models/fleet.cattle.io.gitrepo.js +56 -13
  124. package/models/management.cattle.io.kontainerdriver.js +1 -1
  125. package/models/management.cattle.io.node.js +18 -14
  126. package/models/management.cattle.io.nodepool.js +17 -0
  127. package/models/namespace.js +1 -1
  128. package/models/pod.js +20 -0
  129. package/models/provisioning.cattle.io.cluster.js +20 -3
  130. package/models/secret.js +117 -18
  131. package/models/workload.js +16 -0
  132. package/models/workload.service.js +18 -0
  133. package/package.json +10 -9
  134. package/pages/about.vue +0 -1
  135. package/pages/account/create-key.vue +0 -1
  136. package/pages/account/index.vue +0 -1
  137. package/pages/auth/login.vue +0 -1
  138. package/pages/auth/logout.vue +0 -2
  139. package/pages/auth/setup.vue +0 -4
  140. package/pages/auth/verify.vue +14 -8
  141. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  142. package/pages/c/_cluster/apps/index.vue +0 -2
  143. package/pages/c/_cluster/auth/index.vue +0 -2
  144. package/pages/c/_cluster/ecm/index.vue +0 -2
  145. package/pages/c/_cluster/explorer/index.vue +28 -2
  146. package/pages/c/_cluster/fleet/index.vue +1 -1
  147. package/pages/c/_cluster/index.vue +0 -2
  148. package/pages/c/_cluster/settings/banners.vue +0 -2
  149. package/pages/c/_cluster/settings/brand.vue +0 -2
  150. package/pages/c/_cluster/settings/index.vue +0 -2
  151. package/pages/c/_cluster/settings/links.vue +0 -1
  152. package/pages/c/_cluster/settings/performance.vue +0 -1
  153. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +2 -1
  154. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +10 -46
  155. package/pages/c/_cluster/uiplugins/index.vue +0 -2
  156. package/pages/diagnostic.vue +1 -2
  157. package/pages/fail-whale.vue +0 -1
  158. package/pages/prefs.vue +0 -1
  159. package/pages/support/index.vue +2 -8
  160. package/pkg/auto-import.js +1 -1
  161. package/plugins/axios.js +0 -36
  162. package/plugins/back-button.js +3 -5
  163. package/plugins/codemirror-loader.js +1 -1
  164. package/plugins/codemirror.js +41 -0
  165. package/plugins/dashboard-store/__tests__/{mutations.spec.ts → mutations.test.ts} +1 -1
  166. package/plugins/dashboard-store/__tests__/resource-class.test.ts +49 -0
  167. package/plugins/dashboard-store/__tests__/utils/store-mocks.ts +7 -0
  168. package/plugins/dashboard-store/actions.js +30 -4
  169. package/plugins/dashboard-store/classify.js +1 -18
  170. package/plugins/dashboard-store/getters.js +10 -5
  171. package/plugins/dashboard-store/index.js +0 -12
  172. package/plugins/dashboard-store/mutations.js +0 -4
  173. package/plugins/dashboard-store/resource-class.js +59 -18
  174. package/plugins/steve/__tests__/steve-class.spec.ts +59 -0
  175. package/plugins/steve/__tests__/utils/steve-mocks.ts +31 -0
  176. package/plugins/steve/getters.js +4 -1
  177. package/plugins/steve/norman-class.js +19 -0
  178. package/plugins/steve/steve-class.js +22 -0
  179. package/plugins/steve/subscribe.js +4 -10
  180. package/rancher-components/Accordion/Accordion.test.ts +45 -0
  181. package/rancher-components/Accordion/Accordion.vue +85 -0
  182. package/rancher-components/Accordion/index.ts +1 -0
  183. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +19 -2
  184. package/rancher-components/Form/LabeledInput/LabeledInput.vue +12 -1
  185. package/rancher-components/Form/Radio/RadioButton.test.ts +7 -3
  186. package/rancher-components/Form/Radio/RadioGroup.test.ts +30 -0
  187. package/rancher-components/Form/Radio/RadioGroup.vue +4 -0
  188. package/rancher-components/StringList/StringList.test.ts +270 -0
  189. package/rancher-components/StringList/StringList.vue +57 -18
  190. package/rancher-components/components/Accordion/Accordion.test.ts +45 -0
  191. package/rancher-components/components/Accordion/Accordion.vue +85 -0
  192. package/rancher-components/components/Accordion/index.ts +1 -0
  193. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +19 -2
  194. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +4 -1
  195. package/scripts/.gitlab/workflows/build-extension-catalog.gitlab-ci.yml +50 -0
  196. package/scripts/extension/parse-tag-name +2 -2
  197. package/scripts/publish-shell.sh +10 -0
  198. package/scripts/test-plugins-build.sh +85 -9
  199. package/server/har-file.js +183 -0
  200. package/store/catalog.js +1 -1
  201. package/store/features.js +1 -0
  202. package/store/i18n.js +11 -0
  203. package/store/index.js +10 -11
  204. package/store/prefs.js +33 -35
  205. package/store/type-map.js +8 -7
  206. package/tsconfig.json +35 -9
  207. package/tsconfig.paths.json +18 -0
  208. package/types/shell/index.d.ts +345 -214
  209. package/utils/__tests__/create-yaml.test.ts +60 -0
  210. package/utils/axios.js +0 -19
  211. package/utils/azure.js +24 -0
  212. package/utils/create-yaml.js +17 -10
  213. package/utils/monitoring.js +1 -1
  214. package/utils/nuxt.js +18 -39
  215. package/utils/object.js +14 -0
  216. package/utils/router.scrollBehavior.js +12 -14
  217. package/utils/time.js +1 -1
  218. package/utils/url.ts +1 -1
  219. package/vue.config.js +23 -2
  220. package/.DS_Store +0 -0
  221. package/assets/images/providers/aks-black.svg +0 -28
  222. package/assets/images/providers/aks.svg +0 -31
  223. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +0 -234
  224. package/initialize/layouts.ts +0 -26
  225. package/mixins/fetch.server.js +0 -73
  226. package/pages/c/index.vue +0 -9
  227. package/pages/rio/mesh.vue +0 -508
  228. package/plugins/transitions.js +0 -4
  229. package/scripts/.DS_Store +0 -0
  230. package/scripts/verdaccio.log +0 -205
  231. package/tsconfig.default.json +0 -46
  232. package/yarn-error.log +0 -200
  233. /package/components/form/__tests__/{NameNsDescription.ts → NameNsDescription.test.ts} +0 -0
  234. /package/edit/networking.k8s.io.networkpolicy/__tests__/utils/{selectors.ts → selectors.test.ts} +0 -0
  235. /package/edit/provisioning.cattle.io.cluster/{AgentConfiguration.vue → tabs/AgentConfiguration.vue} +0 -0
  236. /package/edit/provisioning.cattle.io.cluster/{MemberRoles.vue → tabs/MemberRoles.vue} +0 -0
  237. /package/edit/provisioning.cattle.io.cluster/{S3Config.vue → tabs/etcd/S3Config.vue} +0 -0
  238. /package/edit/provisioning.cattle.io.cluster/{ACE.vue → tabs/networking/ACE.vue} +0 -0
  239. /package/edit/provisioning.cattle.io.cluster/{RegistryConfigs.vue → tabs/registries/RegistryConfigs.vue} +0 -0
  240. /package/edit/provisioning.cattle.io.cluster/{RegistryMirrors.vue → tabs/registries/RegistryMirrors.vue} +0 -0
  241. /package/edit/provisioning.cattle.io.cluster/{DrainOptions.vue → tabs/upgrade/DrainOptions.vue} +0 -0
  242. /package/plugins/dashboard-store/__tests__/{actions.spec.ts → actions.test.ts} +0 -0
  243. /package/plugins/dashboard-store/__tests__/{getters.spec.ts → getters.test.ts} +0 -0
@@ -398,6 +398,276 @@ describe('stringList.vue', () => {
398
398
  });
399
399
  });
400
400
 
401
+ describe('bulk delimiter', () => {
402
+ const delimiter = /;/;
403
+
404
+ describe('add', () => {
405
+ const items: string[] = [];
406
+
407
+ beforeEach(() => {
408
+ wrapper = mount(StringList, {
409
+ propsData: {
410
+ items,
411
+ bulkAdditionDelimiter: delimiter,
412
+ errorMessages: { duplicate: 'error, item is duplicate.' },
413
+ }
414
+ });
415
+ });
416
+
417
+ it('should split values if delimiter set', async() => {
418
+ const value = 'test;test1;test2';
419
+ const result = ['test', 'test1', 'test2'];
420
+
421
+ // activate create mode
422
+ await wrapper.setData({ isCreateItem: true });
423
+ const inputField = wrapper.find('[data-testid="item-create"]');
424
+
425
+ await inputField.setValue(value);
426
+
427
+ // press enter
428
+ await inputField.trigger('keydown.enter');
429
+ await wrapper.vm.$nextTick();
430
+
431
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
432
+
433
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
434
+ });
435
+
436
+ it('should show warning if one of the values is a duplicate', async() => {
437
+ const value = 'test;test1;test2';
438
+
439
+ await wrapper.setProps({ items: ['test1'] });
440
+
441
+ // activate create mode
442
+ await wrapper.setData({ isCreateItem: true });
443
+ const inputField = wrapper.find('[data-testid="item-create"]');
444
+
445
+ await inputField.setValue(value);
446
+
447
+ // press enter
448
+ await inputField.trigger('keydown.enter');
449
+ await wrapper.vm.$nextTick();
450
+
451
+ const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
452
+
453
+ expect(isDuplicate).toBe(true);
454
+ });
455
+
456
+ it('should show a warning if the new values are all duplicates', async() => {
457
+ const value = 'test;test';
458
+
459
+ // activate create mode
460
+ await wrapper.setData({ isCreateItem: true });
461
+ const inputField = wrapper.find('[data-testid="item-create"]');
462
+
463
+ await inputField.setValue(value);
464
+
465
+ // press enter
466
+ await inputField.trigger('keydown.enter');
467
+ await wrapper.vm.$nextTick();
468
+
469
+ const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
470
+
471
+ expect(isDuplicate).toBe(true);
472
+ });
473
+
474
+ it('should not consider empty strings at the beginning', async() => {
475
+ const value = ';test;test1;test2';
476
+ const result = ['test', 'test1', 'test2'];
477
+
478
+ // activate create mode
479
+ await wrapper.setData({ isCreateItem: true });
480
+ const inputField = wrapper.find('[data-testid="item-create"]');
481
+
482
+ await inputField.setValue(value);
483
+
484
+ // press enter
485
+ await inputField.trigger('keydown.enter');
486
+ await wrapper.vm.$nextTick();
487
+
488
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
489
+
490
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
491
+ });
492
+
493
+ it('should not consider empty strings in the middle', async() => {
494
+ const value = 'test;test1;;test2';
495
+ const result = ['test', 'test1', 'test2'];
496
+
497
+ // activate create mode
498
+ await wrapper.setData({ isCreateItem: true });
499
+ const inputField = wrapper.find('[data-testid="item-create"]');
500
+
501
+ await inputField.setValue(value);
502
+
503
+ // press enter
504
+ await inputField.trigger('keydown.enter');
505
+ await wrapper.vm.$nextTick();
506
+
507
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
508
+
509
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
510
+ });
511
+
512
+ it('should not consider empty strings at the end', async() => {
513
+ const value = 'test;test1;test2;';
514
+ const result = ['test', 'test1', 'test2'];
515
+
516
+ // activate create mode
517
+ await wrapper.setData({ isCreateItem: true });
518
+ const inputField = wrapper.find('[data-testid="item-create"]');
519
+
520
+ await inputField.setValue(value);
521
+
522
+ // press enter
523
+ await inputField.trigger('keydown.enter');
524
+ await wrapper.vm.$nextTick();
525
+
526
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
527
+
528
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
529
+ });
530
+ });
531
+
532
+ describe('edit', () => {
533
+ const items = ['test1', 'test2'];
534
+
535
+ beforeEach(() => {
536
+ wrapper = mount(StringList, {
537
+ propsData: {
538
+ items,
539
+ bulkAdditionDelimiter: delimiter,
540
+ errorMessages: { duplicate: 'error, item is duplicate.' },
541
+ }
542
+ });
543
+ });
544
+
545
+ it('should split values if delimiter set', async() => {
546
+ const newValue = 'test1;new;values';
547
+ const result = ['test1', 'new', 'values', 'test2'];
548
+
549
+ await wrapper.setData({ editedItem: items[0] });
550
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
551
+
552
+ await inputField.setValue(newValue);
553
+
554
+ // press enter
555
+ await inputField.trigger('keydown.enter');
556
+ await wrapper.vm.$nextTick();
557
+
558
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
559
+
560
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
561
+ });
562
+
563
+ it('should show warning if one of the values is a duplicate', async() => {
564
+ const newValue = 'test1;test2';
565
+
566
+ await wrapper.setData({ editedItem: items[0] });
567
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
568
+
569
+ await inputField.setValue(newValue);
570
+
571
+ // press enter
572
+ await inputField.trigger('keydown.enter');
573
+ await wrapper.vm.$nextTick();
574
+
575
+ const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
576
+
577
+ expect(isDuplicate).toBe(true);
578
+ });
579
+
580
+ it('should show a warning if the new values are all duplicates', async() => {
581
+ const newValue = 'test;test';
582
+
583
+ await wrapper.setData({ editedItem: items[0] });
584
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
585
+
586
+ await inputField.setValue(newValue);
587
+
588
+ // press enter
589
+ await inputField.trigger('keydown.enter');
590
+ await wrapper.vm.$nextTick();
591
+
592
+ const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
593
+
594
+ expect(isDuplicate).toBe(true);
595
+ });
596
+
597
+ it('should not consider empty strings at the beginning', async() => {
598
+ const newValue = ';test1;new;value';
599
+ const result = ['test1', 'new', 'value', 'test2'];
600
+
601
+ await wrapper.setData({ editedItem: items[0] });
602
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
603
+
604
+ await inputField.setValue(newValue);
605
+
606
+ // press enter
607
+ await inputField.trigger('keydown.enter');
608
+ await wrapper.vm.$nextTick();
609
+
610
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
611
+
612
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
613
+ });
614
+
615
+ it('should not consider empty strings in the middle 1', async() => {
616
+ const newValue = 'test1; ;new;value';
617
+ const result = ['test1', 'new', 'value', 'test2'];
618
+
619
+ await wrapper.setData({ editedItem: items[0] });
620
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
621
+
622
+ await inputField.setValue(newValue);
623
+
624
+ // press enter
625
+ await inputField.trigger('keydown.enter');
626
+ await wrapper.vm.$nextTick();
627
+
628
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
629
+
630
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
631
+ });
632
+
633
+ it('should not consider empty strings in the middle 2', async() => {
634
+ const newValue = 'test1;;new;value';
635
+ const result = ['test1', 'new', 'value', 'test2'];
636
+
637
+ await wrapper.setData({ editedItem: items[0] });
638
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
639
+
640
+ await inputField.setValue(newValue);
641
+
642
+ // press enter
643
+ await inputField.trigger('keydown.enter');
644
+ await wrapper.vm.$nextTick();
645
+
646
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
647
+
648
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
649
+ });
650
+
651
+ it('should not consider empty strings at the end', async() => {
652
+ const newValue = 'test1;new;value;';
653
+ const result = ['test1', 'new', 'value', 'test2'];
654
+
655
+ await wrapper.setData({ editedItem: items[0] });
656
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
657
+
658
+ await inputField.setValue(newValue);
659
+
660
+ // press enter
661
+ await inputField.trigger('keydown.enter');
662
+ await wrapper.vm.$nextTick();
663
+
664
+ const itemsResult = (wrapper.emitted('change') || [])[0][0];
665
+
666
+ expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
667
+ });
668
+ });
669
+ });
670
+
401
671
  describe('errors handling', () => {
402
672
  it('show duplicate warning icon when errorMessages is defined', async() => {
403
673
  const items = ['test'];
@@ -82,6 +82,13 @@ export default Vue.extend({
82
82
  return {} as ErrorMessages;
83
83
  },
84
84
  },
85
+ /**
86
+ * Enables bulk addition and defines the delimiter to split the input string.
87
+ */
88
+ bulkAdditionDelimiter: {
89
+ type: RegExp,
90
+ default: null,
91
+ }
85
92
  },
86
93
  data() {
87
94
  return {
@@ -125,13 +132,9 @@ export default Vue.extend({
125
132
  },
126
133
 
127
134
  methods: {
128
- onChange(value: string) {
135
+ onChange(value: string, index?: number) {
129
136
  this.value = value;
130
-
131
- const items = [
132
- ...this.items,
133
- this.value
134
- ];
137
+ const items = this.addValueToItems(this.items, value, index);
135
138
 
136
139
  this.toggleError(
137
140
  'duplicate',
@@ -321,10 +324,7 @@ export default Vue.extend({
321
324
  const value = this.value?.trim();
322
325
 
323
326
  if (value) {
324
- const items = [
325
- ...this.items,
326
- value,
327
- ];
327
+ const items = this.addValueToItems(this.items, value);
328
328
 
329
329
  if (!hasDuplicatedStrings(items, this.caseSensitive)) {
330
330
  this.updateItems(items);
@@ -343,12 +343,8 @@ export default Vue.extend({
343
343
  const value = this.value?.trim();
344
344
 
345
345
  if (value) {
346
- const items = [...this.items];
347
- const index = findStringIndex(items, item, false);
348
-
349
- if (index !== -1) {
350
- items[index] = value;
351
- }
346
+ const index = findStringIndex(this.items, item, false);
347
+ const items = index !== -1 ? this.addValueToItems(this.items, value, index) : this.items;
352
348
 
353
349
  if (!hasDuplicatedStrings(items, this.caseSensitive)) {
354
350
  this.updateItems(items);
@@ -360,6 +356,49 @@ export default Vue.extend({
360
356
  }
361
357
  },
362
358
 
359
+ /**
360
+ * Add a new or update an exiting item in the items list.
361
+ *
362
+ * @param items The current items list.
363
+ * @param value The new value to be added.
364
+ * @param index The list index of the item to be updated (optional).
365
+ * @returns Updated items list.
366
+ */
367
+ addValueToItems(items: string[], value: string, index?: number): string[] {
368
+ const updatedItems = [...items];
369
+
370
+ // Add new item
371
+ if (index === undefined) {
372
+ if (this.bulkAdditionDelimiter && value.search(this.bulkAdditionDelimiter) >= 0) {
373
+ updatedItems.push(...this.splitBulkValue(value));
374
+ } else {
375
+ updatedItems.push(value);
376
+ }
377
+ } else { // Update existing item
378
+ if (this.bulkAdditionDelimiter && value.search(this.bulkAdditionDelimiter) >= 0) {
379
+ updatedItems.splice(index, 1, ...this.splitBulkValue(value));
380
+ } else {
381
+ updatedItems[index] = value;
382
+ }
383
+ }
384
+
385
+ return updatedItems;
386
+ },
387
+
388
+ /**
389
+ * Split the value by the defined delimiter and remove empty strings.
390
+ *
391
+ * @param value The value to be split.
392
+ * @returns Array containing split values.
393
+ */
394
+ splitBulkValue(value: string): string[] {
395
+ return value
396
+ .split(this.bulkAdditionDelimiter)
397
+ .filter((item) => {
398
+ return item.trim().length > 0;
399
+ });
400
+ },
401
+
363
402
  /**
364
403
  * Remove an item from items list
365
404
  */
@@ -393,7 +432,7 @@ export default Vue.extend({
393
432
  @dblclick="onClickEmptyBody()"
394
433
  >
395
434
  <div
396
- v-for="item in items"
435
+ v-for="(item, index) in items"
397
436
  :key="item"
398
437
  :ref="item"
399
438
  :class="{
@@ -421,7 +460,7 @@ export default Vue.extend({
421
460
  :data-testid="`item-edit-${item}`"
422
461
  class="edit-input static"
423
462
  :value="value != null ? value : item"
424
- @input="onChange($event)"
463
+ @input="onChange($event, index)"
425
464
  @blur.prevent="updateItem(item)"
426
465
  @keydown.native.enter="updateItem(item, !errors.duplicate)"
427
466
  />
@@ -0,0 +1,45 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { Accordion } from './index';
3
+
4
+ describe('component: Accordion', () => {
5
+ it('is closed initially by default', () => {
6
+ const title = 'Test Title';
7
+
8
+ const wrapper = shallowMount(Accordion, { propsData: { title } });
9
+
10
+ expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(false);
11
+ expect(wrapper.find('[data-testid="accordion-header"]').text()).toBe(title);
12
+ });
13
+
14
+ it('is opened initially when openInitially prop is true', () => {
15
+ const wrapper = shallowMount(Accordion, { propsData: { openInitially: true } });
16
+
17
+ expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(true);
18
+ });
19
+
20
+ it('when closed, opens when the header is clicked', async() => {
21
+ const wrapper = shallowMount(Accordion, { });
22
+
23
+ await wrapper.find('[data-testid="accordion-header"]').trigger('click');
24
+ expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(true);
25
+ });
26
+
27
+ it('when open, closes when the header is clicked', async() => {
28
+ const wrapper = shallowMount(Accordion, { propsData: { openInitially: true } });
29
+
30
+ await wrapper.find('[data-testid="accordion-header"]').trigger('click');
31
+ expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(false);
32
+ });
33
+
34
+ it('displays a chevron when closed', async() => {
35
+ const wrapper = shallowMount(Accordion, { propsData: { } });
36
+
37
+ expect(wrapper.find('[data-testid="accordion-header"] .icon-chevron-up').exists()).toBe(true);
38
+ });
39
+
40
+ it('displays an inverted chevron when open', async() => {
41
+ const wrapper = shallowMount(Accordion, { propsData: { openInitially: true } });
42
+
43
+ expect(wrapper.find('[data-testid="accordion-header"] .icon-chevron-down').exists()).toBe(true);
44
+ });
45
+ });
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ import Vue from 'vue';
3
+ import { mapGetters } from 'vuex';
4
+ export default Vue.extend({
5
+ props: {
6
+ title: {
7
+ type: String,
8
+ default: ''
9
+ },
10
+
11
+ titleKey: {
12
+ type: String,
13
+ default: null
14
+ },
15
+
16
+ openInitially: {
17
+ type: Boolean,
18
+ default: false
19
+ }
20
+ },
21
+
22
+ data() {
23
+ return { isOpen: this.openInitially };
24
+ },
25
+
26
+ computed: { ...mapGetters({ t: 'i18n/t' }) },
27
+
28
+ methods: {
29
+ toggle() {
30
+ this.isOpen = !this.isOpen;
31
+ }
32
+ }
33
+ });
34
+ </script>
35
+
36
+ <template>
37
+ <div class="accordion-container">
38
+ <div
39
+ class="accordion-header"
40
+ data-testid="accordion-header"
41
+ @click="toggle"
42
+ >
43
+ <i
44
+ class="icon text-primary"
45
+ :class="{'icon-chevron-down':isOpen, 'icon-chevron-up':!isOpen}"
46
+ data-testid="accordion-chevron"
47
+ />
48
+ <slot name="header">
49
+ <h4
50
+ data-testid="accordion-title-slot-content"
51
+ class="mb-0"
52
+ >
53
+ {{ titleKey ? t(titleKey) : title }}
54
+ </h4>
55
+ </slot>
56
+ </div>
57
+ <div
58
+ v-show="isOpen"
59
+ class="accordion-body"
60
+ data-testid="accordion-body"
61
+ >
62
+ <slot />
63
+ </div>
64
+ </div>
65
+ </template>
66
+
67
+ <style lang="scss" scoped>
68
+ .accordion-container {
69
+ border: 1px solid var(--border)
70
+ }
71
+ .accordion-header {
72
+ padding: 5px;
73
+ display: flex;
74
+ align-items: center;
75
+ &>*{
76
+ padding: 5px 0px 5px 0px;
77
+ }
78
+ I {
79
+ margin: 0px 10px 0px 10px;
80
+ }
81
+ }
82
+ .accordion-body {
83
+ padding: 10px;
84
+ }
85
+ </style>
@@ -0,0 +1 @@
1
+ export { default as Accordion } from './Accordion.vue';
@@ -1,4 +1,3 @@
1
-
2
1
  import { mount } from '@vue/test-utils';
3
2
  import { LabeledInput } from './index';
4
3
 
@@ -6,7 +5,7 @@ describe('component: LabeledInput', () => {
6
5
  it('should emit input only once', () => {
7
6
  const value = '2';
8
7
  const delay = 1;
9
- const wrapper = mount(LabeledInput, {
8
+ const wrapper = mount(LabeledInput as any, {
10
9
  propsData: { delay },
11
10
  mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
12
11
  });
@@ -20,4 +19,22 @@ describe('component: LabeledInput', () => {
20
19
  expect(wrapper.emitted('input')).toHaveLength(1);
21
20
  expect(wrapper.emitted('input')![0][0]).toBe(value);
22
21
  });
22
+
23
+ it('using mode "multiline" should emit input value correctly', () => {
24
+ const value = 'any-string';
25
+ const delay = 1;
26
+ const wrapper = mount(LabeledInput as any, {
27
+ propsData: { delay, multiline: true },
28
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
29
+ });
30
+
31
+ jest.useFakeTimers();
32
+ wrapper.find('input').setValue('1');
33
+ wrapper.find('input').setValue(value);
34
+ jest.advanceTimersByTime(delay);
35
+ jest.useRealTimers();
36
+
37
+ expect(wrapper.emitted('input')).toHaveLength(1);
38
+ expect(wrapper.emitted('input')![0][0]).toBe(value);
39
+ });
23
40
  });
@@ -90,7 +90,7 @@ export default (
90
90
  delay: {
91
91
  type: Number,
92
92
  default: 0
93
- }
93
+ },
94
94
  },
95
95
 
96
96
  data() {
@@ -216,6 +216,9 @@ export default (
216
216
  /**
217
217
  * Emit on input with delay. Note: Arrow function is avoided due context
218
218
  * binding.
219
+ *
220
+ * NOTE: In multiline, TextAreaAutoGrow emits a string with the value
221
+ * https://github.com/rancher/dashboard/issues/10249
219
222
  */
220
223
  delayInput(value: string): void {
221
224
  this.$emit('input', value);
@@ -0,0 +1,50 @@
1
+ .podman-setup: &podman-setup
2
+ - zypper --non-interactive install jq podman
3
+ - usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(id -nu)
4
+ - podman login -u $REGISTRY_USER -p $REGISTRY_PASSWORD $REGISTRY
5
+
6
+ check_version_collisions:
7
+ stage: check_version
8
+ rules:
9
+ - changes:
10
+ - package.json
11
+ script:
12
+ - *podman-setup
13
+ - |
14
+ PACKAGE_VERSION=`jq -r .version package.json`
15
+ PACKAGE_NAME=`jq -r .name package.json`
16
+
17
+ readarray -t VERSIONS < <(podman search $REGISTRY/$IMAGE_NAMESPACE/ui-extension-$PACKAGE_NAME --list-tags --format {{.Tag}})
18
+
19
+ echo -e "Checking for version collisions with Extension version: ${PACKAGE_VERSION}"
20
+ for version in ${VERSIONS[@]}; do
21
+ if [[ ${version} == ${PACKAGE_VERSION} ]]; then
22
+ echo "Cannot overwrite production image version ${version} since it already exists."
23
+ podman logout $REGISTRY
24
+ exit 1
25
+ fi
26
+ done
27
+
28
+ echo -e "Published image not found for version ${PACKAGE_VERSION}, continuing build..."
29
+ tags:
30
+ - linux
31
+
32
+ build_and_release:
33
+ stage: build_catalog
34
+ rules:
35
+ - changes:
36
+ - package.json
37
+ script:
38
+ - *podman-setup
39
+ - zypper addrepo https://download.opensuse.org/repositories/devel:languages:nodejs/SLE_12_SP5/devel:languages:nodejs.repo
40
+ - zypper --non-interactive --no-gpg-checks refresh
41
+ - zypper --non-interactive install go git nodejs14 npm helm
42
+ - YQ_URL="https://github.com/mikefarah/yq/releases/download/v4.35.2/yq_linux_amd64"
43
+ - curl -sLf ${YQ_URL} -o /usr/bin/yq && chmod +x /usr/bin/yq
44
+ - npm install -g --unsafe-perm yarn
45
+ - yarn
46
+ - yarn publish-pkgs -cl -r $REGISTRY -o $IMAGE_NAMESPACE
47
+ - podman push `podman images -f reference!=registry.suse.com/bci/bci-base --format "{{index .Names 0}}"`
48
+ - podman logout $REGISTRY
49
+ tags:
50
+ - linux
@@ -5,7 +5,7 @@ GITHUB_RUN_ID=$2
5
5
  GITHUB_WORKFLOW_TYPE=$3
6
6
 
7
7
  # Check packages for released tag name
8
- if [[ "${GITHUB_WORKFLOW_TYPE}" == "container" ]]; then
8
+ if [[ "${GITHUB_WORKFLOW_TYPE}" == "charts" ]]; then
9
9
  for d in pkg/*/ ; do
10
10
  pkg=$(basename $d)
11
11
 
@@ -27,4 +27,4 @@ else
27
27
  echo -e "tag: ${GITHUB_RELEASE_TAG}"
28
28
  gh run cancel ${GITHUB_RUN_ID}
29
29
  fi
30
- fi
30
+ fi
@@ -8,6 +8,8 @@ BASE_DIR="$(
8
8
  SHELL_DIR=$BASE_DIR/shell/
9
9
  TMP_DIR=$BASE_DIR/tmp
10
10
  PUBLISH_ARGS="--no-git-tag-version --access public $PUBLISH_ARGS"
11
+ FORCE_PUBLISH_TO_NPM="false"
12
+ DEFAULT_YARN_REGISTRY="https://registry.npmjs.org"
11
13
 
12
14
  if [ ! -d "${BASE_DIR}/node_modules" ]; then
13
15
  echo "You need to run 'yarn install' first"
@@ -16,6 +18,14 @@ fi
16
18
 
17
19
  echo "Publishing Shell Packages"
18
20
 
21
+ if [ "$1" == "--npm" ]; then
22
+ FORCE_PUBLISH_TO_NPM="true"
23
+ fi
24
+
25
+ if [ "$FORCE_PUBLISH_TO_NPM" == "true" ]; then
26
+ export YARN_REGISTRY=$DEFAULT_YARN_REGISTRY
27
+ fi
28
+
19
29
  # We use the version from the shell package for the creator packages
20
30
  # Need to copy them to a temporary location, so we can patch the version number
21
31
  # before publishing