foreman_scc_manager 1.8.20 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -11
- data/app/controllers/api/v2/scc_accounts_controller.rb +55 -4
- data/app/controllers/api/v2/scc_products_controller.rb +1 -1
- data/app/controllers/scc_accounts_controller.rb +49 -0
- data/app/lib/actions/scc_manager/subscribe_product.rb +56 -22
- data/app/models/scc_account.rb +22 -1
- data/app/models/scc_product.rb +2 -5
- data/app/views/api/v2/scc_accounts/main.json.rabl +1 -1
- data/app/views/scc_accounts/_form.html.erb +8 -4
- data/app/views/scc_accounts/show.html.erb +7 -51
- data/config/routes.rb +1 -0
- data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +5 -0
- data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
- data/db/migrate/20221013214310_add_sync_options_to_scc_account.rb +11 -0
- data/lib/foreman_scc_manager/engine.rb +1 -1
- data/lib/foreman_scc_manager/version.rb +1 -1
- data/lib/tasks/republish_repositories.rake +13 -0
- data/package.json +54 -0
- data/test/controllers/scc_accounts_controller_test.rb +7 -1
- data/test/fixtures/models/katello_root_repositories.yml +0 -12
- data/test/test_plugin_helper.rb +0 -6
- data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
- data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
- data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
- data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
- data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
- data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
- data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
- data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
- data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/index.js +18 -0
- data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
- data/webpack/index.js +11 -0
- data/webpack/reducer.js +7 -0
- metadata +43 -15
data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import { useDispatch } from 'react-redux';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import {
|
6
|
+
TreeView,
|
7
|
+
Button,
|
8
|
+
Tooltip,
|
9
|
+
Switch,
|
10
|
+
Flex,
|
11
|
+
FlexItem,
|
12
|
+
Card,
|
13
|
+
CardBody,
|
14
|
+
} from '@patternfly/react-core';
|
15
|
+
import { cloneDeep, merge, clone } from 'lodash';
|
16
|
+
import SCCRepoPicker from './components/SCCRepoPicker';
|
17
|
+
import { subscribeProductsWithReposAction } from '../../../../SCCProductPageActions';
|
18
|
+
import SCCProductTreeExpander from '../../../common/SCCProductTreeExpander';
|
19
|
+
|
20
|
+
const addCheckBoxToTree = (tree) => {
|
21
|
+
const checkProps = {};
|
22
|
+
checkProps.checked = tree.product_id !== null;
|
23
|
+
checkProps.disabled = tree.product_id !== null;
|
24
|
+
tree.checkProps = checkProps;
|
25
|
+
|
26
|
+
return tree;
|
27
|
+
};
|
28
|
+
|
29
|
+
const addParentToTree = (tree, par) => {
|
30
|
+
tree.parent = par;
|
31
|
+
return tree;
|
32
|
+
};
|
33
|
+
|
34
|
+
const addSCCRepoPickerToTree = (
|
35
|
+
tree,
|
36
|
+
disableRepos,
|
37
|
+
activateDebugFilter,
|
38
|
+
setSelectedReposFromChild
|
39
|
+
) => {
|
40
|
+
tree.customBadgeContent[0] = (
|
41
|
+
<Tooltip content={__('Filter repositories')}>
|
42
|
+
<SCCRepoPicker
|
43
|
+
sccRepos={tree.scc_repositories}
|
44
|
+
disableRepos={tree.product_id === null && !tree.checkProps.checked}
|
45
|
+
activateDebugFilter={activateDebugFilter}
|
46
|
+
productAlreadySynced={tree.product_id !== null}
|
47
|
+
sccProductId={tree.id}
|
48
|
+
sccProductName={tree.name}
|
49
|
+
setSelectedReposFromChild={setSelectedReposFromChild}
|
50
|
+
/>{' '}
|
51
|
+
</Tooltip>
|
52
|
+
);
|
53
|
+
return tree;
|
54
|
+
};
|
55
|
+
|
56
|
+
const setupTreeViewListItem = (
|
57
|
+
tree,
|
58
|
+
isRoot,
|
59
|
+
activateDebugFilter,
|
60
|
+
setSelectedReposFromChild
|
61
|
+
) => {
|
62
|
+
tree.customBadgeContent = [];
|
63
|
+
addCheckBoxToTree(tree);
|
64
|
+
addSCCRepoPickerToTree(
|
65
|
+
tree,
|
66
|
+
tree.product_id === null,
|
67
|
+
activateDebugFilter,
|
68
|
+
setSelectedReposFromChild
|
69
|
+
);
|
70
|
+
if ('children' in tree) {
|
71
|
+
tree.children = tree.children.map((p) =>
|
72
|
+
setupTreeViewListItem(
|
73
|
+
p,
|
74
|
+
false,
|
75
|
+
activateDebugFilter,
|
76
|
+
setSelectedReposFromChild
|
77
|
+
)
|
78
|
+
);
|
79
|
+
tree.children.map((child) => addParentToTree(child, tree));
|
80
|
+
}
|
81
|
+
return tree;
|
82
|
+
};
|
83
|
+
|
84
|
+
const checkAllParents = (
|
85
|
+
tree,
|
86
|
+
activateDebugFilter,
|
87
|
+
setSelectedReposFromChild
|
88
|
+
) => {
|
89
|
+
if (!tree.checkProps.checked) {
|
90
|
+
tree.checkProps.checked = true;
|
91
|
+
addSCCRepoPickerToTree(
|
92
|
+
tree,
|
93
|
+
false,
|
94
|
+
activateDebugFilter,
|
95
|
+
setSelectedReposFromChild
|
96
|
+
);
|
97
|
+
}
|
98
|
+
if (tree.parent)
|
99
|
+
checkAllParents(
|
100
|
+
tree.parent,
|
101
|
+
activateDebugFilter,
|
102
|
+
setSelectedReposFromChild
|
103
|
+
);
|
104
|
+
|
105
|
+
return tree;
|
106
|
+
};
|
107
|
+
|
108
|
+
const uncheckAllChildren = (
|
109
|
+
tree,
|
110
|
+
activateDebugFilter,
|
111
|
+
setSelectedReposFromChild
|
112
|
+
) => {
|
113
|
+
if (tree.product_id === null) {
|
114
|
+
tree.checkProps.checked = false;
|
115
|
+
addSCCRepoPickerToTree(
|
116
|
+
tree,
|
117
|
+
true,
|
118
|
+
activateDebugFilter,
|
119
|
+
setSelectedReposFromChild
|
120
|
+
);
|
121
|
+
}
|
122
|
+
if ('children' in tree)
|
123
|
+
tree.children = tree.children.map((c) =>
|
124
|
+
uncheckAllChildren(c, activateDebugFilter, setSelectedReposFromChild)
|
125
|
+
);
|
126
|
+
|
127
|
+
return tree;
|
128
|
+
};
|
129
|
+
|
130
|
+
const getRootParent = (tree) => {
|
131
|
+
if (tree.parent) return getRootParent(tree.parent);
|
132
|
+
|
133
|
+
return tree;
|
134
|
+
};
|
135
|
+
|
136
|
+
const SCCTreePicker = ({
|
137
|
+
sccProducts,
|
138
|
+
sccAccountId,
|
139
|
+
resetFormFromParent,
|
140
|
+
handleSubscribeCallback,
|
141
|
+
}) => {
|
142
|
+
const dispatch = useDispatch();
|
143
|
+
// this needs to be uninitialized such that the first call to setAllExpanded can actually
|
144
|
+
// change the value of allExpanded
|
145
|
+
const [expandAll, setExpandAll] = useState();
|
146
|
+
const [selectedRepos, setSelectedRepos] = useState({});
|
147
|
+
// the debug filter is actually a 'includeDebugRepos' setting which should not be active by default
|
148
|
+
const [activateDebugFilter, setActivateDebugFilter] = useState(false);
|
149
|
+
|
150
|
+
const setSelectedReposFromChild = (
|
151
|
+
productId,
|
152
|
+
productName,
|
153
|
+
repoIds,
|
154
|
+
repoNames
|
155
|
+
) => {
|
156
|
+
if (repoIds.length !== 0) {
|
157
|
+
selectedRepos[productId] = {};
|
158
|
+
selectedRepos[productId].repoIds = repoIds;
|
159
|
+
selectedRepos[productId].productName = productName;
|
160
|
+
selectedRepos[productId].repoNames = repoNames;
|
161
|
+
const newSelectedRepos = clone(selectedRepos);
|
162
|
+
setSelectedRepos(newSelectedRepos);
|
163
|
+
} else if (selectedRepos !== {} && productId in selectedRepos) {
|
164
|
+
delete selectedRepos[productId];
|
165
|
+
const newSelectedRepos = clone(selectedRepos);
|
166
|
+
setSelectedRepos(newSelectedRepos);
|
167
|
+
}
|
168
|
+
};
|
169
|
+
|
170
|
+
const [sccProductTree, setSccProductTree] = useState(
|
171
|
+
cloneDeep(sccProducts).map((p) =>
|
172
|
+
setupTreeViewListItem(
|
173
|
+
p,
|
174
|
+
true,
|
175
|
+
activateDebugFilter,
|
176
|
+
setSelectedReposFromChild
|
177
|
+
)
|
178
|
+
)
|
179
|
+
);
|
180
|
+
|
181
|
+
useEffect(() => {
|
182
|
+
setSccProductTree(
|
183
|
+
cloneDeep(sccProducts).map((p) =>
|
184
|
+
setupTreeViewListItem(
|
185
|
+
p,
|
186
|
+
true,
|
187
|
+
activateDebugFilter,
|
188
|
+
setSelectedReposFromChild
|
189
|
+
)
|
190
|
+
)
|
191
|
+
);
|
192
|
+
// some thorough cleaning is required for hash maps
|
193
|
+
Object.keys(selectedRepos).forEach((k) => delete selectedRepos[k]);
|
194
|
+
}, [sccProducts]);
|
195
|
+
|
196
|
+
const setExpandAllFromChild = (expandAllFromChild) => {
|
197
|
+
setExpandAll(expandAllFromChild);
|
198
|
+
};
|
199
|
+
|
200
|
+
const debugFilterChange = (evt) => {
|
201
|
+
setActivateDebugFilter(!activateDebugFilter);
|
202
|
+
};
|
203
|
+
|
204
|
+
const onCheck = (evt, treeViewItem) => {
|
205
|
+
if (evt.target.checked) {
|
206
|
+
checkAllParents(
|
207
|
+
treeViewItem,
|
208
|
+
activateDebugFilter,
|
209
|
+
setSelectedReposFromChild
|
210
|
+
);
|
211
|
+
} else {
|
212
|
+
uncheckAllChildren(
|
213
|
+
treeViewItem,
|
214
|
+
activateDebugFilter,
|
215
|
+
setSelectedReposFromChild
|
216
|
+
);
|
217
|
+
}
|
218
|
+
|
219
|
+
setSccProductTree([...merge(sccProductTree, getRootParent(treeViewItem))]);
|
220
|
+
};
|
221
|
+
|
222
|
+
const submitForm = (evt) => {
|
223
|
+
const productsToSubscribe = [];
|
224
|
+
Object.keys(selectedRepos).forEach((k) => {
|
225
|
+
const repo = {
|
226
|
+
scc_product_id: parseInt(k, 10),
|
227
|
+
repository_list: selectedRepos[k].repoIds,
|
228
|
+
};
|
229
|
+
productsToSubscribe.push(repo);
|
230
|
+
});
|
231
|
+
dispatch(
|
232
|
+
subscribeProductsWithReposAction(
|
233
|
+
sccAccountId,
|
234
|
+
productsToSubscribe,
|
235
|
+
handleSubscribeCallback,
|
236
|
+
selectedRepos
|
237
|
+
)
|
238
|
+
);
|
239
|
+
// reset data structure and form
|
240
|
+
setSelectedRepos({});
|
241
|
+
resetFormFromParent();
|
242
|
+
};
|
243
|
+
|
244
|
+
return (
|
245
|
+
<Card>
|
246
|
+
<CardBody>
|
247
|
+
<Flex direction={{ default: 'column' }}>
|
248
|
+
<Flex>
|
249
|
+
<SCCProductTreeExpander
|
250
|
+
setExpandAllInParent={setExpandAllFromChild}
|
251
|
+
/>
|
252
|
+
<FlexItem>
|
253
|
+
<Tooltip
|
254
|
+
content={__(
|
255
|
+
'If this option is enabled, debug and source pool repositories are automatically selected if you select a product. This option is disabled by default. It applies for unselected products, only. Already selected products are not filtered.'
|
256
|
+
)}
|
257
|
+
>
|
258
|
+
<Switch
|
259
|
+
id="filter-debug-switch"
|
260
|
+
onChange={debugFilterChange}
|
261
|
+
isChecked={activateDebugFilter}
|
262
|
+
label={__('Include Debug and Source Pool repositories')}
|
263
|
+
/>
|
264
|
+
</Tooltip>
|
265
|
+
</FlexItem>
|
266
|
+
</Flex>
|
267
|
+
<Flex>
|
268
|
+
<TreeView
|
269
|
+
data={sccProductTree}
|
270
|
+
allExpanded={expandAll}
|
271
|
+
onCheck={onCheck}
|
272
|
+
hasChecks
|
273
|
+
hasBadges
|
274
|
+
hasGuides
|
275
|
+
/>
|
276
|
+
</Flex>
|
277
|
+
<Flex>
|
278
|
+
<Button
|
279
|
+
variant="primary"
|
280
|
+
onClick={submitForm}
|
281
|
+
isDisabled={Object.keys(selectedRepos).length === 0}
|
282
|
+
>
|
283
|
+
{__('Add product(s)')}
|
284
|
+
</Button>
|
285
|
+
</Flex>
|
286
|
+
</Flex>
|
287
|
+
</CardBody>
|
288
|
+
</Card>
|
289
|
+
);
|
290
|
+
};
|
291
|
+
|
292
|
+
SCCTreePicker.propTypes = {
|
293
|
+
sccProducts: PropTypes.array,
|
294
|
+
sccAccountId: PropTypes.number.isRequired,
|
295
|
+
resetFormFromParent: PropTypes.func.isRequired,
|
296
|
+
handleSubscribeCallback: PropTypes.func.isRequired,
|
297
|
+
};
|
298
|
+
|
299
|
+
SCCTreePicker.defaultProps = {
|
300
|
+
sccProducts: [],
|
301
|
+
};
|
302
|
+
|
303
|
+
export default SCCTreePicker;
|
@@ -0,0 +1,235 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { TimesIcon } from '@patternfly/react-icons';
|
4
|
+
import {
|
5
|
+
Button,
|
6
|
+
Card,
|
7
|
+
CardTitle,
|
8
|
+
CardBody,
|
9
|
+
CardHeader,
|
10
|
+
CardExpandableContent,
|
11
|
+
Flex,
|
12
|
+
FlexItem,
|
13
|
+
} from '@patternfly/react-core';
|
14
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
15
|
+
import { uniq } from 'lodash';
|
16
|
+
import './styles.scss';
|
17
|
+
import SCCGenericPicker from './components/SCCGenericPicker';
|
18
|
+
import SCCTreePicker from './components/SCCTreePicker';
|
19
|
+
|
20
|
+
const resetSelectionStringProduct = __(' -- Select Product --');
|
21
|
+
const resetSelectionStringVersion = __(' -- Select Version --');
|
22
|
+
const resetSelectionStringArch = __(' -- Select Architecture --');
|
23
|
+
|
24
|
+
const genericFilter = (object, comparator) =>
|
25
|
+
// we can have architectures that are not set
|
26
|
+
comparator === '' ||
|
27
|
+
comparator === resetSelectionStringProduct ||
|
28
|
+
comparator === resetSelectionStringVersion ||
|
29
|
+
comparator === resetSelectionStringArch ||
|
30
|
+
object === comparator ||
|
31
|
+
(object === null && comparator === 'no arch');
|
32
|
+
|
33
|
+
const filterVersionByProduct = (sccProducts, product) =>
|
34
|
+
uniq(
|
35
|
+
sccProducts
|
36
|
+
.filter((p) => p.product_category === product)
|
37
|
+
.map((i) => i.version)
|
38
|
+
).sort();
|
39
|
+
|
40
|
+
const filterArchByVersionAndProduct = (sccProducts, product, version) =>
|
41
|
+
uniq(
|
42
|
+
sccProducts
|
43
|
+
.filter((p) => p.product_category === product && p.version === version)
|
44
|
+
.map((i) => i.arch)
|
45
|
+
).sort();
|
46
|
+
|
47
|
+
const SCCProductPicker = ({
|
48
|
+
sccProducts,
|
49
|
+
sccAccountId,
|
50
|
+
editProductId,
|
51
|
+
handleSubscribeCallback,
|
52
|
+
}) => {
|
53
|
+
const [productItems, setProductItems] = useState(
|
54
|
+
uniq(sccProducts.map((p) => p.product_category))
|
55
|
+
);
|
56
|
+
const [selectedProduct, setSelectedProduct] = useState('');
|
57
|
+
const [archItems, setArchItems] = useState([]);
|
58
|
+
const [selectedArch, setSelectedArch] = useState('');
|
59
|
+
const [versionItems, setVersionItems] = useState([]);
|
60
|
+
const [selectedVersion, setSelectedVersion] = useState('');
|
61
|
+
const [filteredSccProducts, setFilteredSccProducts] = useState([]);
|
62
|
+
const [showSearchTree, setShowSearchTree] = useState(false);
|
63
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
64
|
+
|
65
|
+
useEffect(() => {
|
66
|
+
if (editProductId !== 0) {
|
67
|
+
// the id is unique, so there never should be more than 1 element in the array
|
68
|
+
const product = sccProducts.filter((p) => p.id === editProductId);
|
69
|
+
if (product.length > 0) {
|
70
|
+
setSelectedProduct(product[0].product_category);
|
71
|
+
setSelectedArch(product[0].arch);
|
72
|
+
setSelectedVersion(product[0].version);
|
73
|
+
setFilteredSccProducts(product);
|
74
|
+
setShowSearchTree(true);
|
75
|
+
setIsExpanded(true);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}, [editProductId, sccProducts]);
|
79
|
+
|
80
|
+
const onProductSelectionChange = (value) => {
|
81
|
+
if (value !== resetSelectionStringProduct) {
|
82
|
+
setVersionItems(filterVersionByProduct(sccProducts, value));
|
83
|
+
} else {
|
84
|
+
setVersionItems([]);
|
85
|
+
}
|
86
|
+
setSelectedProduct(value);
|
87
|
+
setArchItems([]);
|
88
|
+
setSelectedVersion('');
|
89
|
+
setSelectedArch('');
|
90
|
+
};
|
91
|
+
|
92
|
+
const onVersionSelectionChange = (value) => {
|
93
|
+
if (value === resetSelectionStringVersion) {
|
94
|
+
setArchItems([]);
|
95
|
+
} else {
|
96
|
+
setArchItems(
|
97
|
+
filterArchByVersionAndProduct(sccProducts, selectedProduct, value)
|
98
|
+
);
|
99
|
+
}
|
100
|
+
setSelectedVersion(value);
|
101
|
+
setSelectedArch('');
|
102
|
+
};
|
103
|
+
|
104
|
+
const onArchSelectionChange = (value) => {
|
105
|
+
setSelectedArch(value);
|
106
|
+
};
|
107
|
+
|
108
|
+
const onExpand = (evt, id) => {
|
109
|
+
setIsExpanded(!isExpanded);
|
110
|
+
};
|
111
|
+
|
112
|
+
const filterProducts = (evt) => {
|
113
|
+
setShowSearchTree(true);
|
114
|
+
setFilteredSccProducts(
|
115
|
+
sccProducts.filter(
|
116
|
+
(p) =>
|
117
|
+
genericFilter(p.product_category, selectedProduct) &&
|
118
|
+
genericFilter(p.arch, selectedArch) &&
|
119
|
+
genericFilter(p.version, selectedVersion)
|
120
|
+
)
|
121
|
+
);
|
122
|
+
};
|
123
|
+
|
124
|
+
const resetTreeForm = () => {
|
125
|
+
setShowSearchTree(false);
|
126
|
+
setSelectedProduct('');
|
127
|
+
setSelectedVersion('');
|
128
|
+
setSelectedArch('');
|
129
|
+
};
|
130
|
+
|
131
|
+
return (
|
132
|
+
<Card border="dark" id="product-selection-card" isExpanded={isExpanded}>
|
133
|
+
<CardHeader onExpand={onExpand}>
|
134
|
+
<CardTitle>{__('Select SUSE products')}</CardTitle>
|
135
|
+
</CardHeader>
|
136
|
+
<CardExpandableContent>
|
137
|
+
<CardBody>
|
138
|
+
<Flex direction={{ default: 'column' }}>
|
139
|
+
<Flex>
|
140
|
+
<FlexItem>
|
141
|
+
<SCCGenericPicker
|
142
|
+
key="prod-select"
|
143
|
+
selectionItems={
|
144
|
+
selectedProduct === ''
|
145
|
+
? productItems
|
146
|
+
: [resetSelectionStringProduct].concat(productItems)
|
147
|
+
}
|
148
|
+
setGlobalSelected={onProductSelectionChange}
|
149
|
+
screenReaderLabel={resetSelectionStringProduct}
|
150
|
+
initialLabel={
|
151
|
+
selectedProduct === ''
|
152
|
+
? resetSelectionStringProduct
|
153
|
+
: selectedProduct
|
154
|
+
}
|
155
|
+
/>
|
156
|
+
</FlexItem>
|
157
|
+
<FlexItem>
|
158
|
+
<SCCGenericPicker
|
159
|
+
key="vers-select"
|
160
|
+
selectionItems={
|
161
|
+
selectedVersion === ''
|
162
|
+
? versionItems
|
163
|
+
: [resetSelectionStringVersion].concat(versionItems)
|
164
|
+
}
|
165
|
+
setGlobalSelected={onVersionSelectionChange}
|
166
|
+
screenReaderLabel={resetSelectionStringVersion}
|
167
|
+
initialLabel={
|
168
|
+
selectedVersion === ''
|
169
|
+
? resetSelectionStringVersion
|
170
|
+
: selectedVersion
|
171
|
+
}
|
172
|
+
/>
|
173
|
+
</FlexItem>
|
174
|
+
<FlexItem>
|
175
|
+
<SCCGenericPicker
|
176
|
+
key="arch-select"
|
177
|
+
selectionItems={
|
178
|
+
selectedArch === ''
|
179
|
+
? archItems
|
180
|
+
: [resetSelectionStringArch].concat(archItems)
|
181
|
+
}
|
182
|
+
setGlobalSelected={onArchSelectionChange}
|
183
|
+
screenReaderLabel={resetSelectionStringArch}
|
184
|
+
initialLabel={
|
185
|
+
selectedArch === ''
|
186
|
+
? resetSelectionStringArch
|
187
|
+
: selectedArch
|
188
|
+
}
|
189
|
+
/>
|
190
|
+
</FlexItem>
|
191
|
+
<FlexItem>
|
192
|
+
<Button variant="primary" onClick={filterProducts}>
|
193
|
+
{__('Search')}
|
194
|
+
</Button>
|
195
|
+
</FlexItem>
|
196
|
+
<FlexItem>
|
197
|
+
<Button
|
198
|
+
variant="link"
|
199
|
+
icon={<TimesIcon />}
|
200
|
+
onClick={resetTreeForm}
|
201
|
+
>
|
202
|
+
{__('Reset Selection')}
|
203
|
+
</Button>
|
204
|
+
</FlexItem>
|
205
|
+
</Flex>
|
206
|
+
<FlexItem>
|
207
|
+
{showSearchTree && (
|
208
|
+
<SCCTreePicker
|
209
|
+
sccProducts={filteredSccProducts}
|
210
|
+
sccAccountId={sccAccountId}
|
211
|
+
resetFormFromParent={resetTreeForm}
|
212
|
+
handleSubscribeCallback={handleSubscribeCallback}
|
213
|
+
/>
|
214
|
+
)}
|
215
|
+
</FlexItem>
|
216
|
+
</Flex>
|
217
|
+
</CardBody>
|
218
|
+
</CardExpandableContent>
|
219
|
+
</Card>
|
220
|
+
);
|
221
|
+
};
|
222
|
+
|
223
|
+
SCCProductPicker.propTypes = {
|
224
|
+
sccProducts: PropTypes.array,
|
225
|
+
sccAccountId: PropTypes.number.isRequired,
|
226
|
+
editProductId: PropTypes.number,
|
227
|
+
handleSubscribeCallback: PropTypes.func.isRequired,
|
228
|
+
};
|
229
|
+
|
230
|
+
SCCProductPicker.defaultProps = {
|
231
|
+
sccProducts: [],
|
232
|
+
editProductId: 0,
|
233
|
+
};
|
234
|
+
|
235
|
+
export default SCCProductPicker;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
@import '~@theforeman/vendor/scss/variables';
|
2
|
+
|
3
|
+
.pf-c-select__toggle {
|
4
|
+
font-size: var(--pf-global--FontSize--xs);
|
5
|
+
font-weight: var(--pf-global--FontWeight--bold);
|
6
|
+
};
|
7
|
+
|
8
|
+
.pf-c-switch__input ~ .pf-c-switch__label {
|
9
|
+
font-size: var(--pf-global--FontSize--xs);
|
10
|
+
};
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import ForemanModal from 'foremanReact/components/ForemanModal';
|
4
|
+
|
5
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
6
|
+
import {
|
7
|
+
Card,
|
8
|
+
CardBody,
|
9
|
+
TextContent,
|
10
|
+
Text,
|
11
|
+
TextList,
|
12
|
+
TextListItem,
|
13
|
+
TextListVariants,
|
14
|
+
TextVariants,
|
15
|
+
} from '@patternfly/react-core';
|
16
|
+
|
17
|
+
const generateRepoList = (repoNames, productName) => (
|
18
|
+
<>
|
19
|
+
<TextListItem key={productName}>{productName}</TextListItem>
|
20
|
+
<TextList key={repoNames}>
|
21
|
+
{repoNames.map((repo) => (
|
22
|
+
<TextListItem key={repo}>{repo}</TextListItem>
|
23
|
+
))}
|
24
|
+
</TextList>
|
25
|
+
</>
|
26
|
+
);
|
27
|
+
|
28
|
+
const generateProductList = (reposToSubscribe) => (
|
29
|
+
<TextList
|
30
|
+
key={reposToSubscribe.map((p) => p.productName)}
|
31
|
+
component={TextListVariants.ol}
|
32
|
+
>
|
33
|
+
{reposToSubscribe.map((p) => generateRepoList(p.repoNames, p.productName))}
|
34
|
+
</TextList>
|
35
|
+
);
|
36
|
+
|
37
|
+
const SCCProductPickerModal = ({ id, taskId, reposToSubscribe }) => (
|
38
|
+
<>
|
39
|
+
<ForemanModal
|
40
|
+
id={id}
|
41
|
+
title={__('Summary of SCC product subscription')}
|
42
|
+
enforceFocus
|
43
|
+
>
|
44
|
+
<Card>
|
45
|
+
<CardBody>
|
46
|
+
<TextContent key={taskId}>
|
47
|
+
<Text key={'modal1'.concat(taskId)}>
|
48
|
+
{__('The subscription task with id ')}
|
49
|
+
<Text
|
50
|
+
key={'modal2'.concat(taskId)}
|
51
|
+
component={TextVariants.a}
|
52
|
+
target="_blank"
|
53
|
+
href={sprintf('/foreman_tasks/tasks/%s', taskId)}
|
54
|
+
>
|
55
|
+
{sprintf('%s', taskId)}
|
56
|
+
</Text>
|
57
|
+
{__(' has started successfully.')}
|
58
|
+
</Text>
|
59
|
+
<Text key={'modal3'.concat(taskId)}>
|
60
|
+
{__('The following products will be imported:')}
|
61
|
+
</Text>
|
62
|
+
{generateProductList(reposToSubscribe)}
|
63
|
+
</TextContent>
|
64
|
+
</CardBody>
|
65
|
+
</Card>
|
66
|
+
</ForemanModal>
|
67
|
+
</>
|
68
|
+
);
|
69
|
+
SCCProductPickerModal.propTypes = {
|
70
|
+
id: PropTypes.string,
|
71
|
+
taskId: PropTypes.string,
|
72
|
+
reposToSubscribe: PropTypes.array,
|
73
|
+
};
|
74
|
+
|
75
|
+
SCCProductPickerModal.defaultProps = {
|
76
|
+
id: '',
|
77
|
+
taskId: '',
|
78
|
+
reposToSubscribe: {},
|
79
|
+
};
|
80
|
+
|
81
|
+
export default SCCProductPickerModal;
|