87d4e6e068
* 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
240 lines
7.7 KiB
Handlebars
240 lines
7.7 KiB
Handlebars
<SplashPage as |Page|>
|
||
{{#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>
|
||
{{#let (or keyData.recovery_keys keyData.keys) as |keyArray|}}
|
||
<h1 class="title is-4">
|
||
Vault has been initialized!
|
||
{{#if (eq keyArray.length 1)}}
|
||
Here is your key.
|
||
{{else}}
|
||
Here are your {{pluralize keyArray.length "key"}}.
|
||
{{/if}}
|
||
</h1>
|
||
{{/let}}
|
||
</Page.header>
|
||
<Page.content>
|
||
<div class="box is-marginless is-shadowless">
|
||
<div class="content">
|
||
<p>
|
||
{{#if keyData.recovery_keys}}
|
||
Please securely distribute the keys below. Certain privileged operations in Vault such as rekeying the
|
||
barrier or generating a new root token will require you to provide
|
||
at least <strong class="has-text-danger">{{secret_threshold}}</strong> of these keys to perform the
|
||
operation.
|
||
{{else}}
|
||
Please securely distribute the keys below. When the Vault is re-sealed, restarted, or stopped, you must
|
||
provide at least <strong class="has-text-danger">{{secret_threshold}}</strong> of these keys to unseal it
|
||
again.
|
||
Vault does not store the master key. Without at least <strong class="has-text-danger">{{secret_threshold}}</strong>
|
||
keys, your Vault will remain permanently sealed.
|
||
{{/if}}
|
||
</p>
|
||
</div>
|
||
<div
|
||
class="message is-list is-highlight"
|
||
>
|
||
<div class="message-body">
|
||
<h4 class="title is-7 is-marginless">
|
||
Initial root token
|
||
</h4>
|
||
<MaskedInput
|
||
@class="is-highlight has-label"
|
||
@displayOnly={{true}}
|
||
@value={{keyData.root_token}}
|
||
@allowCopy={{true}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
{{#each (or keyData.recovery_keys_base64 keyData.recovery_keys keyData.keys_base64 keyData.keys) as |key index|}}
|
||
<div
|
||
data-test-key-box
|
||
class="message is-list"
|
||
>
|
||
<div class="message-body">
|
||
<h4 class="title is-7 is-marginless">
|
||
Key {{add index 1}}
|
||
</h4>
|
||
<MaskedInput
|
||
@class="has-label"
|
||
@displayOnly={{true}}
|
||
@value={{key}}
|
||
@allowCopy={{true}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
{{/each}}
|
||
</div>
|
||
<div class="box is-marginless is-shadowless">
|
||
<div class="field is-grouped-split">
|
||
{{#if (and model.sealed (not keyData.recovery_keys))}}
|
||
<div
|
||
data-test-advance-button
|
||
class="control"
|
||
>
|
||
{{#link-to 'vault.cluster.unseal' model.name class="button is-primary"}}
|
||
Continue to Unseal
|
||
{{/link-to}}
|
||
</div>
|
||
{{else}}
|
||
<div
|
||
data-test-advance-button
|
||
class="control"
|
||
>
|
||
{{#link-to 'vault.cluster.auth'
|
||
model.name
|
||
class=(concat (if model.sealed 'is-loading ' '') 'button is-primary')
|
||
disabled=model.sealed
|
||
}}
|
||
Continue to Authenticate
|
||
{{/link-to}}
|
||
</div>
|
||
{{/if}}
|
||
<DownloadButton
|
||
@data={{keyData}}
|
||
@filename={{keyFilename}}
|
||
@mime="application/json"
|
||
@extension="json"
|
||
@class="button is-ghost"
|
||
@stringify={{true}}
|
||
>
|
||
<Icon @glyph="download" /> Download keys
|
||
</DownloadButton>
|
||
</div>
|
||
</div>
|
||
</Page.content>
|
||
{{else}}
|
||
<Page.header>
|
||
<h1 class="title h5">
|
||
Let's set up the initial set of master keys that you’ll need in case of an emergency
|
||
</h1>
|
||
</Page.header>
|
||
<Page.content>
|
||
<form
|
||
{{action 'initCluster' (hash
|
||
secret_shares=secret_shares
|
||
secret_threshold=secret_threshold
|
||
pgp_keys=pgp_keys
|
||
use_pgp=use_pgp
|
||
use_pgp_for_root=use_pgp_for_root
|
||
root_token_pgp_key=root_token_pgp_key
|
||
)
|
||
on="submit"
|
||
}}
|
||
id="init"
|
||
>
|
||
<div class="box is-marginless is-shadowless">
|
||
<MessageError @errors={{errors}} />
|
||
<div class="field">
|
||
<label
|
||
for="key-shares"
|
||
class="is-label"
|
||
>
|
||
Key shares
|
||
</label>
|
||
<div class="control">
|
||
{{input
|
||
data-test-key-shares="true"
|
||
class="input"
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
name="key-shares"
|
||
type="number"
|
||
step="1"
|
||
min="1"
|
||
pattern="[0-9]*"
|
||
value=secret_shares
|
||
}}
|
||
</div>
|
||
<p class="help has-text-grey">
|
||
The number of key shares to split the master key into
|
||
</p>
|
||
</div>
|
||
<div class="field">
|
||
<label
|
||
for="key-threshold"
|
||
class="is-label"
|
||
>
|
||
Key threshold
|
||
</label>
|
||
<div class="control">
|
||
{{input
|
||
data-test-key-threshold="true"
|
||
class="input"
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
name="key-threshold"
|
||
type="number"
|
||
step="1"
|
||
min="1"
|
||
pattern="[0-9]*"
|
||
value=secret_threshold
|
||
}}
|
||
</div>
|
||
<p class="help has-text-grey">
|
||
The number of key shares required to reconstruct the master key
|
||
</p>
|
||
</div>
|
||
<ToggleButton
|
||
@openLabel="Encrypt output with PGP"
|
||
@closedLabel="Encrypt output with PGP"
|
||
@toggleTarget={{this}}
|
||
@toggleAttr="use_pgp"
|
||
@class="is-block"
|
||
/>
|
||
{{#if use_pgp}}
|
||
<div class="box init-box">
|
||
<p class="help has-text-grey">
|
||
The output unseal keys will be encrypted and hex-encoded, in order, with the given public keys.
|
||
</p>
|
||
<PgpList
|
||
@listLength={{secret_shares}}
|
||
@onDataUpdate={{action 'setKeys'}}
|
||
/>
|
||
</div>
|
||
{{/if}}
|
||
<ToggleButton
|
||
@openLabel="Encrypt root token with PGP"
|
||
@closedLabel="Encrypt root token with PGP"
|
||
@toggleTarget={{this}}
|
||
@toggleAttr="use_pgp_for_root"
|
||
@class="is-block"
|
||
/>
|
||
{{#if use_pgp_for_root}}
|
||
<div class="box init-box">
|
||
<p class="help has-text-grey">
|
||
The root unseal key will be encrypted and hex-encoded with the given public key.
|
||
</p>
|
||
<PgpList
|
||
@listLength=1
|
||
@onDataUpdate={{action 'setRootKey'}}
|
||
/>
|
||
</div>
|
||
{{/if}}
|
||
</div>
|
||
<div class="box is-marginless is-shadowless">
|
||
<button
|
||
data-test-init-submit
|
||
type="submit"
|
||
class="button is-primary {{if loading 'is-loading'}}"
|
||
disabled={{loading}}
|
||
>
|
||
Initialize
|
||
</button>
|
||
<div class="init-illustration">
|
||
{{svg-jar "initialize"}}
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</Page.content>
|
||
{{/if}}
|
||
</SplashPage>
|