UI - raft config and snapshotting (#7410)
* add storage route * template out the routes and new raft storage overview * fetch raft config and add new server model * pngcrush the favicon * add view components and binary-file component * add form-save-buttons component * adjust rawRequest so that it can send a request body and returns the response on errors * hook up restore * rename binary-file to file-to-array-buffer * add ember-service-worker * use forked version of ember-service-worker for now * scope the service worker to a single endpoint * show both download buttons for now * add service worker download with a fallback to JS in-mem download * add remove peer functionality * lint go file * add storage-type to the cluster and node models * update edit for to take a cancel action * separate out a css table styles to be used by http-requests-table and on the raft-overview component * add raft-join adapter, model, component and use on the init page * fix styling and gate the menu item on the cluster using raft storage * style tweaks to the raft-join component * fix linting * add form-save-buttons component to storybook * add cancel functionality for backup uploads, and add a success message for successful uploads * add component tests * add filesize.js * add filesize and modified date to file-to-array-buffer * fix linting * fix server section showing in the cluster nav * don't use babel transforms in service worker lib because we don't want 2 copies of babel polyfill * add file-to-array-buffer to storybook * add comments and use removeObjectURL to raft-storage-overview * update alert-banner markdown * messaging change for upload alert banner * Update ui/app/templates/components/raft-storage-restore.hbs Co-Authored-By: Joshua Ogle <joshua@joshuaogle.com> * more comments * actually render the label if passed and update stories with knobs
This commit is contained in:
parent
e8432f1ebe
commit
87d4e6e068
|
@ -101,11 +101,13 @@ export default DS.RESTAdapter.extend(AdapterFetch, {
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: type || 'GET',
|
method: type || 'GET',
|
||||||
headers: opts.headers || {},
|
headers: opts.headers || {},
|
||||||
|
body: opts.body,
|
||||||
|
signal: opts.signal,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
if (response.status >= 200 && response.status < 300) {
|
if (response.status >= 200 && response.status < 300) {
|
||||||
return RSVP.resolve(response);
|
return RSVP.resolve(response);
|
||||||
} else {
|
} else {
|
||||||
return RSVP.reject();
|
return RSVP.reject(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
urlForCreateRecord() {
|
||||||
|
return '/v1/sys/storage/raft/join';
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
|
export default ApplicationAdapter.extend({
|
||||||
|
urlForFindAll() {
|
||||||
|
return '/v1/sys/storage/raft/configuration';
|
||||||
|
},
|
||||||
|
urlForDeleteRecord() {
|
||||||
|
return '/v1/sys/storage/raft/remove-peer';
|
||||||
|
},
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
let server_id = snapshot.attr('nodeId');
|
||||||
|
let url = '/v1/sys/storage/raft/remove-peer';
|
||||||
|
return this.ajax(url, 'POST', { data: { server_id } });
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import filesize from 'filesize';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module FileToArrayBuffer
|
||||||
|
* `FileToArrayBuffer` is a component that will allow you to pick a file from the local file system. Once
|
||||||
|
* loaded, this file will be emitted as a JS ArrayBuffer to the passed `onChange` callback.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* <FileToArrayBuffer @onChange={{action (mut file)}} />
|
||||||
|
* ```
|
||||||
|
* @param onChange=null {Function} - The function to call when the file read is complete. This function
|
||||||
|
* recieves the file as a JS ArrayBuffer
|
||||||
|
* @param [label=null {String}] - Text to use as the label for the file input
|
||||||
|
* @param [fileHelpText=null {String} - Text to use as help under the file input
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export default Component.extend({
|
||||||
|
classNames: ['box', 'is-fullwidth', 'is-marginless', 'is-shadowless'],
|
||||||
|
onChange: () => {},
|
||||||
|
label: null,
|
||||||
|
fileHelpText: null,
|
||||||
|
|
||||||
|
file: null,
|
||||||
|
fileName: null,
|
||||||
|
fileSize: null,
|
||||||
|
fileLastModified: null,
|
||||||
|
|
||||||
|
readFile(file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => this.send('onChange', reader.result, file);
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
pickedFile(e) {
|
||||||
|
let { files } = e.target;
|
||||||
|
if (!files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0, len = files.length; i < len; i++) {
|
||||||
|
this.readFile(files[i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearFile() {
|
||||||
|
this.send('onChange');
|
||||||
|
},
|
||||||
|
onChange(fileAsBytes, fileMeta) {
|
||||||
|
let { name, size, lastModifiedDate } = fileMeta || {};
|
||||||
|
let fileSize = size ? filesize(size) : null;
|
||||||
|
this.set('file', fileAsBytes);
|
||||||
|
this.set('fileName', name);
|
||||||
|
this.set('fileSize', fileSize);
|
||||||
|
this.set('fileLastModified', lastModifiedDate);
|
||||||
|
this.onChange(fileAsBytes, name);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module RaftJoin
|
||||||
|
* RaftJoin component presents the user with a choice to join an existing raft cluster when a new Vault
|
||||||
|
* server is brought up
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* <RaftJoin @onDismiss={{action (mut attr)}} />
|
||||||
|
* ```
|
||||||
|
* @param {function} onDismiss - This function will be called if the user decides not to join an existing
|
||||||
|
* raft cluster
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNames: 'raft-join',
|
||||||
|
store: service(),
|
||||||
|
onDismiss() {},
|
||||||
|
preference: 'join',
|
||||||
|
showJoinForm: false,
|
||||||
|
actions: {
|
||||||
|
advanceFirstScreen() {
|
||||||
|
if (this.preference !== 'join') {
|
||||||
|
this.onDismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.set('showJoinForm', true);
|
||||||
|
},
|
||||||
|
newModel() {
|
||||||
|
return this.store.createRecord('raft-join');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,81 @@
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
|
import config from '../config/environment';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
flashMessages: service(),
|
||||||
|
useServiceWorker: null,
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
if (this.useServiceWorker === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// check to see if we support ServiceWorker
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
// this checks to see if there's an active service worker - if it failed to register
|
||||||
|
// for any reason, then this would be null
|
||||||
|
let worker = await navigator.serviceWorker.getRegistration(config.serviceWorkerScope);
|
||||||
|
if (worker) {
|
||||||
|
this.set('useServiceWorker', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async removePeer(model) {
|
||||||
|
let { nodeId } = model;
|
||||||
|
try {
|
||||||
|
await model.destroyRecord();
|
||||||
|
} catch (e) {
|
||||||
|
let errString = e.errors ? e.errors.join(' ') : e.message || e;
|
||||||
|
this.flashMessages.danger(`There was an issue removing the peer ${nodeId}: ${errString}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.flashMessages.success(`Successfully removed the peer: ${nodeId}.`);
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadViaServiceWorker() {
|
||||||
|
// the actual download happens when the user clicks the anchor link, and then the ServiceWorker
|
||||||
|
// intercepts the request and adds auth headers.
|
||||||
|
// Here we just want to notify users that something is happening before the browser starts the download
|
||||||
|
this.flashMessages.success('The snapshot download will begin shortly.');
|
||||||
|
},
|
||||||
|
|
||||||
|
async downloadSnapshot() {
|
||||||
|
// this entire method is the fallback behavior in case the browser either doesn't support ServiceWorker
|
||||||
|
// or the UI is not being run on https.
|
||||||
|
// here we're downloading the entire snapshot in memory, creating a dataurl with createObjectURL, and
|
||||||
|
// then forcing a download by clicking a link that has a download attribute
|
||||||
|
//
|
||||||
|
// this is not the default because
|
||||||
|
let adapter = getOwner(this).lookup('adapter:application');
|
||||||
|
|
||||||
|
this.flashMessages.success('The snapshot download has begun.');
|
||||||
|
let resp, blob;
|
||||||
|
try {
|
||||||
|
resp = await adapter.rawRequest('/v1/sys/storage/raft/snapshot', 'GET');
|
||||||
|
blob = await resp.blob();
|
||||||
|
} catch (e) {
|
||||||
|
let errString = e.errors ? e.errors.join(' ') : e.message || e;
|
||||||
|
this.flashMessages.danger(`There was an error trying to download the snapshot: ${errString}`);
|
||||||
|
}
|
||||||
|
let filename = 'snapshot.gz';
|
||||||
|
let file = new Blob([blob], { type: 'application/x-gzip' });
|
||||||
|
file.name = filename;
|
||||||
|
if ('msSaveOrOpenBlob' in navigator) {
|
||||||
|
navigator.msSaveOrOpenBlob(file, filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let a = document.createElement('a');
|
||||||
|
let objectURL = window.URL.createObjectURL(file);
|
||||||
|
a.href = objectURL;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(objectURL);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,45 @@
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { alias } from '@ember/object/computed';
|
||||||
|
import { AbortController } from 'fetch';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
file: null,
|
||||||
|
errors: null,
|
||||||
|
forceRestore: false,
|
||||||
|
flashMessages: service(),
|
||||||
|
isUploading: alias('restore.isRunning'),
|
||||||
|
abortController: null,
|
||||||
|
restore: task(function*() {
|
||||||
|
this.set('errors', null);
|
||||||
|
let adapter = getOwner(this).lookup('adapter:application');
|
||||||
|
try {
|
||||||
|
let url = '/v1/sys/storage/raft/snapshot';
|
||||||
|
if (this.forceRestore) {
|
||||||
|
url = `${url}-force`;
|
||||||
|
}
|
||||||
|
let file = new Blob([this.file], { type: 'application/gzip' });
|
||||||
|
let controller = new AbortController();
|
||||||
|
this.set('abortController', controller);
|
||||||
|
yield adapter.rawRequest(url, 'POST', { body: file, signal: controller.signal });
|
||||||
|
this.flashMessages.success('The snapshot was successfully uploaded!');
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name === 'AbortError') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let resp;
|
||||||
|
if (e.json) {
|
||||||
|
resp = yield e.json();
|
||||||
|
}
|
||||||
|
let err = resp ? resp.errors : [e];
|
||||||
|
this.set('errors', err);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
cancelUpload() {
|
||||||
|
this.abortController.abort();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,5 +1,5 @@
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { not, gte, alias, and, or } from '@ember/object/computed';
|
import { alias, and, equal, gte, not, or } from '@ember/object/computed';
|
||||||
import { get, computed } from '@ember/object';
|
import { get, computed } from '@ember/object';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||||
|
@ -38,7 +38,9 @@ export default DS.Model.extend({
|
||||||
sealThreshold: alias('leaderNode.sealThreshold'),
|
sealThreshold: alias('leaderNode.sealThreshold'),
|
||||||
sealProgress: alias('leaderNode.progress'),
|
sealProgress: alias('leaderNode.progress'),
|
||||||
sealType: alias('leaderNode.type'),
|
sealType: alias('leaderNode.type'),
|
||||||
|
storageType: alias('leaderNode.storageType'),
|
||||||
hasProgress: gte('sealProgress', 1),
|
hasProgress: gte('sealProgress', 1),
|
||||||
|
usingRaft: equal('storageType', 'raft'),
|
||||||
|
|
||||||
//replication mode - will only ever be 'unsupported'
|
//replication mode - will only ever be 'unsupported'
|
||||||
//otherwise the particular mode will have the relevant mode attr through replication-attributes
|
//otherwise the particular mode will have the relevant mode attr through replication-attributes
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default DS.Model.extend({
|
||||||
sealNumShares: alias('n'),
|
sealNumShares: alias('n'),
|
||||||
version: attr('string'),
|
version: attr('string'),
|
||||||
type: attr('string'),
|
type: attr('string'),
|
||||||
|
storageType: attr('string'),
|
||||||
|
|
||||||
//https://www.vaultproject.io/docs/http/sys-leader.html
|
//https://www.vaultproject.io/docs/http/sys-leader.html
|
||||||
haEnabled: attr('boolean'),
|
haEnabled: attr('boolean'),
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import DS from 'ember-data';
|
||||||
|
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
const { attr } = DS;
|
||||||
|
|
||||||
|
//leader_api_addr (string: <required>) – Address of the leader node in the Raft cluster to which this node is trying to join.
|
||||||
|
|
||||||
|
//retry (bool: false) - Retry joining the Raft cluster in case of failures.
|
||||||
|
|
||||||
|
//leader_ca_cert (string: "") - CA certificate used to communicate with Raft's leader node.
|
||||||
|
|
||||||
|
//leader_client_cert (string: "") - Client certificate used to communicate with Raft's leader node.
|
||||||
|
|
||||||
|
//leader_client_key (string: "") - Client key used to communicate with Raft's leader node.
|
||||||
|
|
||||||
|
export default DS.Model.extend({
|
||||||
|
leaderApiAddr: attr('string', {
|
||||||
|
label: 'Leader API Address',
|
||||||
|
}),
|
||||||
|
retry: attr('boolean', {
|
||||||
|
label: 'Keep retrying to join in case of failures',
|
||||||
|
}),
|
||||||
|
leaderCaCert: attr('string', {
|
||||||
|
label: 'Leader CA Certificate',
|
||||||
|
editType: 'file',
|
||||||
|
}),
|
||||||
|
leaderClientCert: attr('string', {
|
||||||
|
label: 'Leader Client Certificate',
|
||||||
|
editType: 'file',
|
||||||
|
}),
|
||||||
|
leaderClientKey: attr('string', {
|
||||||
|
label: 'Leader Client Key',
|
||||||
|
editType: 'file',
|
||||||
|
}),
|
||||||
|
fields: computed(function() {
|
||||||
|
return expandAttributeMeta(this, [
|
||||||
|
'leaderApiAddr',
|
||||||
|
'leaderCaCert',
|
||||||
|
'leaderClientCert',
|
||||||
|
'leaderClientKey',
|
||||||
|
'retry',
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
import DS from 'ember-data';
|
||||||
|
const { attr } = DS;
|
||||||
|
|
||||||
|
//{"node_id":"1249bfbc-b234-96f3-0c66-07078ac3e16e","address":"127.0.0.1:8201","leader":true,"protocol_version":"3","voter":true}
|
||||||
|
export default DS.Model.extend({
|
||||||
|
address: attr('string'),
|
||||||
|
nodeId: attr('string'),
|
||||||
|
protocolVersion: attr('string'),
|
||||||
|
voter: attr('boolean'),
|
||||||
|
leader: attr('boolean'),
|
||||||
|
});
|
|
@ -16,6 +16,8 @@ Router.map(function() {
|
||||||
this.mount('open-api-explorer', { path: '/api-explorer' });
|
this.mount('open-api-explorer', { path: '/api-explorer' });
|
||||||
this.route('license');
|
this.route('license');
|
||||||
this.route('requests', { path: '/metrics/requests' });
|
this.route('requests', { path: '/metrics/requests' });
|
||||||
|
this.route('storage', { path: '/storage/raft' });
|
||||||
|
this.route('storage-restore', { path: '/storage/raft/restore' });
|
||||||
this.route('settings', function() {
|
this.route('settings', function() {
|
||||||
this.route('index', { path: '/' });
|
this.route('index', { path: '/' });
|
||||||
this.route('seal');
|
this.route('seal');
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
import ClusterRoute from 'vault/mixins/cluster-route';
|
||||||
|
|
||||||
|
export default Route.extend(ClusterRoute, {
|
||||||
|
model() {
|
||||||
|
return this.store.findAll('server');
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
doRefresh() {
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
import ApplicationSerializer from './application';
|
||||||
|
|
||||||
|
export default ApplicationSerializer.extend({
|
||||||
|
primaryKey: 'node_id',
|
||||||
|
normalizeItems(payload) {
|
||||||
|
if (payload.data && payload.data.config) {
|
||||||
|
// rewrite the payload from data.config.servers to data.keys so we can use the application serializer
|
||||||
|
// on it
|
||||||
|
return payload.data.config.servers.slice(0);
|
||||||
|
}
|
||||||
|
return this._super(payload);
|
||||||
|
},
|
||||||
|
});
|
|
@ -27,6 +27,7 @@ const API_PATHS = {
|
||||||
replication: 'sys/replication',
|
replication: 'sys/replication',
|
||||||
license: 'sys/license',
|
license: 'sys/license',
|
||||||
seal: 'sys/seal',
|
seal: 'sys/seal',
|
||||||
|
raft: 'sys/storage/raft/configuration',
|
||||||
},
|
},
|
||||||
metrics: {
|
metrics: {
|
||||||
requests: 'sys/internal/counters/requests',
|
requests: 'sys/internal/counters/requests',
|
||||||
|
|
|
@ -1,31 +1,4 @@
|
||||||
.http-requests-table {
|
.http-requests-table {
|
||||||
& .is-collapsed {
|
|
||||||
visibility: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
& th,
|
|
||||||
td {
|
|
||||||
padding: $spacing-s;
|
|
||||||
}
|
|
||||||
|
|
||||||
& th {
|
|
||||||
color: $grey-dark;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: $size-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
& tbody th {
|
|
||||||
font-size: $size-7;
|
|
||||||
}
|
|
||||||
|
|
||||||
& tr {
|
|
||||||
border-bottom: 1px solid $grey-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
& td {
|
|
||||||
color: $grey-darkest;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .percent-change {
|
& .percent-change {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: $size-7;
|
font-size: $size-7;
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
.raft-join .field {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.raft-join .box.is-fullwidth {
|
||||||
|
padding-top: $spacing-s;
|
||||||
|
padding-bottom: $spacing-s;
|
||||||
|
}
|
||||||
|
.raft-join-unseal {
|
||||||
|
color: $orange;
|
||||||
|
font-size: $size-6;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
.vlt-table {
|
||||||
|
.is-collapsed {
|
||||||
|
visibility: collapse;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: $spacing-s;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: $grey-dark;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: $size-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody th {
|
||||||
|
font-size: $size-7;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
border-bottom: 1px solid $grey-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
color: $grey-darkest;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.middle {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.no-padding {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,8 +55,8 @@
|
||||||
@import './components/form-section';
|
@import './components/form-section';
|
||||||
@import './components/global-flash';
|
@import './components/global-flash';
|
||||||
@import './components/hover-copy-button';
|
@import './components/hover-copy-button';
|
||||||
@import './components/http-requests-table';
|
|
||||||
@import './components/http-requests-bar-chart';
|
@import './components/http-requests-bar-chart';
|
||||||
|
@import './components/http-requests-table';
|
||||||
@import './components/init-illustration';
|
@import './components/init-illustration';
|
||||||
@import './components/info-table-row';
|
@import './components/info-table-row';
|
||||||
@import './components/input-hint';
|
@import './components/input-hint';
|
||||||
|
@ -73,6 +73,7 @@
|
||||||
@import './components/page-header';
|
@import './components/page-header';
|
||||||
@import './components/popup-menu';
|
@import './components/popup-menu';
|
||||||
@import './components/radial-progress';
|
@import './components/radial-progress';
|
||||||
|
@import './components/raft-join';
|
||||||
@import './components/role-item';
|
@import './components/role-item';
|
||||||
@import './components/search-select';
|
@import './components/search-select';
|
||||||
@import './components/shamir-progress';
|
@import './components/shamir-progress';
|
||||||
|
@ -87,6 +88,7 @@
|
||||||
@import './components/ui-wizard';
|
@import './components/ui-wizard';
|
||||||
@import './components/vault-loading';
|
@import './components/vault-loading';
|
||||||
@import './components/vlt-radio';
|
@import './components/vlt-radio';
|
||||||
|
@import './components/vlt-table';
|
||||||
|
|
||||||
// bulma-free-zone
|
// bulma-free-zone
|
||||||
@import './components/hs-icon';
|
@import './components/hs-icon';
|
||||||
|
|
|
@ -17,3 +17,9 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox-help-text {
|
||||||
|
font-size: $size-7;
|
||||||
|
color: $ui-gray-700;
|
||||||
|
padding-left: 28px;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<div class="field">
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<label class="is-label">
|
||||||
|
{{#if label}}
|
||||||
|
{{label}}
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
<div class="file has-name is-fullwidth">
|
||||||
|
<label class="file-label">
|
||||||
|
<input class="file-input" type="file" onchange={{action "pickedFile"}} data-test-file-input>
|
||||||
|
<span class="file-cta button">
|
||||||
|
<Icon @glyph="upload" class="has-light-grey-text" />
|
||||||
|
Choose a file…
|
||||||
|
</span>
|
||||||
|
<span class="file-name has-text-grey-dark" data-test-text-file-input-label=true>
|
||||||
|
{{or this.fileName "No file chosen"}}
|
||||||
|
</span>
|
||||||
|
{{#if this.fileName}}
|
||||||
|
<button type="button" class="file-delete-button" {{action 'clearFile'}} data-test-text-clear>
|
||||||
|
<Icon @glyph="cancel-circle-outline" />
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{#if this.fileName}}
|
||||||
|
<p class="help has-text-grey">
|
||||||
|
This file is {{this.fileSize}} and was created on {{date-format this.fileLastModified 'MMM DD, YYYY hh:mm:ss A'}}.
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
{{#if @fileHelpText}}
|
||||||
|
<p class="help has-text-grey">
|
||||||
|
{{@fileHelpText}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="http-requests-table">
|
<div class="http-requests-table vlt-table">
|
||||||
<table class="is-fullwidth">
|
<table class="is-fullwidth">
|
||||||
<caption class="is-collapsed">HTTP Request Volume</caption>
|
<caption class="is-collapsed">HTTP Request Volume</caption>
|
||||||
<thead class="has-text-weight-semibold">
|
<thead class="has-text-weight-semibold">
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
<AlertBanner
|
||||||
|
@type="warning"
|
||||||
|
@message="Vault is sealed"
|
||||||
|
@yieldWithoutColumn={{true}}
|
||||||
|
class="is-marginless"
|
||||||
|
>
|
||||||
|
<div class="is-flex is-flex-v-centered">
|
||||||
|
<div>
|
||||||
|
<span class="message-title">Warning</span> Vault is sealed
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AlertBanner>
|
||||||
|
{{#if this.showJoinForm}}
|
||||||
|
<div class="box is-marginless is-shadowless">
|
||||||
|
<h2 class="title is-5" data-test-join-header>
|
||||||
|
Join an existing Raft cluster
|
||||||
|
</h2>
|
||||||
|
<EditForm
|
||||||
|
@model={{compute (action "newModel")}}
|
||||||
|
@saveButtonText="Join"
|
||||||
|
@cancelButtonText="Back"
|
||||||
|
@onCancel={{action (mut this.showJoinForm) false}}
|
||||||
|
@onSave={{transition-to "vault.cluster.unseal"}}
|
||||||
|
@flashEnabled={{false}}
|
||||||
|
@includeBox={{false}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<form onsubmit={{action "advanceFirstScreen" }} data-test-join-choice>
|
||||||
|
<div class="box is-marginless is-shadowless">
|
||||||
|
<h2 class="title is-6">
|
||||||
|
This server is configured to use Raft Storage.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
How do you want to get started?
|
||||||
|
</h2>
|
||||||
|
<RadioButton
|
||||||
|
@value="join"
|
||||||
|
@groupValue={{this.preference}}
|
||||||
|
@changed={{action (mut this.preference) }}
|
||||||
|
@name="setup-pref"
|
||||||
|
@radioId="join"
|
||||||
|
@classNames="vlt-radio is-block"
|
||||||
|
>
|
||||||
|
<label for="join" />
|
||||||
|
Join an existing Raft cluster
|
||||||
|
</RadioButton>
|
||||||
|
<RadioButton
|
||||||
|
@value="init"
|
||||||
|
@groupValue={{this.preference}}
|
||||||
|
@changed={{action (mut this.preference) }}
|
||||||
|
@name="setup-pref"
|
||||||
|
@radioId="init"
|
||||||
|
@classNames="vlt-radio is-block"
|
||||||
|
>
|
||||||
|
<label for="init" data-test-join-init />
|
||||||
|
Create a new Raft cluster
|
||||||
|
</RadioButton>
|
||||||
|
</div>
|
||||||
|
<div class="box is-marginless is-shadowless">
|
||||||
|
<button type="submit" class="button is-primary" data-test-next>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,109 @@
|
||||||
|
<PageHeader as |p|>
|
||||||
|
<p.levelLeft>
|
||||||
|
<h1 class="title is-3">
|
||||||
|
Raft Storage
|
||||||
|
</h1>
|
||||||
|
</p.levelLeft>
|
||||||
|
</PageHeader>
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarActions>
|
||||||
|
<BasicDropdown
|
||||||
|
@class="popup-menu"
|
||||||
|
@horizontalPosition="auto-right"
|
||||||
|
@verticalPosition="below"
|
||||||
|
as |D|
|
||||||
|
>
|
||||||
|
<D.trigger
|
||||||
|
data-test-popup-menu-trigger="true"
|
||||||
|
@class={{concat "toolbar-link" (if D.isOpen " is-active")}}
|
||||||
|
@tagName="button"
|
||||||
|
>
|
||||||
|
Snapshots
|
||||||
|
<Chevron @direction="down" @isButton={{true}} />
|
||||||
|
</D.trigger>
|
||||||
|
<D.content @class="popup-menu-content">
|
||||||
|
<nav class="box menu">
|
||||||
|
<ul class="menu-list">
|
||||||
|
<li class="action">
|
||||||
|
{{#if this.useServiceWorker}}
|
||||||
|
<a href="/v1/sys/storage/raft/snapshot" onclick={{queue (action "downloadViaServiceWorker") (action D.actions.close)}}>
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
<button type="button" class="link is-ghost" onclick={{queue (action "downloadSnapshot") (action D.actions.close)}}>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
<li class="action">
|
||||||
|
{{#link-to "vault.cluster.storage-restore"}}
|
||||||
|
Restore
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</D.content>
|
||||||
|
</BasicDropdown>
|
||||||
|
</ToolbarActions>
|
||||||
|
</Toolbar>
|
||||||
|
|
||||||
|
<table class="vlt-table is-fullwidth">
|
||||||
|
<caption class="is-collapsed">Raft servers</caption>
|
||||||
|
<thead class="has-text-weight-semibold">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Address</th>
|
||||||
|
<th scope="col">Voter</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each @model as |server|}}
|
||||||
|
<tr data-raft-row>
|
||||||
|
<td>
|
||||||
|
{{server.address}}
|
||||||
|
{{#if server.leader}}
|
||||||
|
<span class="tag">Leader</span>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
{{#if server.voter}}
|
||||||
|
<Icon
|
||||||
|
aria-label="Yes"
|
||||||
|
class="icon-true has-text-success"
|
||||||
|
@size="l"
|
||||||
|
@glyph="check-circle-outline"
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<Icon
|
||||||
|
aria-label="No"
|
||||||
|
class="icon-false"
|
||||||
|
@size="l"
|
||||||
|
@glyph="cancel-square-outline"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td class="middle no-padding has-text-right">
|
||||||
|
<PopupMenu>
|
||||||
|
<Confirm as |c|>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li class="action">
|
||||||
|
<c.Message
|
||||||
|
@id={{server.nodeId}}
|
||||||
|
@onConfirm={{action "removePeer" server}}
|
||||||
|
@triggerText="Remove Peer"
|
||||||
|
@confirmButtonText="Remove"
|
||||||
|
@title={{concat "Remove " server.nodeId "?"}}
|
||||||
|
@message="This will remove the server from the raft cluster."
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</Confirm>
|
||||||
|
</PopupMenu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,70 @@
|
||||||
|
<PageHeader as |p|>
|
||||||
|
<p.top>
|
||||||
|
<nav class="key-value-header breadcrumb">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
{{#link-to "vault.cluster.storage"}}
|
||||||
|
Raft Storage
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</p.top>
|
||||||
|
<p.levelLeft>
|
||||||
|
<h1 class="title is-3">
|
||||||
|
Restore Snapshot
|
||||||
|
</h1>
|
||||||
|
</p.levelLeft>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<form {{action (perform this.restore this.file) on="submit"}}>
|
||||||
|
<MessageError @errors={{this.errors}} />
|
||||||
|
|
||||||
|
{{#if this.isUploading}}
|
||||||
|
<div class="box is-sideless is-fullwidth is-marginless">
|
||||||
|
<AlertBanner
|
||||||
|
@type="warning"
|
||||||
|
@title="Uploading your file..."
|
||||||
|
@message="Raft snapshots can be very large files. Uploading the snapshot may take some time."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="box is-fullwidth is-shadowless">
|
||||||
|
<button type="button" class="button" onclick={{action "cancelUpload"}}>
|
||||||
|
Cancel upload
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="box is-sideless is-fullwidth is-marginless">
|
||||||
|
<AlertBanner
|
||||||
|
@type="warning"
|
||||||
|
@title="This might take a while"
|
||||||
|
@message="Raft snapshots can be very large files. Uploading the snapshot may take some time."
|
||||||
|
/>
|
||||||
|
<FileToArrayBuffer @onChange={{action (mut file)}} />
|
||||||
|
<div class="b-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="force-restore"
|
||||||
|
class="styled"
|
||||||
|
checked={{this.forceRestore}}
|
||||||
|
onchange={{action
|
||||||
|
(mut this.forceRestore)
|
||||||
|
value="target.checked"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label for="force-restore" class="is-label">
|
||||||
|
Force restore
|
||||||
|
</label>
|
||||||
|
<p class="checkbox-help-text">
|
||||||
|
Bypass checks to ensure the AutoUnseal or Shamir keys are consistent with the snapshot data.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FormSaveButtons
|
||||||
|
@saveButtonText="Restore"
|
||||||
|
@isSaving={{this.restore.isRunning}}
|
||||||
|
@cancelLinkParams={{array "vault.cluster.storage"}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</form>
|
|
@ -62,25 +62,41 @@
|
||||||
<hr/>
|
<hr/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (has-permission 'status' routeParams='license')}}
|
{{/unless}}
|
||||||
|
{{#if (or
|
||||||
|
(and version.features (has-permission 'status' routeParams='license'))
|
||||||
|
(and cluster.usingRaft (has-permission 'status' routeParams='raft'))
|
||||||
|
)
|
||||||
|
}}
|
||||||
<nav class="menu">
|
<nav class="menu">
|
||||||
<div class="menu-label">
|
<div class="menu-label">
|
||||||
License
|
Server
|
||||||
</div>
|
</div>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li class="action">
|
{{#if (and version.features (has-permission 'status' routeParams='license'))}}
|
||||||
{{#link-to "vault.cluster.license" activeCluster.name invokeAction=onLinkClick}}
|
<li class="action">
|
||||||
<div class="level is-mobile">
|
{{#link-to "vault.cluster.license" activeCluster.name invokeAction=onLinkClick}}
|
||||||
<span class="level-left">See details</span>
|
<div class="level is-mobile">
|
||||||
<Chevron class="has-text-grey-light level-right" />
|
<span class="level-left">License</span>
|
||||||
</div>
|
<Chevron class="has-text-grey-light level-right" />
|
||||||
{{/link-to}}
|
</div>
|
||||||
</li>
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (and cluster.usingRaft (has-permission 'status' routeParams='raft'))}}
|
||||||
|
<li class="action">
|
||||||
|
{{#link-to "vault.cluster.storage" activeCluster.name invokeAction=onLinkClick}}
|
||||||
|
<div class="level is-mobile">
|
||||||
|
<span class="level-left">Raft Storage</span>
|
||||||
|
<Chevron class="has-text-grey-light level-right" />
|
||||||
|
</div>
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<hr/>
|
<hr/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/unless}}
|
|
||||||
<nav class="menu">
|
<nav class="menu">
|
||||||
<div class="menu-label">
|
<div class="menu-label">
|
||||||
Seal status
|
Seal status
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
<SplashPage as |Page|>
|
<SplashPage as |Page|>
|
||||||
{{#if keyData}}
|
{{#if (and this.model.usingRaft (not this.prefersInit))}}
|
||||||
|
<Page.header>
|
||||||
|
<h1 class="title is-4">
|
||||||
|
Raft Storage
|
||||||
|
</h1>
|
||||||
|
</Page.header>
|
||||||
|
<Page.content>
|
||||||
|
<RaftJoin @onDismiss={{action (mut prefersInit) true}} />
|
||||||
|
</Page.content>
|
||||||
|
{{else if keyData}}
|
||||||
<Page.header>
|
<Page.header>
|
||||||
{{#let (or keyData.recovery_keys keyData.keys) as |keyArray|}}
|
{{#let (or keyData.recovery_keys keyData.keys) as |keyArray|}}
|
||||||
<h1 class="title is-4">
|
<h1 class="title is-4">
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<RaftStorageRestore />
|
|
@ -0,0 +1 @@
|
||||||
|
<RaftStorageOverview @model={{this.model}} />
|
|
@ -6,6 +6,7 @@ module.exports = function(environment) {
|
||||||
modulePrefix: 'vault',
|
modulePrefix: 'vault',
|
||||||
environment: environment,
|
environment: environment,
|
||||||
rootURL: '/ui/',
|
rootURL: '/ui/',
|
||||||
|
serviceWorkerScope: '/v1/sys/storage/raft/snapshot',
|
||||||
locationType: 'auto',
|
locationType: 'auto',
|
||||||
EmberENV: {
|
EmberENV: {
|
||||||
FEATURES: {
|
FEATURES: {
|
||||||
|
|
|
@ -11,6 +11,10 @@ const isCI = !!process.env.CI;
|
||||||
|
|
||||||
module.exports = function(defaults) {
|
module.exports = function(defaults) {
|
||||||
var app = new EmberApp(defaults, {
|
var app = new EmberApp(defaults, {
|
||||||
|
'ember-service-worker': {
|
||||||
|
serviceWorkerScope: config.serviceWorkerScope,
|
||||||
|
skipWaitingOnMessage: true,
|
||||||
|
},
|
||||||
svgJar: {
|
svgJar: {
|
||||||
//optimize: false,
|
//optimize: false,
|
||||||
//paths: [],
|
//paths: [],
|
||||||
|
|
|
@ -14,6 +14,7 @@ import layout from '../templates/components/alert-banner';
|
||||||
*
|
*
|
||||||
* @param type=null {String} - The banner type. This comes from the message-types helper.
|
* @param type=null {String} - The banner type. This comes from the message-types helper.
|
||||||
* @param [message=null {String}] - The message to display within the banner.
|
* @param [message=null {String}] - The message to display within the banner.
|
||||||
|
* @param [title=null {String}] - A title to show above the message. If this is not provided, there are default values for each type of alert.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ export default Component.extend({
|
||||||
layout,
|
layout,
|
||||||
type: null,
|
type: null,
|
||||||
message: null,
|
message: null,
|
||||||
|
title: null,
|
||||||
yieldWithoutColumn: false,
|
yieldWithoutColumn: false,
|
||||||
classNameBindings: ['containerClass'],
|
classNameBindings: ['containerClass'],
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,10 @@ export default Component.extend({
|
||||||
deleteSuccessMessage: 'Deleted!',
|
deleteSuccessMessage: 'Deleted!',
|
||||||
deleteButtonText: 'Delete',
|
deleteButtonText: 'Delete',
|
||||||
saveButtonText: 'Save',
|
saveButtonText: 'Save',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
cancelLink: null,
|
cancelLink: null,
|
||||||
|
flashEnabled: true,
|
||||||
|
includeBox: true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @param Function
|
* @param Function
|
||||||
|
@ -42,7 +45,9 @@ export default Component.extend({
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.flashMessages.success(this.get(messageKey));
|
if (this.flashEnabled) {
|
||||||
|
this.flashMessages.success(this.get(messageKey));
|
||||||
|
}
|
||||||
if (this.callOnSaveAfterRender) {
|
if (this.callOnSaveAfterRender) {
|
||||||
next(() => {
|
next(() => {
|
||||||
this.onSave({ saveType: method, model });
|
this.onSave({ saveType: method, model });
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import layout from '../templates/components/form-save-buttons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module FormSaveButtons
|
||||||
|
* `FormSaveButtons` displays a button save and a cancel button at the bottom of a form.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* <FormSaveButtons @saveButtonText="Save" @isSaving={{isSaving}} @cancelLinkParams={{array
|
||||||
|
* "foo.route"}} />
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param [saveButtonText="Save" {String}] - The text that will be rendered on the Save button.
|
||||||
|
* @param [isSaving=false {Boolean}] - If the form is saving, this should be true. This will disable the save button and render a spinner on it;
|
||||||
|
* @param [cancelLinkParams=[] {Array}] - An array of arguments used to construct a link to navigate back to when the Cancel button is clicked.
|
||||||
|
* @param [onCancel=null {Fuction}] - If the form should call an action on cancel instead of route somewhere, the fucntion can be passed using onCancel instead of passing an array to cancelLinkParams.
|
||||||
|
* @param [includeBox=true {Boolean}] - By default we include padding around the form with underlines. Passing this value as false will remove that padding.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
layout,
|
||||||
|
tagName: '',
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
<form {{action (perform save model) on="submit"}}>
|
<form {{action (perform save model) on="submit"}}>
|
||||||
<MessageError @model={{model}} data-test-edit-form-error />
|
<MessageError @model={{model}} data-test-edit-form-error />
|
||||||
<div class="box is-sideless is-fullwidth is-marginless">
|
<div class="{{if this.includeBox 'box is-sideless is-fullwidth is-marginless'}}">
|
||||||
<NamespaceReminder @mode="save" />
|
<NamespaceReminder @mode="save" />
|
||||||
{{#if (or model.fields model.attrs)}}
|
{{#if (or model.fields model.attrs)}}
|
||||||
{{#each (or model.fields model.attrs) as |attr|}}
|
{{#each (or model.fields model.attrs) as |attr|}}
|
||||||
|
@ -10,21 +10,11 @@
|
||||||
<FormFieldGroups @model={{model}} @mode={{@mode}} />
|
<FormFieldGroups @model={{model}} @mode={{@mode}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless">
|
<FormSaveButtons
|
||||||
<div class="field is-grouped">
|
@isSaving={{this.save.isRunning}}
|
||||||
<div class="control">
|
@saveButtonText={{this.saveButtonText}}
|
||||||
<button type="submit" data-test-edit-form-submit class="button is-primary {{if save.isRunning 'loading'}}"
|
@canceLinkParams={{@canceLinkParams}}
|
||||||
disabled={{save.isRunning}}>
|
@includeBox={{this.includeBox}}
|
||||||
{{saveButtonText}}
|
@onCancel={{@onCancel}}
|
||||||
</button>
|
/>
|
||||||
</div>
|
|
||||||
{{#if cancelLinkParams}}
|
|
||||||
<div class="control">
|
|
||||||
{{#link-to params=cancelLinkParams class="button"}}
|
|
||||||
Cancel
|
|
||||||
{{/link-to}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless {{if (eq @includeBox false) 'is-shadowless'}}">
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button type="submit" data-test-edit-form-submit class="button is-primary {{if @isSaving 'loading'}}"
|
||||||
|
disabled={{@isSaving}}>
|
||||||
|
{{or @saveButtonText "Save"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{#if @cancelLinkParams}}
|
||||||
|
<div class="control">
|
||||||
|
{{#link-to params=@cancelLinkParams class="button"}}
|
||||||
|
{{or @cancelButtonText "Cancel"}}
|
||||||
|
{{/link-to}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if @onCancel}}
|
||||||
|
<div class="control">
|
||||||
|
<button type="button" class="button" onclick={{action onCancel}} data-test-cancel-button>
|
||||||
|
{{or @cancelButtonText "Cancel"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from 'core/components/form-save-buttons';
|
|
@ -1,13 +1,15 @@
|
||||||
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/alert-banner.js. To make changes, first edit that file and run "yarn gen-story-md alert-banner" to re-generate the content.-->
|
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in lib/core/addon/components/alert-banner.js. To make changes, first edit that file and run "yarn gen-story-md alert-banner" to re-generate the content.-->
|
||||||
|
|
||||||
## AlertBanner
|
## AlertBanner
|
||||||
`AlertBanner` components are used to inform users of important messages.
|
`AlertBanner` components are used to inform users of important messages.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
| Param | Type | Default | Description |
|
| Param | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| type | <code>String</code> | <code></code> | The banner type. This comes from the message-types helper. |
|
| type | <code>String</code> | <code></code> | The banner type. This comes from the message-types helper. |
|
||||||
| [message] | <code>String</code> | <code></code> | The message to display within the banner. |
|
| [message] | <code>String</code> | <code></code> | The message to display within the banner. |
|
||||||
|
| [title] | <code>String</code> | <code></code> | A title to show above the message. If this is not provided, there are default values for each type of alert. |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
|
@ -17,7 +19,7 @@
|
||||||
|
|
||||||
**See**
|
**See**
|
||||||
|
|
||||||
- [Uses of AlertBanner](https://github.com/hashicorp/vault/search?l=Handlebars&q=AlertBanner)
|
- [Uses of AlertBanner](https://github.com/hashicorp/vault/search?l=Handlebars&q=AlertBanner+OR+alert-banner)
|
||||||
- [AlertBanner Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/alert-banner.js)
|
- [AlertBanner Source Code](https://github.com/hashicorp/vault/blob/master/ui/lib/core/addon/components/alert-banner.js)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in lib/core/addon/components/form-save-buttons.js. To make changes, first edit that file and run "yarn gen-story-md form-save-buttons" to re-generate the content.-->
|
||||||
|
|
||||||
|
## FormSaveButtons
|
||||||
|
`FormSaveButtons` displays a button save and a cancel button at the bottom of a form.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [saveButtonText] | <code>String</code> | <code>"Save"</code> | The text that will be rendered on the Save button. |
|
||||||
|
| [isSaving] | <code>Boolean</code> | <code>false</code> | If the form is saving, this should be true. This will disable the save button and render a spinner on it; |
|
||||||
|
| [cancelLinkParams] | <code>Array</code> | <code>[]</code> | An array of arguments used to construct a link to navigate back to when the Cancel button is clicked. |
|
||||||
|
| [onCancel] | <code>Fuction</code> | <code></code> | If the form should call an action on cancel instead of route somewhere, the fucntion can be passed using onCancel instead of passing an array to cancelLinkParams. |
|
||||||
|
| [includeBox] | <code>Boolean</code> | <code>true</code> | By default we include padding around the form with underlines. Passing this value as false will remove that padding. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```js
|
||||||
|
<FormSaveButtons @saveButtonText="Save" @isSaving={{isSaving}} @cancelLinkParams={{array
|
||||||
|
"foo.route"}} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**See**
|
||||||
|
|
||||||
|
- [Uses of FormSaveButtons](https://github.com/hashicorp/vault/search?l=Handlebars&q=FormSaveButtons+OR+form-save-buttons)
|
||||||
|
- [FormSaveButtons Source Code](https://github.com/hashicorp/vault/blob/master/ui/lib/core/addon/components/form-save-buttons.js)
|
||||||
|
|
||||||
|
---
|
|
@ -0,0 +1,38 @@
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
import { storiesOf } from '@storybook/ember';
|
||||||
|
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
|
||||||
|
import notes from './form-save-buttons.md';
|
||||||
|
|
||||||
|
storiesOf('FormSaveButtons/', module)
|
||||||
|
.addParameters({ options: { showPanel: true } })
|
||||||
|
.addDecorator(
|
||||||
|
withKnobs({
|
||||||
|
escapeHTML: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
`FormSaveButtons`,
|
||||||
|
() => ({
|
||||||
|
template: hbs`
|
||||||
|
<h5 class="title is-5">Form save buttons</h5>
|
||||||
|
<FormSaveButtons
|
||||||
|
@isSaving={{this.save}}
|
||||||
|
@saveButtonText={{this.saveButtonText}}
|
||||||
|
@cancelButtonText={{this.cancelButtonText}}
|
||||||
|
@includeBox={{this.includeBox}}
|
||||||
|
@onCancel={{this.onCancel}}
|
||||||
|
/>
|
||||||
|
`,
|
||||||
|
|
||||||
|
context: {
|
||||||
|
save: boolean('saving?', false),
|
||||||
|
includeBox: boolean('include box?', true),
|
||||||
|
saveButtonText: text('save button text', 'Save'),
|
||||||
|
cancelButtonText: text('cancel button text', 'Cancel'),
|
||||||
|
onCancel: () => {
|
||||||
|
console.log('Canceled!');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ notes }
|
||||||
|
);
|
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: require('./package').name,
|
||||||
|
|
||||||
|
isDevelopingAddon() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
serverMiddleware({ app }) {
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.setHeader('Service-Worker-Allowed', '/');
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "service-worker-authenticated-download",
|
||||||
|
"keywords": [
|
||||||
|
"ember-addon",
|
||||||
|
"ember-service-worker-plugin"
|
||||||
|
],
|
||||||
|
"ember-addon": {
|
||||||
|
"before": [
|
||||||
|
"serve-files-middleware",
|
||||||
|
"broccoli-serve-files",
|
||||||
|
"history-support-middleware",
|
||||||
|
"proxy-server-middleware"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ember-cli-babel": "*",
|
||||||
|
"ember-auto-import": "*",
|
||||||
|
"ember-source": "*",
|
||||||
|
"ember-service-worker": "*"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { addSuccessHandler } from 'ember-service-worker/service-worker-registration';
|
||||||
|
import Namespace from '@ember/application/namespace';
|
||||||
|
|
||||||
|
function getToken() {
|
||||||
|
// fix this later by allowing registration somewhere in the app lifecycle were we can have access to
|
||||||
|
// services, etc.
|
||||||
|
return Namespace.NAMESPACES_BY_ID['vault'].__container__.lookup('service:auth').currentToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSuccessHandler(function(registration) {
|
||||||
|
// attach the handler for the message event so we can send over the auth token
|
||||||
|
navigator.serviceWorker.addEventListener('message', event => {
|
||||||
|
let { action } = event.data;
|
||||||
|
let port = event.ports[0];
|
||||||
|
|
||||||
|
if (action === 'getToken') {
|
||||||
|
let token = getToken();
|
||||||
|
if (!token) {
|
||||||
|
console.error('Unable to retrieve Vault tokent');
|
||||||
|
}
|
||||||
|
port.postMessage({ token: token });
|
||||||
|
} else {
|
||||||
|
console.error('Unknown event', event);
|
||||||
|
port.postMessage({
|
||||||
|
error: 'Unknown request',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// attempt to unregister the service worker on unload because we're not doing any sort of caching
|
||||||
|
window.addEventListener('unload', function() {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { createUrlRegEx, urlMatchesAnyPattern } from 'ember-service-worker/service-worker/url-utils';
|
||||||
|
|
||||||
|
var patterns = ['/v1/sys/storage/raft/snapshot'];
|
||||||
|
var REGEXES = patterns.map(createUrlRegEx);
|
||||||
|
|
||||||
|
function sendMessage(message) {
|
||||||
|
return self.clients.matchAll({ includeUncontrolled: true, type: 'window' }).then(function(results) {
|
||||||
|
var client = results[0];
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var messageChannel = new MessageChannel();
|
||||||
|
messageChannel.port2.onmessage = function(event) {
|
||||||
|
if (event.data.error) {
|
||||||
|
reject(event.data.error);
|
||||||
|
} else {
|
||||||
|
resolve(event.data.token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client.postMessage(message, [messageChannel.port1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function authenticateRequest(request) {
|
||||||
|
// copy the reaquest headers so we can mutate them
|
||||||
|
let headers = new Headers(request.headers);
|
||||||
|
|
||||||
|
// get and set vault token so the request is authenticated
|
||||||
|
return sendMessage({ action: 'getToken' }).then(function(token) {
|
||||||
|
headers.set('X-Vault-Token', token);
|
||||||
|
|
||||||
|
// continue the fetch with the new request
|
||||||
|
// that has the auth header
|
||||||
|
return fetch(
|
||||||
|
new Request(request.url, {
|
||||||
|
method: request.method,
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('fetch', function(fetchEvent) {
|
||||||
|
const request = fetchEvent.request;
|
||||||
|
|
||||||
|
if (urlMatchesAnyPattern(request.url, REGEXES) && request.method === 'GET') {
|
||||||
|
return fetchEvent.respondWith(authenticateRequest(request));
|
||||||
|
} else {
|
||||||
|
return fetchEvent.respondWith(fetch(request));
|
||||||
|
}
|
||||||
|
});
|
|
@ -107,6 +107,7 @@
|
||||||
"ember-resolver": "^5.0.1",
|
"ember-resolver": "^5.0.1",
|
||||||
"ember-responsive": "^3.0.0-beta.3",
|
"ember-responsive": "^3.0.0-beta.3",
|
||||||
"ember-router-helpers": "^0.2.0",
|
"ember-router-helpers": "^0.2.0",
|
||||||
|
"ember-service-worker": "meirish/ember-service-worker#configurable-scope",
|
||||||
"ember-sinon": "^4.0.0",
|
"ember-sinon": "^4.0.0",
|
||||||
"ember-source": "~3.8.0",
|
"ember-source": "~3.8.0",
|
||||||
"ember-svg-jar": "^2.1.0",
|
"ember-svg-jar": "^2.1.0",
|
||||||
|
@ -117,6 +118,7 @@
|
||||||
"eslint-config-prettier": "^6.0.0",
|
"eslint-config-prettier": "^6.0.0",
|
||||||
"eslint-plugin-ember": "^6.7.0",
|
"eslint-plugin-ember": "^6.7.0",
|
||||||
"eslint-plugin-prettier": "^3.1.0",
|
"eslint-plugin-prettier": "^3.1.0",
|
||||||
|
"filesize": "^4.2.1",
|
||||||
"flat": "^4.1.0",
|
"flat": "^4.1.0",
|
||||||
"ivy-codemirror": "IvyApp/ivy-codemirror#fb09333c5144da47e14a9e6260f80577d5408374",
|
"ivy-codemirror": "IvyApp/ivy-codemirror#fb09333c5144da47e14a9e6260f80577d5408374",
|
||||||
"jsonlint": "^1.6.3",
|
"jsonlint": "^1.6.3",
|
||||||
|
@ -163,7 +165,8 @@
|
||||||
"lib/css",
|
"lib/css",
|
||||||
"lib/kmip",
|
"lib/kmip",
|
||||||
"lib/open-api-explorer",
|
"lib/open-api-explorer",
|
||||||
"lib/replication"
|
"lib/replication",
|
||||||
|
"lib/service-worker-authenticated-download"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
ui/public/favicon.png (Stored with Git LFS)
BIN
ui/public/favicon.png (Stored with Git LFS)
Binary file not shown.
|
@ -0,0 +1,26 @@
|
||||||
|
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/file-to-array-buffer.js. To make changes, first edit that file and run "yarn gen-story-md file-to-array-buffer" to re-generate the content.-->
|
||||||
|
|
||||||
|
## FileToArrayBuffer
|
||||||
|
`FileToArrayBuffer` is a component that will allow you to pick a file from the local file system. Once
|
||||||
|
loaded, this file will be emitted as a JS ArrayBuffer to the passed `onChange` callback.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| onChange | <code>function</code> | <code></code> | The function to call when the file read is complete. This function recieves the file as a JS ArrayBuffer |
|
||||||
|
| [label] | <code>String</code> | <code></code> | Text to use as the label for the file input |
|
||||||
|
| fileHelpText | <code>String</code> | <code></code> | Text to use as help under the file input |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```js
|
||||||
|
<FileToArrayBuffer @onChange={{action (mut file)}} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**See**
|
||||||
|
|
||||||
|
- [Uses of FileToArrayBuffer](https://github.com/hashicorp/vault/search?l=Handlebars&q=FileToArrayBuffer+OR+file-to-array-buffer)
|
||||||
|
- [FileToArrayBuffer Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/file-to-array-buffer.js)
|
||||||
|
|
||||||
|
---
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
import { storiesOf } from '@storybook/ember';
|
||||||
|
import { withKnobs, text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import notes from './file-to-array-buffer.md';
|
||||||
|
|
||||||
|
storiesOf('FileToArrayBuffer/', module)
|
||||||
|
.addParameters({ options: { showPanel: true } })
|
||||||
|
.addDecorator(
|
||||||
|
withKnobs()
|
||||||
|
)
|
||||||
|
.add(`FileToArrayBuffer`, () => ({
|
||||||
|
template: hbs`
|
||||||
|
<h5 class="title is-5">File To Array Buffer</h5>
|
||||||
|
<FileToArrayBuffer @onChange={{this.onChange}} @label={{this.label}}
|
||||||
|
@fileHelpText={{this.fileHelpText}} />
|
||||||
|
{{#if this.fileName}}
|
||||||
|
{{this.fileName}} as bytes: {{this.fileBytes}}
|
||||||
|
{{/if}}
|
||||||
|
`,
|
||||||
|
context: {
|
||||||
|
onChange(file, name) {
|
||||||
|
console.log(`${name} contents as an ArrayBuffer:`, file);
|
||||||
|
},
|
||||||
|
label: text('Label'),
|
||||||
|
fileHelpText: text('Help text'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{notes}
|
||||||
|
);
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
|
import { click, render } from '@ember/test-helpers';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
module('Integration | Component | raft-join', function(hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function(assert) {
|
||||||
|
await render(hbs`<RaftJoin />`);
|
||||||
|
assert.dom('[data-test-join-choice]').exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it shows the join form when clicking next', async function(assert) {
|
||||||
|
await render(hbs`<RaftJoin />`);
|
||||||
|
await click('[data-test-next]');
|
||||||
|
assert.dom('[data-test-join-header]').exists();
|
||||||
|
});
|
||||||
|
test('it returns to the first screen when clicking back', async function(assert) {
|
||||||
|
await render(hbs`<RaftJoin />`);
|
||||||
|
await click('[data-test-next]');
|
||||||
|
assert.dom('[data-test-join-header]').exists();
|
||||||
|
await click('[data-test-cancel-button]');
|
||||||
|
assert.dom('[data-test-join-choice]').exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it calls onDismiss when a user chooses to init', async function(assert) {
|
||||||
|
let spy = sinon.spy();
|
||||||
|
this.set('onDismiss', spy);
|
||||||
|
await render(hbs`<RaftJoin @onDismiss={{onDismiss}} />`);
|
||||||
|
|
||||||
|
await click('[data-test-join-init]');
|
||||||
|
await click('[data-test-next]');
|
||||||
|
assert.ok(spy.calledOnce, 'it calls the passed onDismiss');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
module('Integration | Component | raft-storage-overview', function(hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function(assert) {
|
||||||
|
let model = [
|
||||||
|
{ address: '127.0.0.1:8200', voter: true },
|
||||||
|
{ address: '127.0.0.1:8200', voter: true, leader: true },
|
||||||
|
];
|
||||||
|
this.set('model', model);
|
||||||
|
await render(hbs`<RaftStorageOverview @model={{this.model}} />`);
|
||||||
|
assert.dom('[data-raft-row]').exists({ count: 2 });
|
||||||
|
});
|
||||||
|
});
|
61
ui/yarn.lock
61
ui/yarn.lock
|
@ -4338,6 +4338,23 @@ broccoli-test-helper@^2.0.0:
|
||||||
tmp "^0.0.33"
|
tmp "^0.0.33"
|
||||||
walk-sync "^0.3.3"
|
walk-sync "^0.3.3"
|
||||||
|
|
||||||
|
broccoli-uglify-sourcemap@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/broccoli-uglify-sourcemap/-/broccoli-uglify-sourcemap-2.2.0.tgz#2ff49389bdf342a550c3596750ba2dde95a8f7d4"
|
||||||
|
integrity sha1-L/STib3zQqVQw1lnULot3pWo99Q=
|
||||||
|
dependencies:
|
||||||
|
async-promise-queue "^1.0.4"
|
||||||
|
broccoli-plugin "^1.2.1"
|
||||||
|
debug "^3.1.0"
|
||||||
|
lodash.defaultsdeep "^4.6.0"
|
||||||
|
matcher-collection "^1.0.5"
|
||||||
|
mkdirp "^0.5.0"
|
||||||
|
source-map-url "^0.4.0"
|
||||||
|
symlink-or-copy "^1.0.1"
|
||||||
|
terser "^3.7.5"
|
||||||
|
walk-sync "^0.3.2"
|
||||||
|
workerpool "^2.3.0"
|
||||||
|
|
||||||
broccoli-uglify-sourcemap@^3.1.0:
|
broccoli-uglify-sourcemap@^3.1.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/broccoli-uglify-sourcemap/-/broccoli-uglify-sourcemap-3.1.1.tgz#c99342fe1da09ff79653b6184ef8efe0b9bac793"
|
resolved "https://registry.yarnpkg.com/broccoli-uglify-sourcemap/-/broccoli-uglify-sourcemap-3.1.1.tgz#c99342fe1da09ff79653b6184ef8efe0b9bac793"
|
||||||
|
@ -7419,6 +7436,22 @@ ember-runtime-enumerable-includes-polyfill@^2.0.0:
|
||||||
ember-cli-babel "^6.9.0"
|
ember-cli-babel "^6.9.0"
|
||||||
ember-cli-version-checker "^2.1.0"
|
ember-cli-version-checker "^2.1.0"
|
||||||
|
|
||||||
|
ember-service-worker@meirish/ember-service-worker#configurable-scope:
|
||||||
|
version "0.8.0"
|
||||||
|
resolved "https://codeload.github.com/meirish/ember-service-worker/tar.gz/86c8dcf5cfc42e1e721b8c7d68afb513a2c288c7"
|
||||||
|
dependencies:
|
||||||
|
broccoli-caching-writer "^3.0.3"
|
||||||
|
broccoli-file-creator "^2.1.1"
|
||||||
|
broccoli-funnel "^2.0.1"
|
||||||
|
broccoli-merge-trees "^3.0.1"
|
||||||
|
broccoli-rollup "^2.1.1"
|
||||||
|
broccoli-uglify-sourcemap "^2.2.0"
|
||||||
|
clone "^2.1.2"
|
||||||
|
ember-cli-babel "^6.16.0"
|
||||||
|
glob "^7.1.3"
|
||||||
|
hash-for-dep "^1.2.3"
|
||||||
|
rollup-plugin-replace "^2.1.0"
|
||||||
|
|
||||||
ember-sinon@^4.0.0:
|
ember-sinon@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-sinon/-/ember-sinon-4.0.0.tgz#bb9bc43b68cc4500457261606a47c7b6ef8c30a3"
|
resolved "https://registry.yarnpkg.com/ember-sinon/-/ember-sinon-4.0.0.tgz#bb9bc43b68cc4500457261606a47c7b6ef8c30a3"
|
||||||
|
@ -8436,6 +8469,11 @@ filesize@^4.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/filesize/-/filesize-4.1.2.tgz#fcd570af1353cea97897be64f56183adb995994b"
|
resolved "https://registry.yarnpkg.com/filesize/-/filesize-4.1.2.tgz#fcd570af1353cea97897be64f56183adb995994b"
|
||||||
integrity sha512-iSWteWtfNcrWQTkQw8ble2bnonSl7YJImsn9OZKpE2E4IHhXI78eASpDYUljXZZdYj36QsEKjOs/CsiDqmKMJw==
|
integrity sha512-iSWteWtfNcrWQTkQw8ble2bnonSl7YJImsn9OZKpE2E4IHhXI78eASpDYUljXZZdYj36QsEKjOs/CsiDqmKMJw==
|
||||||
|
|
||||||
|
filesize@^4.2.1:
|
||||||
|
version "4.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/filesize/-/filesize-4.2.1.tgz#ab1cb2069db5d415911c1a13e144c0e743bc89bc"
|
||||||
|
integrity sha512-bP82Hi8VRZX/TUBKfE24iiUGsB/sfm2WUrwTQyAzQrhO3V9IhcBBNBXMyzLY5orACxRyYJ3d2HeRVX+eFv4lmA==
|
||||||
|
|
||||||
fill-range@^4.0.0:
|
fill-range@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
|
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
|
||||||
|
@ -11278,6 +11316,13 @@ magic-string@^0.24.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
sourcemap-codec "^1.4.1"
|
sourcemap-codec "^1.4.1"
|
||||||
|
|
||||||
|
magic-string@^0.25.2:
|
||||||
|
version "0.25.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9"
|
||||||
|
integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==
|
||||||
|
dependencies:
|
||||||
|
sourcemap-codec "^1.4.4"
|
||||||
|
|
||||||
make-dir@^1.0.0:
|
make-dir@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||||
|
@ -11401,7 +11446,7 @@ marked@^0.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e"
|
resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e"
|
||||||
integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==
|
integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==
|
||||||
|
|
||||||
matcher-collection@^1.0.0, matcher-collection@^1.1.1:
|
matcher-collection@^1.0.0, matcher-collection@^1.0.5, matcher-collection@^1.1.1:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-1.1.2.tgz#1076f506f10ca85897b53d14ef54f90a5c426838"
|
resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-1.1.2.tgz#1076f506f10ca85897b53d14ef54f90a5c426838"
|
||||||
integrity sha512-YQ/teqaOIIfUHedRam08PB3NK7Mjct6BvzRnJmpGDm8uFXpNr1sbY4yuflI5JcEs6COpYA0FpRQhSDBf1tT95g==
|
integrity sha512-YQ/teqaOIIfUHedRam08PB3NK7Mjct6BvzRnJmpGDm8uFXpNr1sbY4yuflI5JcEs6COpYA0FpRQhSDBf1tT95g==
|
||||||
|
@ -14300,7 +14345,15 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||||
hash-base "^3.0.0"
|
hash-base "^3.0.0"
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
|
|
||||||
rollup-pluginutils@^2.0.1:
|
rollup-plugin-replace@^2.1.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz#f41ae5372e11e7a217cde349c8b5d5fd115e70e3"
|
||||||
|
integrity sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==
|
||||||
|
dependencies:
|
||||||
|
magic-string "^0.25.2"
|
||||||
|
rollup-pluginutils "^2.6.0"
|
||||||
|
|
||||||
|
rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.6.0:
|
||||||
version "2.8.1"
|
version "2.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97"
|
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97"
|
||||||
integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==
|
integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==
|
||||||
|
@ -15027,7 +15080,7 @@ source-map@~0.1.x:
|
||||||
dependencies:
|
dependencies:
|
||||||
amdefine ">=0.0.4"
|
amdefine ">=0.0.4"
|
||||||
|
|
||||||
sourcemap-codec@^1.4.1:
|
sourcemap-codec@^1.4.1, sourcemap-codec@^1.4.4:
|
||||||
version "1.4.6"
|
version "1.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9"
|
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9"
|
||||||
integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==
|
integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==
|
||||||
|
@ -15688,7 +15741,7 @@ terser@^3.16.1:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
source-map-support "~0.5.9"
|
source-map-support "~0.5.9"
|
||||||
|
|
||||||
terser@^3.17.0:
|
terser@^3.17.0, terser@^3.7.5:
|
||||||
version "3.17.0"
|
version "3.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
|
resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
|
||||||
integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==
|
integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==
|
||||||
|
|
|
@ -33,6 +33,7 @@ type UIConfig struct {
|
||||||
func NewUIConfig(enabled bool, physicalStorage physical.Backend, barrierStorage logical.Storage) *UIConfig {
|
func NewUIConfig(enabled bool, physicalStorage physical.Backend, barrierStorage logical.Storage) *UIConfig {
|
||||||
defaultHeaders := http.Header{}
|
defaultHeaders := http.Header{}
|
||||||
defaultHeaders.Set("Content-Security-Policy", "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'")
|
defaultHeaders.Set("Content-Security-Policy", "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'")
|
||||||
|
defaultHeaders.Set("Service-Worker-Allowed", "/")
|
||||||
|
|
||||||
return &UIConfig{
|
return &UIConfig{
|
||||||
physicalStorage: physicalStorage,
|
physicalStorage: physicalStorage,
|
||||||
|
|
Loading…
Reference in New Issue