-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3564 from hashicorp/f-ui-log-streaming
UI: Log streaming
- Loading branch information
Showing
34 changed files
with
961 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import Ember from 'ember'; | ||
import { task } from 'ember-concurrency'; | ||
import { logger } from 'nomad-ui/utils/classes/log'; | ||
import WindowResizable from 'nomad-ui/mixins/window-resizable'; | ||
|
||
const { Component, computed, inject, run } = Ember; | ||
|
||
export default Component.extend(WindowResizable, { | ||
token: inject.service(), | ||
|
||
classNames: ['boxed-section', 'task-log'], | ||
|
||
allocation: null, | ||
task: null, | ||
|
||
didReceiveAttrs() { | ||
if (this.get('allocation') && this.get('task')) { | ||
this.send('toggleStream'); | ||
} | ||
}, | ||
|
||
didInsertElement() { | ||
this.fillAvailableHeight(); | ||
}, | ||
|
||
windowResizeHandler() { | ||
run.once(this, this.fillAvailableHeight); | ||
}, | ||
|
||
fillAvailableHeight() { | ||
// This math is arbitrary and far from bulletproof, but the UX | ||
// of having the log window fill available height is worth the hack. | ||
const cliWindow = this.$('.cli-window'); | ||
cliWindow.height(window.innerHeight - cliWindow.offset().top - 25); | ||
}, | ||
|
||
mode: 'stdout', | ||
|
||
logUrl: computed('allocation.id', 'allocation.node.httpAddr', function() { | ||
const address = this.get('allocation.node.httpAddr'); | ||
const allocation = this.get('allocation.id'); | ||
|
||
return `//${address}/v1/client/fs/logs/${allocation}`; | ||
}), | ||
|
||
logParams: computed('task', 'mode', function() { | ||
return { | ||
task: this.get('task'), | ||
type: this.get('mode'), | ||
}; | ||
}), | ||
|
||
logger: logger('logUrl', 'logParams', function() { | ||
const token = this.get('token'); | ||
return token.authorizedRequest.bind(token); | ||
}), | ||
|
||
head: task(function*() { | ||
yield this.get('logger.gotoHead').perform(); | ||
run.scheduleOnce('afterRender', () => { | ||
this.$('.cli-window').scrollTop(0); | ||
}); | ||
}), | ||
|
||
tail: task(function*() { | ||
yield this.get('logger.gotoTail').perform(); | ||
run.scheduleOnce('afterRender', () => { | ||
const cliWindow = this.$('.cli-window'); | ||
cliWindow.scrollTop(cliWindow[0].scrollHeight); | ||
}); | ||
}), | ||
|
||
stream: task(function*() { | ||
this.get('logger').on('tick', () => { | ||
run.scheduleOnce('afterRender', () => { | ||
const cliWindow = this.$('.cli-window'); | ||
cliWindow.scrollTop(cliWindow[0].scrollHeight); | ||
}); | ||
}); | ||
|
||
yield this.get('logger').startStreaming(); | ||
this.get('logger').off('tick'); | ||
}), | ||
|
||
willDestroy() { | ||
this.get('logger').stop(); | ||
}, | ||
|
||
actions: { | ||
setMode(mode) { | ||
this.get('logger').stop(); | ||
this.set('mode', mode); | ||
this.get('stream').perform(); | ||
}, | ||
toggleStream() { | ||
if (this.get('logger.isStreaming')) { | ||
this.get('logger').stop(); | ||
} else { | ||
this.get('stream').perform(); | ||
} | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Ember from 'ember'; | ||
|
||
const { Route } = Ember; | ||
|
||
export default Route.extend({ | ||
model() { | ||
const task = this._super(...arguments); | ||
return task.get('allocation.node').then(() => task); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.cli-window { | ||
background: transparent; | ||
color: $white; | ||
|
||
height: 500px; | ||
overflow: auto; | ||
|
||
.is-light { | ||
color: $text; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{{#global-header class="page-header"}} | ||
<span class="breadcrumb">Allocations</span> | ||
{{#link-to "allocations.allocation" model.allocation class="breadcrumb"}} | ||
{{model.allocation.shortId}} | ||
{{/link-to}} | ||
{{#link-to "allocations.allocation.task" model.allocation model class="breadcrumb"}} | ||
{{model.name}} | ||
{{/link-to}} | ||
{{/global-header}} | ||
{{#gutter-menu class="page-body"}} | ||
{{partial "allocations/allocation/task/subnav"}} | ||
<section class="section"> | ||
{{task-log allocation=model.allocation task=model.name}} | ||
</section> | ||
{{/gutter-menu}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
<div class="tabs is-subnav"> | ||
<ul> | ||
<li>{{#link-to "allocations.allocation.task.index" model.allocation model activeClass="is-active"}}Overview{{/link-to}}</li> | ||
<li>{{#link-to "allocations.allocation.task.logs" model.allocation model activeClass="is-active"}}Logs{{/link-to}}</li> | ||
</ul> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<div class="boxed-section-head"> | ||
<span> | ||
<button class="button {{if (eq mode "stdout") "is-info"}} action-stdout" {{action "setMode" "stdout"}}>stdout</button> | ||
<button class="button {{if (eq mode "stderr") "is-danger"}} action-stderr" {{action "setMode" "stderr"}}>stderr</button> | ||
</span> | ||
<span class="pull-right"> | ||
<button class="button is-white action-head" onclick={{perform head}}>Head</button> | ||
<button class="button is-white action-tail" onclick={{perform tail}}>Tail</button> | ||
<button class="button is-white action-toggle-stream" onclick={{action "toggleStream"}}> | ||
{{x-icon (if logger.isStreaming "media-pause" "media-play") class="is-text"}} | ||
</button> | ||
</span> | ||
</div> | ||
<div class="boxed-section-body is-dark is-full-bleed"> | ||
<pre class="cli-window"><code>{{logger.output}}</code></pre> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import Ember from 'ember'; | ||
import queryString from 'npm:query-string'; | ||
|
||
const { Mixin, computed, assign } = Ember; | ||
const MAX_OUTPUT_LENGTH = 50000; | ||
|
||
export default Mixin.create({ | ||
url: '', | ||
params: computed(() => ({})), | ||
logFetch() { | ||
Ember.assert( | ||
'Loggers need a logFetch method, which should have an interface like window.fetch' | ||
); | ||
}, | ||
|
||
endOffset: null, | ||
|
||
offsetParams: computed('endOffset', function() { | ||
const endOffset = this.get('endOffset'); | ||
return endOffset | ||
? { origin: 'start', offset: endOffset } | ||
: { origin: 'end', offset: MAX_OUTPUT_LENGTH }; | ||
}), | ||
|
||
additionalParams: computed(() => ({})), | ||
|
||
fullUrl: computed('url', 'params', 'offsetParams', 'additionalParams', function() { | ||
const queryParams = queryString.stringify( | ||
assign({}, this.get('params'), this.get('offsetParams'), this.get('additionalParams')) | ||
); | ||
return `${this.get('url')}?${queryParams}`; | ||
}), | ||
}); |
Oops, something went wrong.