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, '&lt;').replace(/>/g, '&gt;');
    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  }