Skip to content

Commit 22228b4

Browse files
ui: ability to search for targets on storage connections pop-up screen
Similar to the functionality provided on the pop up of adding a storage domain, new storage connections for ISCSI can be discovered and attached. Signed-off-by: Jasper Berton <jasper.berton@team.blue>
1 parent 901d1e8 commit 22228b4

9 files changed

Lines changed: 325 additions & 64 deletions

File tree

src/components/helper/DataProviderHook.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState, useMemo, useRef } from 'react'
1+
import { useEffect, useState, useMemo, useRef, useCallback } from 'react'
22

33
export function useDataProvider ({ fetchData, parameters = [], trigger, enabled = true, debugState = false }) {
44
const [{ data, fetchError, fetchInProgress }, setState] = useState({
@@ -8,9 +8,9 @@ export function useDataProvider ({ fetchData, parameters = [], trigger, enabled
88
})
99
const prevParams = useRef(null)
1010
const prevTrigger = useRef(null)
11-
const params = useMemo(() => ([...parameters]), [...parameters])
11+
const params = useMemo(() => ([...parameters]), [parameters])
1212

13-
const debug = (msg) => {
13+
const debug = useCallback((msg) => {
1414
if (!debugState) {
1515
return
1616
}
@@ -23,21 +23,22 @@ export function useDataProvider ({ fetchData, parameters = [], trigger, enabled
2323
fetchInProgress,
2424
}
2525
console.log(`${msg} in state: ${JSON.stringify(state)}`)
26-
}
26+
}, [data, debugState, fetchData, fetchError, fetchInProgress, parameters.length, trigger])
2727

2828
debug('render')
2929

3030
useEffect(() => {
3131
if (fetchInProgress || !enabled) {
3232
return
3333
}
34+
const paramsKey = JSON.stringify(params)
3435

35-
if (data && !fetchError && prevParams.current === params) {
36+
if (data && !fetchError && prevParams.current === paramsKey) {
3637
// re-fetch when params changed
3738
return
3839
}
3940

40-
if (fetchError && prevParams.current === params && prevTrigger.current === trigger) {
41+
if (fetchError && prevParams.current === paramsKey && prevTrigger.current === trigger) {
4142
// re-fetch when params or trigger changed
4243
return
4344
}
@@ -47,7 +48,7 @@ export function useDataProvider ({ fetchData, parameters = [], trigger, enabled
4748
fetchError: false,
4849
fetchInProgress: true,
4950
})
50-
prevParams.current = params
51+
prevParams.current = paramsKey
5152
prevTrigger.current = trigger
5253
debug('start')
5354
fetchData(...params)
@@ -67,7 +68,7 @@ export function useDataProvider ({ fetchData, parameters = [], trigger, enabled
6768
fetchInProgress: false,
6869
})
6970
})
70-
})
71+
}, [fetchInProgress, enabled, data, fetchError, params, trigger, debug, fetchData])
7172

7273
return useMemo(() => ({
7374
data,

src/intl/locales/en-US.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@
170170
"storage.domains.connections.table.connection.attached": "ATTACHED",
171171
"storage.domains.connections.connection.remove.button": "Remove",
172172
"storage.domains.connections.connection.add.button": "Add",
173+
"storage.domains.connections.discover.targets.button": "Discover Targets",
174+
"storage.domains.connections.connection.host.label": "Select host: ",
175+
"storage.domains.connections.found.title": "Following connections were found:",
176+
"storage.domains.connections.connection.address.label": "Address: ",
177+
"storage.domains.connections.connection.port.label": "Port: ",
178+
"storage.domains.connections.connection.empty": "No connections found that aren't yet active",
173179
"storage.domains.connections.connection.detach.button": "Detach",
174180
"storage.domains.connections.domain.maintenance.warning": "Storage Domain is not in Maintenance mode",
175181
"storage.domains.connections.domain?.maintenance.warning.detail": "Connections cannot be attached or detached, cannot edit attached connections",

src/intl/messages.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,18 @@ const messageDescriptors = {
12261226
description: 'label for Storage Connections management dialog button',
12271227
},
12281228

1229+
storageConnectionsDiscoverTargets: {
1230+
id: 'storage.domains.connections.discover.targets.button',
1231+
defaultMessage: 'Discover Targets',
1232+
description: 'Button to discover targets based on search input',
1233+
},
1234+
1235+
storageConnectionsFoundTitle: {
1236+
id: 'storage.domains.connections.found.title',
1237+
defaultMessage: 'Following connections were found:',
1238+
description: 'Title above found targets from search input',
1239+
},
1240+
12291241
storageConnectionsDataError: {
12301242
id: 'storage.domains.connections.dataError',
12311243
defaultMessage: 'Could not fetch data needed for showing Storage Connections',
@@ -1292,6 +1304,30 @@ const messageDescriptors = {
12921304
description: 'add Connection button',
12931305
},
12941306

1307+
storageConnectionsHostSelectionLabel: {
1308+
id: 'storage.domains.connections.connection.host.label',
1309+
defaultMessage: 'Select host: ',
1310+
description: 'Label for selection of host',
1311+
},
1312+
1313+
storageConnectionAddressSelectionLabel: {
1314+
id: 'storage.domains.connections.connection.address.label',
1315+
defaultMessage: 'Address: ',
1316+
description: 'Label for selection of address',
1317+
},
1318+
1319+
storageConnectionPortSelectionLabel: {
1320+
id: 'storage.domains.connections.connection.port.label',
1321+
defaultMessage: 'Port: ',
1322+
description: 'Label for selection of port',
1323+
},
1324+
1325+
storageConnectionsEmptyList: {
1326+
id: 'storage.domains.connections.connection.empty',
1327+
defaultMessage: 'No connections found that aren\'t yet active',
1328+
description: 'Pop up message when empty list of discovered targets is returned',
1329+
},
1330+
12951331
storageConnectionsAttachConnectionButton: {
12961332
id: 'storage.domains.connections.connection.attach.button',
12971333
defaultMessage: 'Attach',

src/modals/storage-connections/StorageConnectionsDataProvider.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,25 @@ const createConnection = async (connection) => {
1313
)
1414
}
1515

16+
const createAndAttachConnection = async (connection, domainId) => {
17+
const allConnections = await engineGet('api/storageconnections')
18+
const connectionsInList = allConnections.storage_connection.filter((con) => con.address === connection.address)
19+
if (connectionsInList.length === 1) {
20+
await attachConnection(connectionsInList[0].id, domainId)
21+
} else {
22+
const createdConnection = await createConnection(connection)
23+
await attachConnection(createdConnection.id, domainId)
24+
}
25+
}
26+
27+
const findConnections = async (ip, hostId, port) => {
28+
const connectionsFoundJson = await enginePost(
29+
`api/hosts/${hostId}/discoveriscsi`,
30+
JSON.stringify({ iscsi: { address: ip, port: port } })
31+
)
32+
return connectionsFoundJson.discovered_targets.iscsi_details
33+
}
34+
1635
const editConnection = async (connection, connectionId) => {
1736
return await enginePut(
1837
`api/storageconnections/${connectionId}`,
@@ -36,13 +55,17 @@ const detachConnection = async (connectionId, domainId) => {
3655
}
3756

3857
const fetchData = async (stgDomain) => {
58+
const headers = {
59+
'Cache-Control': 'no-cache',
60+
}
3961
if (!stgDomain?.id || !stgDomain?.dataCenterId) {
4062
throw new Error('StorageConnectionsDataProvider: invalid Storage Domain')
4163
}
42-
const [allConnectionsJson, storageDomain, domainConnectionsJson] = await Promise.all([
64+
const [allConnectionsJson, storageDomain, domainConnectionsJson, hostsJson] = await Promise.all([
4365
engineGet('api/storageconnections'),
4466
engineGet(`api/datacenters/${stgDomain.dataCenterId}/storagedomains/${stgDomain.id}`),
4567
engineGet(`api/storagedomains/${stgDomain.id}/storageconnections`),
68+
engineGet('api/hosts', headers),
4669
])
4770

4871
if (!storageDomain) {
@@ -51,6 +74,7 @@ const fetchData = async (stgDomain) => {
5174

5275
const allConnections = allConnectionsJson?.storage_connection
5376
const domainConnections = domainConnectionsJson?.storage_connection
77+
const hosts = hostsJson?.host.filter(host => host.status === 'up')
5478

5579
if (!allConnections || allConnections.error) {
5680
throw new Error('StorageConnectionsDataProvider: failed to fetch storage connections')
@@ -64,16 +88,17 @@ const fetchData = async (stgDomain) => {
6488
const domainConnectionsIds = new Set(domainConnections.map(connection => connection.id))
6589
const allConnectionsByTypeSorted = allConnections
6690
.filter(connection => connection.type === storageDomain.storage?.type)
67-
.map((conn) => {
68-
return { ...conn, isAttachedToDomain: domainConnectionsIds.has(conn.id) }
91+
.map((connection) => {
92+
return { ...connection, isAttachedToDomain: domainConnectionsIds.has(connection.id) }
6993
})
70-
.sort((conn1, conn2) => {
71-
return (conn1.isAttachedToDomain === conn2.isAttachedToDomain) ? 0 : conn1.isAttachedToDomain ? -1 : 1
94+
.sort((firstConnection, secondConnection) => {
95+
return (firstConnection.isAttachedToDomain === secondConnection.isAttachedToDomain) ? 0 : firstConnection.isAttachedToDomain ? -1 : 1
7296
})
7397

7498
return {
7599
storageDomain: storageDomain,
76100
connections: allConnectionsByTypeSorted,
101+
hosts: hosts,
77102
}
78103
}
79104

@@ -93,17 +118,20 @@ const StorageConnectionsDataProvider = ({ children, storageDomain }) => (
93118
return React.cloneElement(child, { isLoading: true })
94119
}
95120

96-
const { storageDomain, connections } = data
121+
const { storageDomain, connections, hosts } = data
97122

98123
return React.cloneElement(child, {
99124
storageDomain,
100125
connections,
126+
hosts,
101127
lastUpdated,
102128
doConnectionCreate: createConnection,
129+
doConnectionCreateAndAttach: createAndAttachConnection,
103130
doConnectionEdit: editConnection,
104131
doConnectionDelete: deleteConnection,
105132
doConnectionAttach: attachConnection,
106133
doConnectionDetach: detachConnection,
134+
doConnectionSearch: findConnections,
107135
doRefreshConnections: () => { fetchAndUpdateData() },
108136
})
109137
}}

src/modals/storage-connections/StorageConnectionsModal.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,27 @@ const StorageConnectionsModal = ({
1616
storageDomain,
1717
connections,
1818
onClose,
19+
hosts,
1920
doConnectionCreate = () => { },
2021
doConnectionEdit = () => { },
2122
doConnectionDelete = () => { },
2223
doConnectionAttach = () => { },
2324
doConnectionDetach = () => { },
2425
doRefreshConnections = () => { },
26+
doConnectionSearch = () => { },
27+
doConnectionCreateAndAttach = () => { },
2528
}) => {
2629
const [isOpen, setOpen] = useState(true)
2730
const [error, setError] = useState(null)
2831
const [isShowAll, setShowAll] = useState(false)
2932
const [shownConnections, setShownConnections] = useState(null)
33+
const [discoveredConnections, setDiscoveredConnections] = useState(null)
34+
const [shownHosts, setShownHosts] = useState(null)
3035

3136
useEffect(() => {
37+
setShownHosts(hosts)
3238
setShownConnections(isShowAll || !connections ? connections : connections.filter(conn => conn.isAttachedToDomain))
33-
}, [isShowAll, connections])
39+
}, [isShowAll, connections, hosts])
3440

3541
if (!connections) {
3642
return null
@@ -62,6 +68,27 @@ const StorageConnectionsModal = ({
6268
}
6369
}
6470

71+
const onConnectionSearch = async (ip, hostId, port) => {
72+
try {
73+
setDiscoveredConnections(null)
74+
const allConnections = await doConnectionSearch(ip, hostId, port)
75+
const filtered = allConnections.filter(item => {
76+
const match = connections.find(secondItem => secondItem.address === item.address)
77+
return !match || (match && !match.isAttachedToDomain)
78+
})
79+
setDiscoveredConnections(filtered)
80+
} catch (e) {
81+
setError(e.detail)
82+
}
83+
}
84+
85+
const onConnectionCreateAndAttach = async (connection, domainId) => {
86+
await doConnectionCreateAndAttach(connection, domainId)
87+
doRefreshConnections()
88+
const newdiscoveredList = discoveredConnections.filter(con => con.address !== connection.address)
89+
setDiscoveredConnections(newdiscoveredList)
90+
}
91+
6592
const onConnectionEdit = async (connection, connectionId) => {
6693
try {
6794
await doConnectionEdit(connection, connectionId)
@@ -119,7 +146,9 @@ const StorageConnectionsModal = ({
119146
<LoadingSpinner isLoading={isLoading}>
120147
<StorageConnectionsModalBody
121148
storageDomain={storageDomain}
149+
hosts={shownHosts}
122150
connections={shownConnections}
151+
foundConnections={discoveredConnections}
123152
isShowAll={isShowAll}
124153
setShowAll={setShowAll}
125154
error={error}
@@ -129,6 +158,8 @@ const StorageConnectionsModal = ({
129158
onDelete={onConnectionDelete}
130159
onAttach={onConnectionAttach}
131160
onDetach={onConnectionDetach}
161+
onSearch={onConnectionSearch}
162+
onCreateAndAttach={onConnectionCreateAndAttach}
132163
/>
133164
</LoadingSpinner>
134165
</PluginApiModal>
@@ -138,14 +169,17 @@ const StorageConnectionsModal = ({
138169
StorageConnectionsModal.propTypes = {
139170
isLoading: PropTypes.bool,
140171
storageDomain: PropTypes.object,
172+
hosts: PropTypes.array,
141173
connections: PropTypes.array,
142174
onClose: PropTypes.func.isRequired,
143175
doConnectionCreate: PropTypes.func,
144176
doConnectionEdit: PropTypes.func,
145177
doConnectionDelete: PropTypes.func,
146178
doConnectionAttach: PropTypes.func,
147179
doConnectionDetach: PropTypes.func,
180+
doConnectionSearch: PropTypes.func,
148181
doRefreshConnections: PropTypes.func,
182+
doConnectionCreateAndAttach: PropTypes.func,
149183
}
150184

151185
export default StorageConnectionsModal

0 commit comments

Comments
 (0)