You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

app.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. 'use strict'
  2. // Disclaimer: this code is quite messy, and will need a rewrite sometime
  3. // read at your own risk ;)
  4. const React = require('react')
  5. const ReactDOM = require('react-dom')
  6. const create = require('create-react-class')
  7. const Promise = require('bluebird')
  8. const rfetch = require('fetch-retry')
  9. const urllib = require('url')
  10. const icons = require('./icons.js')
  11. let apiUrl = urllib.parse("https://neo.lain.haus/api/report")
  12. let options = {retries: 5, retryDelay: 200}
  13. let App = create({
  14. displayName: "App",
  15. getInitialState: function() {
  16. return({
  17. json: undefined
  18. })
  19. },
  20. componentDidMount: function() {
  21. setTimeout(() => {
  22. if (window.location.hash) {
  23. var hash = window.location.hash.substring(1)
  24. this.state.ref.value = hash
  25. this.submit()
  26. }
  27. }, 100)
  28. },
  29. setRef: function(e) {
  30. if (e == null) {
  31. return
  32. }
  33. e.addEventListener('keydown', (e) => {
  34. if (e.keyCode == 13) {
  35. this.submit()
  36. }
  37. })
  38. e.focus()
  39. this.setState({
  40. ref: e
  41. })
  42. },
  43. submit: function() {
  44. let url = Object.assign(apiUrl, {
  45. query: {
  46. server_name: this.state.ref.value.toLowerCase()
  47. }
  48. })
  49. this.setState({
  50. loading: true,
  51. json: undefined
  52. })
  53. console.log("Querying API", urllib.format(url))
  54. rfetch(urllib.format(url), options)
  55. .then((res) => res.json())
  56. .then((json) => {
  57. // The tldr block will be displayed before the full table
  58. let tldr = []
  59. Object.keys(json.ConnectionReports).forEach((ip) => {
  60. let report = json.ConnectionReports[ip]
  61. if (!report.ValidCertificates) {
  62. tldr.push(<div className="warning" key={`cert-${tldr.length}`}>
  63. WARN: Self-signed cert found for {ip}, this will need to be replaced in the future <a href="https://github.com/matrix-org/matrix-doc/pull/1711">MSC1711</a>
  64. </div>)
  65. }
  66. Object.keys(report.Info).forEach((infoKey) => {
  67. let bool = report.Info[infoKey]
  68. if (!bool) {
  69. tldr.push(<div className="info" key={`cert-${tldr.length}`}>
  70. INFO: No {infoKey} for {ip}
  71. </div>)
  72. }
  73. })
  74. recursiveCheck(report.Checks, "Checks", (path) => {
  75. // Found an error
  76. tldr.push(<div className="error" key={`${path}-${tldr.length}`}>
  77. ERROR: on {ip}: {path} failed
  78. </div>)
  79. })
  80. })
  81. this.setState({
  82. json: json,
  83. tldr: tldr,
  84. loading: false
  85. })
  86. })
  87. },
  88. render: function() {
  89. let result
  90. let errors
  91. let active = ""
  92. if (this.state.loading) {
  93. active = " active";
  94. }
  95. if (this.state.json != undefined) {
  96. let reportCount = Object.keys(this.state.json.ConnectionReports).length
  97. if (reportCount == 0) {
  98. errors = (
  99. <div className="error">
  100. No connection reports, is this even a matrix server?
  101. </div>
  102. )
  103. } else {
  104. result = <>
  105. Got {reportCount} connection report{reportCount > 1 && <>s</>}
  106. <div className="tldr">
  107. {this.state.tldr}
  108. </div>
  109. <TestResults json={this.state.json}/>
  110. </>
  111. }
  112. }
  113. return (
  114. <div className="block">
  115. <div className="text">
  116. <span id="jok">unlike the name suggests, this won't help you find three letter agencies :p</span><br/>
  117. However, it might help you debug your Matrix instance<br/><br/>
  118. Made with love by <a href="https://f.0x52.eu">f0x</a>, sourcecode <a href="https://git.lain.haus/f0x/fed-tester">here</a>, powered by the <a href="https://github.com/matrix-org/matrix-federation-tester">matrix-federation-tester</a> backend <br/>
  119. <a href="https://liberapay.com/f0x/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"/></a>
  120. </div>
  121. <div className="input">
  122. <input ref={this.setRef}/>
  123. <div className={"sk-cube-grid" + active} onClick={this.submit}>
  124. <span>Go</span>
  125. <div className="sk-cube sk-cube1"></div>
  126. <div className="sk-cube sk-cube2"></div>
  127. <div className="sk-cube sk-cube3"></div>
  128. <div className="sk-cube sk-cube4"></div>
  129. <div className="sk-cube sk-cube5"></div>
  130. <div className="sk-cube sk-cube6"></div>
  131. <div className="sk-cube sk-cube7"></div>
  132. <div className="sk-cube sk-cube8"></div>
  133. <div className="sk-cube sk-cube9"></div>
  134. </div>
  135. </div>
  136. {result}
  137. {errors}
  138. </div>
  139. )
  140. }
  141. })
  142. let TestResults = create({
  143. displayName: "TestResults",
  144. render: function() {
  145. return (
  146. <div className="results">
  147. <ConnectionErrors json={this.props.json.ConnectionErrors}/>
  148. <ConnectionReports json={this.props.json.ConnectionReports}/>
  149. <DNSResult json={this.props.json.DNSResult}/>
  150. </div>
  151. );
  152. }
  153. })
  154. let ConnectionReports = create({
  155. displayName: "ConnectionErrors",
  156. render: function() {
  157. let j = this.props.json;
  158. let connections = Object.keys(j).map((ip, id) => {
  159. let info = j[ip];
  160. return <ReportTable info={info} key={id} ip={ip}/>;
  161. });
  162. return (
  163. <div className="connection">
  164. <h2>Connection Reports</h2>
  165. {connections}
  166. </div>
  167. );
  168. }
  169. });
  170. let ConnectionErrors = create({
  171. displayName: "ConnectionErrors",
  172. render: function() {
  173. let j = this.props.json;
  174. if (Object.keys(j).length == 0) {
  175. return null;
  176. }
  177. let connections = Object.keys(j).map((ip, id) => {
  178. let info = j[ip];
  179. if (info.Message != null) {
  180. return info.Message;
  181. }
  182. return <ReportTable info={info} key={id} ip={ip}/>;
  183. });
  184. return (
  185. <div className="connection err">
  186. <h2>Connection Errors</h2>
  187. {connections}
  188. </div>
  189. );
  190. }
  191. });
  192. let ReportTable = create({
  193. displayName: "ReportTable",
  194. getInitialState: function() {
  195. return ({
  196. collapsed: {
  197. info: true,
  198. checks: this.props.info.Checks.AllChecksOK
  199. }
  200. });
  201. },
  202. toggle: function(element) {
  203. let collapsed = this.state.collapsed
  204. collapsed[element] = !collapsed[element]
  205. this.setState({
  206. collapsed: collapsed
  207. });
  208. },
  209. render: function() {
  210. let checks = <TableFromObject object={this.props.info.Checks} collapsed={this.state.collapsed.checks} tree={1} type="error" />;
  211. let info = <TableFromObject object={this.props.info.Info} collapsed={this.state.collapsed.info} tree={1} type="info" />;
  212. let checksIcon = icons.right;
  213. let infoIcon = icons.right;
  214. let falseRow = {
  215. symbol: "Error",
  216. className: "false"
  217. }
  218. let trueRow = {
  219. symbol: "Success",
  220. className: "true"
  221. }
  222. let rows = {
  223. checks: falseRow,
  224. cert: {
  225. symbol: "Warning",
  226. className: "warn"
  227. }
  228. }
  229. if (!this.state.collapsed["checks"]) {
  230. checksIcon = icons.down;
  231. }
  232. if (!this.state.collapsed["info"]) {
  233. infoIcon = icons.down;
  234. }
  235. if (this.props.info.Checks.AllChecksOK) {
  236. rows.checks = trueRow
  237. }
  238. if (this.props.info.ValidCertificates) {
  239. rows.cert = trueRow
  240. }
  241. return (
  242. <div>
  243. <h3>{this.props.ip}</h3>
  244. <div className="table">
  245. <div className="body">
  246. <div className="row">
  247. <div className="col">Valid Certificate</div>
  248. <div className={"col bool " + rows.cert.className}>{rows.cert.symbol}</div>
  249. </div>
  250. <div className="row toggle" onClick={() => this.toggle("info")}>
  251. {infoIcon}
  252. <div className="col">Information</div>
  253. <div className="col bool info">Information</div>
  254. </div>
  255. {info}
  256. <div className="row toggle" onClick={() => this.toggle("checks")}>
  257. {checksIcon}
  258. <div className="col">Other Checks</div>
  259. <div className={"col bool " + rows.checks.className}>{rows.checks.symbol}</div>
  260. </div>
  261. {checks}
  262. </div>
  263. </div>
  264. </div>
  265. );
  266. }
  267. });
  268. function recursiveCheck(objectOrBool, path, bubble) {
  269. if (typeof objectOrBool == typeof true) {
  270. if (!objectOrBool) {
  271. if (bubble) {
  272. bubble(path)
  273. }
  274. return true
  275. }
  276. } else {
  277. let anyErrors
  278. Object.keys(objectOrBool).forEach((childKey) => {
  279. let childValue = objectOrBool[childKey]
  280. if (recursiveCheck(childValue, path + `.${childKey}`, bubble)) {
  281. anyErrors = true
  282. }
  283. })
  284. if (anyErrors) {
  285. return true
  286. }
  287. }
  288. }
  289. let TableFromObject = create({
  290. displayName: "TableFromObject",
  291. getInitialState: function() {
  292. return ({
  293. collapsed: this.props.collapsed
  294. });
  295. },
  296. toggle: function() {
  297. let collapsed = this.state.collapsed;
  298. if (collapsed) {
  299. collapsed = false;
  300. } else {
  301. collapsed = true;
  302. }
  303. this.setState({
  304. collapsed: collapsed
  305. });
  306. },
  307. render: function() {
  308. let objectArray = Object.keys(this.props.object);
  309. return objectArray.map((check, id) => {
  310. let symbol
  311. let className
  312. if (this.props.type == "error") {
  313. symbol = "Error";
  314. className = "false";
  315. if (this.props.object[check]) {
  316. symbol = "Success";
  317. className = "true";
  318. }
  319. } else if (this.props.type == "info") {
  320. symbol = "No";
  321. className = "false";
  322. if (this.props.object[check]) {
  323. symbol = "Yes";
  324. className = "true";
  325. }
  326. }
  327. if (check == "AllChecksOK") {
  328. return null;
  329. } else if (!this.props.collapsed) {
  330. if (typeof(this.props.object[check]) == "boolean") {
  331. return (
  332. <div className={`row toggle tree-${this.props.tree} ${this.props.type}Row`} key={id}>
  333. <div className="col">{check}</div>
  334. <div className={"col bool " + className}>{symbol}</div>
  335. </div>
  336. );
  337. } else {
  338. let childrenBool = "true"
  339. let childrenSymbol = "Success"
  340. if (recursiveCheck(this.props.object[check], "Checks")) {
  341. //will return true if any children are false
  342. childrenBool = "false"
  343. childrenSymbol = "Error"
  344. }
  345. return (
  346. <div key={id}>
  347. <div className={"row toggle tree-" + this.props.tree} onClick={this.toggle}>
  348. <div className="col">{check}</div>
  349. <div className={"col bool " + childrenBool}>{childrenSymbol}</div>
  350. </div>
  351. <TableFromObject object={this.props.object[check]} collapsed={false} key={id} tree={this.props.tree+1} />
  352. </div>
  353. );
  354. }
  355. }
  356. return null;
  357. });
  358. }
  359. });
  360. let DNSResult = create({
  361. displayName: "DNS",
  362. render: function() {
  363. let j = this.props.json;
  364. if (j.SRVRecords == null) {
  365. return (
  366. <div className="dns">
  367. <h2>No SRV Records</h2>
  368. </div>
  369. );
  370. }
  371. let records = j.SRVRecords.map((record, id) => {
  372. return (
  373. <div className="row" key={id}>
  374. <div className="col">{record.Target}</div>
  375. <div className="col">{record.Port}</div>
  376. <div className="col">{record.Priority}</div>
  377. <div className="col">{record.Target}</div>
  378. </div>
  379. );
  380. });
  381. let hosts = Object.keys(j.Hosts).map((host) => {
  382. return j.Hosts[host].Addrs.map((address, id) => {
  383. return (
  384. <div className="row" key={id}>
  385. <div className="col">{address}</div>
  386. </div>
  387. );
  388. });
  389. });
  390. return (
  391. <div className="dns">
  392. <h2>DNS records for {j.SRVCName}</h2>
  393. <div className="table">
  394. <div className="header">
  395. <span className="col">Target</span>
  396. <span className="col">Port</span>
  397. <span className="col">Priority</span>
  398. <span className="col">Target</span>
  399. </div>
  400. <div className="body">
  401. {records}
  402. </div>
  403. </div>
  404. <div className="table">
  405. <div className="head">
  406. Address
  407. </div>
  408. <div className="body">
  409. {hosts}
  410. </div>
  411. </div>
  412. </div>
  413. );
  414. }
  415. });
  416. ReactDOM.render(
  417. <App />,
  418. document.getElementById('root')
  419. )