-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(network nodes): add Network Nodes Plugin
- Loading branch information
1 parent
e1bbed1
commit 12e541d
Showing
13 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Page from './src/networkNodesPage'; | ||
import Menu from './src/networkNodesMenu'; | ||
|
||
export default { | ||
name: 'networkNodes', | ||
page: Page, | ||
menu: Menu, | ||
menuView: 'community' | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Here you define tests that closely resemble how your component is used | ||
// Using the testing-library: https://testing-library.com | ||
|
||
import { h } from 'preact'; | ||
import { fireEvent, screen, cleanup, act } from '@testing-library/preact'; | ||
import '@testing-library/jest-dom'; | ||
import { render } from 'utils/test_utils'; | ||
import queryCache from 'utils/queryCache'; | ||
|
||
import NetworkNodes from './src/networkNodesPage'; | ||
import { getNodes } from './src/networkNodesApi'; | ||
|
||
jest.mock('./src/networkNodesApi'); | ||
|
||
describe('networkNodes', () => { | ||
beforeEach(() => { | ||
getNodes.mockImplementation(async () => ({ | ||
"ql-berta": { | ||
ipv4: '10.5.0.16', | ||
ipv6: 'fd0d:fe46:8ce8::8bbf:7500', | ||
board: 'LibreRouter v1', | ||
fw_version: 'LibreRouterOS 1.4' | ||
}, | ||
"ql-nelson": { | ||
ipv4: '10.5.0.17', | ||
ipv6: 'fd0d:fe46:8ce8::8bbf:75bf', | ||
board: 'LibreRouter v1', | ||
fw_version: 'LibreRouterOS 1.4' | ||
} | ||
})); | ||
}); | ||
|
||
afterEach(() => { | ||
cleanup(); | ||
act(() => queryCache.clear()); | ||
}); | ||
|
||
it('test that nodes are shown', async () => { | ||
render(<NetworkNodes />); | ||
expect(await screen.findByText('ql-nelson')).toBeInTheDocument(); | ||
expect(await screen.findByText('ql-berta')).toBeInTheDocument(); | ||
}); | ||
|
||
it('test that details are shown on click', async () => { | ||
render(<NetworkNodes />); | ||
const element = await screen.findByText('ql-nelson'); | ||
fireEvent.click(element); | ||
expect(await screen.findByRole('link', { name: '10.5.0.17'})).toBeInTheDocument(); | ||
expect(await screen.findByText('IPv6: fd0d:fe46:8ce8::8bbf:75bf')).toBeInTheDocument(); | ||
expect(await screen.findByText('Device: LibreRouter v1')).toBeInTheDocument(); | ||
expect(await screen.findByText('Firmware: LibreRouterOS 1.4')).toBeInTheDocument(); | ||
}) | ||
|
||
}); |
25 changes: 25 additions & 0 deletions
25
plugins/lime-plugin-network-nodes/src/components/expandableNode/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { h } from 'preact'; | ||
import I18n from 'i18n-js'; | ||
import { ListItem } from 'components/list'; | ||
import style from './style.less'; | ||
|
||
export const ExpandableNode = ({ node, showMore, onClick }) => { | ||
const { hostname, ipv4, ipv6, board, fw_version } = node; | ||
return ( | ||
<ListItem onClick={onClick}> | ||
<div class="flex-grow-1"> | ||
<div class="d-flex align-items-baseline"> | ||
<div class={style.hostname}>{hostname}</div> | ||
</div> | ||
{showMore && | ||
<div class={style.moreData}> | ||
{ipv4 && <div>IPv4: <a href={`http://${ipv4}`}>{ipv4}</a></div>} | ||
{ipv6 && <div>IPv6: {ipv6}</div>} | ||
{board && <div>{I18n.t('Device')}: {board}</div>} | ||
{fw_version && <div>{I18n.t('Firmware')}: {fw_version}</div>} | ||
</div> | ||
} | ||
</div> | ||
</ListItem> | ||
) | ||
} |
20 changes: 20 additions & 0 deletions
20
plugins/lime-plugin-network-nodes/src/components/expandableNode/stories.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ExpandableNode } from './index'; | ||
|
||
export default { | ||
title: 'Containers/NetworkNodes/Components/ExpandableNode', | ||
component: ExpandableNode | ||
}; | ||
|
||
const node = { | ||
hostname: 'ql-flor', | ||
ipv4:'10.5.0.16', | ||
ipv6: 'fd0d:fe46:8ce8::8bbf:7500', | ||
board: 'LibreRouter v1', | ||
fw_version: 'LibreRouterOS 1.4' | ||
}; | ||
|
||
export const folded = () => | ||
<ExpandableNode node={node} showMore={false}/> | ||
|
||
export const unfolded = () => | ||
<ExpandableNode node={node} showMore={true}/> |
14 changes: 14 additions & 0 deletions
14
plugins/lime-plugin-network-nodes/src/components/expandableNode/style.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
.moreData { | ||
padding-left: 2em; | ||
cursor: text; | ||
} | ||
|
||
.hostname { | ||
font-size: 2em; | ||
} | ||
|
||
.threeDots { | ||
font-size: 1.5em; | ||
font-weight: bold; | ||
cursor: pointer; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import api from 'utils/uhttpd.service'; | ||
|
||
export const getNodes = () => | ||
api.call('network-nodes', 'get_nodes', {}).toPromise() | ||
.then(res => res.nodes); | ||
|
||
export const markNodesAsGone = () => api.call('network-nodes', 'mark_nodes_as_gone', {}).toPromise(); |
34 changes: 34 additions & 0 deletions
34
plugins/lime-plugin-network-nodes/src/networkNodesApi.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { getNodes, markNodesAsGone } from './networkNodesApi' | ||
import api from 'utils/uhttpd.service'; | ||
import { of } from 'rxjs'; | ||
jest.mock('utils/uhttpd.service') | ||
|
||
beforeEach(() => { | ||
api.call.mockImplementation(() => of({ status: 'ok' })) | ||
}) | ||
|
||
describe('getNodes', () => { | ||
it('hits the expected endpoint', async () => { | ||
getNodes(); | ||
expect(api.call).toBeCalledWith('network-nodes', 'get_nodes', {}); | ||
}); | ||
|
||
it('test resolves to nodes data', async () => { | ||
const nodes = { | ||
'host1': { | ||
ipv4: '10.5.0.16', | ||
ipv6: 'fd0d:fe46:8ce8::8bbf:7500', | ||
board: 'LibreRouter v1', | ||
fw_version: 'LibreRouterOS 1.4' | ||
}, | ||
'host2': { | ||
ipv4: '10.5.0.17', | ||
ipv6: 'fd0d:fe46:8ce8::8bbf:75bf', | ||
board: 'TL-WDR3500', | ||
fw_version: 'LibreRouterOS 1.4' | ||
} | ||
}; | ||
api.call.mockImplementation(() => of({ status: 'ok', nodes })); | ||
expect(await getNodes()).toEqual(nodes); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { h } from 'preact'; | ||
import I18n from 'i18n-js'; | ||
|
||
const Menu = () => ( | ||
<a href={'#/networknodes'}>{I18n.t('Network Nodes')}</a> | ||
); | ||
|
||
export default Menu; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// NetworkNodes will be rendered when navigating to this plugin | ||
import { h } from 'preact'; | ||
import { useNetworkNodes } from './networkNodesQueries'; | ||
import { List } from 'components/list'; | ||
import { Loading } from 'components/loading'; | ||
import { ExpandableNode } from './components/expandableNode'; | ||
import style from './networkNodesStyle.less'; | ||
import { useState } from 'preact/hooks'; | ||
import I18n from 'i18n-js'; | ||
|
||
export const _NetworkNodes = ({ nodes, isLoading, unfoldedNode, onUnfold }) => { | ||
if (isLoading) { | ||
return <div class="container container-center"><Loading /></div> | ||
} | ||
return ( | ||
<div class="d-flex flex-column flex-grow-1 overflow-auto"> | ||
<div class={style.title}>{I18n.t("Network Nodes")}</div> | ||
<List> | ||
{nodes.map((node) => | ||
<ExpandableNode key={node.hostname} | ||
node={node} showMore={node.hostname === unfoldedNode} | ||
onClick={() => onUnfold(node.hostname)} /> | ||
)} | ||
</List> | ||
</div> | ||
) | ||
}; | ||
|
||
const NetworkNodes = () => { | ||
const { data: networkNodes, isLoading } = useNetworkNodes(); | ||
const [ unfoldedNode, setunfoldedNode ] = useState(null); | ||
const sortedNodes = (networkNodes && | ||
Object.entries(networkNodes) | ||
.map(([k, v]) => ({ ...v, hostname: k })) | ||
.sort((a, b) => a.hostname > b.hostname ? -1 : 1)); | ||
|
||
function changeUnfolded(hostname) { | ||
if (unfoldedNode == hostname) { | ||
setunfoldedNode(null); | ||
return; | ||
} | ||
setunfoldedNode(hostname); | ||
} | ||
|
||
return <_NetworkNodes nodes={sortedNodes} isLoading={isLoading} | ||
unfoldedNode={unfoldedNode} onUnfold={changeUnfolded}/>; | ||
} | ||
|
||
export default NetworkNodes; |
51 changes: 51 additions & 0 deletions
51
plugins/lime-plugin-network-nodes/src/networkNodesPage.stories.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import NetworkNodes, {_NetworkNodes} from './networkNodesPage'; | ||
|
||
export default { | ||
title: 'Containers/networkNodes' | ||
} | ||
|
||
const nodes = [ | ||
{ | ||
hostname: 'ql-berta', | ||
ipv4:'10.5.0.16', | ||
ipv6: 'fd0d:fe46:8ce8::8bbf:7500', | ||
board: 'LibreRouter v1', | ||
fw_version: 'LibreRouterOS 1.4' | ||
}, | ||
{ | ||
hostname: 'ql-nelson', | ||
ipv4:'10.5.0.17', | ||
ipv6: 'fd0d:fe46:8ce8::8bbf:75bf', | ||
board: 'LibreRouter v1', | ||
fw_version: 'LibreRouterOS 1.4' | ||
} | ||
]; | ||
|
||
export const networkNodesNonUnfolded = () => | ||
<_NetworkNodes nodes={nodes} /> | ||
|
||
export const networkNodesOneUnfolded = () => | ||
<_NetworkNodes nodes={nodes} unfoldedNode={'ql-berta'} /> | ||
|
||
export const networkNodesLoading = () => | ||
<_NetworkNodes isLoading={true} /> | ||
|
||
const manyNodes = []; | ||
for (let i = 0; i < 15; i++) { | ||
const hostname = `host${i}`; | ||
const node = {...nodes[0]}; | ||
node.hostname = hostname; | ||
manyNodes.push(node); | ||
} | ||
|
||
export const networkNodesManyNodes = () => | ||
<_NetworkNodes nodes={manyNodes} /> | ||
|
||
export const networkNodesInteractive = () => | ||
<NetworkNodes /> | ||
networkNodesInteractive.args = { | ||
queries: [ | ||
[['network-nodes', 'get_nodes'], | ||
Object.fromEntries(nodes.map(n => [n.hostname, n]))] | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { useQuery } from 'react-query'; | ||
import { getNodes } from './networkNodesApi'; | ||
|
||
export const useNetworkNodes = () => | ||
useQuery(['network-nodes', 'get_nodes'], getNodes); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.title { | ||
font-size: 2em; | ||
padding-top: 1rem; | ||
padding-left: 1rem; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters