github.com/emate/nomad@v0.8.2-wo-binpacking/ui/app/utils/classes/stream-logger.js (about)

     1  import EmberObject, { computed } from '@ember/object';
     2  import { task } from 'ember-concurrency';
     3  import TextDecoder from 'nomad-ui/utils/classes/text-decoder';
     4  import AbstractLogger from './abstract-logger';
     5  import { fetchFailure } from './log';
     6  
     7  export default EmberObject.extend(AbstractLogger, {
     8    reader: null,
     9  
    10    additionalParams: computed(() => ({
    11      follow: true,
    12    })),
    13  
    14    start() {
    15      return this.get('poll').perform();
    16    },
    17  
    18    stop() {
    19      const reader = this.get('reader');
    20      if (reader) {
    21        reader.cancel();
    22      }
    23      return this.get('poll').cancelAll();
    24    },
    25  
    26    poll: task(function*() {
    27      const url = this.get('fullUrl');
    28      const logFetch = this.get('logFetch');
    29  
    30      let streamClosed = false;
    31      let buffer = '';
    32  
    33      const decoder = new TextDecoder();
    34      const reader = yield logFetch(url).then(res => res.body.getReader(), fetchFailure(url));
    35  
    36      if (!reader) {
    37        return;
    38      }
    39  
    40      this.set('reader', reader);
    41  
    42      while (!streamClosed) {
    43        yield reader.read().then(({ value, done }) => {
    44          streamClosed = done;
    45  
    46          // There is no guarantee that value will be a complete JSON object,
    47          // so it needs to be buffered.
    48          buffer += decoder.decode(value, { stream: true });
    49  
    50          // Only when the buffer contains a close bracket can we be sure the buffer
    51          // is in a complete state
    52          if (buffer.indexOf('}') !== -1) {
    53            // The buffer can be one or more complete frames with additional text for the
    54            // next frame
    55            const [, chunk, newBuffer] = buffer.match(/(.*\})(.*)$/);
    56  
    57            // Peel chunk off the front of the buffer (since it represents complete frames)
    58            // and set the buffer to be the remainder
    59            buffer = newBuffer;
    60  
    61            // Assuming the logs endpoint never returns nested JSON (it shouldn't), at this
    62            // point chunk is a series of valid JSON objects with no delimiter.
    63            const lines = chunk.replace(/\}\{/g, '}\n{').split('\n');
    64            const frames = lines.map(line => JSON.parse(line)).filter(frame => frame.Data);
    65  
    66            if (frames.length) {
    67              frames.forEach(frame => (frame.Data = window.atob(frame.Data)));
    68              this.set('endOffset', frames[frames.length - 1].Offset);
    69              this.get('write')(frames.mapBy('Data').join(''));
    70            }
    71          }
    72        });
    73      }
    74    }),
    75  }).reopenClass({
    76    isSupported: !!window.ReadableStream,
    77  });