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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/assets/styles/base/_basic.scss +7 -8
  2. package/assets/styles/global/_button.scss +10 -0
  3. package/assets/styles/global/_form.scss +2 -1
  4. package/assets/styles/global/_tooltip.scss +2 -2
  5. package/assets/styles/themes/_dark.scss +15 -3
  6. package/assets/styles/themes/_light.scss +7 -2
  7. package/assets/styles/vendor/vue-select.scss +4 -0
  8. package/assets/translations/en-us.yaml +66 -9
  9. package/assets/translations/zh-hans.yaml +2 -3
  10. package/components/AppModal.vue +50 -0
  11. package/components/BannerGraphic.vue +0 -42
  12. package/components/ButtonMultiAction.vue +1 -1
  13. package/components/Carousel.vue +88 -74
  14. package/components/CommunityLinks.vue +6 -1
  15. package/components/CopyToClipboardText.vue +3 -0
  16. package/components/Dialog.vue +20 -1
  17. package/components/GrowlManager.vue +9 -2
  18. package/components/LocaleSelector.vue +8 -1
  19. package/components/PaginatedResourceTable.vue +4 -7
  20. package/components/ProgressBarMulti.vue +14 -0
  21. package/components/PromptChangePassword.vue +3 -0
  22. package/components/Questions/Reference.vue +57 -28
  23. package/components/ResourceDetail/Masthead.vue +1 -1
  24. package/components/SelectIconGrid.vue +12 -1
  25. package/components/SideNav.vue +12 -38
  26. package/components/SortableTable/index.vue +1 -0
  27. package/components/Tabbed/index.vue +9 -1
  28. package/components/YamlEditor.vue +1 -0
  29. package/components/__tests__/Carousel.test.ts +56 -27
  30. package/components/auth/Principal.vue +5 -3
  31. package/components/fleet/FleetClusters.vue +82 -1
  32. package/components/fleet/FleetRepos.vue +13 -30
  33. package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
  34. package/components/form/ChangePassword.vue +2 -0
  35. package/components/form/ColorInput.vue +24 -1
  36. package/components/form/FileSelector.vue +2 -0
  37. package/components/form/KeyValue.vue +230 -160
  38. package/components/form/LabeledSelect.vue +2 -2
  39. package/components/form/PlusMinus.vue +14 -2
  40. package/components/form/ResourceLabeledSelect.vue +13 -53
  41. package/components/form/ResourceSelector.vue +1 -0
  42. package/components/form/ResourceTabs/index.vue +79 -36
  43. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +192 -0
  44. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +104 -0
  45. package/components/form/SSHKnownHosts/index.vue +101 -0
  46. package/components/form/SecretSelector.vue +2 -2
  47. package/components/form/Select.vue +1 -1
  48. package/components/form/SelectOrCreateAuthSecret.vue +43 -11
  49. package/components/form/__tests__/KeyValue.test.ts +1 -1
  50. package/components/form/__tests__/SSHKnownHosts.test.ts +59 -0
  51. package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
  52. package/components/formatter/FleetSummaryGraph.vue +6 -7
  53. package/components/formatter/WorkloadHealthScale.vue +7 -0
  54. package/components/nav/Group.vue +30 -4
  55. package/components/nav/Header.vue +82 -114
  56. package/components/nav/HeaderPageActionMenu.vue +27 -131
  57. package/components/nav/NamespaceFilter.vue +1 -1
  58. package/components/nav/Type.vue +15 -0
  59. package/composables/focusTrap.ts +68 -0
  60. package/config/home-links.js +21 -13
  61. package/config/labels-annotations.js +2 -0
  62. package/config/page-actions.js +1 -0
  63. package/config/pagination-table-headers.js +15 -1
  64. package/config/product/explorer.js +7 -17
  65. package/config/table-headers.js +6 -0
  66. package/config/version.js +5 -1
  67. package/core/plugin.ts +41 -1
  68. package/core/plugins.js +125 -72
  69. package/core/types-provisioning.ts +91 -2
  70. package/core/types.ts +55 -0
  71. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
  72. package/detail/catalog.cattle.io.app.vue +1 -1
  73. package/detail/fleet.cattle.io.cluster.vue +3 -3
  74. package/detail/namespace.vue +13 -19
  75. package/detail/networking.k8s.io.ingress.vue +13 -53
  76. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  77. package/detail/secret.vue +25 -0
  78. package/detail/workload/index.vue +3 -3
  79. package/dialog/AddCustomBadgeDialog.vue +5 -1
  80. package/edit/auth/ldap/__tests__/config.test.ts +18 -0
  81. package/edit/auth/ldap/config.vue +24 -0
  82. package/edit/auth/saml.vue +8 -6
  83. package/edit/fleet.cattle.io.gitrepo.vue +34 -23
  84. package/edit/logging-flow/index.vue +4 -19
  85. package/edit/networking.k8s.io.ingress/index.vue +18 -65
  86. package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
  87. package/edit/provisioning.cattle.io.cluster/index.vue +27 -8
  88. package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
  89. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
  90. package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
  91. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
  92. package/edit/secret/index.vue +1 -1
  93. package/edit/secret/ssh.vue +21 -3
  94. package/edit/service.vue +1 -2
  95. package/list/networking.k8s.io.ingress.vue +1 -1
  96. package/list/node.vue +15 -8
  97. package/list/persistentvolume.vue +12 -4
  98. package/list/provisioning.cattle.io.cluster.vue +1 -0
  99. package/list/service.vue +1 -1
  100. package/list/workload.vue +4 -0
  101. package/mixins/chart.js +4 -1
  102. package/models/catalog.cattle.io.app.js +3 -1
  103. package/models/catalog.cattle.io.clusterrepo.js +56 -7
  104. package/models/fleet.cattle.io.bundle.js +0 -11
  105. package/models/fleet.cattle.io.cluster.js +17 -1
  106. package/models/fleet.cattle.io.gitrepo.js +88 -52
  107. package/models/provisioning.cattle.io.cluster.js +36 -1
  108. package/models/secret.js +5 -0
  109. package/models/service.js +1 -0
  110. package/models/workload.js +19 -1
  111. package/package.json +5 -4
  112. package/pages/account/index.vue +4 -0
  113. package/pages/c/_cluster/apps/charts/index.vue +4 -0
  114. package/pages/c/_cluster/explorer/ConfigBadge.vue +4 -2
  115. package/pages/c/_cluster/explorer/index.vue +13 -6
  116. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
  117. package/pages/c/_cluster/fleet/index.vue +75 -89
  118. package/pages/c/_cluster/settings/links.vue +2 -2
  119. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +3 -1
  120. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +3 -0
  121. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +7 -1
  122. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +3 -1
  123. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +10 -7
  124. package/pages/c/_cluster/uiplugins/InstallDialog.vue +7 -0
  125. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +181 -106
  126. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +2 -0
  127. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +9 -1
  128. package/pages/c/_cluster/uiplugins/index.vue +50 -12
  129. package/pages/diagnostic.vue +17 -15
  130. package/pages/home.vue +32 -6
  131. package/plugins/clean-html.js +50 -0
  132. package/plugins/dashboard-store/resource-class.js +4 -0
  133. package/plugins/plugin.js +54 -49
  134. package/plugins/steve/mutations.js +1 -1
  135. package/plugins/steve/steve-class.js +8 -0
  136. package/plugins/steve/steve-pagination-utils.ts +3 -1
  137. package/rancher-components/Accordion/Accordion.vue +4 -4
  138. package/rancher-components/BadgeState/BadgeState.vue +7 -0
  139. package/rancher-components/Card/Card.vue +12 -0
  140. package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
  141. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
  142. package/rancher-components/Form/LabeledInput/LabeledInput.vue +19 -1
  143. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
  144. package/rancher-components/RcButton/RcButton.vue +90 -0
  145. package/rancher-components/RcButton/index.ts +2 -0
  146. package/rancher-components/RcButton/types.ts +17 -0
  147. package/rancher-components/RcDropdown/RcDropdown.vue +122 -0
  148. package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
  149. package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
  150. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +42 -0
  151. package/rancher-components/RcDropdown/index.ts +4 -0
  152. package/rancher-components/RcDropdown/types.ts +22 -0
  153. package/rancher-components/RcDropdown/useDropdownCollection.ts +46 -0
  154. package/rancher-components/RcDropdown/useDropdownContext.ts +110 -0
  155. package/scripts/test-plugins-build.sh +2 -0
  156. package/scripts/typegen.sh +2 -0
  157. package/store/catalog.js +1 -1
  158. package/tsconfig.json +2 -1
  159. package/types/components/paginatedResourceTable.ts +25 -0
  160. package/types/components/resourceLabeledSelect.ts +48 -0
  161. package/types/resources/fleet.d.ts +17 -0
  162. package/types/shell/index.d.ts +61 -0
  163. package/utils/auth.js +5 -1
  164. package/utils/cluster.js +106 -0
  165. package/utils/fleet.ts +35 -3
  166. package/utils/ingress.ts +64 -0
  167. package/utils/uiplugins.ts +56 -44
  168. package/utils/validators/cron-schedule.js +7 -2
  169. package/utils/validators/formRules/__tests__/index.test.ts +53 -17
  170. package/utils/validators/formRules/index.ts +20 -5
  171. package/vue.config.js +1 -1
  172. package/components/RelatedWorkloadsTable.vue +0 -50
@@ -8,10 +8,14 @@ import Tab from '@shell/components/Tabbed/Tab';
8
8
  import CreateEditView from '@shell/mixins/create-edit-view';
9
9
  import Conditions from '@shell/components/form/Conditions';
10
10
  import { EVENT } from '@shell/config/types';
11
- import SortableTable from '@shell/components/SortableTable';
11
+ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
12
12
  import { _VIEW } from '@shell/config/query-params';
13
13
  import RelatedResources from '@shell/components/RelatedResources';
14
14
  import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
15
+ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
16
+ import { MESSAGE, REASON } from '@shell/config/table-headers';
17
+ import { STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
18
+ import { headerFromSchemaColString } from '@shell/store/type-map.utils';
15
19
 
16
20
  export default {
17
21
 
@@ -21,7 +25,7 @@ export default {
21
25
  Tabbed,
22
26
  Tab,
23
27
  Conditions,
24
- SortableTable,
28
+ PaginatedResourceTable,
25
29
  RelatedResources,
26
30
  },
27
31
 
@@ -69,14 +73,25 @@ export default {
69
73
 
70
74
  data() {
71
75
  const inStore = this.$store.getters['currentStore'](EVENT);
76
+ const eventSchema = this.$store.getters[`${ inStore }/schemaFor`](EVENT); // @TODO be smarter about which resources actually ever have events
72
77
 
73
78
  return {
74
- hasEvents: this.$store.getters[`${ inStore }/schemaFor`](EVENT), // @TODO be smarter about which resources actually ever have events
75
- allEvents: [],
76
- selectedTab: this.defaultTab,
77
- didLoadEvents: false,
79
+ eventSchema,
80
+ EVENT,
81
+ selectedTab: this.defaultTab,
78
82
  inStore,
79
- showConditions: false,
83
+ showConditions: false,
84
+ paginationHeaders: [
85
+ STEVE_EVENT_LAST_SEEN,
86
+ STEVE_EVENT_TYPE,
87
+ REASON,
88
+ headerFromSchemaColString('Subobject', eventSchema, this.$store.getters, true),
89
+ headerFromSchemaColString('Source', eventSchema, this.$store.getters, true),
90
+ MESSAGE,
91
+ headerFromSchemaColString('First Seen', eventSchema, this.$store.getters, true),
92
+ headerFromSchemaColString('Count', eventSchema, this.$store.getters, true),
93
+ STEVE_NAME_COL,
94
+ ]
80
95
  };
81
96
  },
82
97
 
@@ -92,7 +107,7 @@ export default {
92
107
 
93
108
  computed: {
94
109
  showEvents() {
95
- return this.isView && this.needEvents && this.hasEvents;
110
+ return this.isView && this.needEvents && this.eventSchema;
96
111
  },
97
112
  showRelated() {
98
113
  return this.isView && this.needRelated;
@@ -128,18 +143,6 @@ export default {
128
143
  },
129
144
  ];
130
145
  },
131
- events() {
132
- return this.allEvents.filter((event) => {
133
- return event.involvedObject?.uid === this.value?.metadata?.uid;
134
- }).map((event) => {
135
- return {
136
- reason: (`${ event.reason || this.t('generic.unknown') }${ event.count > 1 ? ` (${ event.count })` : '' }`).trim(),
137
- message: event.message || this.t('generic.unknown'),
138
- date: event.lastTimestamp || event.firstTimestamp || event.metadata.creationTimestamp,
139
- eventType: event.eventType
140
- };
141
- });
142
- },
143
146
  conditionsHaveIssues() {
144
147
  if (this.showConditions) {
145
148
  return this.value.status?.conditions?.filter((cond) => !isConditionReadyAndWaiting(cond)).some((cond) => cond.error);
@@ -153,15 +156,6 @@ export default {
153
156
  // Ensures we only fetch events and show the table when the events tab has been activated
154
157
  tabChange(neu) {
155
158
  this.selectedTab = neu?.selectedName;
156
-
157
- if (!this.didLoadEvents && this.selectedTab === 'events') {
158
- const inStore = this.$store.getters['currentStore'](EVENT);
159
-
160
- this.$store.dispatch(`${ inStore }/findAll`, { type: EVENT }).then((events) => {
161
- this.allEvents = events;
162
- this.didLoadEvents = true;
163
- });
164
- }
165
159
  },
166
160
 
167
161
  /**
@@ -180,6 +174,54 @@ export default {
180
174
  this.showConditions = this.$store.getters[`${ this.inStore }/pathExistsInSchema`](this.value.type, 'status.conditions');
181
175
  }
182
176
  },
177
+
178
+ /**
179
+ * Filter out hidden repos from list of all repos
180
+ */
181
+ filterEventsLocal(rows) {
182
+ return rows.filter((event) => event.involvedObject?.uid === this.value?.metadata?.uid);
183
+ },
184
+
185
+ /**
186
+ * Filter out hidden repos via api
187
+ *
188
+ * pagination: PaginationArgs
189
+ * returns: PaginationArgs
190
+ */
191
+ filterEventsApi(pagination) {
192
+ if (!pagination.filters) {
193
+ pagination.filters = [];
194
+ }
195
+
196
+ const field = `involvedObject.uid`; // Pending API Support - https://github.com/rancher/rancher/issues/48603
197
+
198
+ // of type PaginationParamFilter
199
+ let existing = null;
200
+
201
+ for (let i = 0; i < pagination.filters.length; i++) {
202
+ const filter = pagination.filters[i];
203
+
204
+ if (!!filter.fields.find((f) => f.field === field)) {
205
+ existing = filter;
206
+ break;
207
+ }
208
+ }
209
+
210
+ const required = PaginationParamFilter.createSingleField({
211
+ field,
212
+ exact: true,
213
+ value: this.value.metadata.uid,
214
+ equals: true
215
+ });
216
+
217
+ if (!!existing) {
218
+ Object.assign(existing, required);
219
+ } else {
220
+ pagination.filters.push(required);
221
+ }
222
+
223
+ return pagination;
224
+ }
183
225
  }
184
226
  };
185
227
  </script>
@@ -208,15 +250,16 @@ export default {
208
250
  name="events"
209
251
  :weight="-2"
210
252
  >
211
- <SortableTable
253
+ <!-- namespaced: false given we don't want the default handling of namespaced resource (apply header filter) -->
254
+ <PaginatedResourceTable
212
255
  v-if="selectedTab === 'events'"
213
- :rows="events"
256
+ :schema="eventSchema"
257
+ :local-filter="filterEventsLocal"
258
+ :api-filter="filterEventsApi"
259
+ :use-query-params-for-simple-filtering="false"
214
260
  :headers="eventHeaders"
215
- key-field="id"
216
- :search="false"
217
- :table-actions="false"
218
- :row-actions="false"
219
- default-sort-by="date"
261
+ :paginationHeaders="paginationHeaders"
262
+ :namespaced="false"
220
263
  />
221
264
  </Tab>
222
265
 
@@ -0,0 +1,192 @@
1
+ <script>
2
+ import { _EDIT, _VIEW } from '@shell/config/query-params';
3
+ import CodeMirror from '@shell/components/CodeMirror';
4
+ import FileSelector from '@shell/components/form/FileSelector.vue';
5
+ import AppModal from '@shell/components/AppModal.vue';
6
+
7
+ export default {
8
+ emits: ['closed'],
9
+
10
+ components: {
11
+ FileSelector,
12
+ AppModal,
13
+ CodeMirror,
14
+ },
15
+
16
+ props: {
17
+ value: {
18
+ type: String,
19
+ required: true
20
+ },
21
+
22
+ mode: {
23
+ type: String,
24
+ default: _EDIT
25
+ },
26
+ },
27
+
28
+ data() {
29
+ const codeMirrorOptions = {
30
+ readOnly: this.isView,
31
+ gutters: ['CodeMirror-foldgutter'],
32
+ mode: 'text/x-properties',
33
+ lint: false,
34
+ lineNumbers: !this.isView,
35
+ styleActiveLine: false,
36
+ tabSize: 2,
37
+ indentWithTabs: false,
38
+ cursorBlinkRate: 530,
39
+ };
40
+
41
+ return {
42
+ codeMirrorOptions,
43
+ text: this.value,
44
+ showModal: false,
45
+ };
46
+ },
47
+
48
+ computed: {
49
+ isView() {
50
+ return this.mode === _VIEW;
51
+ }
52
+ },
53
+
54
+ methods: {
55
+ onTextChange(value) {
56
+ this.text = value?.trim();
57
+ },
58
+
59
+ showDialog() {
60
+ this.showModal = true;
61
+ },
62
+
63
+ closeDialog(result) {
64
+ if (!result) {
65
+ this.text = this.value;
66
+ }
67
+
68
+ this.showModal = false;
69
+
70
+ this.$emit('closed', {
71
+ success: result,
72
+ value: this.text,
73
+ });
74
+ },
75
+ }
76
+ };
77
+ </script>
78
+
79
+ <template>
80
+ <app-modal
81
+ v-if="showModal"
82
+ ref="sshKnownHostsDialog"
83
+ height="auto"
84
+ :scrollable="true"
85
+ @close="closeDialog(false)"
86
+ >
87
+ <div
88
+ class="ssh-known-hosts-dialog"
89
+ >
90
+ <h4 class="mt-10">
91
+ {{ t('secret.ssh.editKnownHosts.title') }}
92
+ </h4>
93
+ <div class="custom mt-10">
94
+ <div class="dialog-panel">
95
+ <CodeMirror
96
+ class="code-mirror"
97
+ :value="text"
98
+ data-testid="ssh-known-hosts-dialog_code-mirror"
99
+ :options="codeMirrorOptions"
100
+ :showKeyMapBox="true"
101
+ @onInput="onTextChange"
102
+ />
103
+ </div>
104
+ <div class="dialog-actions">
105
+ <div class="action-pannel file-selector">
106
+ <FileSelector
107
+ class="btn role-secondary"
108
+ data-testid="ssh-known-hosts-dialog_file-selector"
109
+ :label="t('generic.readFromFile')"
110
+ @selected="onTextChange"
111
+ />
112
+ </div>
113
+ <div class="action-pannel form-actions">
114
+ <button
115
+ class="btn role-secondary"
116
+ data-testid="ssh-known-hosts-dialog_cancel-btn"
117
+ @click="closeDialog(false)"
118
+ >
119
+ {{ t('generic.cancel') }}
120
+ </button>
121
+ <button
122
+ class="btn role-primary"
123
+ data-testid="ssh-known-hosts-dialog_save-btn"
124
+ @click="closeDialog(true)"
125
+ >
126
+ {{ t('generic.save') }}
127
+ </button>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </app-modal>
133
+ </template>
134
+
135
+ <style lang="scss" scoped>
136
+ .ssh-known-hosts-dialog {
137
+ padding: 15px;
138
+
139
+ h4 {
140
+ font-weight: bold;
141
+ margin-bottom: 20px;
142
+ }
143
+
144
+ .dialog-panel {
145
+ display: flex;
146
+ flex-direction: column;
147
+ min-height: 100px;
148
+ border: 1px solid var(--border);
149
+
150
+ :deep() .code-mirror {
151
+ display: flex;
152
+ flex-direction: column;
153
+ resize: none;
154
+ max-height: 400px;
155
+ height: 50vh;
156
+
157
+ .CodeMirror,
158
+ .CodeMirror-gutters {
159
+ min-height: 400px;
160
+ max-height: 400px;
161
+ background-color: var(--yaml-editor-bg);
162
+ }
163
+
164
+ .CodeMirror-gutters {
165
+ width: 25px;
166
+ }
167
+
168
+ .CodeMirror-linenumber {
169
+ padding-left: 0;
170
+ }
171
+ }
172
+ }
173
+
174
+ .dialog-actions {
175
+ display: flex;
176
+ justify-content: space-between;
177
+
178
+ .action-pannel {
179
+ margin-top: 10px;
180
+ }
181
+
182
+ .form-actions {
183
+ display: flex;
184
+ justify-content: flex-end;
185
+
186
+ > *:not(:last-child) {
187
+ margin-right: 10px;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ </style>
@@ -0,0 +1,104 @@
1
+ import { mount, VueWrapper } from '@vue/test-utils';
2
+ import { _EDIT } from '@shell/config/query-params';
3
+ import KnownHostsEditDialog from '@shell/components/form/SSHKnownHosts/KnownHostsEditDialog.vue';
4
+ import CodeMirror from '@shell/components/CodeMirror.vue';
5
+ import FileSelector from '@shell/components/form/FileSelector.vue';
6
+
7
+ let wrapper: VueWrapper<InstanceType<typeof KnownHostsEditDialog>>;
8
+
9
+ const mockedStore = () => {
10
+ return { getters: { 'prefs/get': () => jest.fn() } };
11
+ };
12
+
13
+ const requiredSetup = () => {
14
+ return { global: { mocks: { $store: mockedStore() } } };
15
+ };
16
+
17
+ describe('component: KnownHostsEditDialog', () => {
18
+ beforeEach(() => {
19
+ document.body.innerHTML = '<div id="modals"></div>';
20
+ wrapper = mount(KnownHostsEditDialog, {
21
+ attachTo: document.body,
22
+ props: {
23
+ mode: _EDIT,
24
+ value: 'line1\nline2\n',
25
+ },
26
+ ...requiredSetup(),
27
+ });
28
+ });
29
+
30
+ afterEach(() => {
31
+ wrapper.unmount();
32
+ document.body.innerHTML = '';
33
+ });
34
+
35
+ it('should update text from CodeMirror', async() => {
36
+ await wrapper.setData({ showModal: true });
37
+
38
+ expect(wrapper.vm.text).toBe('line1\nline2\n');
39
+
40
+ const codeMirror = wrapper.getComponent(CodeMirror);
41
+
42
+ expect(codeMirror.element).toBeDefined();
43
+
44
+ await codeMirror.setData({ loaded: true });
45
+
46
+ // Emit CodeMirror value
47
+ codeMirror.vm.$emit('onInput', 'bar');
48
+ await codeMirror.vm.$nextTick();
49
+
50
+ expect(wrapper.vm.text).toBe('bar');
51
+ });
52
+
53
+ it('should update text from FileSelector', async() => {
54
+ await wrapper.setData({ showModal: true });
55
+
56
+ expect(wrapper.vm.text).toBe('line1\nline2\n');
57
+
58
+ const fileSelector = wrapper.getComponent(FileSelector);
59
+
60
+ expect(fileSelector.element).toBeDefined();
61
+
62
+ // Emit Fileselector value
63
+ fileSelector.vm.$emit('selected', 'foo');
64
+ await fileSelector.vm.$nextTick();
65
+
66
+ expect(wrapper.vm.text).toBe('foo');
67
+ });
68
+
69
+ it('should save changes and close dialog', async() => {
70
+ await wrapper.setData({
71
+ showModal: true,
72
+ text: 'foo',
73
+ });
74
+
75
+ expect(wrapper.vm.value).toBe('line1\nline2\n');
76
+ expect(wrapper.vm.text).toBe('foo');
77
+
78
+ await wrapper.vm.closeDialog(true);
79
+
80
+ expect((wrapper.emitted('closed') as any)[0][0].value).toBe('foo');
81
+
82
+ const dialog = wrapper.vm.$refs['sshKnownHostsDialog'];
83
+
84
+ expect(dialog).toBeNull();
85
+ });
86
+
87
+ it('should discard changes and close dialog', async() => {
88
+ await wrapper.setData({
89
+ showModal: true,
90
+ text: 'foo',
91
+ });
92
+
93
+ expect(wrapper.vm.value).toBe('line1\nline2\n');
94
+ expect(wrapper.vm.text).toBe('foo');
95
+
96
+ await wrapper.vm.closeDialog(false);
97
+
98
+ expect((wrapper.emitted('closed') as any)[0][0].value).toBe('line1\nline2\n');
99
+
100
+ const dialog = wrapper.vm.$refs['sshKnownHostsDialog'];
101
+
102
+ expect(dialog).toBeNull();
103
+ });
104
+ });
@@ -0,0 +1,101 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue';
3
+ import KnownHostsEditDialog from './KnownHostsEditDialog.vue';
4
+ import { _EDIT, _VIEW } from '@shell/config/query-params';
5
+
6
+ export default defineComponent({
7
+ name: 'SSHKnownHosts',
8
+
9
+ emits: ['update:value'],
10
+
11
+ props: {
12
+ value: {
13
+ type: String,
14
+ required: true
15
+ },
16
+
17
+ mode: {
18
+ type: String,
19
+ default: _EDIT
20
+ },
21
+ },
22
+
23
+ components: { KnownHostsEditDialog },
24
+
25
+ computed: {
26
+ isViewMode() {
27
+ return this.mode === _VIEW;
28
+ },
29
+
30
+ // The number of entries - exclude empty lines and comments
31
+ entries() {
32
+ return this.value.split('\n').filter((line: string) => !!line.trim().length && !line.startsWith('#')).length;
33
+ },
34
+
35
+ summary() {
36
+ return this.t('secret.ssh.editKnownHosts.entries', { entries: this.entries });
37
+ }
38
+ },
39
+
40
+ methods: {
41
+ openDialog() {
42
+ (this.$refs.button as HTMLInputElement)?.blur();
43
+ (this.$refs.editDialog as any).showDialog();
44
+ },
45
+
46
+ dialogClosed(result: any) {
47
+ if (result.success) {
48
+ this.$emit('update:value', result.value);
49
+ }
50
+ }
51
+ }
52
+ });
53
+ </script>
54
+ <template>
55
+ <div
56
+ class="input-known-ssh-hosts labeled-input"
57
+ data-testid="input-known-ssh-hosts"
58
+ >
59
+ <label>{{ t('secret.ssh.knownHosts') }}</label>
60
+ <div
61
+ class="hosts-input"
62
+ data-testid="input-known-ssh-hosts_summary"
63
+ >
64
+ {{ summary }}
65
+ </div>
66
+ <template v-if="!isViewMode">
67
+ <button
68
+ ref="button"
69
+ data-testid="input-known-ssh-hosts_open-dialog"
70
+ class="show-dialog-btn btn"
71
+ @click="openDialog"
72
+ >
73
+ <i class="icon icon-edit" />
74
+ </button>
75
+
76
+ <KnownHostsEditDialog
77
+ ref="editDialog"
78
+ :value="value"
79
+ :mode="mode"
80
+ @closed="dialogClosed"
81
+ />
82
+ </template>
83
+ </div>
84
+ </template>
85
+ <style lang="scss" scoped>
86
+ .input-known-ssh-hosts {
87
+ display: flex;
88
+ justify-content: space-between;
89
+
90
+ .hosts-input {
91
+ cursor: default;
92
+ line-height: calc(18px + 1px);
93
+ padding: 18px 0 0 0;
94
+ }
95
+
96
+ .show-dialog-btn {
97
+ display: contents;
98
+ background-color: transparent;
99
+ }
100
+ }
101
+ </style>
@@ -70,7 +70,7 @@ export default {
70
70
  secrets: null,
71
71
  SECRET,
72
72
  allSecretsSettings: {
73
- mapResult: (secrets) => {
73
+ updateResources: (secrets) => {
74
74
  const allSecretsInNamespace = secrets.filter((secret) => this.types.includes(secret._type) && secret.namespace === this.namespace);
75
75
  const mappedSecrets = this.mapSecrets(allSecretsInNamespace.sort((a, b) => a.name.localeCompare(b.name)));
76
76
 
@@ -81,7 +81,7 @@ export default {
81
81
  },
82
82
  paginateSecretsSetting: {
83
83
  requestSettings: this.paginatePageOptions,
84
- mapResult: (secrets) => {
84
+ updateResources: (secrets) => {
85
85
  const mappedSecrets = this.mapSecrets(secrets);
86
86
 
87
87
  this.secrets = secrets; // We need the key from the selected secret. When paginating we won't touch the store, so just pass back here
@@ -256,7 +256,7 @@ export default {
256
256
  }"
257
257
  :tabindex="disabled || isView ? -1 : 0"
258
258
  @click="focusSearch"
259
- @keyup.enter.space.down="focusSearch"
259
+ @keydown.enter.space.down="focusSearch"
260
260
  >
261
261
  <v-select
262
262
  ref="select-input"