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