github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/ui/app/utils/classes/log.js (about) 1 import { alias } from '@ember/object/computed'; 2 import { assert } from '@ember/debug'; 3 import { htmlSafe } from '@ember/template'; 4 import Evented from '@ember/object/evented'; 5 import EmberObject, { computed } from '@ember/object'; 6 import { assign } from '@ember/polyfills'; 7 import queryString from 'query-string'; 8 import { task } from 'ember-concurrency'; 9 import StreamLogger from 'nomad-ui/utils/classes/stream-logger'; 10 import PollLogger from 'nomad-ui/utils/classes/poll-logger'; 11 import Anser from 'anser'; 12 13 const MAX_OUTPUT_LENGTH = 50000; 14 15 // eslint-disable-next-line 16 export const fetchFailure = url => () => console.warn(`LOG FETCH: Couldn't connect to ${url}`); 17 18 const Log = EmberObject.extend(Evented, { 19 // Parameters 20 21 url: '', 22 params: computed(() => ({})), 23 logFetch() { 24 assert('Log objects need a logFetch method, which should have an interface like window.fetch'); 25 }, 26 27 // Read-only state 28 29 isStreaming: alias('logStreamer.poll.isRunning'), 30 logPointer: null, 31 logStreamer: null, 32 33 // The top of the log 34 head: '', 35 36 // The bottom of the log 37 tail: '', 38 39 // The top or bottom of the log, depending on whether 40 // the logPointer is pointed at head or tail 41 output: computed('logPointer', 'head', 'tail', function() { 42 let logs = this.logPointer === 'head' ? this.head : this.tail; 43 let colouredLogs = Anser.ansiToHtml(logs); 44 return htmlSafe(colouredLogs); 45 }), 46 47 init() { 48 this._super(); 49 50 const args = this.getProperties('url', 'params', 'logFetch'); 51 args.write = chunk => { 52 let newTail = this.tail + chunk; 53 if (newTail.length > MAX_OUTPUT_LENGTH) { 54 newTail = newTail.substr(newTail.length - MAX_OUTPUT_LENGTH); 55 } 56 this.set('tail', newTail); 57 this.trigger('tick', chunk); 58 }; 59 60 if (StreamLogger.isSupported) { 61 this.set('logStreamer', StreamLogger.create(args)); 62 } else { 63 this.set('logStreamer', PollLogger.create(args)); 64 } 65 }, 66 67 destroy() { 68 this.stop(); 69 this._super(); 70 }, 71 72 gotoHead: task(function*() { 73 const logFetch = this.logFetch; 74 const queryParams = queryString.stringify( 75 assign(this.params, { 76 plain: true, 77 origin: 'start', 78 offset: 0, 79 }) 80 ); 81 const url = `${this.url}?${queryParams}`; 82 83 this.stop(); 84 let text = yield logFetch(url).then(res => res.text(), fetchFailure(url)); 85 86 if (text && text.length > MAX_OUTPUT_LENGTH) { 87 text = text.substr(0, MAX_OUTPUT_LENGTH); 88 text += '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------'; 89 } 90 this.set('head', text); 91 this.set('logPointer', 'head'); 92 }), 93 94 gotoTail: task(function*() { 95 const logFetch = this.logFetch; 96 const queryParams = queryString.stringify( 97 assign(this.params, { 98 plain: true, 99 origin: 'end', 100 offset: MAX_OUTPUT_LENGTH, 101 }) 102 ); 103 const url = `${this.url}?${queryParams}`; 104 105 this.stop(); 106 let text = yield logFetch(url).then(res => res.text(), fetchFailure(url)); 107 108 this.set('tail', text); 109 this.set('logPointer', 'tail'); 110 }), 111 112 startStreaming() { 113 this.set('logPointer', 'tail'); 114 return this.logStreamer.start(); 115 }, 116 117 stop() { 118 this.logStreamer.stop(); 119 }, 120 }); 121 122 export default Log; 123 124 export function logger(urlProp, params, logFetch) { 125 return computed(urlProp, params, function() { 126 return Log.create({ 127 logFetch: logFetch.call(this), 128 params: this.get(params), 129 url: this.get(urlProp), 130 }); 131 }); 132 }