ZeroTierNode.jsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. var ZeroTierNode = React.createClass({
  2. getInitialState: function() {
  3. return {
  4. address: '----------',
  5. online: false,
  6. version: '_._._',
  7. _networks: [],
  8. _peers: []
  9. };
  10. },
  11. ago: function(ms) {
  12. if (ms > 0) {
  13. var tmp = Math.round((Date.now() - ms) / 1000);
  14. return ((tmp > 0) ? tmp : 0);
  15. } else return 0;
  16. },
  17. updatePeers: function() {
  18. Ajax.call({
  19. url: 'peer?auth='+this.props.authToken,
  20. cache: false,
  21. type: 'GET',
  22. success: function(data) {
  23. if (data) {
  24. var pl = JSON.parse(data);
  25. if (Array.isArray(pl)) {
  26. this.setState({_peers: pl});
  27. }
  28. }
  29. }.bind(this),
  30. error: function() {
  31. }.bind(this)
  32. });
  33. },
  34. updateNetworks: function() {
  35. Ajax.call({
  36. url: 'network?auth='+this.props.authToken,
  37. cache: false,
  38. type: 'GET',
  39. success: function(data) {
  40. if (data) {
  41. var nwl = JSON.parse(data);
  42. if (Array.isArray(nwl)) {
  43. this.setState({_networks: nwl});
  44. }
  45. }
  46. }.bind(this),
  47. error: function() {
  48. }.bind(this)
  49. });
  50. },
  51. updateAll: function() {
  52. Ajax.call({
  53. url: 'status?auth='+this.props.authToken,
  54. cache: false,
  55. type: 'GET',
  56. success: function(data) {
  57. this.alertedToFailure = false;
  58. if (data) {
  59. var status = JSON.parse(data);
  60. this.setState(status);
  61. document.title = 'ZeroTier One [' + status.address + ']';
  62. }
  63. this.updateNetworks();
  64. this.updatePeers();
  65. }.bind(this),
  66. error: function() {
  67. this.setState(this.getInitialState());
  68. if (!this.alertedToFailure) {
  69. this.alertedToFailure = true;
  70. alert('Authorization token invalid or ZeroTier One service not running.');
  71. }
  72. }.bind(this)
  73. });
  74. },
  75. joinNetwork: function(event) {
  76. event.preventDefault();
  77. if ((this.networkToJoin)&&(this.networkToJoin.length === 16)) {
  78. Ajax.call({
  79. url: 'network/'+this.networkToJoin+'?auth='+this.props.authToken,
  80. cache: false,
  81. type: 'POST',
  82. success: function(data) {
  83. this.networkToJoin = '';
  84. if (this.networkInputElement)
  85. this.networkInputElement.value = '';
  86. this.updateNetworks();
  87. }.bind(this),
  88. error: function() {
  89. }.bind(this)
  90. });
  91. } else {
  92. alert('To join a network, enter its 16-digit network ID.');
  93. }
  94. },
  95. handleNetworkIdEntry: function(event) {
  96. this.networkInputElement = event.target;
  97. var nid = this.networkInputElement.value;
  98. if (nid) {
  99. nid = nid.toLowerCase();
  100. var nnid = '';
  101. for(var i=0;((i<nid.length)&&(i<16));++i) {
  102. if ("0123456789abcdef".indexOf(nid.charAt(i)) >= 0)
  103. nnid += nid.charAt(i);
  104. }
  105. this.networkToJoin = nnid;
  106. this.networkInputElement.value = nnid;
  107. } else {
  108. this.networkToJoin = '';
  109. this.networkInputElement.value = '';
  110. }
  111. },
  112. handleNetworkDelete: function(nwid) {
  113. var networks = [];
  114. for(var i=0;i<this.state._networks.length;++i) {
  115. if (this.state._networks[i].nwid !== nwid)
  116. networks.push(this.state._networks[i]);
  117. }
  118. this.setState({_networks: networks});
  119. },
  120. componentDidMount: function() {
  121. this.tabIndex = 0;
  122. this.updateAll();
  123. this.updateIntervalId = setInterval(this.updateAll,2500);
  124. },
  125. componentWillUnmount: function() {
  126. clearInterval(this.updateIntervalId);
  127. },
  128. render: function() {
  129. /* We implement tabs in a very simple way here with a React JSX conditional. The tabIndex
  130. * local variable indicates the tab, and switching it determines which set of things we
  131. * render in the main middle portion. On tab switch calls forceUpdate(). */
  132. return (
  133. <div className="zeroTierNode">
  134. <div className="top">&nbsp;&nbsp;
  135. <button disabled={this.tabIndex === 0} onClick={function() {this.tabIndex = 0; this.forceUpdate();}.bind(this)}>Networks</button>
  136. <button disabled={this.tabIndex === 1} onClick={function() {this.tabIndex = 1; this.forceUpdate();}.bind(this)}>Peers</button>
  137. </div>
  138. <div className="middle"><div className="middleCell">
  139. <div className="middleScroll">
  140. {
  141. (this.tabIndex === 1) ? (
  142. <div className="peers" key="_peers">
  143. <div className="peerHeader" key="_peersHeader">
  144. <div className="f">Address</div>
  145. <div className="f">Version</div>
  146. <div className="f">Latency</div>
  147. <div className="f">Data&nbsp;Paths</div>
  148. <div className="f">Last&nbsp;Unicast</div>
  149. <div className="f">Last&nbsp;Multicast</div>
  150. <div className="f">Role</div>
  151. </div>
  152. {
  153. this.state._peers.map(function(peer) {
  154. return (
  155. <div className="peer" key={peer['address']}>
  156. <div className="f zeroTierAddress">{peer['address']}</div>
  157. <div className="f">{(peer['version'] === '-1.-1.-1') ? '-' : peer['version']}</div>
  158. <div className="f">{peer['latency']}</div>
  159. <div className="f">
  160. {
  161. (peer['paths'].length === 0) ? (
  162. <div className="peerPath"></div>
  163. ) : (
  164. <div>
  165. {
  166. peer['paths'].map(function(path) {
  167. var cn = ((path.active)||(path.fixed)) ? (path.preferred ? 'peerPathPreferred' : 'peerPathActive') : 'peerPathInactive';
  168. return (
  169. <div className={cn}>{path.address}&nbsp;&nbsp;{this.ago(path.lastSend)}/{this.ago(path.lastReceive)}</div>
  170. );
  171. }.bind(this))
  172. }
  173. </div>
  174. )
  175. }
  176. </div>
  177. <div className="f">{this.ago(peer['lastUnicastFrame'])}</div>
  178. <div className="f">{this.ago(peer['lastMulticastFrame'])}</div>
  179. <div className="f">{peer['role']}</div>
  180. </div>
  181. );
  182. }.bind(this))
  183. }
  184. </div>
  185. ) : (
  186. <div className="networks" key="_networks">
  187. {
  188. this.state._networks.map(function(network) {
  189. network['authToken'] = this.props.authToken;
  190. network['onNetworkDeleted'] = this.handleNetworkDelete;
  191. return React.createElement('div',{className: 'network',key: network.nwid},React.createElement(ZeroTierNetwork,network));
  192. }.bind(this))
  193. }
  194. </div>
  195. )
  196. }
  197. </div>
  198. </div></div>
  199. <div className="bottom">
  200. <div className="left">
  201. <span className="statusLine"><span className="zeroTierAddress">{this.state.address}</span>&nbsp;&nbsp;{this.state.online ? (this.state.tcpFallbackActive ? 'TUNNELED' : 'ONLINE') : 'OFFLINE'}&nbsp;&nbsp;{this.state.version}</span>
  202. </div>
  203. <div className="right">
  204. <form onSubmit={this.joinNetwork}><input type="text" maxlength="16" placeholder="[ Network ID ]" onChange={this.handleNetworkIdEntry} size="16"/><button type="button" onClick={this.joinNetwork}>Join</button></form>
  205. </div>
  206. </div>
  207. </div>
  208. );
  209. }
  210. });