mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-09 00:33:28 -04:00
Merge branch 'master' into jo-upgrade-copy
This commit is contained in:
commit
0a598dd37b
101 changed files with 2725 additions and 471 deletions
|
|
@ -16,6 +16,10 @@ FEATURES:
|
|||
* Userpass auth CIDR restrictions: When using the `userpass` auth method you
|
||||
can now limit authentication to specific CIDRs; these will also be encoded
|
||||
in resultant tokens to limit their use.
|
||||
* Vault Browser CLI: The UI now supports usage of read/write/list/delete
|
||||
commands in a CLI that can be accessed from the nav bar. Complex inputs such
|
||||
as JSON files are not currently supported. This surfaces features otherwise
|
||||
unsupported in Vault's UI.
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
|
|
|
|||
8
ui/app/adapters/console.js
Normal file
8
ui/app/adapters/console.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import ApplicationAdapter from './application';
|
||||
|
||||
export default ApplicationAdapter.extend({
|
||||
namespace: 'v1',
|
||||
pathForType(modelName) {
|
||||
return modelName;
|
||||
},
|
||||
});
|
||||
36
ui/app/components/console/command-input.js
Normal file
36
ui/app/components/console/command-input.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import Ember from 'ember';
|
||||
import keys from 'vault/lib/keycodes';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
'data-test-component': 'console/command-input',
|
||||
classNames: 'console-ui-input',
|
||||
onExecuteCommand() {},
|
||||
onFullscreen() {},
|
||||
onValueUpdate() {},
|
||||
onShiftCommand() {},
|
||||
value: null,
|
||||
isFullscreen: null,
|
||||
|
||||
didRender() {
|
||||
this.element.scrollIntoView();
|
||||
},
|
||||
actions: {
|
||||
handleKeyUp(event) {
|
||||
const keyCode = event.keyCode;
|
||||
switch (keyCode) {
|
||||
case keys.ENTER:
|
||||
this.get('onExecuteCommand')(event.target.value);
|
||||
break;
|
||||
case keys.UP:
|
||||
case keys.DOWN:
|
||||
this.get('onShiftCommand')(keyCode);
|
||||
break;
|
||||
default:
|
||||
this.get('onValueUpdate')(event.target.value);
|
||||
}
|
||||
},
|
||||
fullscreen() {
|
||||
this.get('onFullscreen')();
|
||||
}
|
||||
},
|
||||
});
|
||||
3
ui/app/components/console/log-command.js
Normal file
3
ui/app/components/console/log-command.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({});
|
||||
3
ui/app/components/console/log-error.js
Normal file
3
ui/app/components/console/log-error.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({});
|
||||
3
ui/app/components/console/log-help.js
Normal file
3
ui/app/components/console/log-help.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({});
|
||||
3
ui/app/components/console/log-json.js
Normal file
3
ui/app/components/console/log-json.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({});
|
||||
9
ui/app/components/console/log-list.js
Normal file
9
ui/app/components/console/log-list.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import Ember from 'ember';
|
||||
const { computed } = Ember;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
content: null,
|
||||
list: computed('content', function() {
|
||||
return this.get('content').keys;
|
||||
}),
|
||||
});
|
||||
28
ui/app/components/console/log-object.js
Normal file
28
ui/app/components/console/log-object.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import Ember from 'ember';
|
||||
import columnify from 'columnify';
|
||||
const { computed } = Ember;
|
||||
|
||||
export function stringifyObjectValues(data) {
|
||||
Object.keys(data).forEach(item => {
|
||||
let val = data[item];
|
||||
if (typeof val !== 'string') {
|
||||
val = JSON.stringify(val);
|
||||
}
|
||||
data[item] = val;
|
||||
});
|
||||
}
|
||||
|
||||
export default Ember.Component.extend({
|
||||
content: null,
|
||||
columns: computed('content', function() {
|
||||
let data = this.get('content');
|
||||
stringifyObjectValues(data);
|
||||
|
||||
return columnify(data, {
|
||||
preserveNewLines: true,
|
||||
headingTransform: function(heading) {
|
||||
return Ember.String.capitalize(heading);
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
3
ui/app/components/console/log-success.js
Normal file
3
ui/app/components/console/log-success.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({});
|
||||
3
ui/app/components/console/log-text.js
Normal file
3
ui/app/components/console/log-text.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({});
|
||||
6
ui/app/components/console/output-log.js
Normal file
6
ui/app/components/console/output-log.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
'data-test-component': 'console/output-log',
|
||||
log: null,
|
||||
});
|
||||
84
ui/app/components/console/ui-panel.js
Normal file
84
ui/app/components/console/ui-panel.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import Ember from 'ember';
|
||||
import {
|
||||
parseCommand,
|
||||
extractDataAndFlags,
|
||||
logFromResponse,
|
||||
logFromError,
|
||||
logErrorFromInput,
|
||||
executeUICommand,
|
||||
} from 'vault/lib/console-helpers';
|
||||
|
||||
const { inject, computed } = Ember;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: 'console-ui-panel-scroller',
|
||||
classNameBindings: ['isFullscreen:fullscreen'],
|
||||
isFullscreen: false,
|
||||
console: inject.service(),
|
||||
inputValue: null,
|
||||
log: computed.alias('console.log'),
|
||||
|
||||
logAndOutput(command, logContent) {
|
||||
this.set('inputValue', '');
|
||||
this.get('console').logAndOutput(command, logContent);
|
||||
},
|
||||
|
||||
executeCommand(command, shouldThrow = false) {
|
||||
let service = this.get('console');
|
||||
let serviceArgs;
|
||||
|
||||
if(executeUICommand(command, (args) => this.logAndOutput(args), (args) => service.clearLog(args), () => this.toggleProperty('isFullscreen'))){
|
||||
return;
|
||||
}
|
||||
|
||||
// parse to verify it's valid
|
||||
try {
|
||||
serviceArgs = parseCommand(command, shouldThrow);
|
||||
} catch (e) {
|
||||
this.logAndOutput(command, { type: 'help' });
|
||||
return;
|
||||
}
|
||||
// we have a invalid command but don't want to throw
|
||||
if (serviceArgs === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
let [method, flagArray, path, dataArray] = serviceArgs;
|
||||
|
||||
if (dataArray || flagArray) {
|
||||
var { data, flags } = extractDataAndFlags(dataArray, flagArray);
|
||||
}
|
||||
|
||||
let inputError = logErrorFromInput(path, method, flags, dataArray);
|
||||
if (inputError) {
|
||||
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));
|
||||
});
|
||||
},
|
||||
|
||||
shiftCommandIndex(keyCode) {
|
||||
this.get('console').shiftCommandIndex(keyCode, (val) => {
|
||||
this.set('inputValue', val);
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
toggleFullscreen() {
|
||||
this.toggleProperty('isFullscreen');
|
||||
},
|
||||
executeCommand(val) {
|
||||
this.executeCommand(val, true);
|
||||
},
|
||||
shiftCommandIndex(direction) {
|
||||
this.shiftCommandIndex(direction);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -18,6 +18,10 @@ export default IvyCodemirrorComponent.extend({
|
|||
'data-test-component': 'json-editor',
|
||||
updateCodeMirrorOptions() {
|
||||
const options = assign({}, JSON_EDITOR_DEFAULTS, this.get('options'));
|
||||
if (options.autoHeight) {
|
||||
options.viewportMargin = Infinity;
|
||||
delete options.autoHeight;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
Object.keys(options).forEach(function(option) {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
import Ember from 'ember';
|
||||
import config from '../config/environment';
|
||||
|
||||
const { computed, inject } = Ember;
|
||||
export default Ember.Controller.extend({
|
||||
env: config.environment,
|
||||
auth: Ember.inject.service(),
|
||||
vaultVersion: Ember.inject.service('version'),
|
||||
activeCluster: Ember.computed('auth.activeCluster', function() {
|
||||
auth: inject.service(),
|
||||
vaultVersion: inject.service('version'),
|
||||
console: inject.service(),
|
||||
consoleOpen: computed.alias('console.isOpen'),
|
||||
activeCluster: computed('auth.activeCluster', function() {
|
||||
return this.store.peekRecord('cluster', this.get('auth.activeCluster'));
|
||||
}),
|
||||
activeClusterName: Ember.computed('auth.activeCluster', function() {
|
||||
activeClusterName: computed('auth.activeCluster', function() {
|
||||
const activeCluster = this.store.peekRecord('cluster', this.get('auth.activeCluster'));
|
||||
return activeCluster ? activeCluster.get('name') : null;
|
||||
}),
|
||||
showNav: Ember.computed(
|
||||
showNav: computed(
|
||||
'activeClusterName',
|
||||
'auth.currentToken',
|
||||
'activeCluster.dr.isSecondary',
|
||||
|
|
@ -30,4 +33,9 @@ export default Ember.Controller.extend({
|
|||
}
|
||||
}
|
||||
),
|
||||
actions: {
|
||||
toggleConsole() {
|
||||
this.toggleProperty('consoleOpen');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
7
ui/app/helpers/multi-line-join.js
Normal file
7
ui/app/helpers/multi-line-join.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export function multiLineJoin([arr]) {
|
||||
return arr.join('\n');
|
||||
}
|
||||
|
||||
export default Ember.Helper.helper(multiLineJoin);
|
||||
183
ui/app/lib/console-helpers.js
Normal file
183
ui/app/lib/console-helpers.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import keys from 'vault/lib/keycodes';
|
||||
import argTokenizer from 'yargs-parser-tokenizer';
|
||||
|
||||
const supportedCommands = ['read', 'write', 'list', 'delete'];
|
||||
const uiCommands = ['clearall', 'clear', 'fullscreen'];
|
||||
|
||||
export function extractDataAndFlags(data, flags) {
|
||||
return data.concat(flags).reduce((accumulator, val) => {
|
||||
// will be "key=value" or "-flag=value" or "foo=bar=baz"
|
||||
// split on the first =
|
||||
let [item, value] = val.split(/=(.+)/);
|
||||
if (item.startsWith('-')) {
|
||||
let flagName = item.replace(/^-/, '');
|
||||
if (flagName === 'wrap-ttl') {
|
||||
flagName = 'wrapTTL';
|
||||
}
|
||||
accumulator.flags[flagName] = value || true;
|
||||
return accumulator;
|
||||
}
|
||||
// if it exists in data already, then we have multiple
|
||||
// foo=bar in the list and need to make it an array
|
||||
if (accumulator.data[item]) {
|
||||
accumulator.data[item] = [].concat(accumulator.data[item], value);
|
||||
return accumulator;
|
||||
}
|
||||
accumulator.data[item] = value;
|
||||
|
||||
return accumulator;
|
||||
}, { data: {}, flags: {} });
|
||||
}
|
||||
|
||||
export function executeUICommand(command, logAndOutput, clearLog, toggleFullscreen){
|
||||
const isUICommand = uiCommands.includes(command);
|
||||
if(isUICommand){
|
||||
logAndOutput(command);
|
||||
}
|
||||
switch(command){
|
||||
case 'clearall':
|
||||
clearLog(true);
|
||||
break;
|
||||
case 'clear':
|
||||
clearLog();
|
||||
break;
|
||||
case 'fullscreen':
|
||||
toggleFullscreen();
|
||||
break;
|
||||
}
|
||||
|
||||
return isUICommand;
|
||||
}
|
||||
|
||||
export function parseCommand(command, shouldThrow) {
|
||||
let args = argTokenizer(command);
|
||||
if (args[0] === 'vault') {
|
||||
args.shift();
|
||||
}
|
||||
|
||||
let [method, ...rest] = args;
|
||||
let path;
|
||||
let flags = [];
|
||||
let data = [];
|
||||
|
||||
rest.forEach(arg => {
|
||||
if (arg.startsWith('-')) {
|
||||
flags.push(arg);
|
||||
} else {
|
||||
if (path) {
|
||||
data.push(arg);
|
||||
} else {
|
||||
path = arg;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!supportedCommands.includes(method)) {
|
||||
if (shouldThrow) {
|
||||
throw new Error('invalid command');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return [method, flags, path, data];
|
||||
}
|
||||
|
||||
export function logFromResponse(response, path, method, flags) {
|
||||
if (!response) {
|
||||
let message =
|
||||
method === 'write'
|
||||
? `Success! Data written to: ${path}`
|
||||
: `Success! Data deleted (if it existed) at: ${path}`;
|
||||
|
||||
return { type: 'success', content: message };
|
||||
}
|
||||
let { format, field } = flags;
|
||||
let secret = response.auth || response.data || response.wrap_info;
|
||||
|
||||
if (field) {
|
||||
let fieldValue = secret[field];
|
||||
let response;
|
||||
if (fieldValue) {
|
||||
if (format && format === 'json') {
|
||||
return { type: 'json', content: fieldValue };
|
||||
}
|
||||
switch (typeof fieldValue) {
|
||||
case 'string':
|
||||
response = { type: 'text', content: fieldValue };
|
||||
break;
|
||||
default:
|
||||
response = { type: 'object', content: fieldValue };
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
response = { type: 'error', content: `Field "${field}" not present in secret` };
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
if (format && format === 'json') {
|
||||
// just print whole response
|
||||
return { type: 'json', content: response };
|
||||
}
|
||||
|
||||
if (method === 'list') {
|
||||
return { type: 'list', content: secret };
|
||||
}
|
||||
|
||||
return { type: 'object', content: secret };
|
||||
}
|
||||
|
||||
export function logFromError(error, vaultPath, method) {
|
||||
let content;
|
||||
let { httpStatus, path } = error;
|
||||
let verbClause = {
|
||||
read: 'reading from',
|
||||
write: 'writing to',
|
||||
list: 'listing',
|
||||
delete: 'deleting at',
|
||||
}[method];
|
||||
|
||||
content = `Error ${verbClause}: ${vaultPath}.\nURL: ${path}\nCode: ${httpStatus}`;
|
||||
|
||||
if (typeof error.errors[0] === 'string') {
|
||||
content = `${content}\nErrors:\n ${error.errors.join('\n ')}`;
|
||||
}
|
||||
|
||||
return { type: 'error', content };
|
||||
}
|
||||
|
||||
export function shiftCommandIndex(keyCode, history, index) {
|
||||
let newInputValue;
|
||||
let commandHistoryLength = history.length;
|
||||
|
||||
if (!commandHistoryLength) { return []; }
|
||||
|
||||
if (keyCode === keys.UP) {
|
||||
index -= 1;
|
||||
if (index < 0) {
|
||||
index = commandHistoryLength - 1;
|
||||
}
|
||||
} else {
|
||||
index += 1;
|
||||
if (index === commandHistoryLength) {
|
||||
newInputValue = '';
|
||||
}
|
||||
if (index > commandHistoryLength) {
|
||||
index -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (newInputValue !== '') {
|
||||
newInputValue = history.objectAt(index).content;
|
||||
}
|
||||
|
||||
return [index, newInputValue];
|
||||
}
|
||||
|
||||
export function logErrorFromInput(path, method, flags, dataArray) {
|
||||
if (path === undefined) {
|
||||
return { type: 'error', content: 'A path is required to make a request.' };
|
||||
}
|
||||
if (method === 'write' && !flags.force && dataArray.length === 0) {
|
||||
return { type: 'error', content: 'Must supply data or use -force' };
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ export default DS.Model.extend({
|
|||
}),
|
||||
|
||||
stateGlyph(state) {
|
||||
const glyph = 'checkmark';
|
||||
const glyph = 'checkmark-circled-outline';
|
||||
|
||||
const glyphs = {
|
||||
'stream-wals': 'android-sync',
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import Ember from 'ember';
|
||||
import ModelBoundaryRoute from 'vault/mixins/model-boundary-route';
|
||||
|
||||
const { inject } = Ember;
|
||||
export default Ember.Route.extend(ModelBoundaryRoute, {
|
||||
auth: Ember.inject.service(),
|
||||
flashMessages: Ember.inject.service(),
|
||||
auth: inject.service(),
|
||||
flashMessages: inject.service(),
|
||||
console: inject.service(),
|
||||
|
||||
modelTypes: ['secret', 'secret-engine'],
|
||||
|
||||
beforeModel() {
|
||||
this.get('auth').deleteCurrentToken();
|
||||
this.get('console').set('isOpen', false);
|
||||
this.get('console').clearLog(true);
|
||||
this.clearModelCache();
|
||||
this.replaceWith('vault.cluster');
|
||||
this.get('flashMessages').clearMessages();
|
||||
|
|
|
|||
|
|
@ -49,25 +49,25 @@ export default Ember.Route.extend({
|
|||
return Ember.RSVP.hash({
|
||||
secret,
|
||||
secrets: this.store
|
||||
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
|
||||
id: secret,
|
||||
backend,
|
||||
responsePath: 'data.keys',
|
||||
page: params.page,
|
||||
pageFilter: params.pageFilter,
|
||||
size: 100,
|
||||
})
|
||||
.then(model => {
|
||||
this.set('has404', false);
|
||||
return model;
|
||||
})
|
||||
.catch(err => {
|
||||
if (backendModel && err.httpStatus === 404 && secret === '') {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
|
||||
id: secret,
|
||||
backend,
|
||||
responsePath: 'data.keys',
|
||||
page: params.page,
|
||||
pageFilter: params.pageFilter,
|
||||
size: 100,
|
||||
})
|
||||
.then(model => {
|
||||
this.set('has404', false);
|
||||
return model;
|
||||
})
|
||||
.catch(err => {
|
||||
if (backendModel && err.httpStatus === 404 && secret === '') {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
105
ui/app/services/console.js
Normal file
105
ui/app/services/console.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// Low level service that allows users to input paths to make requests to vault
|
||||
// this service provides the UI synecdote to the cli commands read, write, delete, and list
|
||||
import Ember from 'ember';
|
||||
import {
|
||||
shiftCommandIndex,
|
||||
} from 'vault/lib/console-helpers';
|
||||
|
||||
const { Service, getOwner, computed } = Ember;
|
||||
|
||||
export function sanitizePath(path) {
|
||||
//remove whitespace + remove trailing and leading slashes
|
||||
return path.trim().replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
export function ensureTrailingSlash(path) {
|
||||
return path.replace(/(\w+[^/]$)/g, '$1/');
|
||||
}
|
||||
|
||||
const VERBS = {
|
||||
read: 'GET',
|
||||
list: 'GET',
|
||||
write: 'POST',
|
||||
delete: 'DELETE',
|
||||
};
|
||||
|
||||
export default Service.extend({
|
||||
isOpen: false,
|
||||
|
||||
adapter() {
|
||||
return getOwner(this).lookup('adapter:console');
|
||||
},
|
||||
commandHistory: computed('log.[]', function() {
|
||||
return this.get('log').filterBy('type', 'command');
|
||||
}),
|
||||
log: computed(function() {
|
||||
return [];
|
||||
}),
|
||||
commandIndex: null,
|
||||
|
||||
shiftCommandIndex(keyCode, setCommandFn = () => {}) {
|
||||
let [newIndex, newCommand] = shiftCommandIndex(
|
||||
keyCode,
|
||||
this.get('commandHistory'),
|
||||
this.get('commandIndex')
|
||||
);
|
||||
if (newCommand !== undefined && newIndex !== undefined) {
|
||||
this.set('commandIndex', newIndex);
|
||||
setCommandFn(newCommand);
|
||||
}
|
||||
},
|
||||
|
||||
clearLog(clearAll=false) {
|
||||
let log = this.get('log');
|
||||
let history;
|
||||
if (!clearAll) {
|
||||
history = this.get('commandHistory').slice();
|
||||
history.setEach('hidden', true);
|
||||
}
|
||||
log.clear();
|
||||
if (history) {
|
||||
log.addObjects(history);
|
||||
}
|
||||
},
|
||||
|
||||
logAndOutput(command, logContent) {
|
||||
let log = this.get('log');
|
||||
log.pushObject({ type: 'command', content: command });
|
||||
this.set('commandIndex', null);
|
||||
if (logContent) {
|
||||
log.pushObject(logContent);
|
||||
}
|
||||
},
|
||||
|
||||
ajax(operation, path, options = {}) {
|
||||
let verb = VERBS[operation];
|
||||
let adapter = this.adapter();
|
||||
let url = adapter.buildURL(path);
|
||||
let { data, wrapTTL } = options;
|
||||
return adapter.ajax(url, verb, {
|
||||
data,
|
||||
wrapTTL,
|
||||
});
|
||||
},
|
||||
|
||||
read(path, data, wrapTTL) {
|
||||
return this.ajax('read', sanitizePath(path), { wrapTTL });
|
||||
},
|
||||
|
||||
write(path, data, wrapTTL) {
|
||||
return this.ajax('write', sanitizePath(path), { data, wrapTTL });
|
||||
},
|
||||
|
||||
delete(path) {
|
||||
return this.ajax('delete', sanitizePath(path));
|
||||
},
|
||||
|
||||
list(path, data, wrapTTL) {
|
||||
let listPath = ensureTrailingSlash(sanitizePath(path));
|
||||
return this.ajax('list', listPath, {
|
||||
data: {
|
||||
list: true,
|
||||
},
|
||||
wrapTTL,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -171,3 +171,7 @@ $gutter-grey: #2a2f36;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-auto-height.CodeMirror {
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
|||
151
ui/app/styles/components/console-ui-panel.scss
Normal file
151
ui/app/styles/components/console-ui-panel.scss
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
.console-ui-panel-scroller {
|
||||
background: linear-gradient(to right, #191A1C, #1B212D);
|
||||
height: 0;
|
||||
left: 0;
|
||||
min-height: 400px;
|
||||
overflow: auto;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
transform: translate3d(0, -400px, 0);
|
||||
transition: min-height $speed ease-out, transform $speed ease-in;
|
||||
will-change: transform, min-height;
|
||||
z-index: 199;
|
||||
}
|
||||
|
||||
.console-ui-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: $size-8 $size-8 $size-4;
|
||||
min-height: 100%;
|
||||
color: $white;
|
||||
font-size: $body-size;
|
||||
font-weight: $font-weight-semibold;
|
||||
transition: justify-content $speed ease-in;
|
||||
|
||||
|
||||
pre, p {
|
||||
background: none;
|
||||
color: inherit;
|
||||
font-size: $body-size;
|
||||
|
||||
&:not(.console-ui-command):not(.CodeMirror-line) {
|
||||
padding-left: $console-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-hashi.CodeMirror {
|
||||
background-color: rgba($black, 0.5) !important;
|
||||
font-weight: $font-weight-normal;
|
||||
margin-left: $console-spacing;
|
||||
padding: $size-8 $size-4;
|
||||
}
|
||||
|
||||
.button,
|
||||
{
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: $grey-dark;
|
||||
min-width: 0;
|
||||
padding: 0 $size-8;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background: $blue;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.console-ui-input {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
|
||||
input {
|
||||
background-color: rgba($black, 0.5);
|
||||
border: 0;
|
||||
caret-color: $white;
|
||||
color: $white;
|
||||
flex: 1;
|
||||
font-family: $family-monospace;
|
||||
font-size: $body-size;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-left: -$size-10;
|
||||
outline: none;
|
||||
padding: $size-10;
|
||||
transition: background-color $speed;
|
||||
}
|
||||
}
|
||||
|
||||
.console-ui-command {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.console-ui-output {
|
||||
transition: background-color $speed;
|
||||
padding-right: $size-2;
|
||||
position: relative;
|
||||
|
||||
.console-ui-output-actions {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: opacity $speed;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba($black, 0.25);
|
||||
|
||||
.console-ui-output-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.console-ui-alert {
|
||||
margin-left: calc(#{$console-spacing} - 0.33rem);
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-open .console-ui-panel-scroller {
|
||||
box-shadow: $box-shadow-highest;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.panel-open .console-ui-panel-scroller.fullscreen {
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.panel-open {
|
||||
.navbar, .navbar-sections{
|
||||
transition: transform $speed ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-open.panel-fullscreen {
|
||||
.navbar, .navbar-sections{
|
||||
transform: translate3d(0, -100px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.page-container > header {
|
||||
background: linear-gradient(to right, #191A1C, #1B212D);
|
||||
}
|
||||
|
||||
header .navbar,
|
||||
header .navbar-sections {
|
||||
z-index: 200;
|
||||
transform: translate3d(0, 0, 0);
|
||||
will-change: transform;
|
||||
}
|
||||
10
ui/app/styles/components/env-banner.scss
Normal file
10
ui/app/styles/components/env-banner.scss
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
.env-banner {
|
||||
&,
|
||||
&:not(:last-child):not(:last-child) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.level-item {
|
||||
padding: $size-10 $size-8;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
z-index: 1;
|
||||
|
||||
.notification {
|
||||
box-shadow: 0 0 25px rgba($black, 0.2);
|
||||
box-shadow: $box-shadow-high;
|
||||
margin: 20px;
|
||||
@include until($desktop) {
|
||||
margin: 1rem 0;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
.linked-block {
|
||||
cursor: pointer;
|
||||
transition: box-shadow $speed;
|
||||
will-change: box-shadow;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
position: relative;
|
||||
box-shadow: $box-link-hover-shadow;
|
||||
}
|
||||
&:focus,
|
||||
&:active {
|
||||
position: relative;
|
||||
box-shadow: $box-link-active-shadow;
|
||||
box-shadow: $box-link-hover-shadow, $box-shadow-middle;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
3
ui/app/styles/components/login-form.scss
Normal file
3
ui/app/styles/components/login-form.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.login-form {
|
||||
box-shadow: $box-shadow, $box-shadow-high;
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
.popup-menu-content {
|
||||
border-radius: 2px;
|
||||
margin: -2px 0 0 0;
|
||||
|
||||
& > .box {
|
||||
border-radius: 2px;
|
||||
box-shadow: $box-shadow, $box-shadow-middle;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: 175px;
|
||||
@include css-top-arrow(8px, $white, 1px, $blue, 155px);
|
||||
}
|
||||
|
||||
&.is-wide > .box {
|
||||
width: 200px;
|
||||
@include css-top-arrow(8px, $white, 1px, $blue, 178px);
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.confirm-action span .button {
|
||||
|
|
@ -18,58 +19,98 @@
|
|||
margin: .25rem auto;
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
.popup-menu-trigger {
|
||||
min-width: auto;
|
||||
}
|
||||
.popup-menu-trigger.is-active {
|
||||
&,
|
||||
&:active,
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
|
||||
.menu {
|
||||
padding: $size-11 0;
|
||||
|
||||
button.link,
|
||||
a {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
color: $menu-item-color;
|
||||
display: block;
|
||||
height: auto;
|
||||
font-size: $size-7;
|
||||
font-weight: $font-weight-semibold;
|
||||
padding: $size-9 $size-8;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: $menu-item-hover-background-color;
|
||||
color: $menu-item-hover-color;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: $menu-item-active-background-color;
|
||||
color: $menu-item-active-color;
|
||||
}
|
||||
|
||||
&.is-destroy {
|
||||
color: $red;
|
||||
|
||||
&:hover {
|
||||
background-color: $red;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ember-basic-dropdown-content--left.popup-menu {
|
||||
margin: 0px 0 0 -8px;
|
||||
}
|
||||
|
||||
.popup-menu-content .menu {
|
||||
button.link,
|
||||
a {
|
||||
border-radius: $menu-item-radius;
|
||||
color: $menu-item-color;
|
||||
font-size: $size-7;
|
||||
.menu-label {
|
||||
color: $grey-dark;
|
||||
font-size: $size-8;
|
||||
font-weight: $font-weight-semibold;
|
||||
display: block;
|
||||
padding: $size-9 $size-6;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
letter-spacing: 0;
|
||||
margin: 0;
|
||||
padding: $size-10 $size-8 $size-11;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $menu-item-hover-background-color;
|
||||
color: $menu-item-hover-color;
|
||||
.menu-content {
|
||||
padding: $size-10 $size-8;
|
||||
}
|
||||
|
||||
hr {
|
||||
background-color: $grey-light;
|
||||
margin: $size-11 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-menu-trigger {
|
||||
height: 2rem;
|
||||
min-width: 0;
|
||||
padding: 0 $size-10;
|
||||
}
|
||||
|
||||
.status-menu-content {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.ember-basic-dropdown-content {
|
||||
&--left.popup-menu {
|
||||
margin: 0px 0 0 -8px;
|
||||
}
|
||||
|
||||
&--below {
|
||||
&.ember-basic-dropdown--transitioning-in {
|
||||
animation: drop-fade-above .15s;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: $menu-item-active-background-color;
|
||||
color: $menu-item-active-color;
|
||||
&.ember-basic-dropdown--transitioning-out {
|
||||
animation: drop-fade-above .15s reverse;
|
||||
}
|
||||
}
|
||||
|
||||
&--above {
|
||||
&.ember-basic-dropdown--transitioning-in {
|
||||
animation: drop-fade-below .15s;
|
||||
}
|
||||
|
||||
&.ember-basic-dropdown--transitioning-out {
|
||||
animation: drop-fade-below .15s reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
.popup-menu-content .menu-list {
|
||||
margin: 0.1rem;
|
||||
}
|
||||
.popup-menu-content .menu-label {
|
||||
background: $grey-lighter;
|
||||
font-size: $size-8;
|
||||
letter-spacing: 0;
|
||||
margin: 0;
|
||||
padding: $size-10 calc(#{$size-6} + .1rem);
|
||||
text-transform: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@
|
|||
}
|
||||
|
||||
.menu-label {
|
||||
color: $grey-light;
|
||||
color: $grey;
|
||||
font-weight: $font-weight-semibold;
|
||||
font-size: $size-small;
|
||||
line-height: 1;
|
||||
margin-bottom: $size-8;
|
||||
|
|
@ -72,14 +73,8 @@
|
|||
transition: 250ms border-width;
|
||||
|
||||
&.is-active {
|
||||
color: $blue;
|
||||
background-color: $dark-white;
|
||||
border-right: 4px solid $blue;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $dark-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,6 @@
|
|||
.status-menu-content {
|
||||
max-width: 360px;
|
||||
min-width: 280px;
|
||||
border-radius: 3px;
|
||||
margin: 0 -17px 0 0;
|
||||
will-change: transform, opacity;
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
border: $base-border;
|
||||
box-shadow: 0 0 4px rgba($black, 0.21);
|
||||
color: $black;
|
||||
@include css-top-arrow(8px, $grey-lighter, 1px, $grey, 100%, -25px);
|
||||
.card-content {
|
||||
padding: $size-6 $size-3;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header-title,
|
||||
.menu-label {
|
||||
background: $grey-lighter;
|
||||
color: $grey-dark;
|
||||
font-size: $size-7;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0;
|
||||
margin: 0;
|
||||
padding: 8px $size-3;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.replication-card {
|
||||
@include css-top-arrow(8px, $grey-lighter, 1px, $grey, 100%, -98px);
|
||||
.box-label {
|
||||
box-shadow: none;
|
||||
}
|
||||
a.is-active,
|
||||
a:hover {
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.ember-basic-dropdown-content--below.ember-basic-dropdown--transitioning-in {
|
||||
animation: drop-fade-above .15s;
|
||||
}
|
||||
&.ember-basic-dropdown-content--below.ember-basic-dropdown--transitioning-out {
|
||||
animation: drop-fade-above .15s reverse;
|
||||
}
|
||||
&.ember-basic-dropdown-content--above.ember-basic-dropdown--transitioning-in {
|
||||
animation: drop-fade-below .15s;
|
||||
}
|
||||
&.ember-basic-dropdown-content--above.ember-basic-dropdown--transitioning-out {
|
||||
animation: drop-fade-below .15s reverse;
|
||||
}
|
||||
}
|
||||
.status-menu-content-replication {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.is-status-chevron {
|
||||
line-height: 0;
|
||||
padding: 0.25em 0 0.25em 0.25em;
|
||||
padding: 0.3em 0 0 $size-11;
|
||||
}
|
||||
|
||||
.status-menu-user-trigger {
|
||||
|
|
@ -73,39 +14,3 @@
|
|||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.status-menu-content .menu {
|
||||
button.link,
|
||||
a {
|
||||
font-size: $size-6;
|
||||
font-weight: 500;
|
||||
padding: $size-9 $size-3;
|
||||
border-radius: $menu-item-radius;
|
||||
color: $menu-item-color;
|
||||
display: block;
|
||||
padding: $size-9 $size-3;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
||||
.icon {
|
||||
color: $menu-item-hover-background-color;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $menu-item-hover-background-color;
|
||||
color: $menu-item-hover-color;
|
||||
.icon {
|
||||
color: $menu-item-hover-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: $menu-item-active-background-color;
|
||||
color: $menu-item-active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,35 @@
|
|||
.sub-nav {
|
||||
&.tabs {
|
||||
background: $grey-lighter;
|
||||
padding: 0 1.25rem;
|
||||
box-shadow: inset 0 -1px 0 $grey-light;
|
||||
|
||||
ul {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
li {
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
&.is-active a {
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $grey-dark;
|
||||
font-weight: $font-weight-semibold;
|
||||
text-decoration: none;
|
||||
padding: 1.5rem 1rem;
|
||||
padding: $size-6 $size-8 $size-8;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: border-color $speed;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
border-color: $grey-light;
|
||||
}
|
||||
}
|
||||
a:hover,
|
||||
a:active {
|
||||
border-color: $grey-light;
|
||||
}
|
||||
li:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
li.is-active a {
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.ember-basic-dropdown-trigger {
|
||||
outline: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
.box {
|
||||
position: relative;
|
||||
color: $white;
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
background: $grey;
|
||||
padding: 0.5rem;
|
||||
line-height: 1.4;
|
||||
|
|
@ -28,6 +28,16 @@
|
|||
.ember-basic-dropdown-content--left.tool-tip {
|
||||
margin: 8px 0 0 -11px;
|
||||
}
|
||||
|
||||
.ember-basic-dropdown-content--below.ember-basic-dropdown-content--right.tool-tip {
|
||||
@include css-top-arrow(8px, $grey, 1px, $grey-dark, calc(100% - 20px));
|
||||
}
|
||||
.ember-basic-dropdown-content--above.ember-basic-dropdown-content--right.tool-tip {
|
||||
@include css-bottom-arrow(8px, $grey, 1px, $grey-dark, calc(100% - 20px));
|
||||
}
|
||||
.ember-basic-dropdown-content--above.tool-tip {
|
||||
margin-top: -2px;
|
||||
}
|
||||
.tool-tip-trigger {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
}
|
||||
|
||||
.modal-background {
|
||||
background-image: url("/ui/vault-hex.svg"), linear-gradient(90deg, #191A1C, #1B212D);
|
||||
background-image: url("/ui/vault-hex.svg"),
|
||||
linear-gradient(90deg, #191a1c, #1b212d);
|
||||
opacity: 0.97;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +1,52 @@
|
|||
@keyframes vault-loading-animation {
|
||||
0%,
|
||||
70%,
|
||||
100% {
|
||||
transform: scale3D(1, 1, 1);
|
||||
}
|
||||
|
||||
35% {
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
@keyframes vault-loading-animation {
|
||||
0%,
|
||||
70%,
|
||||
100% {
|
||||
transform: scale3D(1, 1, 1);
|
||||
}
|
||||
|
||||
#vault-loading {
|
||||
polygon {
|
||||
animation: vault-loading-animation 1.3s infinite ease-in-out;
|
||||
transform-origin: 50% 50%;
|
||||
fill: #DCE2E9;
|
||||
}
|
||||
|
||||
.vault-loading-order-1 {
|
||||
animation-delay: .1s;
|
||||
}
|
||||
|
||||
.vault-loading-order-2 {
|
||||
animation-delay: .2s;
|
||||
}
|
||||
|
||||
.vault-loading-order-3 {
|
||||
animation-delay: .3s;
|
||||
}
|
||||
|
||||
.vault-loading-order-4 {
|
||||
animation-delay: .4s;
|
||||
}
|
||||
35% {
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#vault-loading {
|
||||
polygon {
|
||||
animation: vault-loading-animation 1.3s infinite ease-in-out;
|
||||
transform-origin: 50% 50%;
|
||||
fill: #dce2e9;
|
||||
}
|
||||
|
||||
#vault-loading-animated {
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
// For IE11
|
||||
display: none;
|
||||
}
|
||||
.vault-loading-order-1 {
|
||||
animation-delay: .1s;
|
||||
}
|
||||
|
||||
#vault-loading-static {
|
||||
.vault-loading-order-2 {
|
||||
animation-delay: .2s;
|
||||
}
|
||||
|
||||
.vault-loading-order-3 {
|
||||
animation-delay: .3s;
|
||||
}
|
||||
|
||||
.vault-loading-order-4 {
|
||||
animation-delay: .4s;
|
||||
}
|
||||
}
|
||||
|
||||
#vault-loading-animated {
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
// For IE11
|
||||
display: none;
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
// For IE11
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
#vault-loading-static {
|
||||
display: none;
|
||||
font-size: 9px;
|
||||
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
// For IE11
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@
|
|||
@import "./components/box-label";
|
||||
@import "./components/codemirror";
|
||||
@import "./components/confirm";
|
||||
@import "./components/console-ui-panel";
|
||||
@import "./components/env-banner";
|
||||
@import "./components/form-section";
|
||||
@import "./components/global-flash";
|
||||
@import "./components/init-illustration";
|
||||
|
|
@ -54,6 +56,7 @@
|
|||
@import "./components/linked-block";
|
||||
@import "./components/list-pagination";
|
||||
@import "./components/loader";
|
||||
@import "./components/login-form";
|
||||
@import "./components/message-in-page";
|
||||
@import "./components/page-header";
|
||||
@import "./components/popup-menu";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
$button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
||||
|
||||
.button {
|
||||
box-shadow: $button-box-shadow-standard;
|
||||
border: 1px solid $grey-light;
|
||||
box-shadow: $box-shadow-low;
|
||||
color: $grey-dark;
|
||||
display: inline-block;
|
||||
font-size: $size-small;
|
||||
|
|
@ -12,7 +12,8 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
|||
min-width: 6rem;
|
||||
padding: $size-10 $size-8;
|
||||
text-decoration: none;
|
||||
transition: background-color $speed, border-color $speed, box-shadow $speed, color $speed;
|
||||
transition: background-color $speed, border-color $speed, box-shadow $speed,
|
||||
color $speed;
|
||||
vertical-align: middle;
|
||||
|
||||
&.is-icon {
|
||||
|
|
@ -57,13 +58,14 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
|||
&.is-hovered {
|
||||
background-color: darken($color, 5%);
|
||||
border-color: darken($color, 5%);
|
||||
box-shadow: $box-shadow-middle;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&.is-active {
|
||||
background-color: darken($color, 10%);
|
||||
border-color: darken($color, 10%);
|
||||
box-shadow: none;
|
||||
box-shadow: $box-shadow-middle;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
|
|
|
|||
|
|
@ -33,13 +33,16 @@ input::-webkit-inner-spin-button {
|
|||
.link {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $blue;
|
||||
cursor: pointer;
|
||||
display: inline;
|
||||
font: inherit;
|
||||
line-height: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-decoration: underline;
|
||||
-moz-user-select: text;
|
||||
color: $blue;
|
||||
cursor: pointer;
|
||||
display: inline;
|
||||
font: inherit;
|
||||
line-height: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-decoration: underline;
|
||||
-webkit-user-select: text; /* Chrome all / Safari all */
|
||||
-moz-user-select: text; /* Firefox all */
|
||||
-ms-user-select: text; /* IE 10+ */
|
||||
user-select: text;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
.column .menu-list a {
|
||||
.column .menu-list a {
|
||||
border-radius: 0;
|
||||
border-right: 0 solid transparent;
|
||||
font-weight: $font-weight-semibold;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
color: $menu-item-hover-background-color;
|
||||
background-color: $menu-item-active-color;
|
||||
color: $menu-item-active-color;
|
||||
background-color: $menu-item-active-background-color;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
.tag:not(body) {
|
||||
background-color: lighten($grey-light, 17%);
|
||||
border-radius: 2px;
|
||||
color: $grey-dark;
|
||||
height: auto;
|
||||
padding: 0 0.75em;
|
||||
padding: 0 $size-10;
|
||||
margin-right: 0.5rem;
|
||||
font-weight: normal;
|
||||
|
||||
code {
|
||||
color: $grey;
|
||||
color: $grey-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.tag.is-outlined {
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
|
||||
.tag.is-inverted {
|
||||
border-color: $grey;
|
||||
background: none;
|
||||
|
|
@ -19,6 +23,7 @@
|
|||
color: $grey-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.tag.is-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ $border: $grey-light;
|
|||
$hr-margin: 1rem 0;
|
||||
|
||||
//typography
|
||||
$family-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
$family-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
$family-primary: $family-sans;
|
||||
$body-size: 14px;
|
||||
$size-3: (24/14) + 0rem;
|
||||
|
|
@ -46,6 +48,7 @@ $size-8: (12/14) + 0rem;
|
|||
$size-9: 0.75rem;
|
||||
$size-10: 0.5rem;
|
||||
$size-11: 0.25rem;
|
||||
$console-spacing: 1.5rem;
|
||||
$size-small: $size-8;
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-semibold: 600;
|
||||
|
|
@ -61,6 +64,10 @@ $radius: 2px;
|
|||
//box
|
||||
$box-radius: 0;
|
||||
$box-shadow: 0 0 0 1px rgba($black, 0.1);
|
||||
$box-shadow-low: 0 5px 1px -2px rgba($black, 0.12), 0 3px 2px -1px rgba($black, 0);
|
||||
$box-shadow-middle: 0 8px 4px -4px rgba($black, 0.10), 0 6px 8px -2px rgba($black, 0.05);
|
||||
$box-shadow-high: 0 12px 5px -7px rgba($black, 0.08), 0 11px 10px -3px rgba($black, 0.10);
|
||||
$box-shadow-highest: 0 16px 6px -10px rgba($black, 0.06), 0 16px 16px -4px rgba($black, 0.20);
|
||||
|
||||
$link: $blue;
|
||||
$text: $black;
|
||||
|
|
@ -78,6 +85,12 @@ $progress-bar-background-color: lighten($grey-light, 15%);
|
|||
|
||||
$base-border: 1px solid $grey-light;
|
||||
|
||||
//menu
|
||||
$menu-item-hover-color: $text;
|
||||
$menu-item-hover-background-color: $grey-lighter;
|
||||
$menu-item-active-color: $link;
|
||||
$menu-item-active-background-color: transparent;
|
||||
|
||||
// animations
|
||||
$speed: 150ms;
|
||||
$speed-slow: $speed * 2;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
}
|
||||
|
||||
@include keyframes(drop-fade-below) {
|
||||
0% {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-1rem);
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
}
|
||||
|
||||
@include keyframes(drop-fade-above) {
|
||||
0% {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(1rem);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
@mixin css-top-arrow($size, $color, $border-width, $border-color, $left: 50%, $left-offset: 0px) {
|
||||
@mixin css-arrow($vertical-direction, $size, $color, $border-width, $border-color, $left: 50%, $left-offset: 0px) {
|
||||
& {
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
bottom: 100%;
|
||||
@if ($vertical-direction == 'top') {
|
||||
bottom: 100%;
|
||||
} @else {
|
||||
top: 100%;
|
||||
}
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
|
|
@ -28,6 +32,12 @@
|
|||
left: calc(#{$left} + #{$left-offset});
|
||||
margin-left: -($size + round(1.41421356 * $border-width));
|
||||
}
|
||||
&:before,
|
||||
&:after {
|
||||
@if ($vertical-direction == 'bottom') {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
@at-root .ember-basic-dropdown-content--left#{&} {
|
||||
&:after,
|
||||
|
|
@ -38,6 +48,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin css-top-arrow($size, $color, $border-width, $border-color, $left: 50%, $left-offset: 0px) {
|
||||
@include css-arrow('top', $size, $color, $border-width, $border-color, $left, $left-offset);
|
||||
}
|
||||
@mixin css-bottom-arrow($size, $color, $border-width, $border-color, $left: 50%, $left-offset: 0px) {
|
||||
@include css-arrow('bottom', $size, $color, $border-width, $border-color, $left, $left-offset);
|
||||
}
|
||||
|
||||
@mixin vault-block {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: (5/14) + 0rem;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="page-container">
|
||||
{{#if showNav}}
|
||||
<header data-test-header-with-nav>
|
||||
<header data-test-header-with-nav class="{{if consoleOpen 'panel-open'}} {{if consoleFullscreen ' panel-fullscreen'}}">
|
||||
<nav class="navbar has-dark-grey-gradient is-grouped-split">
|
||||
<div class="navbar-brand">
|
||||
{{#home-link class="navbar-item has-text-white has-current-color-fill"}}
|
||||
|
|
@ -8,6 +8,17 @@
|
|||
{{/home-link}}
|
||||
</div>
|
||||
<div class="navbar-end is-divider-list is-flex">
|
||||
<div class="navbar-item">
|
||||
<button type="button" class="button is-transparent" {{action 'toggleConsole'}}>
|
||||
{{#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"}}
|
||||
{{else}}
|
||||
{{i-con glyph="console" size=24}}
|
||||
{{i-con glyph="chevron-down" aria-hidden="true" size=8 class="has-text-white auto-width is-status-chevron"}}
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
{{status-menu}}
|
||||
</div>
|
||||
|
|
@ -55,6 +66,7 @@
|
|||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{console/ui-panel isFullscreen=consoleFullscreen}}
|
||||
</header>
|
||||
{{/if}}
|
||||
<div class="global-flash">
|
||||
|
|
@ -129,7 +141,7 @@
|
|||
</div>
|
||||
</footer>
|
||||
{{#if (eq env "development") }}
|
||||
<div class="level development">
|
||||
<div class="env-banner level development">
|
||||
<div class="level-item notification has-background-dark has-text-white">
|
||||
{{i-con glyph="wand" class="type-icon"}}Local Development
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,56 +1,57 @@
|
|||
<div class="card has-arrow">
|
||||
<header class="card-header is-grey">
|
||||
<p class="card-header-title is-uppercase has-background-grey-lighter has-text-grey-dark is-size-7">
|
||||
<div class="popup-menu-content">
|
||||
<div class="box">
|
||||
<div class="menu-label">
|
||||
{{auth.authData.displayName}}
|
||||
</p>
|
||||
</header>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if (is-before (now interval=1000) auth.tokenExpirationDate)}}
|
||||
{{#if auth.authData.renewable}}
|
||||
<li class="action">
|
||||
<button type="button" {{action "renewToken"}} class="link button {{if isRenewing 'is-loading'}}">
|
||||
Renew token
|
||||
</button>
|
||||
</li>
|
||||
<li class="action">
|
||||
{{#confirm-action
|
||||
onConfirmAction=(action "revokeToken")
|
||||
confirmMessage=(concat "Are you sure you want to revoke the token for " (get auth 'authData.displayName') "?")
|
||||
confirmButtonText="Revoke"
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="button link"
|
||||
showConfirm=shouldRevoke
|
||||
class=(if shouldRevoke "message is-block is-warning is-outline")
|
||||
containerClasses="message-body is-block"
|
||||
messageClasses="is-block"
|
||||
}}
|
||||
Revoke token
|
||||
{{/confirm-action}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="action text-right">
|
||||
{{#confirm-action
|
||||
onConfirmAction=(action "revokeToken")
|
||||
confirmMessage=(concat "Are you sure you want to revoke the token for " (get auth 'authData.displayName') "?")
|
||||
confirmButtonText="Revoke"
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="button link"
|
||||
showConfirm=shouldRevoke
|
||||
class=(if shouldRevoke "message is-block is-warning is-outline")
|
||||
containerClasses="message-body is-block"
|
||||
messageClasses="is-block"
|
||||
}}
|
||||
Revoke token
|
||||
{{/confirm-action}}
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if (is-before (now interval=1000) auth.tokenExpirationDate)}}
|
||||
{{#if auth.authData.renewable}}
|
||||
<li class="action">
|
||||
<button type="button" {{action "renewToken"}} class="link button {{if isRenewing 'is-loading'}}">
|
||||
Renew token
|
||||
</button>
|
||||
</li>
|
||||
<li class="action">
|
||||
{{#confirm-action
|
||||
onConfirmAction=(action "revokeToken")
|
||||
confirmMessage=(concat "Are you sure you want to revoke the token for " (get auth 'authData.displayName') "?")
|
||||
confirmButtonText="Revoke"
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="button link"
|
||||
showConfirm=shouldRevoke
|
||||
class=(if shouldRevoke "message is-block is-warning is-outline")
|
||||
containerClasses="message-body is-block"
|
||||
messageClasses="is-block"
|
||||
}}
|
||||
Revoke token
|
||||
{{/confirm-action}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="action text-right">
|
||||
{{#confirm-action
|
||||
onConfirmAction=(action "revokeToken")
|
||||
confirmMessage=(concat "Are you sure you want to revoke the token for " (get auth 'authData.displayName') "?")
|
||||
confirmButtonText="Revoke"
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="button link"
|
||||
showConfirm=shouldRevoke
|
||||
class=(if shouldRevoke "message is-block is-warning is-outline")
|
||||
containerClasses="message-body is-block"
|
||||
messageClasses="is-block"
|
||||
}}
|
||||
Revoke token
|
||||
{{/confirm-action}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<li class="action">
|
||||
<a href="{{href-to "vault.cluster.logout" activeClusterName }}" id="logout">
|
||||
Sign out
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<li class="action">
|
||||
<a href="{{href-to "vault.cluster.logout" activeClusterName }}" id="logout">
|
||||
Sign out
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
16
ui/app/templates/components/console/command-input.hbs
Normal file
16
ui/app/templates/components/console/command-input.hbs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{{i-con glyph="chevron-right" size=12}}
|
||||
<input onkeyup={{action 'handleKeyUp'}} value={{value}} />
|
||||
{{#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}}
|
||||
{{i-con glyph=(if isFullscreen "fullscreen-close" "fullscreen-open") aria-hidden="true" size=16}}
|
||||
{{/d.trigger}}
|
||||
{{#d.content class="tool-tip"}}
|
||||
<div class="box">
|
||||
{{#if isFullscreen}}
|
||||
Minimize
|
||||
{{else}}
|
||||
Maximize
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/d.content}}
|
||||
{{/tool-tip}}
|
||||
1
ui/app/templates/components/console/log-command.hbs
Normal file
1
ui/app/templates/components/console/log-command.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
<pre class="console-ui-command">{{i-con glyph="chevron-right" size=12}}{{content}}</pre>
|
||||
4
ui/app/templates/components/console/log-error.hbs
Normal file
4
ui/app/templates/components/console/log-error.hbs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<div class="console-ui-alert has-text-danger">
|
||||
{{i-con glyph="close-circled" aria-hidden="true" size=12}}
|
||||
<pre>{{content}}</pre>
|
||||
</div>
|
||||
16
ui/app/templates/components/console/log-help.hbs
Normal file
16
ui/app/templates/components/console/log-help.hbs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<div class="console-ui-alert has-text-grey">
|
||||
{{i-con glyph="information-circled" aria-hidden="true" size=12}}
|
||||
<pre>Usage: vault <command> [args]
|
||||
|
||||
Commands:
|
||||
read Read data and retrieves secrets
|
||||
write Write data, configuration, and secrets
|
||||
delete Delete secrets and configuration
|
||||
list List data or secrets
|
||||
|
||||
Web CLI Commands:
|
||||
fullscreen Toggle fullscreen display
|
||||
clear Clear output from the log
|
||||
clearall Clear output and command history
|
||||
</pre>
|
||||
</div>
|
||||
10
ui/app/templates/components/console/log-json.hbs
Normal file
10
ui/app/templates/components/console/log-json.hbs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{{json-editor
|
||||
value=(stringify content)
|
||||
options=(hash
|
||||
readOnly=true
|
||||
lineNumbers=false
|
||||
autoHeight=true
|
||||
gutters=false
|
||||
theme='hashi auto-height'
|
||||
)
|
||||
}}
|
||||
21
ui/app/templates/components/console/log-list.hbs
Normal file
21
ui/app/templates/components/console/log-list.hbs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<div class="console-ui-output">
|
||||
<pre>Keys
|
||||
{{#each list as |item|}}
|
||||
{{item}}
|
||||
{{/each}}
|
||||
</pre>
|
||||
<div class="console-ui-output-actions">
|
||||
{{#tool-tip renderInPlace=true as |d|}}
|
||||
{{#d.trigger data-test-tool-tip-trigger=true}}
|
||||
{{#copy-button clipboardText=(multi-line-join list) class="button is-compact"}}
|
||||
{{i-con glyph="copy" aria-hidden="true" size=16}}
|
||||
{{/copy-button}}
|
||||
{{/d.trigger}}
|
||||
{{#d.content class="tool-tip"}}
|
||||
<div class="box">
|
||||
Copy
|
||||
</div>
|
||||
{{/d.content}}
|
||||
{{/tool-tip}}
|
||||
</div>
|
||||
</div>
|
||||
18
ui/app/templates/components/console/log-object.hbs
Normal file
18
ui/app/templates/components/console/log-object.hbs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<div class="console-ui-output">
|
||||
<pre>{{columns}}</pre>
|
||||
|
||||
<div class="console-ui-output-actions">
|
||||
{{#tool-tip renderInPlace=true as |d|}}
|
||||
{{#d.trigger data-test-tool-tip-trigger=true}}
|
||||
{{#copy-button clipboardText=columns class="button is-compact"}}
|
||||
{{i-con glyph="copy" aria-hidden="true" size=16}}
|
||||
{{/copy-button}}
|
||||
{{/d.trigger}}
|
||||
{{#d.content class="tool-tip"}}
|
||||
<div class="box">
|
||||
Copy
|
||||
</div>
|
||||
{{/d.content}}
|
||||
{{/tool-tip}}
|
||||
</div>
|
||||
</div>
|
||||
4
ui/app/templates/components/console/log-success.hbs
Normal file
4
ui/app/templates/components/console/log-success.hbs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<div class="console-ui-alert has-text-success">
|
||||
{{i-con glyph="checkmark-circled" aria-hidden="true" size=12}}
|
||||
<pre>{{content}}</pre>
|
||||
</div>
|
||||
1
ui/app/templates/components/console/log-text.hbs
Normal file
1
ui/app/templates/components/console/log-text.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
<pre>{{content}}</pre>
|
||||
5
ui/app/templates/components/console/output-log.hbs
Normal file
5
ui/app/templates/components/console/output-log.hbs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{{#each log as |message|}}
|
||||
{{#unless message.hidden}}
|
||||
{{component (concat 'console/log-' message.type) content=message.content}}
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
16
ui/app/templates/components/console/ui-panel.hbs
Normal file
16
ui/app/templates/components/console/ui-panel.hbs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<div class="console-ui-panel">
|
||||
<div class="content">
|
||||
<p class="has-text-grey is-font-mono">
|
||||
The Vault Browser CLI provides an easy way to execute the most common CLI commands, such as write, read, delete, and list.
|
||||
</p>
|
||||
</div>
|
||||
{{console/output-log log=log}}
|
||||
{{console/command-input
|
||||
isFullscreen=isFullscreen
|
||||
value=inputValue
|
||||
onValueUpdate=(action (mut inputValue))
|
||||
onFullscreen=(action 'toggleFullscreen')
|
||||
onExecuteCommand=(action 'executeCommand')
|
||||
onShiftCommand=(action 'shiftCommandIndex')
|
||||
}}
|
||||
</div>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<div>
|
||||
{{model.mountPath}}
|
||||
<div>
|
||||
<span class="tag"><code>{{model.mountType}}</code></span>
|
||||
<span class="tag">{{model.mountType}}</span>
|
||||
<code class="has-text-grey is-size-8">{{model.mountAccessor}}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
size=14
|
||||
class="has-text-grey-light"
|
||||
}}{{item.name}}</a>
|
||||
<span class="tag"><code>{{item.mountType}}</code></span>
|
||||
<span class="tag">{{item.mountType}}</span>
|
||||
<code class="has-text-grey is-size-8">{{item.mountAccessor}}</code>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
{{#confirm-action
|
||||
data-test-item-delete=true
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "performTransaction" item)
|
||||
confirmMessage=(concat "Are you sure you want to delete " item.id "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" item.id))
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
confirmButtonText="Remove"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "performTransaction" model groupArray memberId)
|
||||
confirmMessage=(concat "Are you sure you want to remove " memberId "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" memberId))
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
confirmButtonText="Remove"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "performTransaction" model key)
|
||||
confirmMessage=(concat "Are you sure you want to remove " key "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" key))
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
confirmButtonText="Remove"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "performTransaction" model policyName)
|
||||
confirmMessage=(concat "Are you sure you want to remove " policyName "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" policyName))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<li class="action">
|
||||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "delete" item)
|
||||
confirmMessage=(concat "Are you sure you want to revoke " item.id "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" item.id))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{{#basic-dropdown class="popup-menu" horizontalPosition="auto-right" verticalPosition="below" onOpen=onOpen as |d|}}
|
||||
{{#d.trigger tagName="button" class=(concat "popup-menu-trigger button is-ghost " (if d.isOpen "is-active")) data-test-popup-menu-trigger=true}}
|
||||
{{#d.trigger tagName="button" class=(concat "popup-menu-trigger button is-ghost" (if d.isOpen "is-active")) data-test-popup-menu-trigger=true}}
|
||||
{{i-con
|
||||
glyph="more"
|
||||
class="has-text-black auto-width"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="is-flex-v-centered-tablet is-flex-1 is-fullwidth">
|
||||
<div class="columns is-centered is-gapless is-fullwidth">
|
||||
<div class="column is-4-desktop is-7-tablet">
|
||||
<div class="box is-paddingless has-glow is-relative">
|
||||
<div class="login-form box is-paddingless is-relative">
|
||||
<div class="hero has-dark-grey-gradient">
|
||||
<div class="hero-body">
|
||||
<div class="has-current-color-fill has-text-white">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{{#basic-dropdown-hover horizontalPosition="auto-right" verticalPosition="below" as |d|}}
|
||||
{{#basic-dropdown-hover horizontalPosition="auto-left" verticalPosition="below" as |d|}}
|
||||
{{#d.trigger tagName=(if (eq type "replication") "span" "button") class=(if (eq type "replication") "" "button is-transparent")}}
|
||||
{{#if (eq type "user")}}
|
||||
<div class="status-menu-user-trigger">
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@
|
|||
{{capitalize modeForUrl}}
|
||||
</span>
|
||||
{{#if secondaryId}}
|
||||
<span class="tag is-light">
|
||||
<span class="tag">
|
||||
<code>
|
||||
{{secondaryId}}
|
||||
</code>
|
||||
</span>
|
||||
{{/if}}
|
||||
<span class="tag is-light">
|
||||
<span class="tag">
|
||||
<code>
|
||||
{{clusterIdDisplay}}
|
||||
</code>
|
||||
|
|
@ -29,7 +29,9 @@
|
|||
<div class="level-right">
|
||||
{{#if replicationEnabled}}
|
||||
{{#if (get cluster (concat mode 'StateGlyph'))}}
|
||||
{{i-con size=14 glyph=(get cluster (concat mode 'StateGlyph'))}}
|
||||
<span class="has-text-success">
|
||||
{{i-con size=16 glyph=(get cluster (concat mode 'StateGlyph'))}}
|
||||
</span>
|
||||
{{else if syncProgress}}
|
||||
<progress value="{{syncProgressPercent}}" max="100" class="progress is-small is-narrow is-info">
|
||||
{{syncProgress.progress}} of {{syncProgress.total}} keys
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@
|
|||
{{capitalize modeForUrl}}
|
||||
</span>
|
||||
{{#if secondaryId}}
|
||||
<span class="tag is-light">
|
||||
<span class="tag">
|
||||
<code>
|
||||
{{secondaryId}}
|
||||
</code>
|
||||
</span>
|
||||
{{/if}}
|
||||
<span class="tag is-light">
|
||||
<span class="tag">
|
||||
<code>
|
||||
{{clusterIdDisplay}}
|
||||
</code>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
<li class="action">
|
||||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "delete" item)
|
||||
confirmMessage=(concat "Are you sure you want to delete " item.id "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" item.id))
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
<li class="action">
|
||||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "delete" item)
|
||||
confirmMessage=(concat "Are you sure you want to delete " item.id "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" item.id))
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
<li class="action">
|
||||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "delete" item)
|
||||
confirmMessage=(concat "Are you sure you want to delete " item.id "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" item.id))
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
<div class="card has-arrow">
|
||||
<header class="card-header is-grey">
|
||||
<p class="card-header-title is-uppercase has-background-grey-lighter has-text-grey-dark is-size-7">
|
||||
In case of emergency
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<p class="is-size-7">If you suspect your data has been compromised, you can <a href="{{href-to 'vault.cluster.settings.seal' cluster.name}}" onclick={{action d.actions.close}} class="link">seal your vault</a> to prevent access to your secrets.</p>
|
||||
<div class="popup-menu-content is-wide">
|
||||
<div class="box">
|
||||
<div class="menu-label">
|
||||
In case of emergency
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<p>
|
||||
If you suspect your data has been compromised, you can
|
||||
<a href="{{href-to 'vault.cluster.settings.seal' cluster.name}}" onclick={{action d.actions.close}} class="link">seal your vault</a>
|
||||
to prevent access to your secrets.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
<div class="card replication-card">
|
||||
<nav class="menu">
|
||||
<p class="menu-label">Disaster Recovery</p>
|
||||
<ul>
|
||||
<li>
|
||||
{{replication-mode-summary
|
||||
mode="dr"
|
||||
display='menu'
|
||||
cluster=cluster
|
||||
tagName='a'
|
||||
click=(action d.actions.close)
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="menu-label">Performance</p>
|
||||
<ul>
|
||||
<li>
|
||||
{{replication-mode-summary
|
||||
mode="performance"
|
||||
display='menu'
|
||||
cluster=cluster
|
||||
tagName='a'
|
||||
click=(action d.actions.close)
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="popup-menu-content is-wide is-top-nav-popup">
|
||||
<div class="box">
|
||||
<nav class="menu">
|
||||
<p class="menu-label">Disaster Recovery</p>
|
||||
<ul>
|
||||
<li>
|
||||
{{replication-mode-summary
|
||||
mode="dr"
|
||||
display='menu'
|
||||
cluster=cluster
|
||||
tagName='a'
|
||||
click=(action d.actions.close)
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<p class="menu-label">Performance</p>
|
||||
<ul>
|
||||
<li>
|
||||
{{replication-mode-summary
|
||||
mode="performance"
|
||||
display='menu'
|
||||
cluster=cluster
|
||||
tagName='a'
|
||||
click=(action d.actions.close)
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
1
ui/app/templates/svg/icons/checkmark-circled-outline.hbs
Normal file
1
ui/app/templates/svg/icons/checkmark-circled-outline.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
<path d="M256,512 C114.615104,512 0,397.384896 0,256 C0,114.615104 114.615104,0 256,0 C397.384896,0 512,114.615104 512,256 C512,397.384896 397.384896,512 256,512 Z M256,480 C379.711784,480 480,379.711784 480,256 C480,132.288216 379.711784,32 256,32 C132.288216,32 32,132.288216 32,256 C32,379.711784 132.288216,480 256,480 Z M385.548422,144 L416,174.545548 L217.753913,376.759626 L96,260.314527 L127.007564,227.142329 L217.753913,312.691266 L385.548422,144 Z"/>
|
||||
21
ui/app/templates/svg/icons/console-active.hbs
Normal file
21
ui/app/templates/svg/icons/console-active.hbs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<g transform="translate(458 28)">
|
||||
<path
|
||||
d="M2.8,39h-409.6c-17,0-30.7,13.9-30.7,30.9V400c0,17.1,13.8,30.9,30.7,30.9H2.8c17,0,30.7-13.9,30.7-30.9 V70C33.5,52.9,19.8,39,2.8,39z"
|
||||
fill="#0068FF"
|
||||
/>
|
||||
<path
|
||||
d="M2.8,18.4h-409.6c-28.3,0-51.2,23.1-51.2,51.6V400c0,28.5,22.9,51.6,51.2,51.6H2.8 c28.3,0,51.2-23.1,51.2-51.6V70C54,41.5,31.1,18.4,2.8,18.4z M33.5,400c0,17.1-13.8,30.9-30.7,30.9h-409.6 c-17,0-30.7-13.9-30.7-30.9V70c0-17.1,13.8-30.9,30.7-30.9H2.8c17,0,30.7,13.9,30.7,30.9V400z"
|
||||
fill="#8AB1FF"
|
||||
/>
|
||||
<polygon
|
||||
points="-241.9,235.9 -241.7,235.7 -262.8,214.6 -263,214.8 -319.1,158.7 -340.2,179.8 -284.1,235.9 -340.2,292 -319.1,313.1 -263,257 -262.8,257.2 -241.7,236.1"
|
||||
fill="#fff"
|
||||
/>
|
||||
<rect
|
||||
x="-241.7"
|
||||
y="283.5"
|
||||
width="157.5"
|
||||
height="29.5"
|
||||
fill="#fff"
|
||||
/>
|
||||
</g>
|
||||
17
ui/app/templates/svg/icons/console.hbs
Normal file
17
ui/app/templates/svg/icons/console.hbs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<g transform="translate(458 28)">
|
||||
<path
|
||||
d="M2.8,18.4h-409.6c-28.3,0-51.2,23.1-51.2,51.6V400c0,28.5,22.9,51.6,51.2,51.6H2.8 c28.3,0,51.2-23.1,51.2-51.6V70C54,41.5,31.1,18.4,2.8,18.4z M33.5,400c0,17.1-13.8,30.9-30.7,30.9h-409.6 c-17,0-30.7-13.9-30.7-30.9V70c0-17.1,13.8-30.9,30.7-30.9H2.8c17,0,30.7,13.9,30.7,30.9V400z"
|
||||
fill="#B3B9C0"
|
||||
/>
|
||||
<polygon
|
||||
points="-241.9,235.9 -241.7,235.7 -262.8,214.6 -263,214.8 -319.1,158.7 -340.2,179.8 -284.1,235.9 -340.2,292 -319.1,313.1 -263,257 -262.8,257.2 -241.7,236.1"
|
||||
fill="#fff"
|
||||
/>
|
||||
<rect
|
||||
x="-241.7"
|
||||
y="283.5"
|
||||
width="157.5"
|
||||
height="29.5"
|
||||
fill="#fff"
|
||||
/>
|
||||
</g>
|
||||
1
ui/app/templates/svg/icons/copy.hbs
Normal file
1
ui/app/templates/svg/icons/copy.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
<path d="M272.769063,297.207977 L173.82716,297.207977 L173.82716,465.646091 C173.82716,464.098951 174.365679,464.592593 176.987654,464.592593 L455.111111,464.592593 C457.733087,464.592593 458.271605,464.098951 458.271605,465.646091 L458.271605,141.168724 C458.271605,142.715864 457.733087,142.222222 455.111111,142.222222 L176.987654,142.222222 C174.365679,142.222222 173.82716,142.715864 173.82716,141.168724 L173.82716,260.740741 L280.463115,260.740741 L230.986572,211.264198 L256.515702,185.735068 L345.867656,275.087023 L345.827346,275.127333 L345.867656,275.167643 L256.515702,364.519598 L230.986572,338.990468 L272.769063,297.207977 Z M126.419753,260.740741 L126.419753,141.168724 C126.419753,115.568167 149.059774,94.8148148 176.987654,94.8148148 L300.246914,94.8148148 L300.246914,47.4074074 L47.4074074,47.4074074 L47.4074074,335.012346 L126.419753,335.012346 L126.419753,297.207977 L79.0123457,297.207977 L79.0123457,260.740741 L126.419753,260.740741 Z M126.419753,382.419753 L46.3539095,382.419753 C20.7533522,382.419753 0,363.395847 0,339.928669 L0,42.4910837 C0,19.0239062 20.7533522,0 46.3539095,0 L301.300412,0 C326.900969,0 347.654321,19.0239062 347.654321,42.4910837 L347.654321,94.8148148 L455.111111,94.8148148 C483.038992,94.8148148 505.679012,115.568167 505.679012,141.168724 L505.679012,465.646091 C505.679012,491.246648 483.038992,512 455.111111,512 L176.987654,512 C149.059774,512 126.419753,491.246648 126.419753,465.646091 L126.419753,382.419753 Z"/>
|
||||
1
ui/app/templates/svg/icons/fullscreen-close.hbs
Normal file
1
ui/app/templates/svg/icons/fullscreen-close.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
<path d="M186.527813,356.143158 L47.8085404,494.86243 L21.6999823,468.753872 L165.927777,324.526077 L53.6999823,324.526077 L53.6999823,287.97114 L223.02503,287.97114 L223.02503,288.02886 L223.08275,288.02886 L223.08275,461.722531 L186.527813,461.722531 L186.527813,356.143158 Z M332.526077,154.910732 L471.245349,16.1914596 L497.353908,42.3000177 L353.126113,186.527813 L465.308859,186.527813 L465.308859,223.08275 L296.02886,223.08275 L296.02886,223.02503 L295.97114,223.02503 L295.97114,53.6999823 L332.526077,53.6999823 L332.526077,154.910732 Z"/>
|
||||
1
ui/app/templates/svg/icons/fullscreen-open.hbs
Normal file
1
ui/app/templates/svg/icons/fullscreen-open.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
<path d="M36.5260773,442.910732 L175.245349,304.19146 L201.353908,330.300018 L57.1261127,474.527813 L159.08275,474.527813 L159.08275,511.08275 L0.0288599258,511.08275 L0.0288599258,511.02503 L-0.0288599258,511.02503 L-0.0288599258,351.97114 L36.5260773,351.97114 L36.5260773,442.910732 Z M474.527813,68.143158 L335.80854,206.86243 L309.699982,180.753872 L453.927777,36.5260773 L351.97114,36.5260773 L351.97114,-0.0288599258 L511.02503,-0.0288599258 L511.02503,0.0288599258 L511.08275,0.0288599258 L511.08275,159.08275 L474.527813,159.08275 L474.527813,68.143158 Z"/>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<g id="vault-loading-y1">
|
||||
<polygon class="vault-loading-order-4" points="0,0 5,10 10,0" style="transform-origin: 5px 5px" />
|
||||
<polygon class="vault-loading-order-2" points="5,10 10,0 15,10" style="transform-origin: 10px 5px" />
|
||||
<polygon class="vault-loading-order-3" points="10,0 15,10 20,0" xstyle="transform-origin: 15px 5px" />
|
||||
<polygon class="vault-loading-order-3" points="10,0 15,10 20,0" style="transform-origin: 15px 5px" />
|
||||
<polygon class="vault-loading-order-1" points="15,10 20,0 25,10" style="transform-origin: 20px 5px" />
|
||||
<polygon class="vault-loading-order-3" points="20,0 25,10 30,0" style="transform-origin: 25px 5px" />
|
||||
<polygon class="vault-loading-order-2" points="25,10 30,0 35,10" style="transform-origin: 30px 5px" />
|
||||
|
|
@ -29,4 +29,4 @@
|
|||
<text id="vault-loading-static" x="20" y="20" text-anchor="middle">
|
||||
Loading...
|
||||
</text>
|
||||
</svg>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
|
@ -26,13 +26,11 @@
|
|||
{{i-con glyph="folder" size=14 class="has-text-grey-light"}} <span data-test-path data-test-id={{method.id}}>{{method.path}}</span>
|
||||
<br />
|
||||
<span class="tag">
|
||||
<code>
|
||||
{{#if (eq method.type 'plugin')}}
|
||||
{{method.type}}: {{method.config.plugin_name}}
|
||||
{{else}}
|
||||
{{method.type}}
|
||||
{{/if}}
|
||||
</code>
|
||||
{{#if (eq method.type 'plugin')}}
|
||||
{{method.type}}: {{method.config.plugin_name}}
|
||||
{{else}}
|
||||
{{method.type}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<code class="has-text-grey is-size-8">
|
||||
{{method.accessor}}
|
||||
|
|
@ -61,7 +59,7 @@
|
|||
<li class="action">
|
||||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(perform disableMethod method)
|
||||
confirmMessage=(concat "Are you sure you want to disable auth via " method.id "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" method.id))
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@
|
|||
<h1 class="title is-3">
|
||||
{{uppercase policyType}} Policies
|
||||
{{#unless (eq policyType "acl")}}
|
||||
<span class="tag is-inverted is-outlined" aria-label="Enforcement level: {{model.enforcementLevel}}">
|
||||
<code>
|
||||
Sentinel
|
||||
</code>
|
||||
<span class="tag" aria-label="Enforcement level: {{model.enforcementLevel}}">
|
||||
Sentinel
|
||||
</span>
|
||||
{{/unless}}
|
||||
</h1>
|
||||
|
|
@ -120,7 +118,7 @@
|
|||
<li class="action">
|
||||
{{#confirm-action
|
||||
confirmButtonClasses="button is-primary"
|
||||
buttonClasses="link"
|
||||
buttonClasses="link is-destroy"
|
||||
onConfirmAction=(action "deletePolicy" item)
|
||||
confirmMessage=(concat "Are you sure you want to delete " item.id "?")
|
||||
showConfirm=(get this (concat "shouldDelete-" item.id))
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@
|
|||
<h1 class="title is-3">
|
||||
{{model.id}}
|
||||
{{#if model.enforcementLevel}}
|
||||
<span class="tag is-medium is-inverted is-outlined" aria-label="Enforcement level: {{model.enforcementLevel}}">
|
||||
<code>
|
||||
{{model.enforcementLevel}}
|
||||
</code>
|
||||
<span class="tag is-medium" aria-label="Enforcement level: {{model.enforcementLevel}}">
|
||||
{{model.enforcementLevel}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<h1 class="title is-3" data-test-policy-name="true">
|
||||
{{model.id}}
|
||||
{{#if model.enforcementLevel}}
|
||||
<span class="tag is-inverted is-outlined is-font-mono has-text-grey-dark" aria-label="Enforcement level: {{model.enforcementLevel}}">
|
||||
<span class="tag" aria-label="Enforcement level: {{model.enforcementLevel}}">
|
||||
{{model.enforcementLevel}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
|
@ -81,4 +81,3 @@
|
|||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
confirmMessage=(concat "Are you sure you want to revoke " secondary "?")
|
||||
confirmButtonText="Revoke"
|
||||
cancelButtonText="Cancel"
|
||||
buttonClasses="button link"
|
||||
buttonClasses="button link is-destroy"
|
||||
confirmButtonClasses="is-danger is-outlined button"
|
||||
showConfirm=(get this (concat secondary '-isRevoking'))
|
||||
containerClasses="is-block"
|
||||
|
|
|
|||
|
|
@ -30,13 +30,11 @@
|
|||
>{{i-con glyph="folder" size=14 class="has-text-grey-light"}}{{backend.path}}</a>
|
||||
<br />
|
||||
<span class="tag">
|
||||
<code>
|
||||
{{#if (eq backend.type 'plugin')}}
|
||||
{{backend.type}}: {{backend.config.plugin_name}}
|
||||
{{else}}
|
||||
{{backend.type}}
|
||||
{{/if}}
|
||||
</code>
|
||||
{{#if (eq backend.type 'plugin')}}
|
||||
{{backend.type}}: {{backend.config.plugin_name}}
|
||||
{{else}}
|
||||
{{backend.type}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<code class="has-text-grey is-size-8">
|
||||
{{#if (eq backend.options.version 2)}}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,18 @@ module.exports = function(defaults) {
|
|||
app.import('node_modules/text-encoder-lite/index.js');
|
||||
app.import('node_modules/Duration.js/duration.js');
|
||||
|
||||
app.import('node_modules/columnify/columnify.js', {
|
||||
using: [
|
||||
{ transformation: 'cjs', as: 'columnify' }
|
||||
]
|
||||
});
|
||||
|
||||
app.import('node_modules/yargs-parser/lib/tokenize-arg-string.js', {
|
||||
using: [
|
||||
{ transformation: 'cjs', as: 'yargs-parser-tokenizer' }
|
||||
]
|
||||
});
|
||||
|
||||
// Use `app.import` to add additional libraries to the generated
|
||||
// output files.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"start2": "ember server --proxy=http://localhost:8202 --port=4202",
|
||||
"test": "node scripts/start-vault.js & ember test",
|
||||
"test-oss": "yarn run test -f='!enterprise'",
|
||||
"fmt-js": "prettier-eslint --single-quote --trailing-comma es5 --print-width=110 --write {app,tests,config,lib,mirage}/**/*.js",
|
||||
"fmt-js": "prettier-eslint --single-quote --no-use-tabs --trailing-comma es5 --print-width=110 --write '{app,tests,config,lib,mirage}/**/*.js'",
|
||||
"fmt-styles": "prettier --write app/styles/**/*.*",
|
||||
"fmt": "yarn run fmt-js && yarn run fmt-styles",
|
||||
"precommit": "lint-staged"
|
||||
|
|
@ -33,19 +33,24 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"Duration.js": "icholy/Duration.js#golang_compatible",
|
||||
"autosize": "3.0.17",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.23.0",
|
||||
"base64-js": "1.2.1",
|
||||
"broccoli-asset-rev": "^2.4.5",
|
||||
"broccoli-sri-hash": "meirish/broccoli-sri-hash#rooturl",
|
||||
"bulma": "^0.5.2",
|
||||
"bulma-switch": "^0.0.1",
|
||||
"codemirror": "5.15.2",
|
||||
"cool-checkboxes-for-bulma.io": "^1.1.0",
|
||||
"ember-ajax": "^3.0.0",
|
||||
"ember-api-actions": "^0.1.8",
|
||||
"ember-basic-dropdown": "^0.33.5",
|
||||
"ember-basic-dropdown-hover": "^0.2.0",
|
||||
"ember-cli": "~2.15.0",
|
||||
"ember-cli": "~2.16.0",
|
||||
"ember-cli-autoprefixer": "^0.8.1",
|
||||
"ember-cli-babel": "^6.3.0",
|
||||
"ember-cli-cjs-transform": "^1.2.0",
|
||||
"ember-cli-clipboard": "^0.8.0",
|
||||
"ember-cli-content-security-policy": "^1.0.0",
|
||||
"ember-cli-dependency-checker": "^1.3.0",
|
||||
|
|
@ -83,18 +88,16 @@
|
|||
"ember-test-selectors": "^0.3.6",
|
||||
"ember-truth-helpers": "1.2.0",
|
||||
"ivy-codemirror": "2.1.0",
|
||||
"jsonlint": "1.6.0",
|
||||
"loader.js": "^4.2.3",
|
||||
"normalize.css": "4.1.1",
|
||||
"prettier": "^1.5.3",
|
||||
"prettier-eslint-cli": "^4.2.1",
|
||||
"qunit-dom": "^0.6.2",
|
||||
"string.prototype.startswith": "mathiasbynens/String.prototype.startsWith",
|
||||
"text-encoder-lite": "1.0.0",
|
||||
"base64-js": "1.2.1",
|
||||
"autosize": "3.0.17",
|
||||
"jsonlint": "1.6.0",
|
||||
"codemirror": "5.15.2",
|
||||
"Duration.js": "icholy/Duration.js#golang_compatible",
|
||||
"string.prototype.startswith": "mathiasbynens/String.prototype.startsWith"
|
||||
"columnify": "^1.5.4",
|
||||
"yargs-parser": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^4.5 || 6.* || >= 7.*"
|
||||
|
|
|
|||
15
ui/tests/integration/components/console/log-command-test.js
Normal file
15
ui/tests/integration/components/console/log-command-test.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
moduleForComponent('console/log-command', 'Integration | Component | console/log command', {
|
||||
integration: true,
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
const commandText = 'list this/path';
|
||||
this.set('content', commandText);
|
||||
|
||||
this.render(hbs`{{console/log-command content=content}}`);
|
||||
|
||||
assert.dom('pre').includesText(commandText);
|
||||
});
|
||||
13
ui/tests/integration/components/console/log-error-test.js
Normal file
13
ui/tests/integration/components/console/log-error-test.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
moduleForComponent('console/log-error', 'Integration | Component | console/log error', {
|
||||
integration: true,
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
const errorText = 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404';
|
||||
this.set('content', errorText);
|
||||
this.render(hbs`{{console/log-error content=content}}`);
|
||||
assert.dom('pre').includesText(errorText);
|
||||
});
|
||||
24
ui/tests/integration/components/console/log-json-test.js
Normal file
24
ui/tests/integration/components/console/log-json-test.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
moduleForComponent('console/log-json', 'Integration | Component | console/log json', {
|
||||
integration: true,
|
||||
|
||||
beforeEach() {
|
||||
this.inject.service('code-mirror', { as: 'codeMirror' });
|
||||
},
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||
const objectContent = { one: 'two', three: 'four', seven: { five: 'six' }, eight: [5, 6] };
|
||||
const expectedText = JSON.stringify(objectContent, null, 2);
|
||||
|
||||
this.set('content', objectContent);
|
||||
|
||||
this.render(hbs`{{console/log-json content=content}}`);
|
||||
const instance = this.codeMirror.instanceFor(this.$('[data-test-component=json-editor]').attr('id'));
|
||||
|
||||
assert.equal(instance.getValue(), expectedText);
|
||||
});
|
||||
19
ui/tests/integration/components/console/log-list-test.js
Normal file
19
ui/tests/integration/components/console/log-list-test.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
moduleForComponent('console/log-list', 'Integration | Component | console/log list', {
|
||||
integration: true,
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||
const listContent = { keys: ['one', 'two'] };
|
||||
const expectedText = 'Keys\none\ntwo';
|
||||
|
||||
this.set('content', listContent);
|
||||
|
||||
this.render(hbs`{{console/log-list content=content}}`);
|
||||
|
||||
assert.dom('pre').includesText(`${expectedText}`);
|
||||
});
|
||||
27
ui/tests/integration/components/console/log-object-test.js
Normal file
27
ui/tests/integration/components/console/log-object-test.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import columnify from 'columnify';
|
||||
import { capitalize } from 'vault/helpers/capitalize';
|
||||
import { stringifyObjectValues } from 'vault/components/console/log-object';
|
||||
|
||||
moduleForComponent('console/log-object', 'Integration | Component | console/log object', {
|
||||
integration: true,
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
const objectContent = { one: 'two', three: 'four', seven: { five: 'six' }, eight: [5, 6] };
|
||||
const data = { one: 'two', three: 'four', seven: { five: 'six' }, eight: [5, 6] };
|
||||
stringifyObjectValues(data);
|
||||
const expectedText = columnify(data, {
|
||||
preserveNewLines: true,
|
||||
headingTransform: function(heading) {
|
||||
return capitalize([heading]);
|
||||
},
|
||||
});
|
||||
|
||||
this.set('content', objectContent);
|
||||
|
||||
this.render(hbs`{{console/log-object content=content}}`);
|
||||
|
||||
assert.dom('pre').includesText(`${expectedText}`);
|
||||
});
|
||||
17
ui/tests/integration/components/console/log-text-test.js
Normal file
17
ui/tests/integration/components/console/log-text-test.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
moduleForComponent('console/log-text', 'Integration | Component | console/log text', {
|
||||
integration: true,
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||
const text = 'Success! You did a thing!';
|
||||
this.set('content', text);
|
||||
|
||||
this.render(hbs`{{console/log-text content=content}}`);
|
||||
|
||||
assert.dom('pre').includesText(text);
|
||||
});
|
||||
118
ui/tests/integration/components/console/ui-panel-test.js
Normal file
118
ui/tests/integration/components/console/ui-panel-test.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import wait from 'ember-test-helpers/wait';
|
||||
import uiPanel from 'vault/tests/pages/components/console/ui-panel';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
const component = create(uiPanel);
|
||||
|
||||
moduleForComponent('console/ui-panel', 'Integration | Component | console/ui panel', {
|
||||
integration: true,
|
||||
|
||||
beforeEach() {
|
||||
component.setContext(this);
|
||||
},
|
||||
|
||||
afterEach() {
|
||||
component.removeContext();
|
||||
},
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
this.render(hbs`{{console/ui-panel}}`);
|
||||
assert.ok(component.hasInput);
|
||||
});
|
||||
|
||||
test('it clears console input on enter', function(assert) {
|
||||
this.render(hbs`{{console/ui-panel}}`);
|
||||
component.consoleInput('list this/thing/here').enter();
|
||||
return wait().then(() => {
|
||||
assert.equal(component.consoleInputValue, '', 'empties input field on enter');
|
||||
});
|
||||
});
|
||||
|
||||
test('it clears the log when using clear command', function(assert) {
|
||||
this.render(hbs`{{console/ui-panel}}`);
|
||||
component.consoleInput('list this/thing/here').enter();
|
||||
component.consoleInput('list this/other/thing').enter();
|
||||
component.consoleInput('read another/thing').enter();
|
||||
wait().then(() => {
|
||||
assert.notEqual(component.logOutput, '', 'there is output in the log');
|
||||
component.consoleInput('clear').enter();
|
||||
});
|
||||
|
||||
wait().then(() => component.up());
|
||||
return wait().then(() => {
|
||||
assert.equal(component.logOutput, '', 'clears the output log');
|
||||
assert.equal(
|
||||
component.consoleInputValue,
|
||||
'clear',
|
||||
'populates console input with previous command on up after enter'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('it adds command to history on enter', function(assert) {
|
||||
this.render(hbs`{{console/ui-panel}}`);
|
||||
component.consoleInput('list this/thing/here').enter();
|
||||
wait().then(() => component.up());
|
||||
wait().then(() => {
|
||||
assert.equal(
|
||||
component.consoleInputValue,
|
||||
'list this/thing/here',
|
||||
'populates console input with previous command on up after enter'
|
||||
);
|
||||
});
|
||||
wait().then(() => component.down());
|
||||
return wait().then(() => {
|
||||
assert.equal(component.consoleInputValue, '', 'populates console input with next command on down');
|
||||
});
|
||||
});
|
||||
|
||||
test('it cycles through history with more than one command', function(assert) {
|
||||
this.render(hbs`{{console/ui-panel}}`);
|
||||
component.consoleInput('list this/thing/here').enter();
|
||||
wait().then(() => component.consoleInput('read that/thing/there').enter());
|
||||
wait().then(() => component.consoleInput('qwerty').enter());
|
||||
|
||||
wait().then(() => component.up());
|
||||
wait().then(() => {
|
||||
assert.equal(
|
||||
component.consoleInputValue,
|
||||
'qwerty',
|
||||
'populates console input with previous command on up after enter'
|
||||
);
|
||||
});
|
||||
wait().then(() => component.up());
|
||||
wait().then(() => {
|
||||
assert.equal(
|
||||
component.consoleInputValue,
|
||||
'read that/thing/there',
|
||||
'populates console input with previous command on up'
|
||||
);
|
||||
});
|
||||
wait().then(() => component.up());
|
||||
wait().then(() => {
|
||||
assert.equal(
|
||||
component.consoleInputValue,
|
||||
'list this/thing/here',
|
||||
'populates console input with previous command on up'
|
||||
);
|
||||
});
|
||||
wait().then(() => component.up());
|
||||
wait().then(() => {
|
||||
assert.equal(
|
||||
component.consoleInputValue,
|
||||
'qwerty',
|
||||
'populates console input with initial command if cycled through all previous commands'
|
||||
);
|
||||
});
|
||||
wait().then(() => component.down());
|
||||
return wait().then(() => {
|
||||
assert.equal(
|
||||
component.consoleInputValue,
|
||||
'',
|
||||
'clears console input if down pressed after history is on most recent command'
|
||||
);
|
||||
});
|
||||
});
|
||||
18
ui/tests/pages/components/console/ui-panel.js
Normal file
18
ui/tests/pages/components/console/ui-panel.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { text, triggerable, fillable, value, isPresent } from 'ember-cli-page-object';
|
||||
import keys from 'vault/lib/keycodes';
|
||||
|
||||
export default {
|
||||
consoleInput: fillable('[data-test-component="console/command-input"] input'),
|
||||
consoleInputValue: value('[data-test-component="console/command-input"] input'),
|
||||
logOutput: text('[data-test-component="console/output-log"]'),
|
||||
up: triggerable('keyup', '[data-test-component="console/command-input"] input', {
|
||||
eventProperties: { keyCode: keys.UP },
|
||||
}),
|
||||
down: triggerable('keyup', '[data-test-component="console/command-input"] input', {
|
||||
eventProperties: { keyCode: keys.DOWN },
|
||||
}),
|
||||
enter: triggerable('keyup', '[data-test-component="console/command-input"] input', {
|
||||
eventProperties: { keyCode: keys.ENTER },
|
||||
}),
|
||||
hasInput: isPresent('[data-test-component="console/command-input"] input'),
|
||||
};
|
||||
13
ui/tests/unit/adapters/console-test.js
Normal file
13
ui/tests/unit/adapters/console-test.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('adapter:console', 'Unit | Adapter | console', {
|
||||
needs: ['service:auth', 'service:flash-messages', 'service:version'],
|
||||
});
|
||||
|
||||
test('it builds the correct URL', function(assert) {
|
||||
let adapter = this.subject();
|
||||
let sysPath = 'sys/health';
|
||||
let awsPath = 'aws/roles/my-other-role';
|
||||
assert.equal(adapter.buildURL(sysPath), '/v1/sys/health');
|
||||
assert.equal(adapter.buildURL(awsPath), '/v1/aws/roles/my-other-role');
|
||||
});
|
||||
328
ui/tests/unit/lib/console-helpers-test.js
Normal file
328
ui/tests/unit/lib/console-helpers-test.js
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
import { module, test } from 'qunit';
|
||||
import {
|
||||
parseCommand,
|
||||
extractDataAndFlags,
|
||||
logFromResponse,
|
||||
logFromError,
|
||||
logErrorFromInput,
|
||||
} from 'vault/lib/console-helpers';
|
||||
|
||||
module('lib/console-helpers', 'Unit | Lib | console helpers');
|
||||
|
||||
const testCommands = [
|
||||
{
|
||||
name: 'write with data',
|
||||
command: `vault write aws/config/root \
|
||||
access_key=AKIAJWVN5Z4FOFT7NLNA \
|
||||
secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i \
|
||||
region=us-east-1`,
|
||||
expected: [
|
||||
'write',
|
||||
[],
|
||||
'aws/config/root',
|
||||
[
|
||||
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
||||
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||
'region=us-east-1',
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'read with field',
|
||||
command: `vault read -field=access_key aws/creds/my-role`,
|
||||
expected: ['read', ['-field=access_key'], 'aws/creds/my-role', []],
|
||||
},
|
||||
];
|
||||
|
||||
testCommands.forEach(function(testCase) {
|
||||
test(`#parseCommand: ${testCase.name}`, function(assert) {
|
||||
let result = parseCommand(testCase.command);
|
||||
assert.deepEqual(result, testCase.expected);
|
||||
});
|
||||
});
|
||||
|
||||
test('#parseCommand: invalid commands', function(assert) {
|
||||
let command = 'vault kv get foo';
|
||||
let result = parseCommand(command);
|
||||
assert.equal(result, false, 'parseCommand returns false by default');
|
||||
|
||||
assert.throws(
|
||||
() => {
|
||||
parseCommand(command, true);
|
||||
},
|
||||
/invalid command/,
|
||||
'throws on invalid command when `shouldThrow` is true'
|
||||
);
|
||||
});
|
||||
|
||||
const testExtractCases = [
|
||||
{
|
||||
name: 'data fields',
|
||||
input: [
|
||||
[
|
||||
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
||||
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||
'region=us-east-1',
|
||||
],
|
||||
[],
|
||||
],
|
||||
expected: {
|
||||
data: {
|
||||
access_key: 'AKIAJWVN5Z4FOFT7NLNA',
|
||||
secret_key: 'R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
||||
region: 'us-east-1',
|
||||
},
|
||||
flags: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'repeated data and a flag',
|
||||
input: [['allowed_domains=example.com', 'allowed_domains=foo.example.com'], ['-wrap-ttl=2h']],
|
||||
expected: {
|
||||
data: {
|
||||
allowed_domains: ['example.com', 'foo.example.com'],
|
||||
},
|
||||
flags: {
|
||||
wrapTTL: '2h',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'data with more than one equals sign',
|
||||
input: [['foo=bar=baz', 'foo=baz=bop', 'some=value=val'], []],
|
||||
expected: {
|
||||
data: {
|
||||
foo: ['bar=baz', 'baz=bop'],
|
||||
some: 'value=val',
|
||||
},
|
||||
flags: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
testExtractCases.forEach(function(testCase) {
|
||||
test(`#extractDataAndFlags: ${testCase.name}`, function(assert) {
|
||||
let { data, flags } = extractDataAndFlags(...testCase.input);
|
||||
assert.deepEqual(data, testCase.expected.data, 'has expected data');
|
||||
assert.deepEqual(flags, testCase.expected.flags, 'has expected flags');
|
||||
});
|
||||
});
|
||||
|
||||
let testResponseCases = [
|
||||
{
|
||||
name: 'write response, no content',
|
||||
args: [null, 'foo/bar', 'write', {}],
|
||||
expectedData: {
|
||||
type: 'success',
|
||||
content: 'Success! Data written to: foo/bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete response, no content',
|
||||
args: [null, 'foo/bar', 'delete', {}],
|
||||
expectedData: {
|
||||
type: 'success',
|
||||
content: 'Success! Data deleted (if it existed) at: foo/bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'write, with content',
|
||||
args: [{ data: { one: 'two' } }, 'foo/bar', 'write', {}],
|
||||
expectedData: {
|
||||
type: 'object',
|
||||
content: { one: 'two' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with wrap-ttl flag',
|
||||
args: [{ wrap_info: { one: 'two' } }, 'foo/bar', 'read', { wrapTTL: '1h' }],
|
||||
expectedData: {
|
||||
type: 'object',
|
||||
content: { one: 'two' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with -format=json flag and wrap-ttl flag',
|
||||
args: [{ foo: 'bar', wrap_info: { one: 'two' } }, 'foo/bar', 'read', { format: 'json', wrapTTL: '1h' }],
|
||||
expectedData: {
|
||||
type: 'json',
|
||||
content: { foo: 'bar', wrap_info: { one: 'two' } },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with -format=json and -field flags',
|
||||
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { format: 'json', field: 'one' }],
|
||||
expectedData: {
|
||||
type: 'json',
|
||||
content: 'two',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with -format=json and -field, and -wrap-ttl flags',
|
||||
args: [
|
||||
{ foo: 'bar', wrap_info: { one: 'two' } },
|
||||
'foo/bar',
|
||||
'read',
|
||||
{ format: 'json', wrapTTL: '1h', field: 'one' },
|
||||
],
|
||||
expectedData: {
|
||||
type: 'json',
|
||||
content: 'two',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with string field flag and wrap-ttl flag',
|
||||
args: [{ foo: 'bar', wrap_info: { one: 'two' } }, 'foo/bar', 'read', { field: 'one', wrapTTL: '1h' }],
|
||||
expectedData: {
|
||||
type: 'text',
|
||||
content: 'two',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with object field flag and wrap-ttl flag',
|
||||
args: [
|
||||
{ foo: 'bar', wrap_info: { one: { two: 'three' } } },
|
||||
'foo/bar',
|
||||
'read',
|
||||
{ field: 'one', wrapTTL: '1h' },
|
||||
],
|
||||
expectedData: {
|
||||
type: 'object',
|
||||
content: { two: 'three' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with response data and string field flag',
|
||||
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { field: 'one', wrapTTL: '1h' }],
|
||||
expectedData: {
|
||||
type: 'text',
|
||||
content: 'two',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with response data and object field flag ',
|
||||
args: [
|
||||
{ foo: 'bar', data: { one: { two: 'three' } } },
|
||||
'foo/bar',
|
||||
'read',
|
||||
{ field: 'one', wrapTTL: '1h' },
|
||||
],
|
||||
expectedData: {
|
||||
type: 'object',
|
||||
content: { two: 'three' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'response with data',
|
||||
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', {}],
|
||||
expectedData: {
|
||||
type: 'object',
|
||||
content: { one: 'two' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with response data, field flag, and field missing',
|
||||
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { field: 'foo' }],
|
||||
expectedData: {
|
||||
type: 'error',
|
||||
content: 'Field "foo" not present in secret',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with response data and auth block',
|
||||
args: [{ data: { one: 'two' }, auth: { three: 'four' } }, 'auth/token/create', 'write', {}],
|
||||
expectedData: {
|
||||
type: 'object',
|
||||
content: { three: 'four' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with -field and -format with an object field',
|
||||
args: [{ data: { one: { three: 'two' } } }, 'sys/mounts', 'read', { field: 'one', format: 'json' }],
|
||||
expectedData: {
|
||||
type: 'json',
|
||||
content: { three: 'two' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'with -field and -format with a string field',
|
||||
args: [{ data: { one: 'two' } }, 'sys/mounts', 'read', { field: 'one', format: 'json' }],
|
||||
expectedData: {
|
||||
type: 'json',
|
||||
content: 'two',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
testResponseCases.forEach(function(testCase) {
|
||||
test(`#logFromResponse: ${testCase.name}`, function(assert) {
|
||||
let data = logFromResponse(...testCase.args);
|
||||
assert.deepEqual(data, testCase.expectedData);
|
||||
});
|
||||
});
|
||||
|
||||
let testErrorCases = [
|
||||
{
|
||||
name: 'AdapterError write',
|
||||
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'write'],
|
||||
expectedContent: 'Error writing to: sys/foo.\nURL: v1/sys/foo\nCode: 404',
|
||||
},
|
||||
{
|
||||
name: 'AdapterError read',
|
||||
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'read'],
|
||||
expectedContent: 'Error reading from: sys/foo.\nURL: v1/sys/foo\nCode: 404',
|
||||
},
|
||||
{
|
||||
name: 'AdapterError list',
|
||||
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'list'],
|
||||
expectedContent: 'Error listing: sys/foo.\nURL: v1/sys/foo\nCode: 404',
|
||||
},
|
||||
{
|
||||
name: 'AdapterError delete',
|
||||
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'delete'],
|
||||
expectedContent: 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404',
|
||||
},
|
||||
{
|
||||
name: 'VaultError single error',
|
||||
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: ['no client token'] }, 'sys/foo', 'delete'],
|
||||
expectedContent: 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404\nErrors:\n no client token',
|
||||
},
|
||||
{
|
||||
name: 'VaultErrors multiple errors',
|
||||
args: [
|
||||
{ httpStatus: 404, path: 'v1/sys/foo', errors: ['no client token', 'this is an error'] },
|
||||
'sys/foo',
|
||||
'delete',
|
||||
],
|
||||
expectedContent:
|
||||
'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404\nErrors:\n no client token\n this is an error',
|
||||
},
|
||||
];
|
||||
|
||||
testErrorCases.forEach(function(testCase) {
|
||||
test(`#logFromError: ${testCase.name}`, function(assert) {
|
||||
let data = logFromError(...testCase.args);
|
||||
assert.deepEqual(data, { type: 'error', content: testCase.expectedContent }, 'returns the expected data');
|
||||
});
|
||||
});
|
||||
|
||||
const testCommandCases = [
|
||||
{
|
||||
name: 'errors when command does not include a path',
|
||||
args: [],
|
||||
expectedContent: 'A path is required to make a request.',
|
||||
},
|
||||
{
|
||||
name: 'errors when write command does not include data and does not have force tag',
|
||||
args: ['foo/bar', 'write', {}, []],
|
||||
expectedContent: 'Must supply data or use -force',
|
||||
},
|
||||
];
|
||||
|
||||
testCommandCases.forEach(function(testCase) {
|
||||
test(`#logErrorFromInput: ${testCase.name}`, function(assert) {
|
||||
let data = logErrorFromInput(...testCase.args);
|
||||
|
||||
assert.deepEqual(data, { type: 'error', content: testCase.expectedContent }, 'returns the pcorrect data');
|
||||
});
|
||||
});
|
||||
94
ui/tests/unit/services/console-test.js
Normal file
94
ui/tests/unit/services/console-test.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
import { sanitizePath, ensureTrailingSlash } from 'vault/services/console';
|
||||
import sinon from 'sinon';
|
||||
|
||||
moduleFor('service:console', 'Unit | Service | console', {
|
||||
needs: ['service:auth'],
|
||||
beforeEach() {},
|
||||
afterEach() {},
|
||||
});
|
||||
|
||||
test('#sanitizePath', function(assert) {
|
||||
assert.equal(sanitizePath(' /foo/bar/baz/ '), 'foo/bar/baz', 'removes spaces and slashs on either side');
|
||||
assert.equal(sanitizePath('//foo/bar/baz/'), 'foo/bar/baz', 'removes more than one slash');
|
||||
});
|
||||
|
||||
test('#ensureTrailingSlash', function(assert) {
|
||||
assert.equal(ensureTrailingSlash('foo/bar'), 'foo/bar/', 'adds trailing slash');
|
||||
assert.equal(ensureTrailingSlash('baz/'), 'baz/', 'keeps trailing slash if there is one');
|
||||
});
|
||||
|
||||
let testCases = [
|
||||
{
|
||||
method: 'read',
|
||||
args: ['/sys/health', {}],
|
||||
expectedURL: 'sys/health',
|
||||
expectedVerb: 'GET',
|
||||
expectedOptions: { data: undefined, wrapTTL: undefined },
|
||||
},
|
||||
|
||||
{
|
||||
method: 'read',
|
||||
args: ['/secrets/foo/bar', {}, '30m'],
|
||||
expectedURL: 'secrets/foo/bar',
|
||||
expectedVerb: 'GET',
|
||||
expectedOptions: { data: undefined, wrapTTL: '30m' },
|
||||
},
|
||||
|
||||
{
|
||||
method: 'write',
|
||||
args: ['aws/roles/my-other-role', { arn: 'arn=arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess' }],
|
||||
expectedURL: 'aws/roles/my-other-role',
|
||||
expectedVerb: 'POST',
|
||||
expectedOptions: {
|
||||
data: { arn: 'arn=arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess' },
|
||||
wrapTTL: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
method: 'list',
|
||||
args: ['secret/mounts', {}],
|
||||
expectedURL: 'secret/mounts/',
|
||||
expectedVerb: 'GET',
|
||||
expectedOptions: { data: { list: true }, wrapTTL: undefined },
|
||||
},
|
||||
|
||||
{
|
||||
method: 'list',
|
||||
args: ['secret/mounts', {}, '1h'],
|
||||
expectedURL: 'secret/mounts/',
|
||||
expectedVerb: 'GET',
|
||||
expectedOptions: { data: { list: true }, wrapTTL: '1h' },
|
||||
},
|
||||
|
||||
{
|
||||
method: 'delete',
|
||||
args: ['secret/secrets/kv'],
|
||||
expectedURL: 'secret/secrets/kv',
|
||||
expectedVerb: 'DELETE',
|
||||
expectedOptions: { data: undefined, wrapTTL: undefined },
|
||||
},
|
||||
];
|
||||
|
||||
test('it reads, writes, lists, deletes', function(assert) {
|
||||
let ajax = sinon.stub();
|
||||
let uiConsole = this.subject({
|
||||
adapter() {
|
||||
return {
|
||||
buildURL(url) {
|
||||
return url;
|
||||
},
|
||||
ajax,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
uiConsole[testCase.method](...testCase.args);
|
||||
let [url, verb, options] = ajax.lastCall.args;
|
||||
assert.equal(url, testCase.expectedURL, `${testCase.method}: uses trimmed passed url`);
|
||||
assert.equal(verb, testCase.expectedVerb, `${testCase.method}: uses the correct verb`);
|
||||
assert.deepEqual(options, testCase.expectedOptions, `${testCase.method}: uses the correct options`);
|
||||
});
|
||||
});
|
||||
824
ui/yarn.lock
824
ui/yarn.lock
File diff suppressed because it is too large
Load diff
|
|
@ -935,7 +935,7 @@ func TestTokenStore_RevokeSelf(t *testing.T) {
|
|||
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
lookup := []string{ent1.ID, ent2.ID, ent3.ID, ent4.ID}
|
||||
for _, id := range lookup {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue