@rancher/shell 0.1.21 → 0.2.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 (32) hide show
  1. package/assets/styles/global/_button.scss +1 -0
  2. package/assets/translations/en-us.yaml +48 -6
  3. package/assets/translations/zh-hans.yaml +18 -0
  4. package/components/HarvesterServiceAddOnConfig.vue +10 -10
  5. package/components/ResourceList/index.vue +34 -14
  6. package/components/ResourceTable.vue +19 -0
  7. package/components/SortableTable/THead.vue +311 -70
  8. package/components/SortableTable/advanced-filtering.js +272 -0
  9. package/components/SortableTable/filtering.js +90 -29
  10. package/components/SortableTable/index.vue +474 -271
  11. package/components/form/WorkloadPorts.vue +2 -2
  12. package/config/product/settings.js +1 -0
  13. package/config/product/uiplugins.js +1 -0
  14. package/config/uiplugins.js +13 -0
  15. package/creators/app/init +2 -2
  16. package/creators/app/package.json +1 -1
  17. package/creators/pkg/package.json +1 -1
  18. package/detail/provisioning.cattle.io.cluster.vue +1 -1
  19. package/edit/provisioning.cattle.io.cluster/ACE.vue +2 -1
  20. package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +0 -1
  21. package/edit/provisioning.cattle.io.cluster/rke2.vue +25 -24
  22. package/edit/service.vue +1 -1
  23. package/models/management.cattle.io.cluster.js +9 -1
  24. package/package.json +1 -1
  25. package/pages/c/_cluster/apps/charts/install.vue +119 -31
  26. package/pages/c/_cluster/uiplugins/InstallDialog.vue +1 -1
  27. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +5 -2
  28. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +1 -1
  29. package/pages/c/_cluster/uiplugins/index.vue +48 -11
  30. package/promptRemove/mixin/roleDeletionCheck.js +15 -1
  31. package/scripts/record-deps.js +3 -3
  32. package/store/type-map.js +2 -0
@@ -0,0 +1,272 @@
1
+ import { ADV_FILTER_ALL_COLS_VALUE, ADV_FILTER_ALL_COLS_LABEL } from './filtering';
2
+
3
+ const DEFAULT_ADV_FILTER_COLS_VALUE = ADV_FILTER_ALL_COLS_VALUE;
4
+
5
+ export default {
6
+ props: {
7
+ /**
8
+ * Group value
9
+ * To be used on the THead component when adv filtering is present
10
+ */
11
+ group: {
12
+ type: String,
13
+ default: () => ''
14
+ },
15
+ /**
16
+ * Group options
17
+ * All of the grouping options available to be used on the THead component when adv filtering is present
18
+ */
19
+ groupOptions: {
20
+ type: Array,
21
+ default: () => []
22
+ },
23
+ /**
24
+ * Flag that controls visibility of advanced filtering feature
25
+ */
26
+ hasAdvancedFiltering: {
27
+ type: Boolean,
28
+ default: false
29
+ },
30
+ /**
31
+ * Flag that controls visibility of labels as possibe toggable cols to be displayed on the Sortable Table
32
+ */
33
+ advFilterHideLabelsAsCols: {
34
+ type: Boolean,
35
+ default: false
36
+ },
37
+ /**
38
+ * Flag that prevents filtering by labels
39
+ */
40
+ advFilterPreventFilteringLabels: {
41
+ type: Boolean,
42
+ default: false
43
+ },
44
+ },
45
+ data() {
46
+ return {
47
+ columnOptions: [],
48
+ colOptionsWatcher: null,
49
+ advancedFilteringVisibility: false,
50
+ advancedFilteringValues: [],
51
+ advFilterSearchTerm: null,
52
+ advFilterSelectedProp: DEFAULT_ADV_FILTER_COLS_VALUE,
53
+ advFilterSelectedLabel: ADV_FILTER_ALL_COLS_LABEL,
54
+ column: null,
55
+ };
56
+ },
57
+
58
+ mounted() {
59
+ if (this.hasAdvancedFiltering) {
60
+ // trigger to first populate the cols options for filters
61
+ this.updateColsOptions();
62
+ }
63
+ },
64
+
65
+ watch: {
66
+ advancedFilteringValues() {
67
+ // passing different dummy args to make sure update is triggered
68
+ this.watcherUpdateLiveAndDelayed(true, false);
69
+ },
70
+ advancedFilteringVisibility(neu) {
71
+ if (neu) {
72
+ // check if user clicked outside the advanced filter box
73
+ window.addEventListener('click', this.onClickOutside);
74
+
75
+ // update filtering options and toggable cols every time dropdown is open
76
+ this.updateColsOptions();
77
+ } else {
78
+ // unregister click event
79
+ window.removeEventListener('click', this.onClickOutside);
80
+ }
81
+ }
82
+ },
83
+
84
+ computed: {
85
+ advFilterSelectOptions() {
86
+ return this.columnOptions.filter(c => c.isFilter && !c.preventFiltering);
87
+ },
88
+
89
+ advGroupOptions() {
90
+ return this.groupOptions.map((item) => {
91
+ return {
92
+ label: this.t(item.tooltipKey),
93
+ value: item.value
94
+ };
95
+ });
96
+ },
97
+ },
98
+
99
+ methods: {
100
+ handleColsVisibilyAndFiltering(cols) {
101
+ const allCols = cols;
102
+
103
+ this.columnOptions.forEach((advCol) => {
104
+ if (advCol.isTableOption) {
105
+ const index = allCols.findIndex(col => col.name === advCol.name);
106
+
107
+ if (index !== -1) {
108
+ allCols[index].isColVisible = advCol.isColVisible;
109
+ allCols[index].isFilter = advCol.isFilter;
110
+ } else {
111
+ allCols.push(advCol);
112
+ }
113
+ }
114
+ });
115
+
116
+ return allCols;
117
+ },
118
+ // advanced filtering methods
119
+ setColsOptions() {
120
+ let opts = [];
121
+ const rowLabels = [];
122
+ const headerProps = [];
123
+
124
+ // Filter out any columns that are too heavy to show for large page sizes
125
+ const filteredHeaders = this.headers.slice().filter(c => (!c.maxPageSize || (c.maxPageSize && c.maxPageSize >= this.perPage)));
126
+
127
+ // add table cols from config (headers)
128
+ filteredHeaders.forEach((prop) => {
129
+ const name = prop.name;
130
+ const label = prop.labelKey ? this.t(`${ prop.labelKey }`) : prop.label;
131
+ const isFilter = !!((!Object.keys(prop).includes('search') || prop.search));
132
+ let sortVal = prop.sort;
133
+ const valueProp = prop.valueProp || prop.value;
134
+ let value = null;
135
+ let isColVisible = true;
136
+
137
+ if (prop.sort && valueProp) {
138
+ if (typeof prop.sort === 'string') {
139
+ sortVal = prop.sort.includes(':') ? [prop.sort.split(':')[0]] : [prop.sort];
140
+ }
141
+
142
+ if (!sortVal.includes(valueProp)) {
143
+ value = JSON.stringify(sortVal.concat([valueProp]));
144
+ } else {
145
+ value = JSON.stringify([valueProp]);
146
+ }
147
+ } else if (valueProp) {
148
+ value = JSON.stringify([valueProp]);
149
+ } else {
150
+ value = null;
151
+ }
152
+
153
+ // maintain current visibility of cols if they exist already
154
+ if (this.columnOptions?.length) {
155
+ const opt = this.columnOptions.find(colOpt => colOpt.name === name && colOpt.label === label);
156
+
157
+ if (opt) {
158
+ isColVisible = opt.isColVisible;
159
+ }
160
+ }
161
+
162
+ headerProps.push({
163
+ name,
164
+ label,
165
+ value,
166
+ isFilter,
167
+ isTableOption: true,
168
+ isColVisible
169
+ });
170
+ });
171
+
172
+ // add labels as table cols
173
+ if (this.rows.length) {
174
+ this.rows.forEach((row) => {
175
+ if (row.metadata?.labels && Object.keys(row.metadata?.labels).length) {
176
+ Object.keys(row.metadata?.labels).forEach((label) => {
177
+ const res = {
178
+ name: label,
179
+ label,
180
+ value: `metadata.labels.${ label }`,
181
+ isFilter: true,
182
+ isTableOption: true,
183
+ isColVisible: false,
184
+ isLabel: true,
185
+ preventFiltering: this.advFilterPreventFilteringLabels,
186
+ preventColToggle: this.advFilterHideLabelsAsCols
187
+ };
188
+
189
+ // maintain current visibility of cols if they exist already
190
+ if (this.columnOptions?.length) {
191
+ const opt = this.columnOptions.find(colOpt => colOpt.name === label && colOpt.label === label);
192
+
193
+ if (opt) {
194
+ res.isColVisible = opt.isColVisible;
195
+ }
196
+ }
197
+
198
+ if (!rowLabels.filter(row => row.label === label).length) {
199
+ rowLabels.push(res);
200
+ }
201
+ });
202
+ }
203
+ });
204
+ }
205
+
206
+ opts = headerProps.concat(rowLabels);
207
+
208
+ // add find on all cols option...
209
+ if (opts.length) {
210
+ opts.unshift({
211
+ name: ADV_FILTER_ALL_COLS_LABEL,
212
+ label: ADV_FILTER_ALL_COLS_LABEL,
213
+ value: ADV_FILTER_ALL_COLS_VALUE,
214
+ isFilter: true,
215
+ isTableOption: false
216
+ });
217
+ }
218
+
219
+ return opts;
220
+ },
221
+ addAdvancedFilter() {
222
+ // set new advanced filter
223
+ if (this.advFilterSelectedProp && this.advFilterSearchTerm) {
224
+ this.advancedFilteringValues.push({
225
+ prop: this.advFilterSelectedProp,
226
+ value: this.advFilterSearchTerm,
227
+ label: this.advFilterSelectedLabel
228
+ });
229
+
230
+ this.eventualSearchQuery = this.advancedFilteringValues;
231
+
232
+ this.advancedFilteringVisibility = false;
233
+ this.advFilterSelectedProp = DEFAULT_ADV_FILTER_COLS_VALUE;
234
+ this.advFilterSelectedLabel = ADV_FILTER_ALL_COLS_LABEL;
235
+ this.advFilterSearchTerm = null;
236
+ }
237
+ },
238
+ clearAllAdvancedFilters() {
239
+ this.advancedFilteringValues = [];
240
+ this.eventualSearchQuery = this.advancedFilteringValues;
241
+
242
+ this.advancedFilteringVisibility = false;
243
+ this.advFilterSelectedProp = DEFAULT_ADV_FILTER_COLS_VALUE;
244
+ this.advFilterSelectedLabel = ADV_FILTER_ALL_COLS_LABEL;
245
+ this.advFilterSearchTerm = null;
246
+ },
247
+ clearAdvancedFilter(index) {
248
+ this.advancedFilteringValues.splice(index, 1);
249
+ this.eventualSearchQuery = this.advancedFilteringValues;
250
+ },
251
+ onClickOutside(event) {
252
+ const advFilterBox = this.$refs['advanced-filter-group'];
253
+
254
+ if (!advFilterBox || advFilterBox.contains(event.target)) {
255
+ return;
256
+ }
257
+ this.advancedFilteringVisibility = false;
258
+ },
259
+ updateColsOptions() {
260
+ this.columnOptions = this.setColsOptions();
261
+ },
262
+
263
+ // cols visibility
264
+ changeColVisibility(colData) {
265
+ const index = this.columnOptions.findIndex(col => col.label === colData.label);
266
+
267
+ if (index !== -1) {
268
+ this.columnOptions[index].isColVisible = colData.value;
269
+ }
270
+ },
271
+ },
272
+ };
@@ -1,6 +1,10 @@
1
1
  import { get } from '@shell/utils/object';
2
2
  import { addObject, addObjects, isArray, removeAt } from '@shell/utils/array';
3
3
 
4
+ export const ADV_FILTER_ALL_COLS_VALUE = 'allcols';
5
+ export const ADV_FILTER_ALL_COLS_LABEL = 'All Columns';
6
+ const LABEL_IDENTIFIER = ':::islabel';
7
+
4
8
  export default {
5
9
  data() {
6
10
  return {
@@ -29,6 +33,69 @@ export default {
29
33
  }),
30
34
  */
31
35
  filteredRows() {
36
+ // PROP hasAdvancedFiltering comes from Advanced Filtering mixin (careful changing data var there...)
37
+ if (!this.hasAdvancedFiltering) {
38
+ return this.handleFiltering();
39
+ } else {
40
+ return this.handleAdvancedFiltering();
41
+ }
42
+ },
43
+ },
44
+
45
+ methods: {
46
+ handleAdvancedFiltering() {
47
+ this.subMatches = null;
48
+
49
+ if (this.searchQuery.length) {
50
+ const out = (this.arrangedRows || []).slice();
51
+
52
+ const res = out.filter((row) => {
53
+ return this.searchQuery.every((f) => {
54
+ if (f.prop === ADV_FILTER_ALL_COLS_VALUE) {
55
+ // advFilterSelectOptions comes from Advanced Filtering mixin
56
+ // remove the All Columns option from the list so that we don't iterate over it
57
+ const allCols = this.advFilterSelectOptions.slice(1);
58
+ let searchFields = [];
59
+
60
+ allCols.forEach((col) => {
61
+ if (col.value.includes('[') && col.value.includes(']')) {
62
+ searchFields = searchFields.concat(JSON.parse(col.value));
63
+ } else {
64
+ // this means we are on the presence of a label, which should be dealt
65
+ // carefully because of object path such row.metadata.labels."app.kubernetes.io/managed-by
66
+ const value = col.isLabel ? `${ col.label }${ LABEL_IDENTIFIER }` : col.value;
67
+
68
+ searchFields.push(value);
69
+ }
70
+ });
71
+
72
+ return handleStringSearch(searchFields, [f.value], row);
73
+ } else {
74
+ if (f.prop.includes('[') && f.prop.includes(']')) {
75
+ return handleStringSearch(JSON.parse(f.prop), [f.value], row);
76
+ }
77
+
78
+ let prop = f.prop;
79
+
80
+ // this means we are on the presence of a label, which should be dealt
81
+ // carefully because of object path such row.metadata.labels."app.kubernetes.io/managed-by"
82
+ if (f.prop.includes('metadata.labels')) {
83
+ prop = `${ f.label }${ LABEL_IDENTIFIER }`;
84
+ }
85
+
86
+ return handleStringSearch([prop], [f.value], row);
87
+ }
88
+ });
89
+ });
90
+
91
+ return res;
92
+ }
93
+
94
+ // return arrangedRows array if we don't have anything to search for...
95
+ return this.arrangedRows;
96
+ },
97
+
98
+ handleFiltering() {
32
99
  const searchText = (this.searchQuery || '').trim().toLowerCase();
33
100
  let out;
34
101
 
@@ -61,20 +128,7 @@ export default {
61
128
  let hits = 0;
62
129
  let mainFound = true;
63
130
 
64
- for ( let j = 0 ; j < searchTokens.length ; j++ ) {
65
- let expect = true;
66
- let token = searchTokens[j];
67
-
68
- if ( token.substr(0, 1) === '!' ) {
69
- expect = false;
70
- token = token.substr(1);
71
- }
72
-
73
- if ( token && matches(searchFields, token, row) !== expect ) {
74
- mainFound = false;
75
- break;
76
- }
77
- }
131
+ mainFound = handleStringSearch(searchFields, searchTokens, row);
78
132
 
79
133
  if ( subFields && subSearch) {
80
134
  const subRows = row[subSearch] || [];
@@ -82,20 +136,7 @@ export default {
82
136
  for ( let k = subRows.length - 1 ; k >= 0 ; k-- ) {
83
137
  let subFound = true;
84
138
 
85
- for ( let l = 0 ; l < searchTokens.length ; l++ ) {
86
- let expect = true;
87
- let token = searchTokens[l];
88
-
89
- if ( token.substr(0, 1) === '!' ) {
90
- expect = false;
91
- token = token.substr(1);
92
- }
93
-
94
- if ( matches(subFields, token, subRows[k]) !== expect ) {
95
- subFound = false;
96
- break;
97
- }
98
- }
139
+ subFound = handleStringSearch(subFields, searchTokens, row);
99
140
 
100
141
  if ( subFound ) {
101
142
  hits++;
@@ -114,7 +155,7 @@ export default {
114
155
  this.previousResult = out;
115
156
 
116
157
  return out;
117
- },
158
+ }
118
159
  },
119
160
 
120
161
  watch: {
@@ -150,6 +191,24 @@ function columnsToSearchField(columns) {
150
191
 
151
192
  const ipLike = /^[0-9a-f\.:]+$/i;
152
193
 
194
+ function handleStringSearch(searchFields, searchTokens, row) {
195
+ for ( let j = 0 ; j < searchTokens.length ; j++ ) {
196
+ let expect = true;
197
+ let token = searchTokens[j];
198
+
199
+ if ( token.substr(0, 1) === '!' ) {
200
+ expect = false;
201
+ token = token.substr(1);
202
+ }
203
+
204
+ if ( token && matches(searchFields, token, row) !== expect ) {
205
+ return false;
206
+ }
207
+
208
+ return true;
209
+ }
210
+ }
211
+
153
212
  function matches(fields, token, item) {
154
213
  for ( let field of fields ) {
155
214
  if ( !field ) {
@@ -161,6 +220,8 @@ function matches(fields, token, item) {
161
220
 
162
221
  if (typeof field === 'function') {
163
222
  val = field(item);
223
+ } else if (field.includes(LABEL_IDENTIFIER)) {
224
+ val = item.metadata.labels[field.replace(LABEL_IDENTIFIER, '')];
164
225
  } else {
165
226
  const idx = field.indexOf(':');
166
227