Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add network nodes list #309

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bkp/src/networkFirmwaresStyle.less
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Here you define the css for this plugin
1 change: 1 addition & 0 deletions i18n/generic.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"most_active_2d5a3cae": "Most Active",
"must_select_a_network_and_a_valid_hostname_ea82e72c": "Must select a network and a valid hostname",
"network_configuration_ea7f4215": "Network Configuration",
"network_nodes_4368eb67": "Network Nodes",
"no_network_found_try_realigning_your_node_and_resc_176a9b3e": "No network found, try realigning your node and rescanning.",
"node_configuration_7342e6f5": "Node Configuration",
"notes_c42e0fd5": "Notes",
Expand Down
3 changes: 2 additions & 1 deletion i18n/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
"select_new_node_5b2e9165": "Selecciona el nodo",
"visit_864b4060": "Visitar",
"go_to_community_view_d12b8d67": "Ir a Vista de Comunidad",
"go_to_node_view_26ba929d": "Ir a Vista de Nodo"
"go_to_node_view_26ba929d": "Ir a Vista de Nodo",
"network_nodes_4368eb67": "Nodos de la Red"
}

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'
};
69 changes: 69 additions & 0 deletions plugins/lime-plugin-network-nodes/networkNodes.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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',
status: 'recently_connected'
},
"ql-nelson": {
ipv4: '10.5.0.17',
ipv6: 'fd0d:fe46:8ce8::8bbf:75bf',
board: 'LibreRouter v1',
fw_version: 'LibreRouterOS 1.4',
status: 'disconnected'
},
"ql-gone-node": {
ipv4: '10.5.0.18',
ipv6: 'fd0d:fe46:8ce8::8bbf:75be',
board: 'LibreRouter v1',
fw_version: 'LibreRouterOS 1.4',
status: 'gone'
}
}));
});

afterEach(() => {
cleanup();
act(() => queryCache.clear());
});

it('test that nodes recently_connected and connected 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();
});

it('test that gone nodes are not shown', async () => {
render(<NetworkNodes />);
await screen.findByText('ql-nelson');
expect(screen.queryByText('ql-gone-node')).toBeNull();
})

});
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} onClick={e => e.stopPropagation()}>
{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;
50 changes: 50 additions & 0 deletions plugins/lime-plugin-network-nodes/src/networkNodesPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 }))
.filter(n => n.status !== 'gone')
.sort((a, b) => a.hostname > b.hostname));

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 {
text-align: center;
font-size: 2em;
padding-top: 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
];