Glimmerize Splash Page (#24104)

* make splash page view only block content

* change invocation of component

* address some of the pr comments

* add test coverage

* remove conditional because of issue with it always showing

* solve for mfa errors

* move altcontent outside
This commit is contained in:
Angel Garbarino
2023-11-27 10:21:35 -07:00
committed by GitHub
parent 904c08e1e4
commit 0ca6135f68
11 changed files with 211 additions and 241 deletions

View File

@@ -0,0 +1,21 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<div class="splash-page-container section is-flex-v-centered-tablet is-flex-grow-1 is-fullwidth">
<div class="columns is-centered is-gapless is-fullwidth">
<div class="column is-4-desktop is-6-tablet">
<div class="splash-page-header" data-test-splash-page-header>
{{yield to="header"}}
</div>
<div class="splash-page-sub-header" data-test-splash-page-sub-header>
{{yield to="subHeader"}}
</div>
<div class="login-form box is-paddingless is-relative" data-test-splash-page-content>
{{yield to="content"}}
</div>
{{yield to="footer"}}
</div>
</div>
</div>

View File

@@ -1,34 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
/**
* @module SplashPage
* SplashPage component is used as a landing page with a box horizontally and center aligned on the page. It's used as the login landing page.
*
*
* @example
* ```js
* <SplashPage >
* content here
* </SplashPage
* ```
* @param {boolean} [hasAltContent] - boolean to bypass container styling
* @param {boolean} [showTruncatedNavBar = true] - boolean to hide or show the navBar. By default this is true.
*
*/
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class SplashPage extends Component {
@service version;
@service auth;
@service store;
get showTruncatedNavBar() {
// default is true unless showTruncatedNavBar is defined as false
return this.args.showTruncatedNavBar === false ? false : true;
}
}

View File

@@ -1,10 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@ember/component';
export default Component.extend({
tagName: '',
});

View File

@@ -1,10 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@ember/component';
export default Component.extend({
tagName: '',
});

View File

@@ -1,10 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@ember/component';
export default Component.extend({
tagName: '',
});

View File

@@ -1,26 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
{{! bypass container styling }}
{{#if @hasAltContent}}
{{yield (hash altContent=(component "splash-page/splash-content"))}}
{{else}}
<div class="splash-page-container section is-flex-v-centered-tablet is-flex-grow-1 is-fullwidth">
<div class="columns is-centered is-gapless is-fullwidth">
<div class="column is-4-desktop is-6-tablet">
<div class="splash-page-header">
{{yield (hash header=(component "splash-page/splash-header"))}}
</div>
<div class="splash-page-sub-header">
{{yield (hash sub-header=(component "splash-page/splash-header"))}}
</div>
<div class="login-form box is-paddingless is-relative">
{{yield (hash content=(component "splash-page/splash-content"))}}
</div>
{{yield (hash footer=(component "splash-page/splash-content"))}}
</div>
</div>
</div>
{{/if}}

View File

@@ -3,126 +3,130 @@
SPDX-License-Identifier: BUSL-1.1
~}}
<SplashPage @hasAltContent={{this.mfaErrors}} as |Page|>
<Page.altContent>
<div class="has-top-margin-xxl" data-test-mfa-error>
<EmptyState
@title="Unauthorized"
@message="Multi-factor authentication is required, but failed. Go back and try again, or contact your administrator."
@icon="alert-circle"
@bottomBorder={{true}}
@subTitle={{join ". " this.mfaErrors}}
class="is-shadowless"
>
<Hds::Button @text="Go back" @icon="chevron-left" @color="tertiary" {{on "click" (action "onMfaErrorDismiss")}} />
</EmptyState>
</div>
</Page.altContent>
<Page.header>
{{#if this.oidcProvider}}
<div class="box is-shadowless is-flex-v-centered" data-test-auth-logo>
<LogoEdition aria-label="Sign in with Hashicorp Vault" />
</div>
{{else}}
<div class="is-flex-v-centered has-bottom-margin-xxl">
<div class="brand-icon-large">
<Icon @name="vault" @size="24" @stretched={{true}} />
{{#if this.mfaErrors}}
<div class="has-top-margin-xxl" data-test-mfa-error>
<EmptyState
@title="Unauthorized"
@message="Multi-factor authentication is required, but failed. Go back and try again, or contact your administrator."
@icon="alert-circle"
@bottomBorder={{true}}
@subTitle={{join ". " this.mfaErrors}}
class="is-shadowless"
>
<Hds::Button @text="Go back" @icon="chevron-left" @color="tertiary" {{on "click" (action "onMfaErrorDismiss")}} />
</EmptyState>
</div>
{{else}}
<SplashPage>
<:header>
{{#if this.oidcProvider}}
<div class="box is-shadowless is-flex-v-centered" data-test-auth-logo>
<LogoEdition aria-label="Sign in with Hashicorp Vault" />
</div>
</div>
<div class="is-flex-row">
{{#if this.mfaAuthData}}
<Hds::Button
@text="Back to login"
@icon="arrow-left"
@isIconOnly={{true}}
@color="tertiary"
{{on "click" (fn (mut this.mfaAuthData) null)}}
/>
{{else if this.waitingForOktaNumberChallenge}}
<Hds::Button
@text="Back to login"
@icon="arrow-left"
@isIconOnly={{true}}
@color="tertiary"
{{on "click" (action "cancelAuthentication")}}
/>
{{/if}}
<h1 class="title is-3">
{{if (or this.mfaAuthData this.waitingForOktaNumberChallenge) "Authenticate" "Sign in to Vault"}}
</h1>
</div>
{{/if}}
</Page.header>
{{#unless this.mfaAuthData}}
{{#if (has-feature "Namespaces")}}
<Page.sub-header>
<Toolbar class="toolbar-namespace-picker">
<div class="field is-horizontal" data-test-namespace-toolbar>
<div class="field-label is-normal">
<label class="is-label" for="namespace">Namespace</label>
</div>
{{#if this.managedNamespaceRoot}}
<div class="field-label">
<span class="has-text-grey" data-test-managed-namespace-root>/{{this.managedNamespaceRoot}}</span>
{{else}}
<div class="is-flex-v-centered has-bottom-margin-xxl">
<div class="brand-icon-large">
<Icon @name="vault" @size="24" @stretched={{true}} />
</div>
</div>
<div class="is-flex-row">
{{#if this.mfaAuthData}}
<Hds::Button
@text="Back to login"
@icon="arrow-left"
@isIconOnly={{true}}
@color="tertiary"
{{on "click" (fn (mut this.mfaAuthData) null)}}
/>
{{else if this.waitingForOktaNumberChallenge}}
<Hds::Button
@text="Back to login"
@icon="arrow-left"
@isIconOnly={{true}}
@color="tertiary"
{{on "click" (action "cancelAuthentication")}}
/>
{{/if}}
<h1 class="title is-3">
{{if (or this.mfaAuthData this.waitingForOktaNumberChallenge) "Authenticate" "Sign in to Vault"}}
</h1>
</div>
{{/if}}
</:header>
<:subHeader>
{{#if (has-feature "Namespaces")}}
{{#unless this.mfaAuthData}}
<Toolbar class="toolbar-namespace-picker">
<div class="field is-horizontal" data-test-namespace-toolbar>
<div class="field-label is-normal">
<label class="is-label" for="namespace">Namespace</label>
</div>
{{/if}}
<div class="field-body">
<div class="field">
<div class="control">
<input
data-test-auth-form-ns-input
value={{this.namespaceInput}}
placeholder={{if this.managedNamespaceRoot "/ (Default)" "/ (Root)"}}
oninput={{perform this.updateNamespace value="target.value"}}
autocomplete="off"
spellcheck="false"
name="namespace"
id="namespace"
class="input"
type="text"
disabled={{this.oidcProvider}}
/>
{{#if this.managedNamespaceRoot}}
<div class="field-label">
<span class="has-text-grey" data-test-managed-namespace-root>/{{this.managedNamespaceRoot}}</span>
</div>
{{/if}}
<div class="field-body">
<div class="field">
<div class="control">
<input
data-test-auth-form-ns-input
value={{this.namespaceInput}}
placeholder={{if this.managedNamespaceRoot "/ (Default)" "/ (Root)"}}
{{on "input" (perform this.updateNamespace value="target.value")}}
autocomplete="off"
spellcheck="false"
name="namespace"
id="namespace"
class="input"
type="text"
disabled={{this.oidcProvider}}
/>
</div>
</div>
</div>
</div>
</div>
</Toolbar>
</Page.sub-header>
{{/if}}
{{/unless}}
<Page.content>
{{#if this.mfaAuthData}}
<Mfa::MfaForm
@clusterId={{this.model.id}}
@authData={{this.mfaAuthData}}
@onSuccess={{action "onMfaSuccess"}}
@onError={{fn (mut this.mfaErrors)}}
/>
{{else}}
<AuthForm
@wrappedToken={{this.wrappedToken}}
@cluster={{this.model}}
@namespace={{this.namespaceQueryParam}}
@redirectTo={{this.redirectTo}}
@selectedAuth={{this.authMethod}}
@onSuccess={{action "onAuthResponse"}}
@setOktaNumberChallenge={{fn (mut this.waitingForOktaNumberChallenge)}}
@waitingForOktaNumberChallenge={{this.waitingForOktaNumberChallenge}}
@setCancellingAuth={{fn (mut this.cancelAuth)}}
@cancelAuthForOktaNumberChallenge={{this.cancelAuth}}
/>
{{/if}}
</Page.content>
<Page.footer>
<div class="has-short-padding">
<p class="help has-text-grey-dark" data-test-auth-helptext>
{{#if this.oidcProvider}}
Once you log in, you will be redirected back to your application. If you require login credentials, contact your
administrator.
{{else}}
Contact your administrator for login credentials
{{/if}}
</p>
</div>
</Page.footer>
</SplashPage>
</Toolbar>
{{/unless}}
{{/if}}
</:subHeader>
<:content>
{{#if this.mfaAuthData}}
<Mfa::MfaForm
@clusterId={{this.model.id}}
@authData={{this.mfaAuthData}}
@onSuccess={{action "onMfaSuccess"}}
@onError={{fn (mut this.mfaErrors)}}
/>
{{else}}
<AuthForm
@wrappedToken={{this.wrappedToken}}
@cluster={{this.model}}
@namespace={{this.namespaceQueryParam}}
@redirectTo={{this.redirectTo}}
@selectedAuth={{this.authMethod}}
@onSuccess={{action "onAuthResponse"}}
@setOktaNumberChallenge={{fn (mut this.waitingForOktaNumberChallenge)}}
@waitingForOktaNumberChallenge={{this.waitingForOktaNumberChallenge}}
@setCancellingAuth={{fn (mut this.cancelAuth)}}
@cancelAuthForOktaNumberChallenge={{this.cancelAuth}}
/>
{{/if}}
</:content>
<:footer>
<div class="has-short-padding">
<p class="help has-text-grey-dark" data-test-auth-helptext>
{{#if this.oidcProvider}}
Once you log in, you will be redirected back to your application. If you require login credentials, contact your
administrator.
{{else}}
Contact your administrator for login credentials.
{{/if}}
</p>
</div>
</:footer>
</SplashPage>
{{/if}}

View File

@@ -3,18 +3,13 @@
SPDX-License-Identifier: BUSL-1.1
~}}
<SplashPage as |Page|>
{{#if (and this.model.usingRaft (not this.prefersInit))}}
<Page.header>
<SplashPage>
<:header>
{{#if (and this.model.usingRaft (not this.prefersInit))}}
<h1 class="title is-4">
Raft Storage
</h1>
</Page.header>
<Page.content>
<RaftJoin @onDismiss={{action (mut this.prefersInit) true}} />
</Page.content>
{{else if this.keyData}}
<Page.header>
{{else if this.keyData}}
{{#let (or this.keyData.recovery_keys this.keyData.keys) as |keyArray|}}
<h1 class="title is-4">
Vault has been initialized!
@@ -26,8 +21,16 @@
{{/if}}
</h1>
{{/let}}
</Page.header>
<Page.content>
{{else}}
<h1 class="title h5">
Let's set up the initial set of root keys that you will need in case of an emergency.
</h1>
{{/if}}
</:header>
<:content>
{{#if (and this.model.usingRaft (not this.prefersInit))}}
<RaftJoin @onDismiss={{action (mut this.prefersInit) true}} />
{{else if this.keyData}}
<div class="box is-marginless is-shadowless">
<div class="content">
<p>
@@ -104,14 +107,7 @@
/>
</div>
</div>
</Page.content>
{{else}}
<Page.header>
<h1 class="title h5">
Let's set up the initial set of root keys that youll need in case of an emergency
</h1>
</Page.header>
<Page.content>
{{else}}
<form
{{action
"initCluster"
@@ -217,6 +213,6 @@
</div>
</div>
</form>
</Page.content>
{{/if}}
{{/if}}
</:content>
</SplashPage>

View File

@@ -3,11 +3,11 @@
SPDX-License-Identifier: BUSL-1.1
~}}
<SplashPage @showTruncatedNavBar={{false}} as |Page|>
<Page.header>
<SplashPage>
<:header>
<h1 class="title is-4">MFA setup</h1>
</Page.header>
<Page.content>
</:header>
<:content>
<div class="auth-form" data-test-mfa-form>
<div class="box">
{{#if (eq this.onStep 1)}}
@@ -31,5 +31,5 @@
{{/if}}
</div>
</div>
</Page.content>
</:content>
</SplashPage>

View File

@@ -23,13 +23,14 @@
</div>
</div>
{{else}}
<SplashPage as |Page|>
<Page.header>
<SplashPage>
<:header>
<h1 class="title is-3">
Unseal Vault
</h1>
</Page.header>
<Page.content>
</:header>
<:content>
<div class="box is-borderless is-shadowless is-marginless">
<p class="title is-5">
{{capitalize this.model.name}}
@@ -57,8 +58,9 @@
/>
{{/if}}
</div>
</Page.content>
<Page.footer>
</:content>
<:footer>
<div class="box is-borderless is-shadowless">
<p>
<ExternalLink @href="https://developer.hashicorp.com/vault/docs/concepts/seal">
@@ -66,6 +68,6 @@
</ExternalLink>
</p>
</div>
</Page.footer>
</:footer>
</SplashPage>
{{/if}}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | splash-page', function (hooks) {
setupRenderingTest(hooks);
test('it should render', async function (assert) {
assert.expect(4);
await render(hbs`<SplashPage>
<:header>
Header here
</:header>
<:subHeader>
sub header
</:subHeader>
<:content>
content
</:content>
<:footer>
<div data-test-footer>footer</div>
</:footer>
</SplashPage>
`);
assert.dom('[data-test-splash-page-header]').includesText('Header here', 'Header renders');
assert.dom('[data-test-splash-page-sub-header]').includesText('sub header', 'SubHeader renders');
assert.dom('[data-test-splash-page-content]').includesText('content', 'Content renders');
assert.dom('[data-test-footer]').includesText('footer', 'Footer renders');
});
});