foreman_scc_manager 1.8.19 → 2.0.0
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.
- checksums.yaml +4 -4
- data/README.md +14 -10
- data/app/controllers/api/v2/scc_accounts_controller.rb +48 -5
- data/app/controllers/api/v2/scc_products_controller.rb +1 -1
- data/app/controllers/scc_accounts_controller.rb +47 -0
- data/app/lib/actions/scc_manager/subscribe_product.rb +61 -19
- data/app/models/scc_account.rb +4 -3
- data/app/models/scc_katello_repository.rb +4 -0
- data/app/models/scc_product.rb +7 -1
- data/app/models/scc_repository.rb +12 -4
- data/app/views/scc_accounts/show.html.erb +7 -51
- data/config/routes.rb +1 -0
- data/db/migrate/20220425132605_add_scc_katello_repository_relation.rb +10 -0
- data/db/migrate/20220429100843_remove_root_repository_id_from_scc_repository.rb +6 -0
- data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +21 -0
- data/db/migrate/20220513132652_populate_upstream_authentication_token.rb +23 -0
- data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -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/lib/tasks/setup_authentication_token.rake +27 -0
- data/package.json +54 -0
- data/test/controllers/scc_accounts_controller_test.rb +6 -0
- data/test/fixtures/models/katello_root_repositories.yml +44 -0
- data/test/fixtures/models/scc_repositories.yml +34 -0
- data/test/support/fixtures_support.rb +3 -1
- 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 +48 -14
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;
|