github.com/manicqin/nomad@v0.9.5/ui/app/components/task-file.js (about)

     1  import { inject as service } from '@ember/service';
     2  import Component from '@ember/component';
     3  import { computed } from '@ember/object';
     4  import { gt } from '@ember/object/computed';
     5  import { equal } from '@ember/object/computed';
     6  import RSVP from 'rsvp';
     7  import Log from 'nomad-ui/utils/classes/log';
     8  import timeout from 'nomad-ui/utils/timeout';
     9  
    10  export default Component.extend({
    11    token: service(),
    12  
    13    classNames: ['boxed-section', 'task-log'],
    14  
    15    'data-test-file-viewer': true,
    16  
    17    allocation: null,
    18    task: null,
    19    file: null,
    20    stat: null, // { Name, IsDir, Size, FileMode, ModTime, ContentType }
    21  
    22    // When true, request logs from the server agent
    23    useServer: false,
    24  
    25    // When true, logs cannot be fetched from either the client or the server
    26    noConnection: false,
    27  
    28    clientTimeout: 1000,
    29    serverTimeout: 5000,
    30  
    31    mode: 'head',
    32  
    33    fileComponent: computed('stat.ContentType', function() {
    34      const contentType = this.stat.ContentType || '';
    35  
    36      if (contentType.startsWith('image/')) {
    37        return 'image';
    38      } else if (contentType.startsWith('text/') || contentType.startsWith('application/json')) {
    39        return 'stream';
    40      } else {
    41        return 'unknown';
    42      }
    43    }),
    44  
    45    isLarge: gt('stat.Size', 50000),
    46  
    47    fileTypeIsUnknown: equal('fileComponent', 'unknown'),
    48    isStreamable: equal('fileComponent', 'stream'),
    49    isStreaming: false,
    50  
    51    catUrl: computed('allocation.id', 'task.name', 'file', function() {
    52      const encodedPath = encodeURIComponent(`${this.task.name}/${this.file}`);
    53      return `/v1/client/fs/cat/${this.allocation.id}?path=${encodedPath}`;
    54    }),
    55  
    56    fetchMode: computed('isLarge', 'mode', function() {
    57      if (this.mode === 'streaming') {
    58        return 'stream';
    59      }
    60  
    61      if (!this.isLarge) {
    62        return 'cat';
    63      } else if (this.mode === 'head' || this.mode === 'tail') {
    64        return 'readat';
    65      }
    66    }),
    67  
    68    fileUrl: computed(
    69      'allocation.id',
    70      'allocation.node.httpAddr',
    71      'fetchMode',
    72      'useServer',
    73      function() {
    74        const address = this.get('allocation.node.httpAddr');
    75        const url = `/v1/client/fs/${this.fetchMode}/${this.allocation.id}`;
    76        return this.useServer ? url : `//${address}${url}`;
    77      }
    78    ),
    79  
    80    fileParams: computed('task.name', 'file', 'mode', function() {
    81      // The Log class handles encoding query params
    82      const path = `${this.task.name}/${this.file}`;
    83  
    84      switch (this.mode) {
    85        case 'head':
    86          return { path, offset: 0, limit: 50000 };
    87        case 'tail':
    88          return { path, offset: this.stat.Size - 50000, limit: 50000 };
    89        case 'streaming':
    90          return { path, offset: 50000, origin: 'end' };
    91        default:
    92          return { path };
    93      }
    94    }),
    95  
    96    logger: computed('fileUrl', 'fileParams', 'mode', function() {
    97      // The cat and readat APIs are in plainText while the stream API is always encoded.
    98      const plainText = this.mode === 'head' || this.mode === 'tail';
    99  
   100      // If the file request can't settle in one second, the client
   101      // must be unavailable and the server should be used instead
   102      const timing = this.useServer ? this.serverTimeout : this.clientTimeout;
   103      const logFetch = url =>
   104        RSVP.race([this.token.authorizedRequest(url), timeout(timing)]).then(
   105          response => {
   106            if (!response || !response.ok) {
   107              this.nextErrorState(response);
   108            }
   109            return response;
   110          },
   111          error => this.nextErrorState(error)
   112        );
   113  
   114      return Log.create({
   115        logFetch,
   116        plainText,
   117        params: this.fileParams,
   118        url: this.fileUrl,
   119      });
   120    }),
   121  
   122    nextErrorState(error) {
   123      if (this.useServer) {
   124        this.set('noConnection', true);
   125      } else {
   126        this.send('failoverToServer');
   127      }
   128      throw error;
   129    },
   130  
   131    actions: {
   132      toggleStream() {
   133        this.set('mode', 'streaming');
   134        this.toggleProperty('isStreaming');
   135      },
   136      gotoHead() {
   137        this.set('mode', 'head');
   138        this.set('isStreaming', false);
   139      },
   140      gotoTail() {
   141        this.set('mode', 'tail');
   142        this.set('isStreaming', false);
   143      },
   144      failoverToServer() {
   145        this.set('useServer', true);
   146      },
   147    },
   148  });