Moving UI assets to OSS
This commit is contained in:
parent
f5ba4796f5
commit
7c92bb9b1d
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"directory": "bower_components",
|
||||||
|
"analytics": false
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.hbs]
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.{diff,md}]
|
||||||
|
trim_trailing_whitespace = false
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
Ember CLI sends analytics information by default. The data is completely
|
||||||
|
anonymous, but there are times when you might want to disable this behavior.
|
||||||
|
|
||||||
|
Setting `disableAnalytics` to true will prevent any data from being sent.
|
||||||
|
*/
|
||||||
|
"disableAnalytics": true,
|
||||||
|
"output-path": "../pkg/web_ui"
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2017,
|
||||||
|
sourceType: 'module',
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"experimentalObjectRestSpread": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extends: 'eslint:recommended',
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": ["error", { "ignoreRestSiblings": true }]
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
base64js: true,
|
||||||
|
TextEncoderLite: true,
|
||||||
|
TextDecoderLite: true,
|
||||||
|
Duration: true
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/bower_components
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.sass-cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage/*
|
||||||
|
/libpeerconnection.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-error.log
|
||||||
|
testem.log
|
||||||
|
|
||||||
|
# ember-try
|
||||||
|
.node_modules.ember-try/
|
||||||
|
bower.json.ember-try
|
||||||
|
package.json.ember-try
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "4"
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.npm
|
||||||
|
- $HOME/.cache # includes bowers cache
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- npm config set spin false
|
||||||
|
- npm install -g bower
|
||||||
|
- bower --version
|
||||||
|
- npm install phantomjs-prebuilt
|
||||||
|
- node_modules/phantomjs-prebuilt/bin/phantomjs --version
|
||||||
|
|
||||||
|
install:
|
||||||
|
- npm install
|
||||||
|
- bower install
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm test
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"ignore_dirs": ["tmp", "dist"]
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
# vault
|
||||||
|
|
||||||
|
This README outlines the details of collaborating on this Ember application.
|
||||||
|
A short introduction of this app could easily go here.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You will need the following things properly installed on your computer.
|
||||||
|
|
||||||
|
* [Git](https://git-scm.com/)
|
||||||
|
* [Node.js](https://nodejs.org/) (with NPM)
|
||||||
|
* [Bower](https://bower.io/)
|
||||||
|
* [Ember CLI](https://ember-cli.com/)
|
||||||
|
|
||||||
|
|
||||||
|
## Running / Development
|
||||||
|
|
||||||
|
* `ember serve`
|
||||||
|
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
||||||
|
|
||||||
|
### Code Generators
|
||||||
|
|
||||||
|
Make use of the many generators for code, try `ember help generate` for more details
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
* `ember test`
|
||||||
|
* `ember test --server`
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
* `ember build` (development)
|
||||||
|
* `ember build --environment production` (production)
|
||||||
|
|
||||||
|
### Deploying
|
||||||
|
|
||||||
|
Specify what it takes to deploy your app.
|
||||||
|
|
||||||
|
## Further Reading / Useful Links
|
||||||
|
|
||||||
|
* [ember.js](http://emberjs.com/)
|
||||||
|
* [ember-cli](https://ember-cli.com/)
|
||||||
|
* Development Browser Extensions
|
||||||
|
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||||
|
* [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)
|
|
@ -0,0 +1,86 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
import fetch from 'fetch';
|
||||||
|
|
||||||
|
const POLLING_URL_PATTERNS = ['sys/seal-status', 'sys/health', 'sys/replication/status'];
|
||||||
|
|
||||||
|
export default DS.RESTAdapter.extend({
|
||||||
|
auth: Ember.inject.service(),
|
||||||
|
|
||||||
|
flashMessages: Ember.inject.service(),
|
||||||
|
|
||||||
|
namespace: 'v1/sys',
|
||||||
|
|
||||||
|
shouldReloadAll() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldReloadRecord() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldBackgroundReloadRecord() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_preRequest(url, options) {
|
||||||
|
const token = this.get('auth.currentToken');
|
||||||
|
if (token && !options.unauthenticated) {
|
||||||
|
options.headers = Ember.assign(options.headers || {}, {
|
||||||
|
'X-Vault-Token': token,
|
||||||
|
});
|
||||||
|
if (options.wrapTTL) {
|
||||||
|
Ember.assign(options.headers, { 'X-Vault-Wrap-TTL': options.wrapTTL });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const isPolling = POLLING_URL_PATTERNS.some(str => url.includes(str));
|
||||||
|
if (!isPolling) {
|
||||||
|
this.get('auth').setLastFetch(Date.now());
|
||||||
|
}
|
||||||
|
if (this.get('auth.shouldRenew')) {
|
||||||
|
this.get('auth').renew();
|
||||||
|
}
|
||||||
|
options.timeout = 60000;
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
|
||||||
|
ajax(url, type, options = {}) {
|
||||||
|
let opts = this._preRequest(url, options);
|
||||||
|
|
||||||
|
return this._super(url, type, opts).then((...args) => {
|
||||||
|
const [resp] = args;
|
||||||
|
if (resp && resp.warnings) {
|
||||||
|
const flash = this.get('flashMessages');
|
||||||
|
resp.warnings.forEach(message => {
|
||||||
|
flash.info(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Ember.RSVP.resolve(...args);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// for use on endpoints that don't return JSON responses
|
||||||
|
rawRequest(url, type, options = {}) {
|
||||||
|
let opts = this._preRequest(url, options);
|
||||||
|
return fetch(url, {
|
||||||
|
method: type | 'GET',
|
||||||
|
headers: opts.headers | {},
|
||||||
|
}).then(response => {
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return Ember.RSVP.resolve(response);
|
||||||
|
} else {
|
||||||
|
return Ember.RSVP.reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleResponse(status, headers, payload, requestData) {
|
||||||
|
const returnVal = this._super(...arguments);
|
||||||
|
// ember data errors don't have the status code, so we add it here
|
||||||
|
if (returnVal instanceof DS.AdapterError) {
|
||||||
|
Ember.set(returnVal, 'httpStatus', status);
|
||||||
|
Ember.set(returnVal, 'path', requestData.url);
|
||||||
|
}
|
||||||
|
return returnVal;
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
import ApplicationAdapter from '../application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: '/v1/auth',
|
||||||
|
|
||||||
|
pathForType(modelType) {
|
||||||
|
// we want the last part of the path
|
||||||
|
const type = modelType.split('/').pop();
|
||||||
|
if (type === 'identity-whitelist' || type === 'roletag-blacklist') {
|
||||||
|
return `tidy/${type}`;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
|
||||||
|
buildURL(modelName, id, snapshot) {
|
||||||
|
const backendId = id ? id : snapshot.belongsTo('backend').id;
|
||||||
|
let url = `${this.get('namespace')}/${backendId}/config`;
|
||||||
|
// aws has a lot more config endpoints
|
||||||
|
if (modelName.includes('aws')) {
|
||||||
|
url = `${url}/${this.pathForType(modelName)}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot) {
|
||||||
|
const id = snapshot.belongsTo('backend').id;
|
||||||
|
return this._super(...arguments).then(() => {
|
||||||
|
return { id };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord(store, type, snapshot) {
|
||||||
|
const id = snapshot.belongsTo('backend').id;
|
||||||
|
return this._super(...arguments).then(() => {
|
||||||
|
return { id };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from '../_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from '../_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from '../_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from './_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from './_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from './_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from './_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from './_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AuthConfig from './_base';
|
||||||
|
export default AuthConfig.extend();
|
|
@ -0,0 +1,41 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
url(path) {
|
||||||
|
const url = `${this.buildURL()}/auth`;
|
||||||
|
return path ? url + '/' + path : url;
|
||||||
|
},
|
||||||
|
|
||||||
|
// used in updateRecord on the model#tune action
|
||||||
|
pathForType() {
|
||||||
|
return 'mounts/auth';
|
||||||
|
},
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return this.ajax(this.url(), 'GET').catch(e => {
|
||||||
|
if (e instanceof DS.AdapterError) {
|
||||||
|
Ember.set(e, 'policyPath', 'sys/auth');
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot);
|
||||||
|
const path = snapshot.attr('path');
|
||||||
|
|
||||||
|
return this.ajax(this.url(path), 'POST', { data }).then(() => {
|
||||||
|
// ember data doesn't like 204s if it's not a DELETE
|
||||||
|
return {
|
||||||
|
data: Ember.assign({}, data, { path: path + '/', id: path }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForDeleteRecord(id, modelName, snapshot) {
|
||||||
|
return this.url(snapshot.id);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
pathForType() {
|
||||||
|
return 'capabilities-self';
|
||||||
|
},
|
||||||
|
|
||||||
|
findRecord(store, type, id) {
|
||||||
|
return this.ajax(this.buildURL(type), 'POST', { data: { path: id } }).catch(e => {
|
||||||
|
if (e instanceof DS.AdapterError) {
|
||||||
|
Ember.set(e, 'policyPath', 'sys/capabilities-self');
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
const { id } = query;
|
||||||
|
return this.findRecord(store, type, id).then(resp => {
|
||||||
|
resp.path = id;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,187 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
|
||||||
|
const { AdapterError } = DS;
|
||||||
|
const { assert, inject } = Ember;
|
||||||
|
|
||||||
|
const ENDPOINTS = ['health', 'seal-status', 'tokens', 'token', 'seal', 'unseal', 'init', 'capabilities-self'];
|
||||||
|
|
||||||
|
const REPLICATION_ENDPOINTS = {
|
||||||
|
reindex: 'reindex',
|
||||||
|
recover: 'recover',
|
||||||
|
status: 'status',
|
||||||
|
|
||||||
|
primary: ['enable', 'disable', 'demote', 'secondary-token', 'revoke-secondary'],
|
||||||
|
|
||||||
|
secondary: ['enable', 'disable', 'promote', 'update-primary'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const REPLICATION_MODES = ['dr', 'performance'];
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
version: inject.service(),
|
||||||
|
shouldBackgroundReloadRecord() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
findRecord(store, type, id, snapshot) {
|
||||||
|
let fetches = {
|
||||||
|
health: this.health(),
|
||||||
|
sealStatus: this.sealStatus().catch(e => e),
|
||||||
|
};
|
||||||
|
if (this.get('version.isEnterprise')) {
|
||||||
|
fetches.replicationStatus = this.replicationStatus().catch(e => e);
|
||||||
|
}
|
||||||
|
return Ember.RSVP.hash(fetches).then(({ health, sealStatus, replicationStatus }) => {
|
||||||
|
let ret = {
|
||||||
|
id,
|
||||||
|
name: snapshot.attr('name'),
|
||||||
|
};
|
||||||
|
ret = Ember.assign(ret, health);
|
||||||
|
if (sealStatus instanceof AdapterError === false) {
|
||||||
|
ret = Ember.assign(ret, { nodes: [sealStatus] });
|
||||||
|
}
|
||||||
|
if (replicationStatus && replicationStatus instanceof AdapterError === false) {
|
||||||
|
ret = Ember.assign(ret, replicationStatus.data);
|
||||||
|
}
|
||||||
|
return Ember.RSVP.resolve(ret);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
pathForType(type) {
|
||||||
|
return type === 'cluster' ? 'clusters' : Ember.String.pluralize(type);
|
||||||
|
},
|
||||||
|
|
||||||
|
health() {
|
||||||
|
return this.ajax(this.urlFor('health'), 'GET', {
|
||||||
|
data: { standbycode: 200, sealedcode: 200, uninitcode: 200, drsecondarycode: 200 },
|
||||||
|
unauthenticated: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
features() {
|
||||||
|
return this.ajax(`${this.buildURL()}/license/features`, 'GET', {
|
||||||
|
unauthenticated: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sealStatus() {
|
||||||
|
return this.ajax(this.urlFor('seal-status'), 'GET', { unauthenticated: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
seal() {
|
||||||
|
return this.ajax(this.urlFor('seal'), 'PUT');
|
||||||
|
},
|
||||||
|
|
||||||
|
unseal(data) {
|
||||||
|
return this.ajax(this.urlFor('unseal'), 'PUT', {
|
||||||
|
data,
|
||||||
|
unauthenticated: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
initCluster(data) {
|
||||||
|
return this.ajax(this.urlFor('init'), 'PUT', {
|
||||||
|
data,
|
||||||
|
unauthenticated: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
authenticate({ backend, data }) {
|
||||||
|
const { token, password, username, path } = data;
|
||||||
|
const url = this.urlForAuth(backend, username, path);
|
||||||
|
const verb = backend === 'token' ? 'GET' : 'POST';
|
||||||
|
let options = {
|
||||||
|
unauthenticated: true,
|
||||||
|
};
|
||||||
|
if (backend === 'token') {
|
||||||
|
options.headers = {
|
||||||
|
'X-Vault-Token': token,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
options.data = token ? { token, password } : { password };
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ajax(url, verb, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
urlFor(endpoint) {
|
||||||
|
if (!ENDPOINTS.includes(endpoint)) {
|
||||||
|
throw new Error(
|
||||||
|
`Calls to a ${endpoint} endpoint are not currently allowed in the vault cluster adapater`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return `${this.buildURL()}/${endpoint}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForAuth(type, username, path) {
|
||||||
|
const authBackend = type.toLowerCase();
|
||||||
|
const authURLs = {
|
||||||
|
github: 'login',
|
||||||
|
userpass: `login/${encodeURIComponent(username)}`,
|
||||||
|
ldap: `login/${encodeURIComponent(username)}`,
|
||||||
|
okta: `login/${encodeURIComponent(username)}`,
|
||||||
|
token: 'lookup-self',
|
||||||
|
};
|
||||||
|
const urlSuffix = authURLs[authBackend];
|
||||||
|
const urlPrefix = path && authBackend !== 'token' ? path : authBackend;
|
||||||
|
if (!urlSuffix) {
|
||||||
|
throw new Error(`There is no auth url for ${type}.`);
|
||||||
|
}
|
||||||
|
return `/v1/auth/${urlPrefix}/${urlSuffix}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForReplication(replicationMode, clusterMode, endpoint) {
|
||||||
|
let suffix;
|
||||||
|
const errString = `Calls to replication ${endpoint} endpoint are not currently allowed in the vault cluster adapater`;
|
||||||
|
if (clusterMode) {
|
||||||
|
assert(errString, REPLICATION_ENDPOINTS[clusterMode].includes(endpoint));
|
||||||
|
suffix = `${replicationMode}/${clusterMode}/${endpoint}`;
|
||||||
|
} else {
|
||||||
|
assert(errString, REPLICATION_ENDPOINTS[endpoint]);
|
||||||
|
suffix = `${endpoint}`;
|
||||||
|
}
|
||||||
|
return `${this.buildURL()}/replication/${suffix}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
replicationStatus() {
|
||||||
|
return this.ajax(`${this.buildURL()}/replication/status`, 'GET', { unauthenticated: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
replicationDrPromote(data, options) {
|
||||||
|
const verb = options && options.checkStatus ? 'GET' : 'PUT';
|
||||||
|
return this.ajax(`${this.buildURL()}/replication/dr/secondary/promote`, verb, {
|
||||||
|
data,
|
||||||
|
unauthenticated: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
generateDrOperationToken(data, options) {
|
||||||
|
const verb = options && options.checkStatus ? 'GET' : 'PUT';
|
||||||
|
let url = `${this.buildURL()}/replication/dr/secondary/generate-operation-token/`;
|
||||||
|
if (!data || data.pgp_key || data.otp) {
|
||||||
|
// start the generation
|
||||||
|
url = url + 'attempt';
|
||||||
|
} else {
|
||||||
|
// progress the operation
|
||||||
|
url = url + 'update';
|
||||||
|
}
|
||||||
|
return this.ajax(url, verb, {
|
||||||
|
data,
|
||||||
|
unauthenticated: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
replicationAction(action, replicationMode, clusterMode, data) {
|
||||||
|
assert(
|
||||||
|
`${replicationMode} is an unsupported replication mode.`,
|
||||||
|
replicationMode && REPLICATION_MODES.includes(replicationMode)
|
||||||
|
);
|
||||||
|
|
||||||
|
const url =
|
||||||
|
action === 'recover' || action === 'reindex'
|
||||||
|
? this.urlForReplication(replicationMode, null, action)
|
||||||
|
: this.urlForReplication(replicationMode, clusterMode, action);
|
||||||
|
|
||||||
|
return this.ajax(url, 'POST', { data });
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
url(role, isSTS) {
|
||||||
|
if (isSTS) {
|
||||||
|
return `/v1/${role.backend}/sts/${role.name}`;
|
||||||
|
}
|
||||||
|
return `/v1/${role.backend}/creds/${role.name}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot) {
|
||||||
|
const isSTS = snapshot.attr('withSTS');
|
||||||
|
const options = isSTS ? { data: { ttl: snapshot.attr('ttl') } } : {};
|
||||||
|
const method = isSTS ? 'POST' : 'GET';
|
||||||
|
const role = snapshot.attr('role');
|
||||||
|
const url = this.url(role, isSTS);
|
||||||
|
|
||||||
|
return this.ajax(url, method, options).then(response => {
|
||||||
|
response.id = snapshot.id;
|
||||||
|
response.modelName = type.modelName;
|
||||||
|
store.pushPayload(type.modelName, response);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
import ApplicationAdapater from '../application';
|
||||||
|
|
||||||
|
export default ApplicationAdapater.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
pathForType(type) {
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForQuery() {
|
||||||
|
return this._super(...arguments) + '?list=true';
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type) {
|
||||||
|
return this.ajax(this.buildURL(type.modelName, null, null, 'query'), 'GET');
|
||||||
|
},
|
||||||
|
|
||||||
|
buildURL(modelName, id, snapshot, requestType, query) {
|
||||||
|
if (requestType === 'createRecord') {
|
||||||
|
return this._super(...arguments);
|
||||||
|
}
|
||||||
|
return this._super(`${modelName}/id`, id, snapshot, requestType, query);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import IdentityAdapter from './base';
|
||||||
|
|
||||||
|
export default IdentityAdapter.extend();
|
|
@ -0,0 +1,17 @@
|
||||||
|
import IdentityAdapter from './base';
|
||||||
|
|
||||||
|
export default IdentityAdapter.extend({
|
||||||
|
buildURL() {
|
||||||
|
// first arg is modelName which we're hardcoding in the call to _super.
|
||||||
|
let [, ...args] = arguments;
|
||||||
|
return this._super('identity/entity/merge', ...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot) {
|
||||||
|
return this._super(...arguments).then(() => {
|
||||||
|
// return the `to` id here so we can redirect to it on success
|
||||||
|
// (and because ember _loves_ 204s for createRecord)
|
||||||
|
return { id: snapshot.attr('toEntityId') };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
import IdentityAdapter from './base';
|
||||||
|
|
||||||
|
export default IdentityAdapter.extend({
|
||||||
|
lookup(store, data) {
|
||||||
|
let url = `/${this.urlPrefix()}/identity/lookup/entity`;
|
||||||
|
return this.ajax(url, 'POST', { data }).then(response => {
|
||||||
|
// unsuccessful lookup is a 204
|
||||||
|
if (!response) return;
|
||||||
|
let modelName = 'identity/entity';
|
||||||
|
store.push(
|
||||||
|
store
|
||||||
|
.serializerFor(modelName)
|
||||||
|
.normalizeResponse(store, store.modelFor(modelName), response, response.data.id, 'findRecord')
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import IdentityAdapter from './base';
|
||||||
|
|
||||||
|
export default IdentityAdapter.extend();
|
|
@ -0,0 +1,18 @@
|
||||||
|
import IdentityAdapter from './base';
|
||||||
|
|
||||||
|
export default IdentityAdapter.extend({
|
||||||
|
lookup(store, data) {
|
||||||
|
let url = `/${this.urlPrefix()}/identity/lookup/group`;
|
||||||
|
return this.ajax(url, 'POST', { data }).then(response => {
|
||||||
|
// unsuccessful lookup is a 204
|
||||||
|
if (!response) return;
|
||||||
|
let modelName = 'identity/group';
|
||||||
|
store.push(
|
||||||
|
store
|
||||||
|
.serializerFor(modelName)
|
||||||
|
.normalizeResponse(store, store.modelFor(modelName), response, response.data.id, 'findRecord')
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
revokePrefix(prefix) {
|
||||||
|
let url = this.buildURL() + '/leases/revoke-prefix/' + prefix;
|
||||||
|
url = url.replace(/\/$/, '');
|
||||||
|
return this.ajax(url, 'PUT');
|
||||||
|
},
|
||||||
|
|
||||||
|
forceRevokePrefix(prefix) {
|
||||||
|
let url = this.buildURL() + '/leases/revoke-prefix/' + prefix;
|
||||||
|
url = url.replace(/\/$/, '');
|
||||||
|
return this.ajax(url, 'PUT');
|
||||||
|
},
|
||||||
|
|
||||||
|
renew(lease_id, interval) {
|
||||||
|
let url = this.buildURL() + '/leases/renew';
|
||||||
|
return this.ajax(url, 'PUT', {
|
||||||
|
data: {
|
||||||
|
lease_id,
|
||||||
|
interval,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const lease_id = snapshot.id;
|
||||||
|
return this.ajax(this.buildURL() + '/leases/revoke', 'PUT', {
|
||||||
|
data: {
|
||||||
|
lease_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
const { lease_id } = query;
|
||||||
|
return this.ajax(this.buildURL() + '/leases/lookup', 'PUT', {
|
||||||
|
data: {
|
||||||
|
lease_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
const prefix = query.prefix || '';
|
||||||
|
return this.ajax(this.buildURL() + '/leases/lookup/' + prefix, 'GET', {
|
||||||
|
data: {
|
||||||
|
list: true,
|
||||||
|
},
|
||||||
|
}).then(resp => {
|
||||||
|
if (prefix) {
|
||||||
|
resp.prefix = prefix;
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
url(id) {
|
||||||
|
return `${this.buildURL()}/replication/performance/primary/mount-filter/${id}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
findRecord(store, type, id) {
|
||||||
|
return this.ajax(this.url(id), 'GET').then(resp => {
|
||||||
|
resp.id = id;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot) {
|
||||||
|
return this.ajax(this.url(snapshot.id), 'PUT', {
|
||||||
|
data: this.serialize(snapshot),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createRecord(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
return this.ajax(this.url(snapshot.id), 'DELETE');
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend();
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Adapter from './pki';
|
||||||
|
|
||||||
|
export default Adapter.extend({
|
||||||
|
url(_, snapshot) {
|
||||||
|
const backend = snapshot.attr('backend');
|
||||||
|
return `/v1/${backend}/root/sign-intermediate`;
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,62 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
defaultSerializer: 'ssh',
|
||||||
|
|
||||||
|
url(snapshot, action) {
|
||||||
|
const { backend, caType, type } = snapshot.attributes();
|
||||||
|
if (action === 'sign-intermediate') {
|
||||||
|
return `/v1/${backend}/root/sign-intermediate`;
|
||||||
|
}
|
||||||
|
if (action === 'set-signed-intermediate') {
|
||||||
|
return `/v1/${backend}/intermediate/set-signed`;
|
||||||
|
}
|
||||||
|
if (action === 'upload') {
|
||||||
|
return `/v1/${backend}/config/ca`;
|
||||||
|
}
|
||||||
|
return `/v1/${backend}/${caType}/generate/${type}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecordOrUpdate(store, type, snapshot, requestType) {
|
||||||
|
const serializer = store.serializerFor(this.get('defaultSerializer'));
|
||||||
|
const isUpload = snapshot.attr('uploadPemBundle');
|
||||||
|
const isSetSignedIntermediate = snapshot.adapterOptions.method === 'setSignedIntermediate';
|
||||||
|
let action = snapshot.adapterOptions.method === 'signIntermediate' ? 'sign-intermediate' : null;
|
||||||
|
let data;
|
||||||
|
if (isUpload) {
|
||||||
|
action = 'upload';
|
||||||
|
data = { pem_bundle: snapshot.attr('pemBundle') };
|
||||||
|
} else if (isSetSignedIntermediate) {
|
||||||
|
action = 'set-signed-intermediate';
|
||||||
|
data = { certificate: snapshot.attr('certificate') };
|
||||||
|
} else {
|
||||||
|
data = serializer.serialize(snapshot, requestType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ajax(this.url(snapshot, action), 'POST', { data }).then(response => {
|
||||||
|
// uploading CA, setting signed intermediate cert, and attempting to generate
|
||||||
|
// a new CA if one exists, all return a 204
|
||||||
|
if (!response) {
|
||||||
|
response = {};
|
||||||
|
}
|
||||||
|
response.id = snapshot.id;
|
||||||
|
response.modelName = type.modelName;
|
||||||
|
store.pushPayload(type.modelName, response);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createRecordOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createRecordOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const backend = snapshot.attr('backend');
|
||||||
|
return this.ajax(`/v1/${backend}/root`, 'DELETE');
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Adapter from './pki';
|
||||||
|
|
||||||
|
export default Adapter.extend({
|
||||||
|
url(role, snapshot) {
|
||||||
|
if (snapshot.attr('signVerbatim') === true) {
|
||||||
|
return `/v1/${role.backend}/sign-verbatim/${role.name}`;
|
||||||
|
}
|
||||||
|
return `/v1/${role.backend}/sign/${role.name}`;
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,66 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import Adapter from './pki';
|
||||||
|
|
||||||
|
export default Adapter.extend({
|
||||||
|
url(role) {
|
||||||
|
return `/v1/${role.backend}/issue/${role.name}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
urlFor(backend, id) {
|
||||||
|
let url = `${this.buildURL()}/${backend}/certs`;
|
||||||
|
if (id) {
|
||||||
|
url = `${this.buildURL()}/${backend}/cert/${id}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsForQuery(id) {
|
||||||
|
let data = {};
|
||||||
|
if (!id) {
|
||||||
|
data['list'] = true;
|
||||||
|
}
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchByQuery(store, query) {
|
||||||
|
const { backend, id } = query;
|
||||||
|
return this.ajax(this.urlFor(backend, id), 'GET', this.optionsForQuery(id)).then(resp => {
|
||||||
|
const data = {
|
||||||
|
backend,
|
||||||
|
};
|
||||||
|
if (id) {
|
||||||
|
data.serial_number = id;
|
||||||
|
data.id = id;
|
||||||
|
data.id_for_nav = `cert/${id}`;
|
||||||
|
}
|
||||||
|
return Ember.assign({}, resp, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord(store, type, snapshot) {
|
||||||
|
if (snapshot.adapterOptions.method !== 'revoke') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const id = snapshot.id;
|
||||||
|
const backend = snapshot.record.get('backend');
|
||||||
|
const data = {
|
||||||
|
serial_number: id,
|
||||||
|
};
|
||||||
|
return this.ajax(`${this.buildURL()}/${backend}/revoke`, 'POST', { data }).then(resp => {
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
serial_number: id,
|
||||||
|
backend,
|
||||||
|
};
|
||||||
|
return Ember.assign({}, resp, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,122 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
defaultSerializer: 'config',
|
||||||
|
|
||||||
|
urlFor(backend, section) {
|
||||||
|
const urls = {
|
||||||
|
tidy: `/v1/${backend}/tidy`,
|
||||||
|
urls: `/v1/${backend}/config/urls`,
|
||||||
|
crl: `/v1/${backend}/config/crl`,
|
||||||
|
};
|
||||||
|
return urls[section];
|
||||||
|
},
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot) {
|
||||||
|
const url = this.urlFor(snapshot.record.get('backend'), snapshot.adapterOptions.method);
|
||||||
|
const serializer = store.serializerFor(this.get('defaultSerializer'));
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = snapshot.adapterOptions.fields.reduce((data, field) => {
|
||||||
|
let attr = snapshot.attr(field);
|
||||||
|
if (attr) {
|
||||||
|
serializer.serializeAttribute(snapshot, data, field, attr);
|
||||||
|
} else {
|
||||||
|
data[serializer.keyForAttribute(field)] = attr;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}, {});
|
||||||
|
return this.ajax(url, 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments, 'update');
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchSection(backendPath, section) {
|
||||||
|
const sections = ['cert', 'urls', 'crl', 'tidy'];
|
||||||
|
if (!section || !sections.includes(section)) {
|
||||||
|
const error = new DS.AdapterError();
|
||||||
|
Ember.set(error, 'httpStatus', 404);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return this[`fetch${Ember.String.capitalize(section)}`](backendPath);
|
||||||
|
},
|
||||||
|
|
||||||
|
id(backendPath) {
|
||||||
|
return backendPath + '-config-ca';
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchCert(backendPath) {
|
||||||
|
// these are all un-authed so using `fetch` directly works
|
||||||
|
const derURL = `/v1/${backendPath}/ca`;
|
||||||
|
const pemURL = `${derURL}/pem`;
|
||||||
|
const chainURL = `${derURL}_chain`;
|
||||||
|
|
||||||
|
return Ember.RSVP.hash({
|
||||||
|
backend: backendPath,
|
||||||
|
id: this.id(backendPath),
|
||||||
|
der: this.rawRequest(derURL, { unauthenticate: true }).then(response => response.blob()),
|
||||||
|
pem: this.rawRequest(pemURL, { unauthenticate: true }).then(response => response.text()),
|
||||||
|
ca_chain: this.rawRequest(chainURL, { unauthenticate: true }).then(response => response.text()),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchUrls(backendPath) {
|
||||||
|
const url = `/v1/${backendPath}/config/urls`;
|
||||||
|
const id = this.id(backendPath);
|
||||||
|
return this.ajax(url, 'GET')
|
||||||
|
.then(resp => {
|
||||||
|
resp.id = id;
|
||||||
|
resp.backend = backendPath;
|
||||||
|
return resp;
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
if (e.httpStatus === 404) {
|
||||||
|
return Ember.RSVP.resolve({ id });
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchCrl(backendPath) {
|
||||||
|
const url = `/v1/${backendPath}/config/crl`;
|
||||||
|
const id = this.id(backendPath);
|
||||||
|
return this.ajax(url, 'GET')
|
||||||
|
.then(resp => {
|
||||||
|
resp.id = id;
|
||||||
|
resp.backend = backendPath;
|
||||||
|
return resp;
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
if (e.httpStatus === 404) {
|
||||||
|
return { id };
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchTidy(backendPath) {
|
||||||
|
const id = this.id(backendPath);
|
||||||
|
return Ember.RSVP.resolve({ id, backend: backendPath });
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
const { backend, section } = query;
|
||||||
|
return this.fetchSection(backend, section).then(resp => {
|
||||||
|
resp.backend = backend;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
defaultSerializer: 'ssh',
|
||||||
|
|
||||||
|
url(/*role*/) {
|
||||||
|
Ember.assert('Override the `url` method to extend the SSH adapter', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot, requestType) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot, requestType);
|
||||||
|
const role = snapshot.attr('role');
|
||||||
|
|
||||||
|
return this.ajax(this.url(role, snapshot), 'POST', { data }).then(response => {
|
||||||
|
response.id = snapshot.id;
|
||||||
|
response.modelName = type.modelName;
|
||||||
|
store.pushPayload(type.modelName, response);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1/sys',
|
||||||
|
pathForType(type) {
|
||||||
|
let path = type.replace('policy', 'policies');
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot) {
|
||||||
|
const serializer = store.serializerFor('policy');
|
||||||
|
const data = serializer.serialize(snapshot);
|
||||||
|
const name = snapshot.attr('name');
|
||||||
|
|
||||||
|
return this.ajax(this.buildURL(type.modelName, name), 'PUT', { data }).then(() => {
|
||||||
|
// doing this to make it like a Vault response - ember data doesn't like 204s if it's not a DELETE
|
||||||
|
return {
|
||||||
|
data: Ember.assign({}, snapshot.record.toJSON(), { id: name }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type) {
|
||||||
|
return this.ajax(this.buildURL(type.modelName), 'GET', { data: { list: true } });
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import PolicyAdapter from '../policy';
|
||||||
|
|
||||||
|
export default PolicyAdapter.extend();
|
|
@ -0,0 +1,3 @@
|
||||||
|
import PolicyAdapter from '../policy';
|
||||||
|
|
||||||
|
export default PolicyAdapter.extend();
|
|
@ -0,0 +1,3 @@
|
||||||
|
import PolicyAdapter from '../policy';
|
||||||
|
|
||||||
|
export default PolicyAdapter.extend();
|
|
@ -0,0 +1,69 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot, requestType) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot, requestType);
|
||||||
|
const { id } = snapshot;
|
||||||
|
let url = this.urlForRole(snapshot.record.get('backend'), id);
|
||||||
|
|
||||||
|
return this.ajax(url, 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments, 'update');
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const { id } = snapshot;
|
||||||
|
return this.ajax(this.urlForRole(snapshot.record.get('backend'), id), 'DELETE');
|
||||||
|
},
|
||||||
|
|
||||||
|
pathForType() {
|
||||||
|
return 'roles';
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForRole(backend, id) {
|
||||||
|
let url = `${this.buildURL()}/${backend}/roles`;
|
||||||
|
if (id) {
|
||||||
|
url = url + '/' + id;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsForQuery(id) {
|
||||||
|
let data = {};
|
||||||
|
if (!id) {
|
||||||
|
data['list'] = true;
|
||||||
|
}
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchByQuery(store, query) {
|
||||||
|
const { id, backend } = query;
|
||||||
|
return this.ajax(this.urlForRole(backend, id), 'GET', this.optionsForQuery(id)).then(resp => {
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
name: id,
|
||||||
|
backend,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ember.assign({}, resp, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,69 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot, requestType) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot, requestType);
|
||||||
|
const { id } = snapshot;
|
||||||
|
let url = this.urlForRole(snapshot.record.get('backend'), id);
|
||||||
|
|
||||||
|
return this.ajax(url, 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments, 'update');
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const { id } = snapshot;
|
||||||
|
return this.ajax(this.urlForRole(snapshot.record.get('backend'), id), 'DELETE');
|
||||||
|
},
|
||||||
|
|
||||||
|
pathForType() {
|
||||||
|
return 'roles';
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForRole(backend, id) {
|
||||||
|
let url = `${this.buildURL()}/${backend}/roles`;
|
||||||
|
if (id) {
|
||||||
|
url = url + '/' + id;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsForQuery(id) {
|
||||||
|
let data = {};
|
||||||
|
if (!id) {
|
||||||
|
data['list'] = true;
|
||||||
|
}
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchByQuery(store, query) {
|
||||||
|
const { id, backend } = query;
|
||||||
|
return this.ajax(this.urlForRole(backend, id), 'GET', this.optionsForQuery(id)).then(resp => {
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
name: id,
|
||||||
|
backend,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ember.assign({}, resp, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,96 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
defaultSerializer: 'role',
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot, requestType) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot, requestType);
|
||||||
|
const { id } = snapshot;
|
||||||
|
let url = this.urlForRole(snapshot.record.get('backend'), id);
|
||||||
|
|
||||||
|
return this.ajax(url, 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments, 'update');
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const { id } = snapshot;
|
||||||
|
return this.ajax(this.urlForRole(snapshot.record.get('backend'), id), 'DELETE');
|
||||||
|
},
|
||||||
|
|
||||||
|
pathForType() {
|
||||||
|
return 'roles';
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForRole(backend, id) {
|
||||||
|
let url = `${this.buildURL()}/${backend}/roles`;
|
||||||
|
if (id) {
|
||||||
|
url = url + '/' + id;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsForQuery(id) {
|
||||||
|
let data = {};
|
||||||
|
if (!id) {
|
||||||
|
data['list'] = true;
|
||||||
|
}
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchByQuery(store, query) {
|
||||||
|
const { id, backend } = query;
|
||||||
|
let zeroAddressAjax = Ember.RSVP.resolve();
|
||||||
|
const queryAjax = this.ajax(this.urlForRole(backend, id), 'GET', this.optionsForQuery(id));
|
||||||
|
if (!id) {
|
||||||
|
zeroAddressAjax = this.findAllZeroAddress(store, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ember.RSVP.allSettled([queryAjax, zeroAddressAjax]).then(results => {
|
||||||
|
// query result 404d, so throw the adapterError
|
||||||
|
if (!results[0].value) {
|
||||||
|
throw results[0].reason;
|
||||||
|
}
|
||||||
|
let resp = {
|
||||||
|
id,
|
||||||
|
name: id,
|
||||||
|
backend,
|
||||||
|
};
|
||||||
|
|
||||||
|
results.forEach(result => {
|
||||||
|
if (result.value) {
|
||||||
|
if (result.value.data.roles) {
|
||||||
|
resp = Ember.assign({}, resp, { zero_address_roles: result.value.data.roles });
|
||||||
|
} else {
|
||||||
|
resp = Ember.assign({}, resp, result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
findAllZeroAddress(store, query) {
|
||||||
|
const { backend } = query;
|
||||||
|
const url = `/v1/${backend}/config/zeroaddress`;
|
||||||
|
return this.ajax(url, 'GET');
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,61 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot);
|
||||||
|
const { id } = snapshot;
|
||||||
|
|
||||||
|
return this.ajax(this.urlForSecret(snapshot.attr('backend'), id), 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const { id } = snapshot;
|
||||||
|
return this.ajax(this.urlForSecret(snapshot.attr('backend'), id), 'DELETE');
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForSecret(backend, id) {
|
||||||
|
let url = this.buildURL() + '/' + backend + '/';
|
||||||
|
if (!Ember.isEmpty(id)) {
|
||||||
|
url = url + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsForQuery(id, action) {
|
||||||
|
let data = {};
|
||||||
|
if (action === 'query') {
|
||||||
|
data['list'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchByQuery(query, action) {
|
||||||
|
const { id, backend } = query;
|
||||||
|
return this.ajax(this.urlForSecret(backend, id), 'GET', this.optionsForQuery(id, action)).then(resp => {
|
||||||
|
resp.id = id;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
return this.fetchByQuery(query, 'query');
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.fetchByQuery(query, 'queryRecord');
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,102 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
url(path) {
|
||||||
|
const url = `${this.buildURL()}/mounts`;
|
||||||
|
return path ? url + '/' + path : url;
|
||||||
|
},
|
||||||
|
|
||||||
|
pathForType(type) {
|
||||||
|
let path;
|
||||||
|
switch (type) {
|
||||||
|
case 'cluster':
|
||||||
|
path = 'clusters';
|
||||||
|
break;
|
||||||
|
case 'secret-engine':
|
||||||
|
path = 'mounts';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
path = Ember.String.pluralize(type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
query() {
|
||||||
|
return this.ajax(this.url(), 'GET').catch(e => {
|
||||||
|
if (e instanceof DS.AdapterError) {
|
||||||
|
Ember.set(e, 'policyPath', 'sys/mounts');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot);
|
||||||
|
const path = snapshot.attr('path');
|
||||||
|
|
||||||
|
return this.ajax(this.url(path), 'POST', { data }).then(() => {
|
||||||
|
// ember data doesn't like 204s if it's not a DELETE
|
||||||
|
return {
|
||||||
|
data: Ember.assign({}, data, { path: path + '/', id: path }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
findRecord(store, type, path, snapshot) {
|
||||||
|
if (snapshot.attr('type') === 'ssh') {
|
||||||
|
return this.ajax(`/v1/${path}/config/ca`, 'GET');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
if (query.type === 'aws') {
|
||||||
|
return this.ajax(`/v1/${query.backend}/config/lease`, 'GET').then(resp => {
|
||||||
|
resp.path = query.backend + '/';
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord(store, type, snapshot) {
|
||||||
|
const { apiPath, options, adapterMethod } = snapshot.adapterOptions;
|
||||||
|
if (adapterMethod) {
|
||||||
|
return this[adapterMethod](...arguments);
|
||||||
|
}
|
||||||
|
if (apiPath) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot);
|
||||||
|
const path = snapshot.id;
|
||||||
|
return this.ajax(`/v1/${path}/${apiPath}`, options.isDelete ? 'DELETE' : 'POST', { data });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveAWSRoot(store, type, snapshot) {
|
||||||
|
let { data } = snapshot.adapterOptions;
|
||||||
|
const path = snapshot.id;
|
||||||
|
return this.ajax(`/v1/${path}/config/root`, 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
saveAWSLease(store, type, snapshot) {
|
||||||
|
let { data } = snapshot.adapterOptions;
|
||||||
|
const path = snapshot.id;
|
||||||
|
return this.ajax(`/v1/${path}/config/lease`, 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
saveZeroAddressConfig(store, type, snapshot) {
|
||||||
|
const path = snapshot.id;
|
||||||
|
const roles = store.peekAll('role-ssh').filterBy('zeroAddress').mapBy('id').join(',');
|
||||||
|
const url = `/v1/${path}/config/zeroaddress`;
|
||||||
|
const data = { roles };
|
||||||
|
if (roles === '') {
|
||||||
|
return this.ajax(url, 'DELETE');
|
||||||
|
}
|
||||||
|
return this.ajax(url, 'POST', { data });
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,86 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
headers: computed(function() {
|
||||||
|
return {
|
||||||
|
'X-Vault-Kv-Client': 'v1',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot);
|
||||||
|
const { id } = snapshot;
|
||||||
|
|
||||||
|
return this.ajax(this.urlForSecret(snapshot.attr('backend'), id), 'POST', {
|
||||||
|
data: { data },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const { id } = snapshot;
|
||||||
|
return this.ajax(this.urlForSecret(snapshot.attr('backend'), id), 'DELETE');
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForSecret(backend, id, infix = 'data') {
|
||||||
|
let url = `${this.buildURL()}/${backend}/${infix}/`;
|
||||||
|
if (!Ember.isEmpty(id)) {
|
||||||
|
url = url + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsForQuery(id, action) {
|
||||||
|
let data = {};
|
||||||
|
if (action === 'query') {
|
||||||
|
data['list'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForQuery(query) {
|
||||||
|
let { id, backend } = query;
|
||||||
|
return this.urlForSecret(backend, id, 'metadata');
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForQueryRecord(query) {
|
||||||
|
let { id, backend } = query;
|
||||||
|
return this.urlForSecret(backend, id);
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
return this.ajax(
|
||||||
|
this.urlForQuery(query, type.modelName),
|
||||||
|
'GET',
|
||||||
|
this.optionsForQuery(query.id, 'query')
|
||||||
|
).then(resp => {
|
||||||
|
resp.id = query.id;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.ajax(
|
||||||
|
this.urlForQueryRecord(query, type.modelName),
|
||||||
|
'GET',
|
||||||
|
this.optionsForQuery(query.id, 'queryRecord')
|
||||||
|
).then(resp => {
|
||||||
|
resp.id = query.id;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import SSHAdapter from './ssh';
|
||||||
|
|
||||||
|
export default SSHAdapter.extend({
|
||||||
|
url(role) {
|
||||||
|
return `/v1/${role.backend}/creds/${role.name}`;
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import SSHAdapter from './ssh';
|
||||||
|
|
||||||
|
export default SSHAdapter.extend({
|
||||||
|
url(role) {
|
||||||
|
return `/v1/${role.backend}/sign/${role.name}`;
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
defaultSerializer: 'ssh',
|
||||||
|
|
||||||
|
url(/*role*/) {
|
||||||
|
Ember.assert('Override the `url` method to extend the SSH adapter', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot, requestType) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot, requestType);
|
||||||
|
const role = snapshot.attr('role');
|
||||||
|
|
||||||
|
return this.ajax(this.url(role), 'POST', { data }).then(response => {
|
||||||
|
response.id = snapshot.id;
|
||||||
|
response.modelName = type.modelName;
|
||||||
|
store.pushPayload(type.modelName, response);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
const WRAPPING_ENDPOINTS = ['lookup', 'wrap', 'unwrap', 'rewrap'];
|
||||||
|
const TOOLS_ENDPOINTS = ['random', 'hash'];
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
toolUrlFor(action) {
|
||||||
|
const isWrapping = WRAPPING_ENDPOINTS.includes(action);
|
||||||
|
const isTool = TOOLS_ENDPOINTS.includes(action);
|
||||||
|
const prefix = isWrapping ? 'wrapping' : 'tools';
|
||||||
|
if (!isWrapping && !isTool) {
|
||||||
|
throw new Error(`Calls to a ${action} endpoint are not currently allowed in the tool adapter`);
|
||||||
|
}
|
||||||
|
return `${this.buildURL()}/${prefix}/${action}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
toolAction(action, data, options = {}) {
|
||||||
|
const { wrapTTL } = options;
|
||||||
|
const url = this.toolUrlFor(action);
|
||||||
|
const ajaxOptions = wrapTTL ? { data, wrapTTL } : { data };
|
||||||
|
return this.ajax(url, 'POST', ajaxOptions);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,114 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
namespace: 'v1',
|
||||||
|
|
||||||
|
createOrUpdate(store, type, snapshot, requestType) {
|
||||||
|
const serializer = store.serializerFor(type.modelName);
|
||||||
|
const data = serializer.serialize(snapshot, requestType);
|
||||||
|
const { id } = snapshot;
|
||||||
|
let url = this.urlForSecret(snapshot.record.get('backend'), id);
|
||||||
|
|
||||||
|
if (requestType === 'update') {
|
||||||
|
url = url + '/config';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ajax(url, 'POST', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord() {
|
||||||
|
return this.createOrUpdate(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecord() {
|
||||||
|
return this.createOrUpdate(...arguments, 'update');
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const { id } = snapshot;
|
||||||
|
return this.ajax(this.urlForSecret(snapshot.record.get('backend'), id), 'DELETE');
|
||||||
|
},
|
||||||
|
|
||||||
|
pathForType(type) {
|
||||||
|
let path;
|
||||||
|
switch (type) {
|
||||||
|
case 'cluster':
|
||||||
|
path = 'clusters';
|
||||||
|
break;
|
||||||
|
case 'secret-engine':
|
||||||
|
path = 'secrets';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
path = Ember.String.pluralize(type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForSecret(backend, id) {
|
||||||
|
let url = `${this.buildURL()}/${backend}/keys/`;
|
||||||
|
if (id) {
|
||||||
|
url += id;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
urlForAction(action, backend, id, param) {
|
||||||
|
let urlBase = `${this.buildURL()}/${backend}/${action}`;
|
||||||
|
// these aren't key-specific
|
||||||
|
if (action === 'hash' || action === 'random') {
|
||||||
|
return urlBase;
|
||||||
|
}
|
||||||
|
if (action === 'datakey' && param) {
|
||||||
|
// datakey action has `wrapped` or `plaintext` as part of the url
|
||||||
|
return `${urlBase}/${param}/${id}`;
|
||||||
|
}
|
||||||
|
if (action === 'export' && param) {
|
||||||
|
let [type, version] = param;
|
||||||
|
const exportBase = `${urlBase}/${type}-key/${id}`;
|
||||||
|
return version ? `${exportBase}/${version}` : exportBase;
|
||||||
|
}
|
||||||
|
return `${urlBase}/${id}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsForQuery(id) {
|
||||||
|
let data = {};
|
||||||
|
if (!id) {
|
||||||
|
data['list'] = true;
|
||||||
|
}
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchByQuery(query) {
|
||||||
|
const { id, backend } = query;
|
||||||
|
return this.ajax(this.urlForSecret(backend, id), 'GET', this.optionsForQuery(id)).then(resp => {
|
||||||
|
resp.id = id;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
query(store, type, query) {
|
||||||
|
return this.fetchByQuery(query);
|
||||||
|
},
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.fetchByQuery(query);
|
||||||
|
},
|
||||||
|
|
||||||
|
// rotate, encrypt, decrypt, sign, verify, hmac, rewrap, datakey
|
||||||
|
keyAction(action, { backend, id, payload }, options = {}) {
|
||||||
|
const verb = action === 'export' ? 'GET' : 'POST';
|
||||||
|
const { wrapTTL } = options;
|
||||||
|
if (action === 'rotate') {
|
||||||
|
return this.ajax(this.urlForSecret(backend, id) + '/rotate', verb);
|
||||||
|
}
|
||||||
|
const { param } = payload;
|
||||||
|
|
||||||
|
delete payload.param;
|
||||||
|
return this.ajax(this.urlForAction(action, backend, id, param), verb, {
|
||||||
|
data: payload,
|
||||||
|
wrapTTL,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import Resolver from './resolver';
|
||||||
|
import loadInitializers from 'ember-load-initializers';
|
||||||
|
import config from './config/environment';
|
||||||
|
|
||||||
|
let App;
|
||||||
|
|
||||||
|
App = Ember.Application.extend({
|
||||||
|
modulePrefix: config.modulePrefix,
|
||||||
|
podModulePrefix: config.podModulePrefix,
|
||||||
|
Resolver,
|
||||||
|
});
|
||||||
|
|
||||||
|
loadInitializers(App, config.modulePrefix);
|
||||||
|
|
||||||
|
export default App;
|
|
@ -0,0 +1,32 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
|
||||||
|
const { inject } = Ember;
|
||||||
|
|
||||||
|
const AuthConfigBase = Ember.Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
model: null,
|
||||||
|
|
||||||
|
flashMessages: inject.service(),
|
||||||
|
|
||||||
|
saveModel: task(function*() {
|
||||||
|
try {
|
||||||
|
yield this.get('model').save();
|
||||||
|
} catch (err) {
|
||||||
|
// AdapterErrors are handled by the error-message component
|
||||||
|
// in the form
|
||||||
|
if (err instanceof DS.AdapterError === false) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.get('flashMessages').success('The configuration was saved successfully.');
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
AuthConfigBase.reopenClass({
|
||||||
|
positionalParams: ['model'],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AuthConfigBase;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import AuthConfigComponent from './config';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
|
||||||
|
export default AuthConfigComponent.extend({
|
||||||
|
saveModel: task(function*() {
|
||||||
|
const model = this.get('model');
|
||||||
|
let data = model.get('config').toJSON();
|
||||||
|
data.description = model.get('description');
|
||||||
|
try {
|
||||||
|
yield model.tune(data);
|
||||||
|
} catch (err) {
|
||||||
|
// AdapterErrors are handled by the error-message component
|
||||||
|
// in the form
|
||||||
|
if (err instanceof DS.AdapterError === false) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.get('flashMessages').success('The configuration options were saved successfully.');
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
|
||||||
|
const BACKENDS = supportedAuthBackends();
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ['auth-form'],
|
||||||
|
routing: Ember.inject.service('-routing'),
|
||||||
|
auth: Ember.inject.service(),
|
||||||
|
flashMessages: Ember.inject.service(),
|
||||||
|
didRender() {
|
||||||
|
// on very narrow viewports the active tab may be overflowed, so we scroll it into view here
|
||||||
|
this.$('li.is-active').get(0).scrollIntoView();
|
||||||
|
},
|
||||||
|
|
||||||
|
cluster: null,
|
||||||
|
redirectTo: null,
|
||||||
|
|
||||||
|
selectedAuthType: 'token',
|
||||||
|
selectedAuthBackend: Ember.computed('selectedAuthType', function() {
|
||||||
|
return BACKENDS.findBy('type', this.get('selectedAuthType'));
|
||||||
|
}),
|
||||||
|
|
||||||
|
providerComponentName: Ember.computed('selectedAuthBackend.type', function() {
|
||||||
|
const type = Ember.String.dasherize(this.get('selectedAuthBackend.type'));
|
||||||
|
return `auth-form/${type}`;
|
||||||
|
}),
|
||||||
|
|
||||||
|
handleError(e) {
|
||||||
|
this.set('loading', false);
|
||||||
|
this.set('error', `Authentication failed: ${e.errors.join('.')}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
doSubmit(data) {
|
||||||
|
this.setProperties({
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
const targetRoute = this.get('redirectTo') || 'vault.cluster';
|
||||||
|
//const {password, token, username} = data;
|
||||||
|
const backend = this.get('selectedAuthBackend.type');
|
||||||
|
const path = this.get('customPath');
|
||||||
|
if (this.get('useCustomPath') && path) {
|
||||||
|
data.path = path;
|
||||||
|
}
|
||||||
|
const clusterId = this.get('cluster.id');
|
||||||
|
this.get('auth').authenticate({ clusterId, backend, data }).then(
|
||||||
|
({ isRoot }) => {
|
||||||
|
this.set('loading', false);
|
||||||
|
const transition = this.get('routing.router').transitionTo(targetRoute);
|
||||||
|
if (isRoot) {
|
||||||
|
transition.followRedirects().then(() => {
|
||||||
|
this.get('flashMessages').warning(
|
||||||
|
'You have logged in with a root token. As a security precaution, this root token will not be stored by your browser and you will need to re-authenticate after the window is closed or refreshed.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(...errArgs) => this.handleError(...errArgs)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
auth: Ember.inject.service(),
|
||||||
|
|
||||||
|
routing: Ember.inject.service('-routing'),
|
||||||
|
|
||||||
|
transitionToRoute: function() {
|
||||||
|
var router = this.get('routing.router');
|
||||||
|
router.transitionTo.apply(router, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
classNames: 'user-menu auth-info',
|
||||||
|
|
||||||
|
isRenewing: Ember.computed.or('fakeRenew', 'auth.isRenewing'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
renewToken() {
|
||||||
|
this.set('fakeRenew', true);
|
||||||
|
Ember.run.later(() => {
|
||||||
|
this.set('fakeRenew', false);
|
||||||
|
this.get('auth').renew();
|
||||||
|
}, 200);
|
||||||
|
},
|
||||||
|
|
||||||
|
revokeToken() {
|
||||||
|
this.get('auth').revokeCurrentToken().then(() => {
|
||||||
|
this.transitionToRoute('vault.cluster.logout');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,128 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { encodeString, decodeString } from 'vault/utils/b64';
|
||||||
|
|
||||||
|
const { computed, get, set } = Ember;
|
||||||
|
const B64 = 'base64';
|
||||||
|
const UTF8 = 'utf-8';
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: 'button',
|
||||||
|
attributeBindings: ['type'],
|
||||||
|
type: 'button',
|
||||||
|
classNames: ['button', 'b64-toggle'],
|
||||||
|
classNameBindings: ['isInput:is-input:is-textarea'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether or not the toggle is associated with an input.
|
||||||
|
* Also bound to `is-input` and `is-textarea` classes
|
||||||
|
* Defaults to true
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @type boolean
|
||||||
|
*/
|
||||||
|
|
||||||
|
isInput: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The value that will be mutated when the encoding is toggled
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
value: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The encoding of `value` when the component is initialized.
|
||||||
|
* Defaults to 'utf-8'.
|
||||||
|
* Possible values: 'utf-8' and 'base64'
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
initialEncoding: UTF8,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A cached version of value - used to determine if the input has changed since encoding.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
_value: '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The current encoding of `value`.
|
||||||
|
* Possible values: 'utf-8' and 'base64'
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
currentEncoding: '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The encoding when we last mutated `value`.
|
||||||
|
* Possible values: 'utf-8' and 'base64'
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
lastEncoding: '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Is the value known to be base64-encoded.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type boolean
|
||||||
|
*/
|
||||||
|
isBase64: computed.equal('currentEncoding', B64),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Does the current value match the cached _value, i.e. has the input been mutated since we encoded.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type boolean
|
||||||
|
*/
|
||||||
|
valuesMatch: computed('value', '_value', function() {
|
||||||
|
const { value, _value } = this.getProperties('value', '_value');
|
||||||
|
const anyBlank = Ember.isBlank(value) || Ember.isBlank(_value);
|
||||||
|
return !anyBlank && value === _value;
|
||||||
|
}),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
const initial = get(this, 'initialEncoding');
|
||||||
|
set(this, 'currentEncoding', initial);
|
||||||
|
if (initial === B64) {
|
||||||
|
set(this, '_value', get(this, 'value'));
|
||||||
|
set(this, 'lastEncoding', B64);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
// if there's no value, reset encoding
|
||||||
|
if (get(this, 'value') === '') {
|
||||||
|
set(this, 'currentEncoding', UTF8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// the value has changed after we transformed it so we reset currentEncoding
|
||||||
|
if (get(this, 'isBase64') && !get(this, 'valuesMatch')) {
|
||||||
|
set(this, 'currentEncoding', UTF8);
|
||||||
|
}
|
||||||
|
// the value changed back to one we previously had transformed
|
||||||
|
if (get(this, 'lastEncoding') === B64 && get(this, 'valuesMatch')) {
|
||||||
|
set(this, 'currentEncoding', B64);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
click() {
|
||||||
|
let val = get(this, 'value');
|
||||||
|
const isUTF8 = get(this, 'currentEncoding') === UTF8;
|
||||||
|
if (!val) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newVal = isUTF8 ? encodeString(val) : decodeString(val);
|
||||||
|
const encoding = isUTF8 ? B64 : UTF8;
|
||||||
|
set(this, 'value', newVal);
|
||||||
|
set(this, '_value', newVal);
|
||||||
|
set(this, 'lastEncoding', encoding);
|
||||||
|
set(this, 'currentEncoding', encoding);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,203 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { computed, inject } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: 'config-pki-ca',
|
||||||
|
store: inject.service('store'),
|
||||||
|
flashMessages: inject.service(),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param boolean
|
||||||
|
* @private
|
||||||
|
* bool that gets flipped if you have a CA cert and click the Replace Cert button
|
||||||
|
*/
|
||||||
|
replaceCA: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param boolean
|
||||||
|
* @private
|
||||||
|
* bool that gets flipped if you push the click the "Set signed intermediate" button
|
||||||
|
*/
|
||||||
|
setSignedIntermediate: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param boolean
|
||||||
|
* @private
|
||||||
|
* bool that gets flipped if you push the click the "Set signed intermediate" button
|
||||||
|
*/
|
||||||
|
signIntermediate: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param boolean
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* true when there's no CA cert currently configured
|
||||||
|
*/
|
||||||
|
needsConfig: computed.not('config.pem'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param DS.Model
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* a `pki-ca-certificate` model used to back the form when uploading or creating a CA cert
|
||||||
|
* created and set on `init`, and unloaded on willDestroy
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
model: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param DS.Model
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* a `pki-config` model - passed in in the component useage
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
config: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param Function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* function that gets called to refresh the config model
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
onRefresh: () => {},
|
||||||
|
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
const ca = this.get('model');
|
||||||
|
if (ca) {
|
||||||
|
ca.unloadRecord();
|
||||||
|
}
|
||||||
|
this._super(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
createOrReplaceModel(modelType) {
|
||||||
|
const ca = this.get('model');
|
||||||
|
const config = this.get('config');
|
||||||
|
const store = this.get('store');
|
||||||
|
const backend = config.get('backend');
|
||||||
|
if (ca) {
|
||||||
|
ca.unloadRecord();
|
||||||
|
}
|
||||||
|
const caCert = store.createRecord(modelType || 'pki-ca-certificate', {
|
||||||
|
id: `${backend}-ca-cert`,
|
||||||
|
backend,
|
||||||
|
});
|
||||||
|
this.set('model', caCert);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @private
|
||||||
|
* @returns array
|
||||||
|
*
|
||||||
|
* When a CA is configured, we let them download
|
||||||
|
* the CA in der, pem, and the CA Chain in pem (if one exists)
|
||||||
|
*
|
||||||
|
* This array provides the text and download hrefs for those links.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
downloadHrefs: computed('config', 'config.{backend,pem,caChain,der}', function() {
|
||||||
|
const config = this.get('config');
|
||||||
|
const { backend, pem, caChain, der } = config.getProperties('backend', 'pem', 'caChain', 'der');
|
||||||
|
|
||||||
|
if (!pem) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const pemFile = new File([pem], { type: 'text/plain' });
|
||||||
|
const links = [
|
||||||
|
{
|
||||||
|
display: 'Download CA Certificate in PEM format',
|
||||||
|
name: `${backend}_ca.pem`,
|
||||||
|
url: URL.createObjectURL(pemFile),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Download CA Certificate in DER format',
|
||||||
|
name: `${backend}_ca.der`,
|
||||||
|
url: URL.createObjectURL(der),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (caChain) {
|
||||||
|
const caChainFile = new File([caChain], { type: 'text/plain' });
|
||||||
|
links.push({
|
||||||
|
display: 'Download CA Certificate Chain',
|
||||||
|
name: `${backend}_ca_chain.pem`,
|
||||||
|
url: URL.createObjectURL(caChainFile),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
saveCA(method) {
|
||||||
|
this.set('loading', true);
|
||||||
|
const model = this.get('model');
|
||||||
|
const isUpload = this.get('model.uploadPemBundle');
|
||||||
|
model
|
||||||
|
.save({ adapterOptions: { method } })
|
||||||
|
.then(m => {
|
||||||
|
if (method === 'setSignedIntermediate' || isUpload) {
|
||||||
|
this.send('refresh');
|
||||||
|
this.get('flashMessages').success('The certificate for this backend has been updated.');
|
||||||
|
} else if (!m.get('certificate') && !m.get('csr')) {
|
||||||
|
// if there's no certificate, it wasn't generated and the generation was a noop
|
||||||
|
this.get('flashMessages').warning(
|
||||||
|
'You tried to generate a new root CA, but one currently exists. To replace the existing one, delete it first and then generate again.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.set('loading', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteCA() {
|
||||||
|
this.set('loading', true);
|
||||||
|
const model = this.get('model');
|
||||||
|
const backend = model.get('backend');
|
||||||
|
//TODO Is there better way to do this? This forces the saved state so Ember Data will make a server call.
|
||||||
|
model.send('pushedData');
|
||||||
|
model
|
||||||
|
.destroyRecord()
|
||||||
|
.then(() => {
|
||||||
|
this.get('flashMessages').success(
|
||||||
|
`The CA key for ${backend} has been deleted. The old CA certificate will still be accessible for reading until a new certificate/key are generated or uploaded.`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.set('loading', false);
|
||||||
|
this.send('refresh');
|
||||||
|
this.createOrReplaceModel();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refresh() {
|
||||||
|
this.setProperties({
|
||||||
|
setSignedIntermediate: false,
|
||||||
|
signIntermediate: false,
|
||||||
|
replaceCA: false,
|
||||||
|
});
|
||||||
|
this.get('onRefresh')();
|
||||||
|
},
|
||||||
|
toggleReplaceCA() {
|
||||||
|
if (!this.get('replaceCA')) {
|
||||||
|
this.createOrReplaceModel();
|
||||||
|
}
|
||||||
|
this.toggleProperty('replaceCA');
|
||||||
|
},
|
||||||
|
toggleVal(name, val) {
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const model = name === 'signIntermediate' ? 'pki-ca-certificate-sign' : null;
|
||||||
|
if (!this.get(name)) {
|
||||||
|
this.createOrReplaceModel(model);
|
||||||
|
}
|
||||||
|
if (val !== undefined) {
|
||||||
|
this.set(name, val);
|
||||||
|
} else {
|
||||||
|
this.toggleProperty(name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,66 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { get, inject } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: 'config-pki',
|
||||||
|
flashMessages: inject.service(),
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @param String
|
||||||
|
* @public
|
||||||
|
* String corresponding to the route parameter for the current section
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
section: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param DS.Model
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* a `pki-config` model - passed in in the component useage
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
config: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param Function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* function that gets called to refresh the config model
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
onRefresh: () => {},
|
||||||
|
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
save(section) {
|
||||||
|
this.set('loading', true);
|
||||||
|
const config = this.get('config');
|
||||||
|
config
|
||||||
|
.save({
|
||||||
|
adapterOptions: {
|
||||||
|
method: section,
|
||||||
|
fields: get(config, `${section}Attrs`).map(attr => attr.name),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.get('flashMessages').success(`The ${section} config for this backend has been updated.`);
|
||||||
|
// attrs aren't persistent for Tidy
|
||||||
|
if (section === 'tidy') {
|
||||||
|
config.rollbackAttributes();
|
||||||
|
}
|
||||||
|
this.send('refresh');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.set('loading', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refresh() {
|
||||||
|
this.get('onRefresh')();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: 'span',
|
||||||
|
classNames: ['confirm-action'],
|
||||||
|
layout: hbs`
|
||||||
|
{{#if showConfirm ~}}
|
||||||
|
<span class={{containerClasses}}>
|
||||||
|
<span class={{concat 'confirm-action-text ' messageClasses}}>{{if disabled disabledMessage confirmMessage}}</span>
|
||||||
|
<button {{action 'onConfirm'}} disabled={{disabled}} class={{confirmButtonClasses}} type="button" data-test-confirm-button=true>{{confirmButtonText}}</button>
|
||||||
|
<button {{action 'toggleConfirm'}} type="button" class={{cancelButtonClasses}} data-test-confirm-cancel-button=true>{{cancelButtonText}}</button>
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
<button
|
||||||
|
class={{buttonClasses}}
|
||||||
|
type="button"
|
||||||
|
disabled={{disabled}}
|
||||||
|
{{action 'toggleConfirm'}}
|
||||||
|
>
|
||||||
|
{{yield}}
|
||||||
|
</button>
|
||||||
|
{{~/if}}
|
||||||
|
`,
|
||||||
|
|
||||||
|
disabled: false,
|
||||||
|
disabledMessage: 'Complete the form to complete this action',
|
||||||
|
showConfirm: false,
|
||||||
|
messageClasses: 'is-size-8 has-text-grey',
|
||||||
|
confirmButtonClasses: 'is-danger is-outlined button',
|
||||||
|
containerClasses: '',
|
||||||
|
buttonClasses: 'button',
|
||||||
|
buttonText: 'Delete',
|
||||||
|
confirmMessage: 'Are you sure you want to do this?',
|
||||||
|
confirmButtonText: 'Delete',
|
||||||
|
cancelButtonClasses: 'button',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
// the action to take when we confirm
|
||||||
|
onConfirmAction: null,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
toggleConfirm() {
|
||||||
|
this.toggleProperty('showConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onConfirm() {
|
||||||
|
const confirmAction = this.get('onConfirmAction');
|
||||||
|
|
||||||
|
if (typeof confirmAction !== 'function') {
|
||||||
|
throw new Error('confirm-action components expects `onConfirmAction` attr to be a function');
|
||||||
|
} else {
|
||||||
|
confirmAction();
|
||||||
|
this.toggleProperty('showConfirm');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
const { Component, computed } = Ember;
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: 'a',
|
||||||
|
attributeBindings: ['target', 'rel', 'href'],
|
||||||
|
|
||||||
|
layout: hbs`{{yield}}`,
|
||||||
|
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noreferrer noopener',
|
||||||
|
|
||||||
|
path: '/',
|
||||||
|
href: computed('path', function() {
|
||||||
|
return `https://www.vaultproject.io/docs${this.get('path')}`;
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
layout: hbs`{{actionText}}`,
|
||||||
|
tagName: 'a',
|
||||||
|
role: 'button',
|
||||||
|
attributeBindings: ['role', 'download', 'href'],
|
||||||
|
download: computed('filename', 'extension', function() {
|
||||||
|
return `${this.get('filename')}-${new Date().toISOString()}.${this.get('extension')}`;
|
||||||
|
}),
|
||||||
|
|
||||||
|
href: computed('data', 'mime', 'stringify', function() {
|
||||||
|
let data = this.get('data');
|
||||||
|
const mime = this.get('mime');
|
||||||
|
if (this.get('stringify')) {
|
||||||
|
data = JSON.stringify(data, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = new File([data], { type: mime });
|
||||||
|
return window.URL.createObjectURL(file);
|
||||||
|
}),
|
||||||
|
|
||||||
|
actionText: 'Download',
|
||||||
|
data: null,
|
||||||
|
filename: null,
|
||||||
|
mime: 'text/plain',
|
||||||
|
extension: 'txt',
|
||||||
|
stringify: false,
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: 'span',
|
||||||
|
classNames: 'badge edition-badge',
|
||||||
|
abbreviation: Ember.computed('edition', function() {
|
||||||
|
const edition = this.get('edition');
|
||||||
|
if (edition == 'Enterprise') {
|
||||||
|
return 'Ent';
|
||||||
|
} else {
|
||||||
|
return edition;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
attributeBindings: ['edition:aria-label'],
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import FlashMessage from 'ember-cli-flash/components/flash-message';
|
||||||
|
|
||||||
|
const { computed, getWithDefault } = Ember;
|
||||||
|
|
||||||
|
export default FlashMessage.extend({
|
||||||
|
// override alertType to get Bulma specific prefix
|
||||||
|
//https://github.com/poteto/ember-cli-flash/blob/master/addon/components/flash-message.js#L35
|
||||||
|
alertType: computed('flash.type', {
|
||||||
|
get() {
|
||||||
|
const flashType = getWithDefault(this, 'flash.type', '');
|
||||||
|
let prefix = 'notification has-border is-';
|
||||||
|
|
||||||
|
return `${prefix}${flashType}`;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: 'column',
|
||||||
|
header: null,
|
||||||
|
content: null,
|
||||||
|
});
|
|
@ -0,0 +1,31 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public String
|
||||||
|
* A whitelist of groups to include in the render
|
||||||
|
*/
|
||||||
|
renderGroup: computed(function() {
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public DS.Model
|
||||||
|
* model to be passed down to form-field component
|
||||||
|
* if `fieldGroups` is present on the model then it will be iterated over and
|
||||||
|
* groups of `form-field` components will be rendered
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
model: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public Function
|
||||||
|
* onChange handler that will get set on the form-field component
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
onChange: () => {},
|
||||||
|
});
|
|
@ -0,0 +1,110 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { capitalize } from 'vault/helpers/capitalize';
|
||||||
|
import { humanize } from 'vault/helpers/humanize';
|
||||||
|
import { dasherize } from 'vault/helpers/dasherize';
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
'data-test-field': true,
|
||||||
|
classNames: ['field'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public Function
|
||||||
|
* called whenever a value on the model changes via the component
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
onChange() {},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public
|
||||||
|
* @param Object
|
||||||
|
* in the form of
|
||||||
|
* {
|
||||||
|
* name: "foo",
|
||||||
|
* options: {
|
||||||
|
* label: "Foo",
|
||||||
|
* defaultValue: "",
|
||||||
|
* editType: "ttl",
|
||||||
|
* helpText: "This will be in a tooltip"
|
||||||
|
* },
|
||||||
|
* type: "boolean"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* this is usually derived from ember model `attributes` lookup,
|
||||||
|
* and all members of `attr.options` are optional
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
attr: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @private
|
||||||
|
* @param string
|
||||||
|
* Computed property used in the label element next to the form element
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
labelString: computed('attr.name', 'attr.options.label', function() {
|
||||||
|
const label = this.get('attr.options.label');
|
||||||
|
const name = this.get('attr.name');
|
||||||
|
if (label) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
return capitalize([humanize([dasherize([name])])]);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// both the path to mutate on the model, and the path to read the value from
|
||||||
|
/*
|
||||||
|
* @private
|
||||||
|
* @param string
|
||||||
|
*
|
||||||
|
* Computed property used to set values on the passed model
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
valuePath: computed('attr.name', 'attr.options.fieldValue', function() {
|
||||||
|
return this.get('attr.options.fieldValue') || this.get('attr.name');
|
||||||
|
}),
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param DS.Model
|
||||||
|
*
|
||||||
|
* the Ember Data model that `attr` is defined on
|
||||||
|
*/
|
||||||
|
model: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @private
|
||||||
|
* @param object
|
||||||
|
*
|
||||||
|
* Used by the pgp-file component when an attr is editType of 'file'
|
||||||
|
*/
|
||||||
|
file: { value: '' },
|
||||||
|
emptyData: '{\n}',
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
setFile(_, keyFile) {
|
||||||
|
const path = this.get('valuePath');
|
||||||
|
const { value } = keyFile;
|
||||||
|
this.get('model').set(path, value);
|
||||||
|
this.get('onChange')(path, value);
|
||||||
|
this.set('file', keyFile);
|
||||||
|
},
|
||||||
|
|
||||||
|
setAndBroadcast(path, value) {
|
||||||
|
this.get('model').set(path, value);
|
||||||
|
this.get('onChange')(path, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
codemirrorUpdated(path, value, codemirror) {
|
||||||
|
codemirror.performLint();
|
||||||
|
const hasErrors = codemirror.state.lint.marked.length > 0;
|
||||||
|
|
||||||
|
if (!hasErrors) {
|
||||||
|
this.get('model').set(path, JSON.parse(value));
|
||||||
|
this.get('onChange')(path, JSON.parse(value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,133 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { get, computed } = Ember;
|
||||||
|
|
||||||
|
const MODEL_TYPES = {
|
||||||
|
'ssh-sign': {
|
||||||
|
model: 'ssh-sign',
|
||||||
|
},
|
||||||
|
'ssh-creds': {
|
||||||
|
model: 'ssh-otp-credential',
|
||||||
|
title: 'Generate SSH Credentials',
|
||||||
|
generatedAttr: 'key',
|
||||||
|
},
|
||||||
|
'aws-creds': {
|
||||||
|
model: 'iam-credential',
|
||||||
|
title: 'Generate IAM Credentials',
|
||||||
|
generateWithoutInput: true,
|
||||||
|
backIsListLink: true,
|
||||||
|
},
|
||||||
|
'aws-sts': {
|
||||||
|
model: 'iam-credential',
|
||||||
|
title: 'Generate IAM Credentials with STS',
|
||||||
|
generatedAttr: 'accessKey',
|
||||||
|
},
|
||||||
|
'pki-issue': {
|
||||||
|
model: 'pki-certificate',
|
||||||
|
title: 'Issue Certificate',
|
||||||
|
generatedAttr: 'certificate',
|
||||||
|
},
|
||||||
|
'pki-sign': {
|
||||||
|
model: 'pki-certificate-sign',
|
||||||
|
title: 'Sign Certificate',
|
||||||
|
generatedAttr: 'certificate',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
store: Ember.inject.service(),
|
||||||
|
routing: Ember.inject.service('-routing'),
|
||||||
|
// set on the component
|
||||||
|
backend: null,
|
||||||
|
action: null,
|
||||||
|
role: null,
|
||||||
|
|
||||||
|
model: null,
|
||||||
|
loading: false,
|
||||||
|
emptyData: '{\n}',
|
||||||
|
|
||||||
|
modelForType() {
|
||||||
|
const type = this.get('options');
|
||||||
|
if (type) {
|
||||||
|
return type.model;
|
||||||
|
}
|
||||||
|
// if we don't have a mode for that type then redirect them back to the backend list
|
||||||
|
const router = this.get('routing.router');
|
||||||
|
router.transitionTo.call(router, 'vault.cluster.secrets.backend.list-root', this.get('model.backend'));
|
||||||
|
},
|
||||||
|
|
||||||
|
options: computed('action', 'backend.type', function() {
|
||||||
|
const action = this.get('action') || 'creds';
|
||||||
|
return MODEL_TYPES[`${this.get('backend.type')}-${action}`];
|
||||||
|
}),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.createOrReplaceModel();
|
||||||
|
this.maybeGenerate();
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
this.get('model').unloadRecord();
|
||||||
|
this._super(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
createOrReplaceModel() {
|
||||||
|
const modelType = this.modelForType();
|
||||||
|
const model = this.get('model');
|
||||||
|
const roleModel = this.get('role');
|
||||||
|
if (!modelType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (model) {
|
||||||
|
model.unloadRecord();
|
||||||
|
}
|
||||||
|
const attrs = {
|
||||||
|
role: roleModel,
|
||||||
|
id: `${get(roleModel, 'backend')}-${get(roleModel, 'name')}`,
|
||||||
|
};
|
||||||
|
if (this.get('action') === 'sts') {
|
||||||
|
attrs.withSTS = true;
|
||||||
|
}
|
||||||
|
const newModel = this.get('store').createRecord(modelType, attrs);
|
||||||
|
this.set('model', newModel);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @function maybeGenerate
|
||||||
|
*
|
||||||
|
* This method is called on `init`. If there is no input requried (as is the case for AWS IAM creds)
|
||||||
|
* then the `create` action is triggered right away.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
maybeGenerate() {
|
||||||
|
if (this.get('backend.type') !== 'aws' || this.get('action') === 'sts') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// for normal IAM creds - there's no input, so just generate right away
|
||||||
|
this.send('create');
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
create() {
|
||||||
|
this.set('loading', true);
|
||||||
|
this.model.save().finally(() => {
|
||||||
|
this.set('loading', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
codemirrorUpdated(attr, val, codemirror) {
|
||||||
|
codemirror.performLint();
|
||||||
|
const hasErrors = codemirror.state.lint.marked.length > 0;
|
||||||
|
|
||||||
|
if (!hasErrors) {
|
||||||
|
Ember.set(this.get('model'), attr, JSON.parse(val));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
newModel() {
|
||||||
|
this.createOrReplaceModel();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
layout: hbs`<a href="{{href-to 'vault.cluster' 'vault'}}" class={{class}}>
|
||||||
|
{{#if hasBlock}}
|
||||||
|
{{yield}}
|
||||||
|
{{else}}
|
||||||
|
{{text}}
|
||||||
|
{{/if}}
|
||||||
|
</a>
|
||||||
|
`,
|
||||||
|
|
||||||
|
tagName: '',
|
||||||
|
|
||||||
|
text: computed(function() {
|
||||||
|
return 'home';
|
||||||
|
}),
|
||||||
|
|
||||||
|
computedClasses: computed('classNames', function() {
|
||||||
|
return this.get('classNames').join(' ');
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,47 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
const { computed } = Ember;
|
||||||
|
const GLYPHS_WITH_SVG_TAG = [
|
||||||
|
'folder',
|
||||||
|
'file',
|
||||||
|
'perf-replication',
|
||||||
|
'role',
|
||||||
|
'information-reversed',
|
||||||
|
'true',
|
||||||
|
'false',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
layout: hbs`
|
||||||
|
{{#if excludeSVG}}
|
||||||
|
{{partial partialName}}
|
||||||
|
{{else}}
|
||||||
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="{{size}}" height="{{size}}" viewBox="0 0 512 512">
|
||||||
|
{{partial partialName}}
|
||||||
|
</svg>
|
||||||
|
{{/if}}
|
||||||
|
`,
|
||||||
|
|
||||||
|
tagName: 'span',
|
||||||
|
excludeIconClass: false,
|
||||||
|
classNameBindings: ['excludeIconClass::icon'],
|
||||||
|
classNames: ['has-current-color-fill'],
|
||||||
|
|
||||||
|
attributeBindings: ['aria-label', 'aria-hidden'],
|
||||||
|
|
||||||
|
glyph: null,
|
||||||
|
|
||||||
|
excludeSVG: computed('glyph', function() {
|
||||||
|
return GLYPHS_WITH_SVG_TAG.includes(this.get('glyph'));
|
||||||
|
}),
|
||||||
|
|
||||||
|
size: computed(function() {
|
||||||
|
return 12;
|
||||||
|
}),
|
||||||
|
|
||||||
|
partialName: computed('glyph', function() {
|
||||||
|
const glyph = this.get('glyph');
|
||||||
|
return `svg/icons/${Ember.String.camelize(glyph)}`;
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
import { humanize } from 'vault/helpers/humanize';
|
||||||
|
|
||||||
|
const { computed } = Ember;
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
model: null,
|
||||||
|
mode: 'create',
|
||||||
|
/*
|
||||||
|
* @param Function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* Optional param to call a function upon successfully mounting a backend
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
onSave: () => {},
|
||||||
|
|
||||||
|
cancelLink: computed('mode', 'model', function() {
|
||||||
|
let { model, mode } = this.getProperties('model', 'mode');
|
||||||
|
let key = `${mode}-${model.get('identityType')}`;
|
||||||
|
let routes = {
|
||||||
|
'create-entity': 'vault.cluster.access.identity',
|
||||||
|
'edit-entity': 'vault.cluster.access.identity.show',
|
||||||
|
'merge-entity-merge': 'vault.cluster.access.identity',
|
||||||
|
'create-entity-alias': 'vault.cluster.access.identity.aliases',
|
||||||
|
'edit-entity-alias': 'vault.cluster.access.identity.aliases.show',
|
||||||
|
'create-group': 'vault.cluster.access.identity',
|
||||||
|
'edit-group': 'vault.cluster.access.identity.show',
|
||||||
|
'create-group-alias': 'vault.cluster.access.identity.aliases',
|
||||||
|
'edit-group-alias': 'vault.cluster.access.identity.aliases.show',
|
||||||
|
};
|
||||||
|
|
||||||
|
return routes[key];
|
||||||
|
}),
|
||||||
|
|
||||||
|
getMessage(model) {
|
||||||
|
let mode = this.get('mode');
|
||||||
|
let typeDisplay = humanize([model.get('identityType')]);
|
||||||
|
if (mode === 'merge') {
|
||||||
|
return 'Successfully merged entities';
|
||||||
|
}
|
||||||
|
if (model.get('id')) {
|
||||||
|
return `Successfully saved ${typeDisplay} ${model.id}.`;
|
||||||
|
}
|
||||||
|
return `Successfully saved ${typeDisplay}.`;
|
||||||
|
},
|
||||||
|
|
||||||
|
save: task(function*() {
|
||||||
|
let model = this.get('model');
|
||||||
|
let message = this.getMessage(model);
|
||||||
|
|
||||||
|
try {
|
||||||
|
yield model.save();
|
||||||
|
} catch (err) {
|
||||||
|
// err will display via model state
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.get('flashMessages').success(message);
|
||||||
|
yield this.get('onSave')(model);
|
||||||
|
}).drop(),
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
let model = this.get('model');
|
||||||
|
if (!model.isDestroyed || !model.isDestroying) {
|
||||||
|
model.rollbackAttributes();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,75 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
import { underscore } from 'vault/helpers/underscore';
|
||||||
|
|
||||||
|
const { inject } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
store: inject.service(),
|
||||||
|
flashMessages: inject.service(),
|
||||||
|
routing: inject.service('-routing'),
|
||||||
|
|
||||||
|
// Public API - either 'entity' or 'group'
|
||||||
|
// this will determine which adapter is used to make the lookup call
|
||||||
|
type: 'entity',
|
||||||
|
|
||||||
|
param: 'alias name',
|
||||||
|
paramValue: null,
|
||||||
|
aliasMountAccessor: null,
|
||||||
|
|
||||||
|
authMethods: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.get('store').findAll('auth-method').then(methods => {
|
||||||
|
this.set('authMethods', methods);
|
||||||
|
this.set('aliasMountAccessor', methods.get('firstObject.accessor'));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
adapter() {
|
||||||
|
let type = this.get('type');
|
||||||
|
let store = this.get('store');
|
||||||
|
return store.adapterFor(`identity/${type}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
let { param, paramValue, aliasMountAccessor } = this.getProperties(
|
||||||
|
'param',
|
||||||
|
'paramValue',
|
||||||
|
'aliasMountAccessor'
|
||||||
|
);
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
data[underscore([param])] = paramValue;
|
||||||
|
if (param === 'alias name') {
|
||||||
|
data.alias_mount_accessor = aliasMountAccessor;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
lookup: task(function*() {
|
||||||
|
let flash = this.get('flashMessages');
|
||||||
|
let type = this.get('type');
|
||||||
|
let store = this.get('store');
|
||||||
|
let { param, paramValue } = this.getProperties('param', 'paramValue');
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = yield this.adapter().lookup(store, this.data());
|
||||||
|
} catch (err) {
|
||||||
|
flash.danger(
|
||||||
|
`We encountered an error attempting the ${type} lookup: ${err.message || err.errors.join('')}.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response) {
|
||||||
|
return this.get('routing.router').transitionTo(
|
||||||
|
'vault.cluster.access.identity.show',
|
||||||
|
response.id,
|
||||||
|
'details'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
flash.danger(`We were unable to find an identity ${type} with a "${param}" of "${paramValue}".`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ['info-table-row'],
|
||||||
|
isVisible: Ember.computed.or('alwaysRender', 'value'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param boolean
|
||||||
|
* indicates if the component content should be always be rendered.
|
||||||
|
* when false, the value of `value` will be used to determine if the component should render
|
||||||
|
*/
|
||||||
|
alwaysRender: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param string
|
||||||
|
* the display name for the value
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
label: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* the value of the data passed in - by default the content of the component will only show if there is a value
|
||||||
|
*/
|
||||||
|
value: null,
|
||||||
|
|
||||||
|
valueIsBoolean: Ember.computed('value', function() {
|
||||||
|
return Ember.typeOf(this.get('value')) === 'boolean';
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
'data-test-component': 'info-tooltip',
|
||||||
|
tagName: 'span',
|
||||||
|
classNames: ['is-inline-block'],
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
import IvyCodemirrorComponent from './ivy-codemirror';
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { assign } = Ember;
|
||||||
|
const JSON_EDITOR_DEFAULTS = {
|
||||||
|
// IMPORTANT: `gutters` must come before `lint` since the presence of
|
||||||
|
// `gutters` is cached internally when `lint` is toggled
|
||||||
|
gutters: ['CodeMirror-lint-markers'],
|
||||||
|
tabSize: 2,
|
||||||
|
mode: 'application/json',
|
||||||
|
lineNumbers: true,
|
||||||
|
lint: { lintOnChange: false },
|
||||||
|
theme: 'hashi',
|
||||||
|
readOnly: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IvyCodemirrorComponent.extend({
|
||||||
|
'data-test-component': 'json-editor',
|
||||||
|
updateCodeMirrorOptions() {
|
||||||
|
const options = assign({}, JSON_EDITOR_DEFAULTS, this.get('options'));
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
Object.keys(options).forEach(function(option) {
|
||||||
|
this.updateCodeMirrorOption(option, options[option]);
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,85 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import utils from 'vault/lib/key-utils';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: 'nav',
|
||||||
|
classNames: 'key-value-header breadcrumb',
|
||||||
|
ariaLabel: 'breadcrumbs',
|
||||||
|
attributeBindings: ['ariaLabel:aria-label', 'aria-hidden'],
|
||||||
|
|
||||||
|
baseKey: null,
|
||||||
|
path: null,
|
||||||
|
showCurrent: true,
|
||||||
|
linkToPaths: true,
|
||||||
|
|
||||||
|
stripTrailingSlash(str) {
|
||||||
|
return str[str.length - 1] === '/' ? str.slice(0, -1) : str;
|
||||||
|
},
|
||||||
|
|
||||||
|
currentPath: Ember.computed('mode', 'path', 'showCurrent', function() {
|
||||||
|
const mode = this.get('mode');
|
||||||
|
const path = this.get('path');
|
||||||
|
const showCurrent = this.get('showCurrent');
|
||||||
|
if (!mode || showCurrent === false) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return `vault.cluster.secrets.backend.${mode}`;
|
||||||
|
}),
|
||||||
|
|
||||||
|
secretPath: Ember.computed('baseKey', 'baseKey.display', 'baseKey.id', 'root', 'showCurrent', function() {
|
||||||
|
let crumbs = [];
|
||||||
|
const root = this.get('root');
|
||||||
|
const baseKey = this.get('baseKey.display') || this.get('baseKey.id');
|
||||||
|
const baseKeyModel = this.get('baseKey.id');
|
||||||
|
|
||||||
|
if (root) {
|
||||||
|
crumbs.push(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!baseKey) {
|
||||||
|
return crumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = this.get('path');
|
||||||
|
const currentPath = this.get('currentPath');
|
||||||
|
const showCurrent = this.get('showCurrent');
|
||||||
|
const ancestors = utils.ancestorKeysForKey(baseKey);
|
||||||
|
const parts = utils.keyPartsForKey(baseKey);
|
||||||
|
if (!ancestors) {
|
||||||
|
crumbs.push({
|
||||||
|
label: baseKey,
|
||||||
|
text: this.stripTrailingSlash(baseKey),
|
||||||
|
path: currentPath,
|
||||||
|
model: baseKeyModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!showCurrent) {
|
||||||
|
crumbs.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return crumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
ancestors.forEach((ancestor, index) => {
|
||||||
|
crumbs.push({
|
||||||
|
label: parts[index],
|
||||||
|
text: this.stripTrailingSlash(parts[index]),
|
||||||
|
path: path,
|
||||||
|
model: ancestor,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
crumbs.push({
|
||||||
|
label: utils.keyWithoutParentKey(baseKey),
|
||||||
|
text: this.stripTrailingSlash(utils.keyWithoutParentKey(baseKey)),
|
||||||
|
path: currentPath,
|
||||||
|
model: baseKeyModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!showCurrent) {
|
||||||
|
crumbs.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return crumbs;
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
});
|
|
@ -0,0 +1,67 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import KVObject from 'vault/lib/kv-object';
|
||||||
|
|
||||||
|
const { assert, Component, computed, guidFor } = Ember;
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
'data-test-component': 'kv-object-editor',
|
||||||
|
classNames: ['field', 'form-section'],
|
||||||
|
// public API
|
||||||
|
// Ember Object to mutate
|
||||||
|
value: null,
|
||||||
|
label: null,
|
||||||
|
helpText: null,
|
||||||
|
// onChange will be called with the changed Value
|
||||||
|
onChange() {},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
const data = KVObject.create({ content: [] }).fromJSON(this.get('value'));
|
||||||
|
this.set('kvData', data);
|
||||||
|
this.addRow();
|
||||||
|
},
|
||||||
|
|
||||||
|
kvData: null,
|
||||||
|
|
||||||
|
kvDataAsJSON: computed('kvData', 'kvData.[]', function() {
|
||||||
|
return this.get('kvData').toJSON();
|
||||||
|
}),
|
||||||
|
|
||||||
|
kvDataIsAdvanced: computed('kvData', 'kvData.[]', function() {
|
||||||
|
return this.get('kvData').isAdvanced();
|
||||||
|
}),
|
||||||
|
|
||||||
|
kvHasDuplicateKeys: computed('kvData', 'kvData.@each.name', function() {
|
||||||
|
let data = this.get('kvData');
|
||||||
|
return data.uniqBy('name').length !== data.get('length');
|
||||||
|
}),
|
||||||
|
|
||||||
|
addRow() {
|
||||||
|
let data = this.get('kvData');
|
||||||
|
let newObj = { name: '', value: '' };
|
||||||
|
if (!Ember.isNone(data.findBy('name', ''))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guidFor(newObj);
|
||||||
|
data.addObject(newObj);
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addRow() {
|
||||||
|
this.addRow();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRow() {
|
||||||
|
let data = this.get('kvData');
|
||||||
|
this.get('onChange')(data.toJSON());
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRow(object, index) {
|
||||||
|
let data = this.get('kvData');
|
||||||
|
let oldObj = data.objectAt(index);
|
||||||
|
|
||||||
|
assert('object guids match', guidFor(oldObj) === guidFor(object));
|
||||||
|
data.removeAt(index);
|
||||||
|
this.get('onChange')(data.toJSON());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
Ember.LinkComponent.reopen({
|
||||||
|
activeClass: 'is-active',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Ember.LinkComponent;
|
|
@ -0,0 +1,35 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
let LinkedBlockComponent = Ember.Component.extend({
|
||||||
|
layout: hbs`{{yield}}`,
|
||||||
|
|
||||||
|
classNames: 'linked-block',
|
||||||
|
|
||||||
|
routing: Ember.inject.service('-routing'),
|
||||||
|
queryParams: null,
|
||||||
|
|
||||||
|
click(event) {
|
||||||
|
const $target = this.$(event.target);
|
||||||
|
const isAnchorOrButton =
|
||||||
|
$target.is('a') ||
|
||||||
|
$target.is('button') ||
|
||||||
|
$target.closest('button', event.currentTarget).length > 0 ||
|
||||||
|
$target.closest('a', event.currentTarget).length > 0;
|
||||||
|
if (!isAnchorOrButton) {
|
||||||
|
const router = this.get('routing.router');
|
||||||
|
const params = this.get('params');
|
||||||
|
const queryParams = this.get('queryParams');
|
||||||
|
if (queryParams) {
|
||||||
|
params.push({ queryParams });
|
||||||
|
}
|
||||||
|
router.transitionTo.apply(router, params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
LinkedBlockComponent.reopenClass({
|
||||||
|
positionalParams: 'params',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LinkedBlockComponent;
|
|
@ -0,0 +1,37 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { range } from 'ember-composable-helpers/helpers/range';
|
||||||
|
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ['box', 'is-shadowless', 'list-pagination'],
|
||||||
|
page: null,
|
||||||
|
lastPage: null,
|
||||||
|
link: null,
|
||||||
|
model: null,
|
||||||
|
// number of links to show on each side of page
|
||||||
|
spread: 2,
|
||||||
|
hasNext: computed('page', 'lastPage', function() {
|
||||||
|
return this.get('page') < this.get('lastPage');
|
||||||
|
}),
|
||||||
|
hasPrevious: computed('page', 'lastPage', function() {
|
||||||
|
return this.get('page') > 1;
|
||||||
|
}),
|
||||||
|
|
||||||
|
segmentLinks: computed.gt('lastPage', 10),
|
||||||
|
|
||||||
|
pageRange: computed('page', 'lastPage', function() {
|
||||||
|
const { spread, page, lastPage } = this.getProperties('spread', 'page', 'lastPage');
|
||||||
|
|
||||||
|
let lower = Math.max(2, page - spread);
|
||||||
|
let upper = Math.min(lastPage - 1, lower + spread * 2);
|
||||||
|
// we're closer to lastPage than the spread
|
||||||
|
if (upper - lower < 5) {
|
||||||
|
lower = upper - 4;
|
||||||
|
}
|
||||||
|
if (lastPage <= 10) {
|
||||||
|
return range([1, lastPage, true]);
|
||||||
|
}
|
||||||
|
return range([lower, upper, true]);
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { inject } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
version: inject.service(),
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ['column', 'is-sidebar'],
|
||||||
|
classNameBindings: ['isActive:is-active'],
|
||||||
|
isActive: false,
|
||||||
|
actions: {
|
||||||
|
openMenu() {
|
||||||
|
this.set('isActive', true);
|
||||||
|
},
|
||||||
|
closeMenu() {
|
||||||
|
this.set('isActive', false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
model: null,
|
||||||
|
errors: [],
|
||||||
|
errorMessage: null,
|
||||||
|
|
||||||
|
displayErrors: Ember.computed(
|
||||||
|
'errorMessage',
|
||||||
|
'model.isError',
|
||||||
|
'model.adapterError.errors.@each',
|
||||||
|
'errors',
|
||||||
|
'errors.@each',
|
||||||
|
function() {
|
||||||
|
const errorMessage = this.get('errorMessage');
|
||||||
|
const errors = this.get('errors');
|
||||||
|
const modelIsError = this.get('model.isError');
|
||||||
|
if (errorMessage) {
|
||||||
|
return [errorMessage];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelIsError) {
|
||||||
|
return this.get('model.adapterError.errors');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
import { messageTypes } from 'vault/helpers/message-types';
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
type: null,
|
||||||
|
|
||||||
|
classNameBindings: ['containerClass'],
|
||||||
|
|
||||||
|
containerClass: computed('type', function() {
|
||||||
|
return 'message ' + messageTypes([this.get('type')]).class;
|
||||||
|
}),
|
||||||
|
|
||||||
|
alertType: computed('type', function() {
|
||||||
|
return messageTypes([this.get('type')]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
messageClass: 'message-body',
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
|
||||||
|
const { inject } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
store: inject.service(),
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
//value for the external mount selector
|
||||||
|
value: null,
|
||||||
|
onChange: () => {},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.get('authMethods').perform();
|
||||||
|
},
|
||||||
|
|
||||||
|
authMethods: task(function*() {
|
||||||
|
let methods = yield this.get('store').findAll('auth-method');
|
||||||
|
if (!this.get('value')) {
|
||||||
|
this.set('value', methods.get('firstObject.accessor'));
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}).drop(),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
change(value) {
|
||||||
|
this.get('onChange')(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,137 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
import { methods } from 'vault/helpers/mountable-auth-methods';
|
||||||
|
|
||||||
|
const { inject } = Ember;
|
||||||
|
const METHODS = methods();
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
store: inject.service(),
|
||||||
|
flashMessages: inject.service(),
|
||||||
|
routing: inject.service('-routing'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param Function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* Optional param to call a function upon successfully mounting a backend
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
onMountSuccess: () => {},
|
||||||
|
onConfigError: () => {},
|
||||||
|
/*
|
||||||
|
* @param String
|
||||||
|
* @public
|
||||||
|
* the type of backend we want to mount
|
||||||
|
* defaults to `auth`
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
mountType: 'auth',
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @param DS.Model
|
||||||
|
* @private
|
||||||
|
* Ember Data model corresponding to the `mountType`.
|
||||||
|
* Created and set during `init`
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
mountModel: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
const type = this.get('mountType');
|
||||||
|
const modelType = type === 'secret' ? 'secret-engine' : 'auth-method';
|
||||||
|
const model = this.get('store').createRecord(modelType);
|
||||||
|
this.set('mountModel', model);
|
||||||
|
this.changeConfigModel(model.get('type'));
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
// if unsaved, we want to unload so it doesn't show up in the auth mount list
|
||||||
|
this.get('mountModel').rollbackAttributes();
|
||||||
|
},
|
||||||
|
|
||||||
|
getConfigModelType(methodType) {
|
||||||
|
let noConfig = ['approle'];
|
||||||
|
if (noConfig.includes(methodType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (methodType === 'aws') {
|
||||||
|
return 'auth-config/aws/client';
|
||||||
|
}
|
||||||
|
return `auth-config/${methodType}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
changeConfigModel(methodType) {
|
||||||
|
const mount = this.get('mountModel');
|
||||||
|
const configRef = mount.hasMany('authConfigs').value();
|
||||||
|
const currentConfig = configRef.get('firstObject');
|
||||||
|
if (currentConfig) {
|
||||||
|
// rollbackAttributes here will remove the the config model from the store
|
||||||
|
// because `isNew` will be true
|
||||||
|
currentConfig.rollbackAttributes();
|
||||||
|
}
|
||||||
|
const configType = this.getConfigModelType(methodType);
|
||||||
|
if (!configType) return;
|
||||||
|
const config = this.get('store').createRecord(configType);
|
||||||
|
config.set('backend', mount);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkPathChange(type) {
|
||||||
|
const mount = this.get('mountModel');
|
||||||
|
const currentPath = mount.get('path');
|
||||||
|
// if the current path matches a type (meaning the user hasn't altered it),
|
||||||
|
// change it here to match the new type
|
||||||
|
const isUnchanged = METHODS.findBy('type', currentPath);
|
||||||
|
if (isUnchanged) {
|
||||||
|
mount.set('path', type);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mountBackend: task(function*() {
|
||||||
|
const mountModel = this.get('mountModel');
|
||||||
|
const { type, path } = mountModel.getProperties('type', 'path');
|
||||||
|
try {
|
||||||
|
yield mountModel.save();
|
||||||
|
} catch (err) {
|
||||||
|
// err will display via model state
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.get('flashMessages').success(
|
||||||
|
`Successfully mounted ${type} ${this.get('mountType')} method at ${path}.`
|
||||||
|
);
|
||||||
|
yield this.get('saveConfig').perform(mountModel);
|
||||||
|
}).drop(),
|
||||||
|
|
||||||
|
saveConfig: task(function*(mountModel) {
|
||||||
|
const configRef = mountModel.hasMany('authConfigs').value();
|
||||||
|
const config = configRef.get('firstObject');
|
||||||
|
const { type, path } = mountModel.getProperties('type', 'path');
|
||||||
|
try {
|
||||||
|
if (config && Object.keys(config.changedAttributes()).length) {
|
||||||
|
yield config.save();
|
||||||
|
this.get('flashMessages').success(
|
||||||
|
`The config for ${type} ${this.get('mountType')} method at ${path} was saved successfully.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
yield this.get('onMountSuccess')();
|
||||||
|
} catch (err) {
|
||||||
|
this.get('flashMessages').danger(
|
||||||
|
`There was an error saving the configuration for ${type} ${this.get(
|
||||||
|
'mountType'
|
||||||
|
)} method at ${path}. ${err.errors.join(' ')}`
|
||||||
|
);
|
||||||
|
yield this.get('onConfigError')(mountModel.id);
|
||||||
|
}
|
||||||
|
}).drop(),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onTypeChange(path, value) {
|
||||||
|
if (path === 'type') {
|
||||||
|
this.changeConfigModel(value);
|
||||||
|
this.checkPathChange(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { get, set } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
config: null,
|
||||||
|
mounts: null,
|
||||||
|
|
||||||
|
// singleton mounts are not eligible for per-mount-filtering
|
||||||
|
singletonMountTypes: ['cubbyhole', 'system', 'token', 'identity'],
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
addOrRemovePath(path, e) {
|
||||||
|
let config = get(this, 'config') || [];
|
||||||
|
let paths = get(config, 'paths').slice();
|
||||||
|
|
||||||
|
if (e.target.checked) {
|
||||||
|
paths.addObject(path);
|
||||||
|
} else {
|
||||||
|
paths.removeObject(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(config, 'paths', paths);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,190 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import utils from 'vault/lib/key-utils';
|
||||||
|
import keys from 'vault/lib/keycodes';
|
||||||
|
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
|
||||||
|
|
||||||
|
const routeFor = function(type, mode) {
|
||||||
|
const MODES = {
|
||||||
|
secrets: 'vault.cluster.secrets.backend',
|
||||||
|
'secrets-cert': 'vault.cluster.secrets.backend',
|
||||||
|
'policy-show': 'vault.cluster.policy',
|
||||||
|
'policy-list': 'vault.cluster.policies',
|
||||||
|
leases: 'vault.cluster.access.leases',
|
||||||
|
};
|
||||||
|
let useSuffix = true;
|
||||||
|
const typeVal = mode === 'secrets' || mode === 'leases' ? type : type.replace('-root', '');
|
||||||
|
const modeKey = mode + '-' + typeVal;
|
||||||
|
const modeVal = MODES[modeKey] || MODES[mode];
|
||||||
|
if (modeKey === 'policy-list') {
|
||||||
|
useSuffix = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return useSuffix ? modeVal + '.' + typeVal : modeVal;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Ember.Component.extend(FocusOnInsertMixin, {
|
||||||
|
classNames: ['navigate-filter'],
|
||||||
|
|
||||||
|
// these get passed in from the outside
|
||||||
|
// actions that get passed in
|
||||||
|
filterFocusDidChange: null,
|
||||||
|
filterDidChange: null,
|
||||||
|
mode: 'secrets',
|
||||||
|
shouldNavigateTree: false,
|
||||||
|
extraNavParams: null,
|
||||||
|
|
||||||
|
baseKey: null,
|
||||||
|
filter: null,
|
||||||
|
filterMatchesKey: null,
|
||||||
|
firstPartialMatch: null,
|
||||||
|
|
||||||
|
routing: Ember.inject.service('-routing'),
|
||||||
|
|
||||||
|
transitionToRoute: function() {
|
||||||
|
var router = this.get('routing.router');
|
||||||
|
router.transitionTo.apply(router, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldFocus: false,
|
||||||
|
|
||||||
|
focusFilter: Ember.observer('filter', function() {
|
||||||
|
if (!this.get('filter')) return;
|
||||||
|
Ember.run.schedule('afterRender', this, 'forceFocus');
|
||||||
|
}).on('didInsertElement'),
|
||||||
|
|
||||||
|
keyForNav(key) {
|
||||||
|
if (this.get('mode') !== 'secrets-cert') {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
return `cert/${key}`;
|
||||||
|
},
|
||||||
|
onEnter: function(val) {
|
||||||
|
let baseKey = this.get('baseKey');
|
||||||
|
let mode = this.get('mode');
|
||||||
|
let extraParams = this.get('extraNavParams');
|
||||||
|
if (mode.startsWith('secrets') && (!val || val === baseKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.get('filterMatchesKey') && !utils.keyIsFolder(val)) {
|
||||||
|
let params = [routeFor('show', mode), extraParams, this.keyForNav(val)].compact();
|
||||||
|
this.transitionToRoute(...params);
|
||||||
|
} else {
|
||||||
|
if (mode === 'policies') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let route = routeFor('create', mode);
|
||||||
|
if (baseKey) {
|
||||||
|
this.transitionToRoute(route, this.keyForNav(baseKey), {
|
||||||
|
queryParams: {
|
||||||
|
initialKey: val.replace(this.keyForNav(baseKey), ''),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.transitionToRoute(route + '-root', {
|
||||||
|
queryParams: {
|
||||||
|
initialKey: this.keyForNav(val),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// pop to the nearest parentKey or to the root
|
||||||
|
onEscape: function(val) {
|
||||||
|
var key = utils.parentKeyForKey(val) || '';
|
||||||
|
this.get('filterDidChange')(key);
|
||||||
|
this.filterUpdated(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
onTab: function(event) {
|
||||||
|
var firstPartialMatch = this.get('firstPartialMatch.id');
|
||||||
|
if (!firstPartialMatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
this.get('filterDidChange')(firstPartialMatch);
|
||||||
|
this.filterUpdated(firstPartialMatch);
|
||||||
|
},
|
||||||
|
|
||||||
|
// as you type, navigates through the k/v tree
|
||||||
|
filterUpdated: function(val) {
|
||||||
|
var mode = this.get('mode');
|
||||||
|
if (mode === 'policies' || !this.get('shouldNavigateTree')) {
|
||||||
|
this.filterUpdatedNoNav(val, mode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// select the key to nav to, assumed to be a folder
|
||||||
|
var key = val ? val.trim() : '';
|
||||||
|
var isFolder = utils.keyIsFolder(key);
|
||||||
|
|
||||||
|
if (!isFolder) {
|
||||||
|
// nav to the closest parentKey (or the root)
|
||||||
|
key = utils.parentKeyForKey(val) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageFilter = val.replace(key, '');
|
||||||
|
this.navigate(this.keyForNav(key), mode, pageFilter);
|
||||||
|
},
|
||||||
|
|
||||||
|
navigate(key, mode, pageFilter) {
|
||||||
|
const route = routeFor(key ? 'list' : 'list-root', mode);
|
||||||
|
let args = [route];
|
||||||
|
if (key) {
|
||||||
|
args.push(key);
|
||||||
|
}
|
||||||
|
if (pageFilter && !utils.keyIsFolder(pageFilter)) {
|
||||||
|
args.push({
|
||||||
|
queryParams: {
|
||||||
|
page: 1,
|
||||||
|
pageFilter,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
args.push({
|
||||||
|
queryParams: {
|
||||||
|
page: 1,
|
||||||
|
pageFilter: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.transitionToRoute(...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
filterUpdatedNoNav: function(val, mode) {
|
||||||
|
var key = val ? val.trim() : null;
|
||||||
|
this.transitionToRoute(routeFor('list-root', mode), {
|
||||||
|
queryParams: {
|
||||||
|
pageFilter: key,
|
||||||
|
page: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
handleInput: function(event) {
|
||||||
|
var filter = event.target.value;
|
||||||
|
this.get('filterDidChange')(filter);
|
||||||
|
Ember.run.debounce(this, 'filterUpdated', filter, 200);
|
||||||
|
},
|
||||||
|
|
||||||
|
setFilterFocused: function(isFocused) {
|
||||||
|
this.get('filterFocusDidChange')(isFocused);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleKeyPress: function(val, event) {
|
||||||
|
if (event.keyCode === keys.TAB) {
|
||||||
|
this.onTab(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleKeyUp: function(val, event) {
|
||||||
|
var keyCode = event.keyCode;
|
||||||
|
if (keyCode === keys.ENTER) {
|
||||||
|
this.onEnter(val);
|
||||||
|
}
|
||||||
|
if (keyCode === keys.ESC) {
|
||||||
|
this.onEscape(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { computed, inject } = Ember;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
// public
|
||||||
|
model: null,
|
||||||
|
|
||||||
|
tagName: '',
|
||||||
|
routing: inject.service('-routing'),
|
||||||
|
path: computed.alias('routing.router.currentURL'),
|
||||||
|
});
|
|
@ -0,0 +1,74 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const { set } = Ember;
|
||||||
|
const BASE_64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gi;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ['box', 'is-fullwidth', 'is-marginless', 'is-shadowless'],
|
||||||
|
key: null,
|
||||||
|
index: null,
|
||||||
|
onChange: () => {},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public
|
||||||
|
* @param String
|
||||||
|
* Text to use as the label for the file input
|
||||||
|
* If null, a default will be rendered
|
||||||
|
*/
|
||||||
|
label: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public
|
||||||
|
* @param String
|
||||||
|
* Text to use as help under the file input
|
||||||
|
* If null, a default will be rendered
|
||||||
|
*/
|
||||||
|
fileHelpText: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @public
|
||||||
|
* @param String
|
||||||
|
* Text to use as help under the textarea in text-input mode
|
||||||
|
* If null, a default will be rendered
|
||||||
|
*/
|
||||||
|
textareaHelpText: null,
|
||||||
|
|
||||||
|
readFile(file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => this.setPGPKey(reader.result, file.name);
|
||||||
|
// this gives us a base64-encoded string which is important in the onload
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
},
|
||||||
|
|
||||||
|
setPGPKey(dataURL, filename) {
|
||||||
|
const b64File = dataURL.split(',')[1].trim();
|
||||||
|
const decoded = atob(b64File).trim();
|
||||||
|
|
||||||
|
// If a b64-encoded file was uploaded, then after decoding, it
|
||||||
|
// will still be b64.
|
||||||
|
// If after decoding it's not b64, we want
|
||||||
|
// the original as it was only encoded when we used `readAsDataURL`.
|
||||||
|
const fileData = decoded.match(BASE_64_REGEX) ? decoded : b64File;
|
||||||
|
this.get('onChange')(this.get('index'), { value: fileData, fileName: filename });
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
pickedFile(e) {
|
||||||
|
const { files } = e.target;
|
||||||
|
if (!files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0, len = files.length; i < len; i++) {
|
||||||
|
this.readFile(files[i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateData(e) {
|
||||||
|
const key = this.get('key');
|
||||||
|
set(key, 'value', e.target.value);
|
||||||
|
this.get('onChange')(this.get('index'), this.get('key'));
|
||||||
|
},
|
||||||
|
clearKey() {
|
||||||
|
this.get('onChange')(this.get('index'), { value: '' });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
onDataUpdate: () => {},
|
||||||
|
listData: Ember.computed('listLength', function() {
|
||||||
|
let num = this.get('listLength');
|
||||||
|
if (num) {
|
||||||
|
num = parseInt(num, 10);
|
||||||
|
}
|
||||||
|
return Array(num || 0).fill(null).map(() => ({ value: '' }));
|
||||||
|
}),
|
||||||
|
listLength: 0,
|
||||||
|
actions: {
|
||||||
|
setKey(index, key) {
|
||||||
|
let listData = this.get('listData');
|
||||||
|
listData.replace(index, 1, key);
|
||||||
|
this.get('onDataUpdate')(listData.compact().map(k => k.value));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
/*
|
||||||
|
* @public
|
||||||
|
* @param DS.Model
|
||||||
|
*
|
||||||
|
* the pki-certificate model
|
||||||
|
*/
|
||||||
|
item: null,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
delete(item) {
|
||||||
|
item.save({ adapterOptions: { method: 'revoke' } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
import RoleEdit from './role-edit';
|
||||||
|
|
||||||
|
export default RoleEdit.extend({
|
||||||
|
actions: {
|
||||||
|
delete() {
|
||||||
|
this.get('model').save({ adapterOptions: { method: 'revoke' } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
});
|
|
@ -0,0 +1,58 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import ReplicationActions from 'vault/mixins/replication-actions';
|
||||||
|
|
||||||
|
const { computed } = Ember;
|
||||||
|
|
||||||
|
const DEFAULTS = {
|
||||||
|
token: null,
|
||||||
|
primary_api_addr: null,
|
||||||
|
primary_cluster_addr: null,
|
||||||
|
errors: [],
|
||||||
|
id: null,
|
||||||
|
replicationMode: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Ember.Component.extend(ReplicationActions, DEFAULTS, {
|
||||||
|
replicationMode: null,
|
||||||
|
selectedAction: null,
|
||||||
|
tagName: 'form',
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
model: null,
|
||||||
|
cluster: computed.alias('model'),
|
||||||
|
loading: false,
|
||||||
|
onSubmit: null,
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
if (!this || this.isDestroyed || this.isDestroying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setProperties(DEFAULTS);
|
||||||
|
},
|
||||||
|
|
||||||
|
replicationDisplayMode: computed('replicationMode', function() {
|
||||||
|
const replicationMode = this.get('replicationMode');
|
||||||
|
if (replicationMode === 'dr') {
|
||||||
|
return 'DR';
|
||||||
|
}
|
||||||
|
if (replicationMode === 'performance') {
|
||||||
|
return 'Performance';
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onSubmit() {
|
||||||
|
return this.submitHandler(...arguments);
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.reset();
|
||||||
|
this.setProperties({
|
||||||
|
token: null,
|
||||||
|
id: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { hrefTo } from 'vault/helpers/href-to';
|
||||||
|
const { computed, get, getProperties } = Ember;
|
||||||
|
|
||||||
|
const replicationAttr = function(attr) {
|
||||||
|
return computed('mode', `cluster.{dr,performance}.${attr}`, function() {
|
||||||
|
const { mode, cluster } = getProperties(this, 'mode', 'cluster');
|
||||||
|
return get(cluster, `${mode}.${attr}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
version: Ember.inject.service(),
|
||||||
|
classNames: ['level', 'box-label'],
|
||||||
|
classNameBindings: ['isMenu:is-mobile'],
|
||||||
|
attributeBindings: ['href', 'target'],
|
||||||
|
display: 'banner',
|
||||||
|
isMenu: computed.equal('display', 'menu'),
|
||||||
|
href: computed('display', 'mode', 'replicationEnabled', 'version.hasPerfReplication', function() {
|
||||||
|
const display = this.get('display');
|
||||||
|
const mode = this.get('mode');
|
||||||
|
if (mode === 'performance' && display === 'menu' && this.get('version.hasPerfReplication') === false) {
|
||||||
|
return 'https://www.hashicorp.com/products/vault';
|
||||||
|
}
|
||||||
|
if (this.get('replicationEnabled') || display === 'menu') {
|
||||||
|
return hrefTo(this, 'vault.cluster.replication.mode.index', this.get('cluster.name'), mode);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
target: computed('isPerformance', 'version.hasPerfReplication', function() {
|
||||||
|
if (this.get('isPerformance') && this.get('version.hasPerfReplication') === false) {
|
||||||
|
return '_blank';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
isPerformance: computed.equal('mode', 'performance'),
|
||||||
|
replicationEnabled: replicationAttr('replicationEnabled'),
|
||||||
|
replicationUnsupported: replicationAttr('replicationUnsupported'),
|
||||||
|
replicationDisabled: replicationAttr('replicationDisabled'),
|
||||||
|
syncProgressPercent: replicationAttr('syncProgressPercent'),
|
||||||
|
syncProgress: replicationAttr('syncProgress'),
|
||||||
|
secondaryId: replicationAttr('secondaryId'),
|
||||||
|
modeForUrl: replicationAttr('modeForUrl'),
|
||||||
|
clusterIdDisplay: replicationAttr('clusterIdDisplay'),
|
||||||
|
mode: null,
|
||||||
|
cluster: null,
|
||||||
|
partialName: computed('display', function() {
|
||||||
|
return this.get('display') === 'menu'
|
||||||
|
? 'partials/replication/replication-mode-summary-menu'
|
||||||
|
: 'partials/replication/replication-mode-summary';
|
||||||
|
}),
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue