dashboard.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. /* configuration */
  2. let socket = "wss://localhost:6789/",
  3. urllogMax = 100;
  4. function formatSize (bytes) {
  5. let prefixes = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
  6. while (bytes >= 1024 && prefixes.length > 1) {
  7. bytes /= 1024;
  8. prefixes.shift ();
  9. }
  10. return bytes.toFixed (1) + ' ' + prefixes[0];
  11. }
  12. class Job {
  13. constructor (id, url, user, queued) {
  14. this.id = id;
  15. this.url = url;
  16. this.user = user;
  17. this.status = undefined;
  18. this.stats = {'pending': 0, 'have': 0, 'running': 0,
  19. 'requests': 0, 'finished': 0, 'failed': 0,
  20. 'bytesRcv': 0, 'crashed': 0, 'ignored': 0};
  21. this.urllog = [];
  22. this.queued = queued;
  23. this.started = undefined;
  24. this.finished = undefined;
  25. this.aborted = undefined;
  26. }
  27. addUrl (url) {
  28. if (this.urllog.push (url) > urllogMax) {
  29. this.urllog.shift ();
  30. }
  31. }
  32. }
  33. let jobs = {};
  34. let ws = new WebSocket(socket);
  35. ws.onmessage = function (event) {
  36. var msg = JSON.parse (event.data);
  37. let msgdate = new Date (Date.parse (msg.date));
  38. var j = undefined;
  39. if (msg.job) {
  40. j = jobs[msg.job];
  41. if (j === undefined) {
  42. j = new Job (msg.job, 'unknown', '<unknown>', new Date ());
  43. Vue.set (jobs, msg.job, j);
  44. }
  45. }
  46. if (msg.uuid == '36cc34a6-061b-4cc5-84a9-4ab6552c8d75') {
  47. j = new Job (msg.job, msg.url, msg.user, msgdate);
  48. /* jobs[msg.job] = j does not work with vue, see
  49. https://vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats
  50. */
  51. Vue.set (jobs, msg.job, j);
  52. j.status = 'pending';
  53. } else if (msg.uuid == '46e62d60-f498-4ab0-90e1-d08a073b10fb') {
  54. j.status = 'running';
  55. j.started = msgdate;
  56. } else if (msg.uuid == '7b40ffbb-faab-4224-90ed-cd4febd8f7ec') {
  57. j.status = 'finished';
  58. j.finished = msgdate;
  59. } else if (msg.uuid == '865b3b3e-a54a-4a56-a545-f38a37bac295') {
  60. j.status = 'aborted';
  61. j.aborted = msgdate;
  62. } else if (msg.uuid == '5c0f9a11-dcd8-4182-a60f-54f4d3ab3687') {
  63. /* forwarded job message */
  64. let rmsg = msg.data;
  65. if (rmsg.uuid == '24d92d16-770e-4088-b769-4020e127a7ff') {
  66. /* job status */
  67. Object.assign (j.stats, rmsg);
  68. } else if (rmsg.uuid == '5b8498e4-868d-413c-a67e-004516b8452c') {
  69. /* recursion status */
  70. Object.assign (j.stats, rmsg);
  71. } else if (rmsg.uuid == 'd1288fbe-8bae-42c8-af8c-f2fa8b41794f') {
  72. /* fetch */
  73. j.addUrl (rmsg.url);
  74. }
  75. }
  76. };
  77. ws.onopen = function (event) {
  78. };
  79. ws.onerror = function (event) {
  80. };
  81. Vue.component('job-item', {
  82. props: ['job', 'jobs'],
  83. template: '<div class="job box" :id="job.id"><ul class="columns"><li class="jid column is-narrow"><a :href="\'#\' + job.id">{{ job.id }}</a></li><li class="url column"><a :href="job.url">{{ job.url }}</a></li><li class="status column is-narrow"><job-status v-bind:job="job"></job-status></li></ul><job-stats v-bind:job="job"></job-stats></div>',
  84. });
  85. Vue.component('job-status', {
  86. props: ['job'],
  87. template: '<span v-if="job.status == \'pending\'">queued on {{ job.queued.toLocaleString() }}</span><span v-else-if="job.status == \'aborted\'">aborted on {{ job.aborted.toLocaleString() }}</span><span v-else-if="job.status == \'running\'">running since {{ job.started.toLocaleString() }}</span><span v-else-if="job.status == \'finished\'">finished since {{ job.finished.toLocaleString() }}</span>'
  88. });
  89. Vue.component('job-stats', {
  90. props: ['job'],
  91. template: '<div><progress class="progress is-info" :value="job.stats.have" :max="job.stats.have+job.stats.pending+job.stats.running"></progress><ul class="stats columns"><li class="column">{{ job.stats.have }} <small>have</small></li><li class="column">{{ job.stats.running }} <small>running</small></li><li class="column">{{ job.stats.pending }} <small>pending</small></li><li class="column">{{ job.stats.requests }} <small>requests</small><li class="column"><filesize v-bind:value="job.stats.bytesRcv"></filesize></li></ul><job-urls v-bind:job="job"></job-urls></div>'
  92. });
  93. Vue.component('job-urls', {
  94. props: ['job'],
  95. template: '<ul class="urls"><li v-for="u in job.urllog">{{ u }}</li></ul>'
  96. });
  97. Vue.component('filesize', {
  98. props: ['value'],
  99. template: '<span class="filesize">{{ fvalue }}</span>',
  100. computed: { fvalue: function () { return formatSize (this.value); } }
  101. });
  102. Vue.component('bot-status', {
  103. props: ['jobs'],
  104. template: '<nav class="level"><div class="level-item has-text-centered"><div><p class="heading">Pending</p><p class="title">{{ stats.pending }}</p></div></div><div class="level-item has-text-centered"><div><p class="heading">Running</p><p class="title">{{ stats.running }}</p></div></div><div class="level-item has-text-centered"><div><p class="heading">Finished</p><p class="title">{{ stats.finished+stats.aborted }}</p></div></div><div class="level-item has-text-centered"><div><p class="heading">Transferred</p><p class="title"><filesize v-bind:value="stats.totalBytes"></filesize></p></div></div></nav>',
  105. computed: {
  106. stats: function () {
  107. let s = {pending: 0, running: 0, finished: 0, aborted: 0, totalBytes: 0};
  108. for (let k in this.jobs) {
  109. let j = this.jobs[k];
  110. s[j.status]++;
  111. s.totalBytes += j.stats.bytesRcv;
  112. }
  113. return s;
  114. }
  115. }
  116. });
  117. let app = new Vue({
  118. el: '#app',
  119. data: {
  120. jobs: jobs,
  121. }
  122. });