diff --git a/webui-src/app/network/network.css b/webui-src/app/network/network.css index a3b62cf..b5a46cc 100644 --- a/webui-src/app/network/network.css +++ b/webui-src/app/network/network.css @@ -20,6 +20,12 @@ .friend button { font-size: 0.9em; } +.friend.hidden { + display: none; +} +.friend .brief-info.online { + color: green; +} .friend .location { margin: 5px; diff --git a/webui-src/app/network/network.js b/webui-src/app/network/network.js index 3449848..f53fd4a 100644 --- a/webui-src/app/network/network.js +++ b/webui-src/app/network/network.js @@ -1,151 +1,151 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let widget = require('widgets'); - +const m = require('mithril'); +const rs = require('rswebui'); +const widget = require('widgets'); +const Data = require('network_data'); const ConfirmRemove = () => { return { - view: (vnode) => [m('h3', 'Remove Friend'), + view: vnode => [ + m('h3', 'Remove Friend'), m('hr'), m('p', 'Are you sure you want to end connections with this node?'), - m('button', { - onclick: () => { - rs.rsJsonApiRequest('/rsPeers/removeFriend', { - pgpId: vnode.attrs.gpg - }); - m.redraw(); - } - }, 'Confirm'), + m( + 'button', + { + onclick: () => { + rs.rsJsonApiRequest('/rsPeers/removeFriend', { + pgpId: vnode.attrs.gpg_id, + }); + m.redraw(); + }, + }, + 'Confirm', + ), ], }; }; -const Node = () => { +const Locations = () => { return { - isOnline: false, - isExpanded: false, - - oninit(v) { - v.state.isOnline = false; - // check if any one location is online - v.attrs.data.map(node => rs.rsJsonApiRequest( - '/rsPeers/isOnline', { - sslId: node.id - }, (data) => data.retval ? v.state.isOnline = true : undefined)); - }, - - view: (vnode) => m('.friend', { - key: vnode.attrs.data[0].gpg_id, - style: "display:" + (vnode.attrs.isSearched ? "block" : "none"), - }, [ - m('i.fas.fa-angle-right', { - class: 'fa-rotate-' + (vnode.state.isExpanded ? '90' : '0'), - onclick: () => vnode.state.isExpanded = !vnode.state.isExpanded, - }), - m('i.fas.fa-2x.fa-user-circle'), - m('span', vnode.attrs.data[0].name), - m('i.fas', { - class: vnode.state.isOnline ? 'fa-check-circle' : 'fa-times-circle' - }), - m('.details', { - style: "display:" + (vnode.state.isExpanded ? "block" : "none"), - }, [ - m('.grid-2col', [ - m('p', 'Last contacted :'), - m('p', new Date(vnode.attrs.data[0].lastConnect * 1000).toDateString()), - m('p', 'Online :'), - m('i.fas', { - class: vnode.state.isOnline ? 'fa-check-circle' : 'fa-times-circle' - }), - ]), - m('h4', 'Locations'), - vnode.attrs.data.map((loc) => m('.location', [ - m('i.fas.fa-user-tag'), m('span', loc.location), + view: v => [ + m('h4', 'Locations'), + v.attrs.locations.map(loc => + m('.location', [ + m('i.fas.fa-user-tag'), + m('span', loc.name), m('p', 'ID :'), m('p', loc.id), + m('p', 'Last contacted :'), + m('p', new Date(loc.lastSeen * 1000).toDateString()), m('p', 'Online :'), m('i.fas', { - class: vnode.state.isOnline ? 'fa-check-circle' : 'fa-times-circle' + class: loc.isOnline ? 'fa-check-circle' : 'fa-times-circle', }), - m('button.red', { - onclick: () => widget.popupMessage(m(ConfirmRemove, { - gpg: loc.gpg_id, - })) - }, 'Remove node'), - ])), - ]) - ]), + m( + 'button.red', + { + onclick: () => + widget.popupMessage( + m(ConfirmRemove, { + gpg: loc.gpg_id, + }), + ), + }, + 'Remove node', + ), + ]), + ), + ], }; }; -let FriendNodes = {}; +const Friend = () => { + return { + isExpanded: false, + + view: vnode => + m( + '.friend', + { + key: vnode.attrs.id, + class: Data.gpgDetails[vnode.attrs.id].isSearched ? '' : 'hidden', + }, + [ + m('i.fas.fa-angle-right', { + class: 'fa-rotate-' + (vnode.state.isExpanded ? '90' : '0'), + onclick: () => (vnode.state.isExpanded = !vnode.state.isExpanded), + }), + m( + '.brief-info', + {class: Data.gpgDetails[vnode.attrs.id].isOnline ? 'online' : ''}, + [ + m('i.fas.fa-2x.fa-user-circle'), + m('span', Data.gpgDetails[vnode.attrs.id].name), + ], + ), + m( + '.details', + { + style: 'display:' + (vnode.state.isExpanded ? 'block' : 'none'), + }, + [ + m(Locations, { + locations: Data.gpgDetails[vnode.attrs.id].locations, + }), + ], + ), + ], + ), + }; +}; -let searchString = ''; const SearchBar = () => { + let searchString = ''; return { - view: () => m('input[type=text][placeholder=search].searchbar', { - value: searchString, - oninput: (e) => { - searchString = e.target.value.toLowerCase(); - for(let id in FriendNodes) { - if(FriendNodes[id].locations[0].name.toLowerCase().indexOf( - searchString) > -1) { - FriendNodes[id].isSearched = true; - } else { - FriendNodes[id].isSearched = false; + view: () => + m('input.searchbar', { + type: 'text', + placeholder: 'search', + value: searchString, + oninput: e => { + searchString = e.target.value.toLowerCase(); + for (let id in Data.gpgDetails) { + if ( + Data.gpgDetails[id].name.toLowerCase().indexOf(searchString) > -1 + ) { + Data.gpgDetails[id].isSearched = true; + } else { + Data.gpgDetails[id].isSearched = false; + } } - } - }, - }) + }, + }), }; }; -const Friends = () => { +const FriendsList = () => { return { - oninit: () => { - FriendNodes = {}; - rs.rsJsonApiRequest( - '/rsPeers/getFriendList', {}, - (friendListIds) => { - friendListIds.sslIds.map( - (sslId) => rs.rsJsonApiRequest( - '/rsPeers/getPeerDetails', { - sslId - }, - (details) => { - // Store nodes obj with gpg id as key - // single node can have multiple identities - if(FriendNodes[details.det.gpg_id] === undefined) - FriendNodes[details.det.gpg_id] = { - locations: [details.det], - isSearched: true - } - else - FriendNodes[details.det.gpg_id].locations.push( - details.det); - }) - ); - }); + oninit: () =>{ + Data.refreshGpgDetails(); + //rs.setBackgroundTask( + // Data.refreshGpgDetails, + // 10000, + // () => m.route.get() === '/network', + //) }, - view: () => m('.widget', [ - m('h3', 'Friend nodes'), - m('hr'), - Object.keys(FriendNodes).map((id) => m(Node, { - data: FriendNodes[id].locations, - isSearched: FriendNodes[id].isSearched, - })), - ]), + view: () => + m('.widget', [ + m('h3', 'Friend nodes'), + m('hr'), + Object.keys(Data.gpgDetails).map(id => m(Friend, {id})), + ]), }; }; const Layout = () => { return { - view: () => m('.tab-page', [ - m(SearchBar), - m(Friends), - ]) - } + view: () => m('.tab-page', [m(SearchBar), m(FriendsList)]), + }; }; module.exports = Layout; - diff --git a/webui-src/app/network/network_data.js b/webui-src/app/network/network_data.js new file mode 100644 index 0000000..0f5cff1 --- /dev/null +++ b/webui-src/app/network/network_data.js @@ -0,0 +1,70 @@ +const rs = require('rswebui'); + +const Data = { + gpgDetails: {}, + refreshGpgDetails, +}; + +async function refreshIds() { + let sslIds = []; + await rs.rsJsonApiRequest( + '/rsPeers/getFriendList', + {}, + data => (sslIds = data.sslIds), + ); + return sslIds; +} + +async function loadSslDetails() { + let sslDetails = []; + let sslIds = await refreshIds(); + await Promise.all( + sslIds.map(sslId => + rs.rsJsonApiRequest('/rsPeers/getPeerDetails', {sslId}, data => + sslDetails.push(data.det), + ), + ), + ); + return sslDetails; +} + +async function refreshGpgDetails() { + let details = {}; + let sslDetails = await loadSslDetails(); + await Promise.all( + sslDetails.map(data => { + let isOnline = false; + return rs + .rsJsonApiRequest( + '/rsPeers/isOnline', + {sslId: data.id}, + stat => (isOnline = stat.retval), + ) + .then(() => { + loc = { + name: data.location, + id: data.id, + lastSeen: data.lastConnect, + isOnline, + gpg_id: data.gpg_id, + }; + + if (details[data.gpg_id] === undefined) { + details[data.gpg_id] = { + name: data.name, + isSearched: true, + isOnline, + locations: [loc], + }; + } else { + details[data.gpg_id].locations.push(loc); + } + details[data.gpg_id].isOnline = + details[data.gpg_id].isOnline || isOnline; + }); + }), + ); + Data.gpgDetails = details; +} + +module.exports = Data;