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