github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/app/components/fs/file.js (about)

     1  import { inject as service } from '@ember/service';
     2  import Component from '@ember/component';
     3  import { action, computed } from '@ember/object';
     4  import { equal, gt } from '@ember/object/computed';
     5  import RSVP from 'rsvp';
     6  import Log from 'nomad-ui/utils/classes/log';
     7  import timeout from 'nomad-ui/utils/timeout';
     8  import { classNames } from '@ember-decorators/component';
     9  import classic from 'ember-classic-decorator';
    10  
    11  @classic
    12  @classNames('boxed-section', 'task-log')
    13  export default class File extends Component {
    14    @service token;
    15    @service system;
    16  
    17    'data-test-file-viewer' = true;
    18  
    19    allocation = null;
    20    taskState = null;
    21    file = null;
    22    stat = null; // { Name, IsDir, Size, FileMode, ModTime, ContentType }
    23  
    24    // When true, request logs from the server agent
    25    useServer = false;
    26  
    27    // When true, logs cannot be fetched from either the client or the server
    28    noConnection = false;
    29  
    30    clientTimeout = 1000;
    31    serverTimeout = 5000;
    32  
    33    mode = 'head';
    34  
    35    @computed('stat.ContentType')
    36    get fileComponent() {
    37      const contentType = this.stat.ContentType || '';
    38  
    39      if (contentType.startsWith('image/')) {
    40        return 'image';
    41      } else if (contentType.startsWith('text/') || contentType.startsWith('application/json')) {
    42        return 'stream';
    43      } else {
    44        return 'unknown';
    45      }
    46    }
    47  
    48    @gt('stat.Size', 50000) isLarge;
    49  
    50    @equal('fileComponent', 'unknown') fileTypeIsUnknown;
    51    @equal('fileComponent', 'stream') isStreamable;
    52    isStreaming = false;
    53  
    54    @computed('allocation.id', 'taskState.name', 'file')
    55    get catUrlWithoutRegion() {
    56      const taskUrlPrefix = this.taskState ? `${this.taskState.name}/` : '';
    57      const encodedPath = encodeURIComponent(`${taskUrlPrefix}${this.file}`);
    58      return `/v1/client/fs/cat/${this.allocation.id}?path=${encodedPath}`;
    59    }
    60  
    61    @computed('catUrlWithoutRegion', 'system.{activeRegion,shouldIncludeRegion}')
    62    get catUrl() {
    63      let apiPath = this.catUrlWithoutRegion;
    64      if (this.system.shouldIncludeRegion) {
    65        apiPath += `&region=${this.system.activeRegion}`;
    66      }
    67      return apiPath;
    68    }
    69  
    70    @computed('isLarge', 'mode')
    71    get fetchMode() {
    72      if (this.mode === 'streaming') {
    73        return 'stream';
    74      }
    75  
    76      if (!this.isLarge) {
    77        return 'cat';
    78      } else if (this.mode === 'head' || this.mode === 'tail') {
    79        return 'readat';
    80      }
    81  
    82      return undefined;
    83    }
    84  
    85    @computed('allocation.{id,node.httpAddr}', 'fetchMode', 'useServer')
    86    get fileUrl() {
    87      const address = this.get('allocation.node.httpAddr');
    88      const url = `/v1/client/fs/${this.fetchMode}/${this.allocation.id}`;
    89      return this.useServer ? url : `//${address}${url}`;
    90    }
    91  
    92    @computed('file', 'mode', 'stat.Size', 'taskState.name')
    93    get fileParams() {
    94      // The Log class handles encoding query params
    95      const taskUrlPrefix = this.taskState ? `${this.taskState.name}/` : '';
    96      const path = `${taskUrlPrefix}${this.file}`;
    97  
    98      switch (this.mode) {
    99        case 'head':
   100          return { path, offset: 0, limit: 50000 };
   101        case 'tail':
   102          return { path, offset: this.stat.Size - 50000, limit: 50000 };
   103        case 'streaming':
   104          return { path, offset: 50000, origin: 'end' };
   105        default:
   106          return { path };
   107      }
   108    }
   109  
   110    @computed('clientTimeout', 'fileParams', 'fileUrl', 'mode', 'serverTimeout', 'useServer')
   111    get logger() {
   112      // The cat and readat APIs are in plainText while the stream API is always encoded.
   113      const plainText = this.mode === 'head' || this.mode === 'tail';
   114  
   115      // If the file request can't settle in one second, the client
   116      // must be unavailable and the server should be used instead
   117      const timing = this.useServer ? this.serverTimeout : this.clientTimeout;
   118      const logFetch = url =>
   119        RSVP.race([this.token.authorizedRequest(url), timeout(timing)]).then(
   120          response => {
   121            if (!response || !response.ok) {
   122              this.nextErrorState(response);
   123            }
   124            return response;
   125          },
   126          error => this.nextErrorState(error)
   127        );
   128  
   129      return Log.create({
   130        logFetch,
   131        plainText,
   132        params: this.fileParams,
   133        url: this.fileUrl,
   134      });
   135    }
   136  
   137    nextErrorState(error) {
   138      if (this.useServer) {
   139        this.set('noConnection', true);
   140      } else {
   141        this.send('failoverToServer');
   142      }
   143      throw error;
   144    }
   145  
   146    @action
   147    toggleStream() {
   148      this.set('mode', 'streaming');
   149      this.toggleProperty('isStreaming');
   150    }
   151  
   152    @action
   153    gotoHead() {
   154      this.set('mode', 'head');
   155      this.set('isStreaming', false);
   156    }
   157  
   158    @action
   159    gotoTail() {
   160      this.set('mode', 'tail');
   161      this.set('isStreaming', false);
   162    }
   163  
   164    @action
   165    failoverToServer() {
   166      this.set('useServer', true);
   167    }
   168  
   169    @action
   170    downloadFile() {
   171      const timing = this.useServer ? this.serverTimeout : this.clientTimeout;
   172      const fileDownload = url =>
   173        RSVP.race([this.token.authorizedRequest(url), timeout(timing)])
   174          .then(
   175            response => {
   176              if (!response || !response.ok) {
   177                this.nextErrorState(response);
   178              }
   179              return response;
   180            },
   181            error => this.nextErrorState(error)
   182          )
   183          .then(response => response.blob())
   184          .then(blob => {
   185            var url = window.URL.createObjectURL(blob);
   186            var a = document.createElement('a');
   187            a.href = url;
   188            a.target = '_blank';
   189            a.rel = 'noopener noreferrer';
   190            a.download = this.file;
   191            document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
   192            a.click();
   193            a.remove(); //afterwards we remove the element again
   194            window.URL.revokeObjectURL(url);
   195          });
   196      fileDownload(this.catUrlWithoutRegion);
   197    }
   198  }