Skip to content

Commit

Permalink
feat(network nodes): add Network Nodes Plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
germanferrero committed Oct 14, 2021
1 parent e1bbed1 commit 12e541d
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 0 deletions.
9 changes: 9 additions & 0 deletions plugins/lime-plugin-network-nodes/index.js
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'
};
54 changes: 54 additions & 0 deletions plugins/lime-plugin-network-nodes/networkNodes.spec.js
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();
})

});
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>
)
}
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}/>
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;
}
7 changes: 7 additions & 0 deletions plugins/lime-plugin-network-nodes/src/networkNodesApi.js
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 plugins/lime-plugin-network-nodes/src/networkNodesApi.spec.js
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);
});
});
8 changes: 8 additions & 0 deletions plugins/lime-plugin-network-nodes/src/networkNodesMenu.js
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;
49 changes: 49 additions & 0 deletions plugins/lime-plugin-network-nodes/src/networkNodesPage.js
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 plugins/lime-plugin-network-nodes/src/networkNodesPage.stories.js
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]))]
]
}
5 changes: 5 additions & 0 deletions plugins/lime-plugin-network-nodes/src/networkNodesQueries.js
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);
5 changes: 5 additions & 0 deletions plugins/lime-plugin-network-nodes/src/networkNodesStyle.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.title {
font-size: 2em;
padding-top: 1rem;
padding-left: 1rem;
}
2 changes: 2 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Fbw from '../plugins/lime-plugin-fbw';
import NetworkAdmin from '../plugins/lime-plugin-network-admin';
import Firmware from '../plugins/lime-plugin-firmware';
import RemoteSupport from '../plugins/lime-plugin-remotesupport';
import NetworkNodes from '../plugins/lime-plugin-network-nodes';

// REGISTER PLUGINS
export const plugins = [
Expand All @@ -22,5 +23,6 @@ export const plugins = [
Firmware,
ChangeNode,
RemoteSupport,
NetworkNodes,
Fbw // fbw does not have menu item
];

0 comments on commit 12e541d

Please sign in to comment.