UI - console refresh (#4679)

* add router service polyfill
* add refresh command
* move async code into ember-concurrency task and implement refresh that way
* use ember-concurrency derived state to show a loading spinner when the task is running
* scroll after appending to log too
This commit is contained in:
Matthew Irish 2018-06-01 17:18:31 -05:00 committed by GitHub
parent 36db9818ab
commit a2fb9ae331
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 120 additions and 32 deletions

View File

@ -11,9 +11,6 @@ export default Ember.Component.extend({
value: null,
isFullscreen: null,
didRender() {
this.element.scrollIntoView();
},
actions: {
handleKeyUp(event) {
const keyCode = event.keyCode;

View File

@ -1,4 +1,5 @@
import Ember from 'ember';
import { task } from 'ember-concurrency';
import {
parseCommand,
extractDataAndFlags,
@ -8,22 +9,31 @@ import {
executeUICommand,
} from 'vault/lib/console-helpers';
const { inject, computed } = Ember;
const { inject, computed, getOwner, run } = Ember;
export default Ember.Component.extend({
classNames: 'console-ui-panel-scroller',
classNameBindings: ['isFullscreen:fullscreen'],
isFullscreen: false,
console: inject.service(),
router: inject.service(),
inputValue: null,
log: computed.alias('console.log'),
logAndOutput(command, logContent) {
this.set('inputValue', '');
this.get('console').logAndOutput(command, logContent);
didRender() {
this._super(...arguments);
this.scrollToBottom();
},
executeCommand(command, shouldThrow = false) {
logAndOutput(command, logContent) {
this.get('console').logAndOutput(command, logContent);
run.schedule('afterRender', () => this.scrollToBottom());
},
isRunning: computed.or('executeCommand.isRunning', 'refreshRoute.isRunning'),
executeCommand: task(function*(command, shouldThrow = false) {
this.set('inputValue', '');
let service = this.get('console');
let serviceArgs;
@ -32,7 +42,8 @@ export default Ember.Component.extend({
command,
args => this.logAndOutput(args),
args => service.clearLog(args),
() => this.toggleProperty('isFullscreen')
() => this.toggleProperty('isFullscreen'),
() => this.get('refreshRoute').perform()
)
) {
return;
@ -61,16 +72,26 @@ export default Ember.Component.extend({
this.logAndOutput(command, inputError);
return;
}
let serviceFn = service[method];
serviceFn
.call(service, path, data, flags.wrapTTL)
.then(resp => {
this.logAndOutput(command, logFromResponse(resp, path, method, flags));
})
.catch(error => {
this.logAndOutput(command, logFromError(error, path, method));
});
},
try {
let resp = yield service[method].call(service, path, data, flags.wrapTTL);
this.logAndOutput(command, logFromResponse(resp, path, method, flags));
} catch(error) {
this.logAndOutput(command, logFromError(error, path, method));
}
}),
refreshRoute:task(function*() {
let owner = getOwner(this);
let routeName = this.get('router.currentRouteName');
let route = owner.lookup(`route:${routeName}`);
try {
yield route.refresh();
this.logAndOutput(null, { type: 'success', content: 'The current screen has been refreshed!' });
} catch (error) {
this.logAndOutput(null, { type: 'error', content: 'The was a problem refreshing the current screen.' });
}
}),
shiftCommandIndex(keyCode) {
this.get('console').shiftCommandIndex(keyCode, val => {
@ -78,12 +99,16 @@ export default Ember.Component.extend({
});
},
scrollToBottom() {
this.element.scrollTop = this.element.scrollHeight;
},
actions: {
toggleFullscreen() {
this.toggleProperty('isFullscreen');
},
executeCommand(val) {
this.executeCommand(val, true);
this.get('executeCommand').perform(val, true);
},
shiftCommandIndex(direction) {
this.shiftCommandIndex(direction);

View File

@ -2,7 +2,7 @@ import keys from 'vault/lib/keycodes';
import argTokenizer from 'yargs-parser-tokenizer';
const supportedCommands = ['read', 'write', 'list', 'delete'];
const uiCommands = ['clearall', 'clear', 'fullscreen'];
const uiCommands = ['clearall', 'clear', 'fullscreen', 'refresh'];
export function extractDataAndFlags(data, flags) {
return data.concat(flags).reduce((accumulator, val) => {
@ -29,7 +29,7 @@ export function extractDataAndFlags(data, flags) {
}, { data: {}, flags: {} });
}
export function executeUICommand(command, logAndOutput, clearLog, toggleFullscreen) {
export function executeUICommand(command, logAndOutput, clearLog, toggleFullscreen, refreshFn) {
const isUICommand = uiCommands.includes(command);
if (isUICommand) {
logAndOutput(command);
@ -44,6 +44,9 @@ export function executeUICommand(command, logAndOutput, clearLog, toggleFullscre
case 'fullscreen':
toggleFullscreen();
break;
case 'refresh':
refreshFn();
break;
}
return isUICommand;

View File

@ -27,10 +27,10 @@ export default IdentityModel.extend({
readOnly: true,
}),
numMemberEntities: attr('number', {
readOnly: true
readOnly: true,
}),
numParentGroups: attr('number', {
readOnly: true
readOnly: true,
}),
metadata: attr('object', {
editType: 'kv',

View File

@ -19,9 +19,8 @@ export default ApplicationSerializer.extend({
return model;
});
delete payload.data.key_info;
return list.sort((a,b) => {
return list.sort((a, b) => {
return a.name.localeCompare(b.name);
});
},
});

View File

@ -61,8 +61,10 @@ export default Service.extend({
logAndOutput(command, logContent) {
let log = this.get('log');
log.pushObject({ type: 'command', content: command });
this.set('commandIndex', null);
if (command) {
log.pushObject({ type: 'command', content: command });
this.set('commandIndex', null);
}
if (logContent) {
log.pushObject(logContent);
}

View File

@ -62,7 +62,6 @@
align-items: center;
display: flex;
input {
background-color: rgba($black, 0.5);
border: 0;
@ -152,3 +151,18 @@ header .navbar-sections {
transform: translate3d(0, 0, 0);
will-change: transform;
}
.console-spinner.control {
height: 21px;
width: 21px;
transform: scale(.75);
transform-origin: center;
&::after {
height: auto;
width: auto;
right: .25rem;
left: .25rem;
top: .25rem;
bottom: .25rem;
}
}

View File

@ -9,7 +9,7 @@
</div>
<div class="navbar-end is-divider-list is-flex">
<div class="navbar-item">
<button type="button" class="button is-transparent" {{action 'toggleConsole'}}>
<button type="button" class="button is-transparent" {{action 'toggleConsole'}} data-test-console-toggle>
{{#if consoleOpen}}
{{i-con glyph="console-active" size=24}}
{{i-con glyph="chevron-up" aria-hidden="true" size=8 class="has-text-white auto-width is-status-chevron"}}

View File

@ -1,4 +1,8 @@
{{i-con glyph="chevron-right" size=12}}
{{#if isRunning}}
<div class="control console-spinner is-loading"></div>
{{else}}
{{i-con glyph="chevron-right" size=12 }}
{{/if}}
<input onkeyup={{action 'handleKeyUp'}} value={{value}} autocomplete="off" spellcheck="false" />
{{#tool-tip horizontalPosition="auto-right" verticalPosition=(if isFullscreen "above" "below") as |d|}}
{{#d.trigger tagName="button" type="button" class=(concat "button is-compact" (if isFullscreen " active")) click=(action "fullscreen") data-test-tool-tip-trigger=true}}

View File

@ -12,5 +12,6 @@ Web CLI Commands:
fullscreen Toggle fullscreen display
clear Clear output from the log
clearall Clear output and command history
refresh Refresh the data on the current screen under the CLI window
</pre>
</div>

View File

@ -7,6 +7,7 @@
{{console/output-log log=log}}
{{console/command-input
isFullscreen=isFullscreen
isRunning=isRunning
value=inputValue
onValueUpdate=(action (mut inputValue))
onFullscreen=(action 'toggleFullscreen')

View File

@ -42,6 +42,7 @@
"bulma": "^0.5.2",
"bulma-switch": "^0.0.1",
"codemirror": "5.15.2",
"columnify": "^1.5.4",
"cool-checkboxes-for-bulma.io": "^1.1.0",
"ember-ajax": "^3.0.0",
"ember-api-actions": "^0.1.8",
@ -83,6 +84,7 @@
"ember-qunit-assert-helpers": "^0.1.3",
"ember-radio-button": "^1.1.1",
"ember-resolver": "^4.0.0",
"ember-router-service-polyfill": "^1.0.2",
"ember-sinon": "^1.0.1",
"ember-source": "~2.14.0",
"ember-test-selectors": "^0.3.6",
@ -96,7 +98,6 @@
"qunit-dom": "^0.6.2",
"string.prototype.startswith": "mathiasbynens/String.prototype.startsWith",
"text-encoder-lite": "1.0.0",
"columnify": "^1.5.4",
"yargs-parser": "^10.0.0"
},
"engines": {

View File

@ -0,0 +1,32 @@
import { test } from 'qunit';
import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance';
import enginesPage from 'vault/tests/pages/secrets/backends';
moduleForAcceptance('Acceptance | console', {
beforeEach() {
return authLogin();
},
});
test('refresh reloads the current route\'s data', function(assert) {
let numEngines;
enginesPage.visit();
andThen(() => {
numEngines = enginesPage.rows().count;
enginesPage.consoleToggle();
let now = Date.now();
[1, 2, 3].forEach(num => {
let inputString = `write sys/mounts/${now+num} type=kv`;
enginesPage.console.consoleInput(inputString);
enginesPage.console.enter();
});
});
andThen(() => {
enginesPage.console.consoleInput('refresh');
enginesPage.console.enter();
});
andThen(() => {
assert.equal(enginesPage.rows().count, numEngines + 3, 'new engines were added to the page');
});
});

View File

@ -1,6 +1,9 @@
import { create, visitable, collection, clickable, text } from 'ember-cli-page-object';
import uiPanel from 'vault/tests/pages/components/console/ui-panel';
export default create({
console: uiPanel,
consoleToggle: clickable('[data-test-console-toggle]'),
visit: visitable('/vault/secrets'),
rows: collection({
itemScope: '[data-test-secret-backend-row]',

View File

@ -3451,6 +3451,12 @@ ember-router-generator@^1.0.0:
dependencies:
recast "^0.11.3"
ember-router-service-polyfill@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ember-router-service-polyfill/-/ember-router-service-polyfill-1.0.2.tgz#6e5565f196fa7045cbe06a6fab861f9e766fe62a"
dependencies:
ember-cli-babel "^6.8.2"
ember-runtime-enumerable-includes-polyfill@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/ember-runtime-enumerable-includes-polyfill/-/ember-runtime-enumerable-includes-polyfill-1.0.4.tgz#16a7612e347a2edf07da8b2f2f09dbfee70deba0"