mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	Sidebar Navigation (#19296)
* Add Helios Design System Components (#19278) * adds hds dependency * updates reset import path * sets minifyCSS advanced option to false * Remove node-sass (#19376) * removes node-sass and fixes sass compilation * fixes active tab li class * Sidebar Navigation Components (#19446) * links ember-shared-components addon and imports styles * adds sidebar frame and nav components * updates HcNav component name to HcAppFrame and adds sidebar UserMenu component * adds tests for sidebar components * fixes tests * updates user menu styling * fixes typos in nav cluster component * changes padding value in sidebar stylesheet to use variable * Replace and remove old nav components with new ones (#19447) * links ember-shared-components addon and imports styles * adds sidebar frame and nav components * updates activeCluster on auth service and adds activeSession prop for sidebar visibility * replaces old nav components with new ones in templates * fixes sidebar visibility issue and updates user menu label class * removes NavHeader usage * adds clients index route to redirect to dashboard * removes unused HcAppFrame footer block and reduces page header top margin * Nav component cleanup (#19681) * removes nav-header components * removes navbar styling * removes status-menu component and styles * removes cluster and auth info components * removes menu-sidebar component and styling * fixes tests * Console Panel Updates (#19741) * updates console panel styling * adds test for opening and closing the console panel * updates console panel background color to use hds token * adds right margin to console panel input * updates link-status banner styling * updates hc nav components to new API * Namespace Picker Updates (#19753) * updates namespace-picker * updates namespace picker menu styling * adds bottom margin to env banner * updates class order on namespace picker link * restores manage namespaces refresh icon * removes manage namespaces nav icon * removes home link component (#20027) * Auth and Error View Updates (#19749) * adds vault logo to auth page * updates top level error template * updates loading substate handling and moves policies link from access to cluster nav (#20033) * moves console panel to bottom of viewport (#20183) * HDS Sidebar Nav Components (#20197) * updates nav components to hds * upgrades project yarn version to 3.5 * fixes issues in app frame component * updates sidenav actions to use icon button component * Sidebar navigation acceptance tests (#20270) * adds sidebar navigation acceptance tests and fixes other test failures * console panel styling tweaks * bumps addon version * remove and ignore yarn install-state file * fixes auth service and console tests * moves classes from deleted files after bulma merge * fixes sass syntax errors blocking build * cleans up dart sass deprecation warnings * adds changelog entry * hides namespace picker when sidebar nav panel is minimized * style tweaks * fixes sidebar nav tests * bumps hds addon to latest version and removes style override * updates modify-passthrough-response helper * updates sidebar nav tests * mfa-setup test fix attempt * fixes cluster mfa setup test * remove deprecated yarn ignore-optional flag from makefile * removes another instance of yarn ignore-optional and updates ui readme * removes unsupported yarn verbose flag from ci-helper * hides nav headings when user does not have access to any sub links * removes unused optional deps and moves lint-staged to dev deps * updates has-permission helper and permissions service tests * fixes issue with console panel not filling container width
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -179,7 +179,7 @@ static-assets-dir: | ||||
|  | ||||
| install-ui-dependencies: | ||||
| 	@echo "--> Installing JavaScript assets" | ||||
| 	@cd ui && yarn --ignore-optional | ||||
| 	@cd ui && yarn | ||||
|  | ||||
| test-ember: install-ui-dependencies | ||||
| 	@echo "--> Running ember tests" | ||||
|   | ||||
							
								
								
									
										3
									
								
								changelog/19296.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/19296.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:feature | ||||
| **Sidebar Navigation in UI**: A new sidebar navigation panel has been added in the UI to replace the top navigation bar. | ||||
| ``` | ||||
| @@ -132,9 +132,9 @@ function build_ui() { | ||||
|   mkdir -p http/web_ui | ||||
|   popd | ||||
|   pushd "$repo_root/ui" | ||||
|   yarn install --ignore-optional | ||||
|   yarn install | ||||
|   npm rebuild node-sass | ||||
|   yarn --verbose run build | ||||
|   yarn run build | ||||
|   popd | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								ui/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								ui/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -29,3 +29,12 @@ package-lock.json | ||||
|  | ||||
| # broccoli-debug | ||||
| /DEBUG/ | ||||
|  | ||||
| # yarn | ||||
| .pnp.* | ||||
| .yarn/* | ||||
| !.yarn/patches | ||||
| !.yarn/plugins | ||||
| !.yarn/releases | ||||
| !.yarn/sdks | ||||
| !.yarn/versions | ||||
|   | ||||
							
								
								
									
										147221
									
								
								ui/.yarn/releases/yarn-1.19.1.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										147221
									
								
								ui/.yarn/releases/yarn-1.19.1.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										175350
									
								
								ui/.yarn/releases/yarn-1.22.19.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										175350
									
								
								ui/.yarn/releases/yarn-1.22.19.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										873
									
								
								ui/.yarn/releases/yarn-3.5.0.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										873
									
								
								ui/.yarn/releases/yarn-3.5.0.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										3
									
								
								ui/.yarnrc.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								ui/.yarnrc.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| nodeLinker: node-modules | ||||
|  | ||||
| yarnPath: .yarn/releases/yarn-3.5.0.cjs | ||||
| @@ -39,12 +39,6 @@ You will need the following things properly installed on your computer. | ||||
| * [Yarn](https://yarnpkg.com/) | ||||
| * [Ember CLI](https://cli.emberjs.com/release/) | ||||
| * [Google Chrome](https://google.com/chrome/) | ||||
| - [lint-staged\*](https://www.npmjs.com/package/lint-staged) | ||||
|  | ||||
| \* lint-staged is an optional dependency - running `yarn` will install it. | ||||
| If don't want optional dependencies installed you can run `yarn --ignore-optional`. If you've ignored the optional deps | ||||
| previously and want to install them, you have to tell yarn to refetch all deps by | ||||
| running `yarn --force`. | ||||
|  | ||||
| In order to enforce the same version of `yarn` across installs, the `yarn` binary is included in the repo | ||||
| in the `.yarn/releases` folder. To update to a different version of `yarn`, use the `yarn policies set-version VERSION` command. For more information on this, see the [documentation](https://yarnpkg.com/en/docs/cli/policies). | ||||
|   | ||||
| @@ -42,7 +42,7 @@ export default ApplicationAdapter.extend({ | ||||
|       } | ||||
|     } catch (error) { | ||||
|       // no path means this was an error on listing | ||||
|       if (!query.path) { | ||||
|       if (!query.path || !mountModel) { | ||||
|         throw error; | ||||
|       } | ||||
|       // control groups will throw a 403 permission denied error. If this happens return the mountModel | ||||
|   | ||||
| @@ -22,7 +22,7 @@ export default ApplicationAdapter.extend({ | ||||
|   // concerns and we only want to send "list" to the server | ||||
|   query(store, type, query) { | ||||
|     let { backend, id } = query; | ||||
|     return this.ajax(this._url(backend, id), 'GET', { data: { list: true } }).then(resp => { | ||||
|     return this.ajax(this._url(backend, id), 'GET', { data: { list: true } }).then((resp) => { | ||||
|       resp.id = id; | ||||
|       resp.backend = backend; | ||||
|       return resp; | ||||
| @@ -36,7 +36,7 @@ export default ApplicationAdapter.extend({ | ||||
|  | ||||
|   queryRecord(store, type, query) { | ||||
|     let { backend, id } = query; | ||||
|     return this.ajax(this._url(backend, id), 'GET').then(resp => { | ||||
|     return this.ajax(this._url(backend, id), 'GET').then((resp) => { | ||||
|       resp.id = id; | ||||
|       resp.backend = backend; | ||||
|       return resp; | ||||
|   | ||||
| @@ -1,32 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import { inject as service } from '@ember/service'; | ||||
| import Component from '@glimmer/component'; | ||||
|  | ||||
| /** | ||||
|  * @module ClusterInfo | ||||
|  * | ||||
|  * @example | ||||
|  * ```js | ||||
|  * <ClusterInfo @cluster={{cluster}} @onLinkClick={{action}} /> | ||||
|  * ``` | ||||
|  * | ||||
|  * @param {object} cluster - details of the current cluster, passed from the parent. | ||||
|  * @param {Function} onLinkClick - parent action which determines the behavior on link click | ||||
|  */ | ||||
| export default class ClusterInfoComponent extends Component { | ||||
|   @service auth; | ||||
|   @service store; | ||||
|   @service version; | ||||
|  | ||||
|   get activeCluster() { | ||||
|     return this.store.peekRecord('cluster', this.auth.activeCluster); | ||||
|   } | ||||
|  | ||||
|   transitionToRoute() { | ||||
|     this.router.transitionTo(...arguments); | ||||
|   } | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import Component from '@glimmer/component'; | ||||
|  | ||||
| /** | ||||
|  * @module HomeLink | ||||
|  * `HomeLink` is a span that contains either the text `home` or the `LogoEdition` component. | ||||
|  * | ||||
|  * @example | ||||
|  * ```js | ||||
|  * <HomeLink @class="navbar-item splash-page-logo"> | ||||
|  *  <LogoEdition /> | ||||
|  * </HomeLink> | ||||
|  * ``` | ||||
|  * @param {string} class - Classes attached to the the component. | ||||
|  * @param {string} text - Text displayed instead of logo. | ||||
|  * | ||||
|  * @see {@link https://github.com/hashicorp/vault/search?l=Handlebars&q=HomeLink|Uses of HomeLink} | ||||
|  * @see {@link https://github.com/hashicorp/vault/blob/main/ui/app/components/home-link.js|HomeLink Source Code} | ||||
|  */ | ||||
|  | ||||
| export default class HomeLink extends Component { | ||||
|   get text() { | ||||
|     return 'home'; | ||||
|   } | ||||
|  | ||||
|   get computedClasses() { | ||||
|     return this.classNames.join(' '); | ||||
|   } | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import Component from '@ember/component'; | ||||
|  | ||||
| export default Component.extend({ | ||||
|   classNames: ['column', 'is-sidebar'], | ||||
|   classNameBindings: ['isActive:is-active'], | ||||
|   isActive: false, | ||||
|   actions: { | ||||
|     openMenu() { | ||||
|       this.set('isActive', true); | ||||
|     }, | ||||
|     closeMenu() { | ||||
|       this.set('isActive', false); | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| @@ -155,7 +155,9 @@ export default Component.extend({ | ||||
|  | ||||
|   namespaceDisplay: computed('namespacePath', 'accessibleNamespaces', 'accessibleNamespaces.[]', function () { | ||||
|     const namespace = this.namespacePath; | ||||
|     if (!namespace) return ''; | ||||
|     if (!namespace) { | ||||
|       return 'root'; | ||||
|     } | ||||
|     const parts = namespace?.split('/'); | ||||
|     return parts[parts.length - 1]; | ||||
|   }), | ||||
|   | ||||
| @@ -1,34 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import Component from '@ember/component'; | ||||
| import { inject as service } from '@ember/service'; | ||||
| import { computed } from '@ember/object'; | ||||
|  | ||||
| export default Component.extend({ | ||||
|   router: service(), | ||||
|   currentCluster: service(), | ||||
|   'data-test-navheader': true, | ||||
|   attributeBindings: ['data-test-navheader'], | ||||
|   classNameBindings: 'consoleFullscreen:panel-fullscreen', | ||||
|   tagName: 'header', | ||||
|   navDrawerOpen: false, | ||||
|   consoleFullscreen: false, | ||||
|   hideLinks: computed('router.currentRouteName', function () { | ||||
|     const currentRoute = this.router.currentRouteName; | ||||
|     if ('vault.cluster.oidc-provider' === currentRoute) { | ||||
|       return true; | ||||
|     } | ||||
|     return false; | ||||
|   }), | ||||
|   actions: { | ||||
|     toggleNavDrawer(isOpen) { | ||||
|       if (isOpen !== undefined) { | ||||
|         return this.set('navDrawerOpen', isOpen); | ||||
|       } | ||||
|       this.toggleProperty('navDrawerOpen'); | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| @@ -1,10 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import Component from '@ember/component'; | ||||
|  | ||||
| export default Component.extend({ | ||||
|   tagName: '', | ||||
| }); | ||||
| @@ -1,10 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import Component from '@ember/component'; | ||||
|  | ||||
| export default Component.extend({ | ||||
|   tagName: '', | ||||
| }); | ||||
| @@ -1,10 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import Component from '@ember/component'; | ||||
|  | ||||
| export default Component.extend({ | ||||
|   tagName: '', | ||||
| }); | ||||
							
								
								
									
										51
									
								
								ui/app/components/sidebar/frame.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								ui/app/components/sidebar/frame.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| <Hds::AppFrame @hasSidebar={{@showSidebar}} @hasHeader={{false}} @hasFooter={{false}} as |Frame|> | ||||
|   <Frame.Sidebar data-test-sidebar-nav> | ||||
|     <Hds::SideNav @isResponsive={{true}} @hasA11yRefocus={{true}} @a11yRefocusSkipTo="app-main-content"> | ||||
|       <:header> | ||||
|         <Hds::SideNav::Header> | ||||
|           <:logo> | ||||
|             <Hds::SideNav::Header::HomeLink | ||||
|               @icon="vault" | ||||
|               @route="vault.cluster" | ||||
|               @model={{this.currentCluster.cluster.name}} | ||||
|               @ariaLabel="home link" | ||||
|               data-test-sidebar-logo | ||||
|             /> | ||||
|           </:logo> | ||||
|           <:actions> | ||||
|             <Hds::SideNav::Header::IconButton | ||||
|               @icon="terminal-screen" | ||||
|               @ariaLabel="Console toggle" | ||||
|               data-test-console-toggle | ||||
|               {{on "click" (fn (mut this.console.isOpen) (not this.console.isOpen))}} | ||||
|             /> | ||||
|             <Sidebar::UserMenu /> | ||||
|           </:actions> | ||||
|         </Hds::SideNav::Header> | ||||
|       </:header> | ||||
|  | ||||
|       {{! this block is where the Hds::SideNav::Portal components render into }} | ||||
|       <:body> | ||||
|         <Hds::SideNav::Portal::Target aria-label="sidebar navigation links" /> | ||||
|       </:body> | ||||
|  | ||||
|       <:footer> | ||||
|         {{#if (has-feature "Namespaces")}} | ||||
|           <NamespacePicker | ||||
|             @namespace={{this.clusterController.namespaceQueryParam}} | ||||
|             class="hds-side-nav-hide-when-minimized" | ||||
|           /> | ||||
|         {{/if}} | ||||
|       </:footer> | ||||
|     </Hds::SideNav> | ||||
|   </Frame.Sidebar> | ||||
|   <Frame.Main id="app-main-content"> | ||||
|     {{! outlet for app content }} | ||||
|     <div id="modal-wormhole"></div> | ||||
|     <LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} /> | ||||
|     {{yield}} | ||||
|     <div data-test-console-panel class={{if this.console.isOpen "panel-open"}}> | ||||
|       <Console::UiPanel @isFullscreen={{this.consoleFullscreen}} /> | ||||
|     </div> | ||||
|   </Frame.Main> | ||||
| </Hds::AppFrame> | ||||
							
								
								
									
										9
									
								
								ui/app/components/sidebar/frame.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								ui/app/components/sidebar/frame.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import Component from '@glimmer/component'; | ||||
| import { inject as service } from '@ember/service'; | ||||
| import { inject as controller } from '@ember/controller'; | ||||
|  | ||||
| export default class SidebarNavComponent extends Component { | ||||
|   @service currentCluster; | ||||
|   @service console; | ||||
|   @controller('vault.cluster') clusterController; | ||||
| } | ||||
							
								
								
									
										65
									
								
								ui/app/components/sidebar/nav/access.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								ui/app/components/sidebar/nav/access.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| <Hds::SideNav::Portal @ariaLabel="Access Navigation Links" data-test-sidebar-nav-panel="Access" as |Nav|> | ||||
|   <Nav.BackLink | ||||
|     @route="vault.cluster" | ||||
|     @current-when={{false}} | ||||
|     @icon="arrow-left" | ||||
|     @text="Back to main navigation" | ||||
|     data-test-sidebar-nav-link="Back to main navigation" | ||||
|   /> | ||||
|  | ||||
|   {{#if (has-permission "access" routeParams=(array "methods" "mfa" "oidc"))}} | ||||
|     <Nav.Title data-test-sidebar-nav-heading="Authentication">Authentication</Nav.Title> | ||||
|   {{/if}} | ||||
|   {{#if (has-permission "access" routeParams="methods")}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.access.methods" | ||||
|       @current-when="vault.cluster.access.methods vault.cluster.access.method" | ||||
|       @text="Authentication methods" | ||||
|       data-test-sidebar-nav-link="Authentication methods" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (has-permission "access" routeParams="mfa")}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.access.mfa.methods" | ||||
|       @current-when="vault.cluster.access.mfa.methods vault.cluster.access.mfa.enforcements vault.cluster.access.mfa.index" | ||||
|       @text="Multi-factor authentication" | ||||
|       data-test-sidebar-nav-link="Multi-factor authentication" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (has-permission "access" routeParams="oidc")}} | ||||
|     <Nav.Link @route="vault.cluster.access.oidc" @text="OIDC" data-test-sidebar-nav-link="OIDC" /> | ||||
|   {{/if}} | ||||
|  | ||||
|   {{#if (and (has-feature "Control Groups") (has-permission "access" routeParams="control-groups"))}} | ||||
|     <Nav.Title data-test-sidebar-nav-heading="Access Control">Access Control</Nav.Title> | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.access.control-groups" | ||||
|       @current-when="vault.cluster.access.control-groups vault.cluster.access.control-group-accessor vault.cluster.access.control-groups-configure" | ||||
|       @text="Control Groups" | ||||
|       data-test-sidebar-nav-link="Control Groups" | ||||
|     /> | ||||
|   {{/if}} | ||||
|  | ||||
|   {{#if (has-permission "access" routeParams=(array "namespaces" "groups" "entities"))}} | ||||
|     <Nav.Title data-test-sidebar-nav-heading="Organization">Organization</Nav.Title> | ||||
|   {{/if}} | ||||
|   {{#if (and (has-feature "Namespaces") (has-permission "access" routeParams="namespaces"))}} | ||||
|     <Nav.Link @route="vault.cluster.access.namespaces" @text="Namespaces" data-test-sidebar-nav-link="Namespaces" /> | ||||
|   {{/if}} | ||||
|   {{#if (has-permission "access" routeParams="groups")}} | ||||
|     <Nav.Link @route="vault.cluster.access.identity" @model="groups" @text="Groups" data-test-sidebar-nav-link="Groups" /> | ||||
|   {{/if}} | ||||
|   {{#if (has-permission "access" routeParams="entities")}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.access.identity" | ||||
|       @model="entities" | ||||
|       @text="Entities" | ||||
|       data-test-sidebar-nav-link="Entities" | ||||
|     /> | ||||
|   {{/if}} | ||||
|  | ||||
|   {{#if (has-permission "access" routeParams="leases")}} | ||||
|     <Nav.Title data-test-sidebar-nav-heading="Administration">Administration</Nav.Title> | ||||
|     <Nav.Link @route="vault.cluster.access.leases" @text="Leases" data-test-sidebar-nav-link="Leases" /> | ||||
|   {{/if}} | ||||
| </Hds::SideNav::Portal> | ||||
							
								
								
									
										98
									
								
								ui/app/components/sidebar/nav/cluster.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								ui/app/components/sidebar/nav/cluster.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| <Hds::SideNav::Portal @ariaLabel="Cluster Navigation Links" data-test-sidebar-nav-panel="Cluster" as |Nav|> | ||||
|   <Nav.Title data-test-sidebar-nav-heading="Vault">Vault</Nav.Title> | ||||
|  | ||||
|   <Nav.Link | ||||
|     @route="vault.cluster.secrets" | ||||
|     @current-when="vault.cluster.secrets vault.cluster.settings.mount-secret-backend vault.cluster.settings.configure-secret-backend" | ||||
|     @text="Secrets engines" | ||||
|     data-test-sidebar-nav-link="Secrets engines" | ||||
|   /> | ||||
|   {{#if (has-permission "access")}} | ||||
|     <Nav.Link | ||||
|       @route={{get (route-params-for "access") "route"}} | ||||
|       @models={{get (route-params-for "access") "models"}} | ||||
|       @current-when="vault.cluster.access vault.cluster.settings.auth" | ||||
|       @text="Access" | ||||
|       @hasSubItems={{true}} | ||||
|       data-test-sidebar-nav-link="Access" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (has-permission "policies")}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.policies" | ||||
|       @models={{get (route-params-for "policies") "models"}} | ||||
|       @text="Policies" | ||||
|       @hasSubItems={{true}} | ||||
|       data-test-sidebar-nav-link="Policies" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (has-permission "tools")}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.tools.tool" | ||||
|       @models={{get (route-params-for "tools") "models"}} | ||||
|       @text="Tools" | ||||
|       @hasSubItems={{true}} | ||||
|       data-test-sidebar-nav-link="Tools" | ||||
|     /> | ||||
|   {{/if}} | ||||
|  | ||||
|   {{#if | ||||
|     (and this.version.isEnterprise this.cluster.anyReplicationEnabled (has-permission "status" routeParams="replication")) | ||||
|   }} | ||||
|     <Nav.Title data-test-sidebar-nav-heading="Replication">Replication</Nav.Title> | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.replication.mode.index" | ||||
|       @model="dr" | ||||
|       @text="DR Primary" | ||||
|       data-test-sidebar-nav-link="DR Primary" | ||||
|     /> | ||||
|  | ||||
|     {{#if (has-feature "Performance Replication")}} | ||||
|       <Nav.Link | ||||
|         @route="vault.cluster.replication.mode.index" | ||||
|         @model="performance" | ||||
|         @text="Performance Secondary" | ||||
|         data-test-sidebar-nav-link="Performance Secondary" | ||||
|       /> | ||||
|     {{/if}} | ||||
|   {{/if}} | ||||
|  | ||||
|   {{#if | ||||
|     (or | ||||
|       (has-permission "status" routeParams=(array "replication" "raft" "license" "seal")) | ||||
|       (has-permission "clients" routeParams="activity") | ||||
|     ) | ||||
|   }} | ||||
|     <Nav.Title data-test-sidebar-nav-heading="Monitoring">Monitoring</Nav.Title> | ||||
|   {{/if}} | ||||
|   {{#if (and this.version.isEnterprise (has-permission "status" routeParams="replication"))}} | ||||
|     <Nav.Link @route="vault.cluster.replication.index" @text="Replication" data-test-sidebar-nav-link="Replication" /> | ||||
|   {{/if}} | ||||
|   {{#if (and this.cluster.usingRaft (has-permission "status" routeParams="raft"))}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.storage" | ||||
|       @model={{this.cluster.name}} | ||||
|       @text="Raft Storage" | ||||
|       data-test-sidebar-nav-link="Raft Storage" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (and (has-permission "clients" routeParams="activity") (not this.cluster.dr.isSecondary))}} | ||||
|     <Nav.Link @route="vault.cluster.clients" @text="Client count" data-test-sidebar-nav-link="Client count" /> | ||||
|   {{/if}} | ||||
|   {{#if (and this.version.features (has-permission "status" routeParams="license") (not this.cluster.dr.isSecondary))}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.license" | ||||
|       @model={{this.cluster.name}} | ||||
|       @text="License" | ||||
|       data-test-sidebar-nav-link="License" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (and (has-permission "status" routeParams="seal") (not this.cluster.dr.isSecondary))}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.settings.seal" | ||||
|       @model={{this.cluster.name}} | ||||
|       @text="Seal Vault" | ||||
|       data-test-sidebar-nav-link="Seal Vault" | ||||
|     /> | ||||
|   {{/if}} | ||||
| </Hds::SideNav::Portal> | ||||
							
								
								
									
										12
									
								
								ui/app/components/sidebar/nav/cluster.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								ui/app/components/sidebar/nav/cluster.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import Component from '@glimmer/component'; | ||||
| import { inject as service } from '@ember/service'; | ||||
|  | ||||
| export default class SidebarNavClusterComponent extends Component { | ||||
|   @service currentCluster; | ||||
|   @service version; | ||||
|   @service auth; | ||||
|  | ||||
|   get cluster() { | ||||
|     return this.currentCluster.cluster; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										39
									
								
								ui/app/components/sidebar/nav/policies.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								ui/app/components/sidebar/nav/policies.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| <Hds::SideNav::Portal @ariaLabel="Policies Navigation Links" data-test-sidebar-nav-panel="Policies" as |Nav|> | ||||
|   <Nav.BackLink | ||||
|     @route="vault.cluster" | ||||
|     @current-when={{false}} | ||||
|     @icon="arrow-left" | ||||
|     @text="Back to main navigation" | ||||
|     data-test-sidebar-nav-link="Back to main navigation" | ||||
|   /> | ||||
|  | ||||
|   <Nav.Title data-test-sidebar-nav-heading="Policies">Policies</Nav.Title> | ||||
|  | ||||
|   {{#if (has-permission "policies" routeParams="acl")}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.policies" | ||||
|       @model="acl" | ||||
|       @current-when="vault.cluster.policies vault.cluster.policy" | ||||
|       @text="ACL Policies" | ||||
|       data-test-sidebar-nav-link="ACL Policies" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="rgp"))}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.policies" | ||||
|       @model="rgp" | ||||
|       @current-when="vault.cluster.policies vault.cluster.policy" | ||||
|       @text="Role-Governing Policies" | ||||
|       data-test-sidebar-nav-link="Role-Governing Policies" | ||||
|     /> | ||||
|   {{/if}} | ||||
|   {{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="egp"))}} | ||||
|     <Nav.Link | ||||
|       @route="vault.cluster.policies" | ||||
|       @model="egp" | ||||
|       @current-when="vault.cluster.policies vault.cluster.policy" | ||||
|       @text="Endpoint Governing Policies" | ||||
|       data-test-sidebar-nav-link="Endpoint Governing Policies" | ||||
|     /> | ||||
|   {{/if}} | ||||
| </Hds::SideNav::Portal> | ||||
							
								
								
									
										22
									
								
								ui/app/components/sidebar/nav/tools.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								ui/app/components/sidebar/nav/tools.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| <Hds::SideNav::Portal @ariaLabel="Tools Navigation Links" data-test-sidebar-nav-panel="Tools" as |Nav|> | ||||
|   <Nav.BackLink | ||||
|     @route="vault.cluster" | ||||
|     @current-when={{false}} | ||||
|     @icon="arrow-left" | ||||
|     @text="Back to main navigation" | ||||
|     data-test-sidebar-nav-link="Back to main navigation" | ||||
|   /> | ||||
|  | ||||
|   <Nav.Title data-test-sidebar-nav-heading="Tools">Tools</Nav.Title> | ||||
|  | ||||
|   {{#each (tools-actions) as |supportedAction|}} | ||||
|     {{#if (has-permission "tools" routeParams=supportedAction)}} | ||||
|       <Nav.Link | ||||
|         @route="vault.cluster.tools.tool" | ||||
|         @model={{supportedAction}} | ||||
|         @text={{capitalize supportedAction}} | ||||
|         data-test-sidebar-nav-link={{capitalize supportedAction}} | ||||
|       /> | ||||
|     {{/if}} | ||||
|   {{/each}} | ||||
| </Hds::SideNav::Portal> | ||||
							
								
								
									
										83
									
								
								ui/app/components/sidebar/user-menu.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								ui/app/components/sidebar/user-menu.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| <BasicDropdown | ||||
|   @horizontalPosition="right" | ||||
|   @verticalPosition="below" | ||||
|   @renderInPlace={{true}} | ||||
|   class="sidebar-user-menu" | ||||
|   data-test-user-menu | ||||
|   as |Dropdown| | ||||
| > | ||||
|   <Dropdown.Trigger data-test-user-menu-trigger> | ||||
|     <Hds::SideNav::Header::IconButton @icon="user" @ariaLabel="User menu" /> | ||||
|   </Dropdown.Trigger> | ||||
|   <Dropdown.Content> | ||||
|     <Confirm as |c|> | ||||
|       <div class="popup-menu-content" data-test-user-menu-content> | ||||
|         <div class="box"> | ||||
|           <div class="menu-label"> | ||||
|             {{capitalize this.auth.authData.displayName}} | ||||
|           </div> | ||||
|           <nav class="menu"> | ||||
|             <ul class="menu-list"> | ||||
|               {{#if this.auth.allowExpiration}} | ||||
|                 <li class="token-alert is-flex" data-test-user-menu-item="token alert"> | ||||
|                   <span><Icon @name="alert-triangle-fill" class="has-text-highlight" /></span> | ||||
|                   <span class="is-size-8 has-text-semibold"> | ||||
|                     We've stopped auto-renewing your token due to inactivity. It will expire on | ||||
|                     {{date-format this.auth.tokenExpirationDate "MMMM do yyyy, h:mm:ss a"}}. | ||||
|                   </span> | ||||
|                 </li> | ||||
|               {{/if}} | ||||
|               {{#if this.hasEntityId}} | ||||
|                 <li class="action"> | ||||
|                   <LinkTo @route="vault.cluster.mfa-setup" data-test-user-menu-item="mfa"> | ||||
|                     Multi-factor authentication | ||||
|                   </LinkTo> | ||||
|                 </li> | ||||
|               {{/if}} | ||||
|               <li class="action"> | ||||
|                 <CopyButton | ||||
|                   @clipboardText={{this.auth.currentToken}} | ||||
|                   class="link" | ||||
|                   @buttonType="button" | ||||
|                   @success={{action (set-flash-message "Token copied!")}} | ||||
|                 > | ||||
|                   Copy token | ||||
|                 </CopyButton> | ||||
|               </li> | ||||
|               {{#if (is-before (now interval=1000) this.auth.tokenExpirationDate)}} | ||||
|                 {{#if this.auth.authData.renewable}} | ||||
|                   <li class="action"> | ||||
|                     <button | ||||
|                       type="button" | ||||
|                       {{on "click" this.renewToken}} | ||||
|                       class="link button {{if this.isRenewing 'is-loading'}}" | ||||
|                       data-test-user-menu-item="renew token" | ||||
|                     > | ||||
|                       Renew token | ||||
|                     </button> | ||||
|                   </li> | ||||
|                 {{/if}} | ||||
|                 <li class="action"> | ||||
|                   <c.Message | ||||
|                     @id={{get this.auth "authData.displayName"}} | ||||
|                     @title={{concat "Revoke " (get this.auth "authData.displayName") "?"}} | ||||
|                     @onConfirm={{action "revokeToken"}} | ||||
|                     @message="You will not be able to log in again with this token." | ||||
|                     @triggerText="Revoke token" | ||||
|                     @confirmButtonText="Revoke" | ||||
|                     data-test-user-menu-item="revoke token" | ||||
|                   /> | ||||
|                 </li> | ||||
|               {{/if}} | ||||
|               <li class="action"> | ||||
|                 <LinkTo @route="vault.cluster.logout" @model={{this.currentCluster.cluster.name}} id="logout"> | ||||
|                   Log out | ||||
|                 </LinkTo> | ||||
|               </li> | ||||
|             </ul> | ||||
|           </nav> | ||||
|         </div> | ||||
|       </div> | ||||
|     </Confirm> | ||||
|   </Dropdown.Content> | ||||
| </BasicDropdown> | ||||
| @@ -1,27 +1,12 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
| 
 | ||||
| import Component from '@glimmer/component'; | ||||
| import { inject as service } from '@ember/service'; | ||||
| import { later } from '@ember/runloop'; | ||||
| import { action } from '@ember/object'; | ||||
| import { tracked } from '@glimmer/tracking'; | ||||
| 
 | ||||
| /** | ||||
|  * @module AuthInfo | ||||
|  * | ||||
|  * @example | ||||
|  * ```js
 | ||||
|  * <AuthInfo @activeClusterName={{cluster.name}} @onLinkClick={{action "onLinkClick"}} /> | ||||
|  * ``` | ||||
|  * | ||||
|  * @param {string} activeClusterName - name of the current cluster, passed from the parent. | ||||
|  * @param {Function} onLinkClick - parent action which determines the behavior on link click | ||||
|  */ | ||||
| export default class AuthInfoComponent extends Component { | ||||
| export default class SidebarUserMenuComponent extends Component { | ||||
|   @service auth; | ||||
|   @service currentCluster; | ||||
|   @service router; | ||||
| 
 | ||||
|   @tracked fakeRenew = false; | ||||
| @@ -29,7 +14,7 @@ export default class AuthInfoComponent extends Component { | ||||
|   get hasEntityId() { | ||||
|     // root users will not have an entity_id because they are not associated with an entity.
 | ||||
|     // in order to use the MFA end user setup they need an entity_id
 | ||||
|     return !!this.auth.authData.entity_id; | ||||
|     return !!this.auth.authData?.entity_id; | ||||
|   } | ||||
| 
 | ||||
|   get isRenewing() { | ||||
| @@ -31,8 +31,4 @@ export default class SplashPage extends Component { | ||||
|     // default is true unless showTruncatedNavBar is defined as false | ||||
|     return this.args.showTruncatedNavBar === false ? false : true; | ||||
|   } | ||||
|  | ||||
|   get activeCluster() { | ||||
|     return this.store.peekRecord('cluster', this.auth.activeCluster); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,49 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import Component from '@glimmer/component'; | ||||
| import { action } from '@ember/object'; | ||||
| import { inject as service } from '@ember/service'; | ||||
| import { next } from '@ember/runloop'; | ||||
|  | ||||
| /** | ||||
|  * @module StatusMenu | ||||
|  * StatusMenu component is the drop down menu on the main navigation. | ||||
|  * | ||||
|  * @example | ||||
|  * ```js | ||||
|  * <StatusMenu @label='user' @onLinkClick={{action Nav.closeDrawer}}/> | ||||
|  * ``` | ||||
|  * @param {string} [ariaLabel] - aria label for the status icon. | ||||
|  * @param {string} [label] - label for the status menu. | ||||
|  * @param {string} [type] - determines where the component is being used. e.g. replication, auth, etc. | ||||
|  * @param {function} [onLinkClick] - function to handle click on the nested links under content. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| export default class StatusMenu extends Component { | ||||
|   @service currentCluster; | ||||
|   @service auth; | ||||
|   @service media; | ||||
|   @service router; | ||||
|  | ||||
|   get type() { | ||||
|     return this.args.type || 'cluster'; | ||||
|   } | ||||
|  | ||||
|   get glyphName() { | ||||
|     return this.type === 'user' ? 'user' : 'circle-dot'; | ||||
|   } | ||||
|  | ||||
|   @action | ||||
|   onLinkClick(dropdown) { | ||||
|     if (dropdown) { | ||||
|       // strange issue where closing dropdown triggers full transition which redirects to auth screen in production builds | ||||
|       // closing dropdown in next tick of run loop fixes it | ||||
|       next(() => dropdown.actions.close()); | ||||
|     } | ||||
|     this.args.onLinkClick(); | ||||
|   } | ||||
| } | ||||
| @@ -5,19 +5,10 @@ | ||||
|  | ||||
| import { inject as service } from '@ember/service'; | ||||
| import Controller from '@ember/controller'; | ||||
| import { computed } from '@ember/object'; | ||||
| import config from '../config/environment'; | ||||
|  | ||||
| export default Controller.extend({ | ||||
|   env: config.environment, | ||||
|   auth: service(), | ||||
|   store: service(), | ||||
|   activeCluster: computed('auth.activeCluster', function () { | ||||
|     const id = this.auth.activeCluster; | ||||
|     return id ? this.store.peekRecord('cluster', id) : null; | ||||
|   }), | ||||
|   activeClusterName: computed('activeCluster', function () { | ||||
|     const activeCluster = this.activeCluster; | ||||
|     return activeCluster ? activeCluster.get('name') : null; | ||||
|   }), | ||||
| }); | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
|  | ||||
| import { inject as service } from '@ember/service'; | ||||
| import Controller from '@ember/controller'; | ||||
| import { computed } from '@ember/object'; | ||||
| import config from '../config/environment'; | ||||
|  | ||||
| export default Controller.extend({ | ||||
| @@ -20,12 +19,4 @@ export default Controller.extend({ | ||||
|   env: config.environment, | ||||
|   auth: service(), | ||||
|   store: service(), | ||||
|   activeCluster: computed('auth.activeCluster', function () { | ||||
|     const id = this.auth.activeCluster; | ||||
|     return id ? this.store.peekRecord('cluster', id) : null; | ||||
|   }), | ||||
|   activeClusterName: computed('activeCluster', function () { | ||||
|     const activeCluster = this.activeCluster; | ||||
|     return activeCluster ? activeCluster.get('name') : null; | ||||
|   }), | ||||
| }); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| import { inject as service } from '@ember/service'; | ||||
| import { alias } from '@ember/object/computed'; | ||||
| import Controller from '@ember/controller'; | ||||
| import { observer, computed } from '@ember/object'; | ||||
| import { observer } from '@ember/object'; | ||||
| export default Controller.extend({ | ||||
|   auth: service(), | ||||
|   store: service(), | ||||
| @@ -36,35 +36,7 @@ export default Controller.extend({ | ||||
|   }), | ||||
|  | ||||
|   consoleOpen: alias('console.isOpen'), | ||||
|  | ||||
|   activeCluster: computed('auth.activeCluster', function () { | ||||
|     return this.store.peekRecord('cluster', this.auth.activeCluster); | ||||
|   }), | ||||
|  | ||||
|   activeClusterName: computed('activeCluster', function () { | ||||
|     const activeCluster = this.activeCluster; | ||||
|     return activeCluster ? activeCluster.get('name') : null; | ||||
|   }), | ||||
|  | ||||
|   showNav: computed( | ||||
|     'router.currentRouteName', | ||||
|     'activeClusterName', | ||||
|     'auth.currentToken', | ||||
|     'activeCluster.{dr.isSecondary,needsInit,sealed}', | ||||
|     function () { | ||||
|       if (this.activeCluster.dr?.isSecondary || this.activeCluster.needsInit || this.activeCluster.sealed) { | ||||
|         return false; | ||||
|       } | ||||
|       if ( | ||||
|         this.activeClusterName && | ||||
|         this.auth.currentToken && | ||||
|         this.router.currentRouteName !== 'vault.cluster.auth' | ||||
|       ) { | ||||
|         return true; | ||||
|       } | ||||
|       return; | ||||
|     } | ||||
|   ), | ||||
|   activeCluster: alias('auth.activeCluster'), | ||||
|  | ||||
|   actions: { | ||||
|     toggleConsole() { | ||||
|   | ||||
| @@ -20,9 +20,9 @@ export default Helper.extend({ | ||||
|   ), | ||||
|  | ||||
|   compute([route], params) { | ||||
|     const { routeParams } = params; | ||||
|     const { routeParams, requireAll } = params; | ||||
|     const permissions = this.permissions; | ||||
|  | ||||
|     return permissions.hasNavPermission(route, routeParams); | ||||
|     return permissions.hasNavPermission(route, routeParams, requireAll); | ||||
|   }, | ||||
| }); | ||||
|   | ||||
| @@ -135,18 +135,5 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, { | ||||
|       } | ||||
|       return true; | ||||
|     }, | ||||
|     loading(transition) { | ||||
|       const isSameRoute = transition.from?.name === transition.to?.name; | ||||
|       if (isSameRoute || Ember.testing) { | ||||
|         return; | ||||
|       } | ||||
|       // eslint-disable-next-line ember/no-controller-access-in-routes | ||||
|       const controller = this.controllerFor('vault.cluster'); | ||||
|       controller.set('currentlyLoading', true); | ||||
|  | ||||
|       transition.finally(function () { | ||||
|         controller.set('currentlyLoading', false); | ||||
|       }); | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
|   | ||||
| @@ -35,16 +35,6 @@ export default class ClientsRoute extends Route { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @action | ||||
|   async loading(transition) { | ||||
|     // eslint-disable-next-line ember/no-controller-access-in-routes | ||||
|     const controller = this.controllerFor(this.routeName); | ||||
|     controller.set('currentlyLoading', true); | ||||
|     transition.promise.finally(function () { | ||||
|       controller.set('currentlyLoading', false); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @action | ||||
|   deactivate() { | ||||
|     // when navigating away from parent route, delete manually inputted license start date | ||||
|   | ||||
							
								
								
									
										10
									
								
								ui/app/routes/vault/cluster/clients/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								ui/app/routes/vault/cluster/clients/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| import Route from '@ember/routing/route'; | ||||
| import { inject as service } from '@ember/service'; | ||||
|  | ||||
| export default class ClientsIndexRoute extends Route { | ||||
|   @service router; | ||||
|  | ||||
|   redirect() { | ||||
|     this.router.transitionTo('vault.cluster.clients.dashboard'); | ||||
|   } | ||||
| } | ||||
| @@ -26,20 +26,24 @@ export { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX }; | ||||
|  | ||||
| export default Service.extend({ | ||||
|   permissions: service(), | ||||
|   store: service(), | ||||
|   router: service(), | ||||
|   namespaceService: service('namespace'), | ||||
|  | ||||
|   IDLE_TIMEOUT: 3 * 60e3, | ||||
|   expirationCalcTS: null, | ||||
|   isRenewing: false, | ||||
|   mfaErrors: null, | ||||
|  | ||||
|   init() { | ||||
|     this._super(...arguments); | ||||
|     this.checkForRootToken(); | ||||
|   get tokenExpired() { | ||||
|     const expiration = this.tokenExpirationDate; | ||||
|     return expiration ? this.now() >= expiration : null; | ||||
|   }, | ||||
|  | ||||
|   clusterAdapter() { | ||||
|     return getOwner(this).lookup('adapter:cluster'); | ||||
|   get activeCluster() { | ||||
|     return this.activeClusterId ? this.store.peekRecord('cluster', this.activeClusterId) : null; | ||||
|   }, | ||||
|  | ||||
|   // eslint-disable-next-line | ||||
|   tokens: computed({ | ||||
|     get() { | ||||
| @@ -50,6 +54,84 @@ export default Service.extend({ | ||||
|     }, | ||||
|   }), | ||||
|  | ||||
|   isActiveSession: computed( | ||||
|     'router.currentRouteName', | ||||
|     'currentToken', | ||||
|     'activeCluster.{dr.isSecondary,needsInit,sealed,name}', | ||||
|     function () { | ||||
|       if (this.activeCluster) { | ||||
|         if (this.activeCluster.dr?.isSecondary || this.activeCluster.needsInit || this.activeCluster.sealed) { | ||||
|           return false; | ||||
|         } | ||||
|         if ( | ||||
|           this.activeCluster.name && | ||||
|           this.currentToken && | ||||
|           this.router.currentRouteName !== 'vault.cluster.auth' | ||||
|         ) { | ||||
|           return true; | ||||
|         } | ||||
|       } | ||||
|       return false; | ||||
|     } | ||||
|   ), | ||||
|  | ||||
|   tokenExpirationDate: computed('currentTokenName', 'expirationCalcTS', function () { | ||||
|     const tokenName = this.currentTokenName; | ||||
|     if (!tokenName) { | ||||
|       return; | ||||
|     } | ||||
|     const { tokenExpirationEpoch } = this.getTokenData(tokenName); | ||||
|     const expirationDate = new Date(0); | ||||
|     return tokenExpirationEpoch ? expirationDate.setUTCMilliseconds(tokenExpirationEpoch) : null; | ||||
|   }), | ||||
|  | ||||
|   renewAfterEpoch: computed('currentTokenName', 'expirationCalcTS', function () { | ||||
|     const tokenName = this.currentTokenName; | ||||
|     const { expirationCalcTS } = this; | ||||
|     const data = this.getTokenData(tokenName); | ||||
|     if (!tokenName || !data || !expirationCalcTS) { | ||||
|       return null; | ||||
|     } | ||||
|     const { ttl, renewable } = data; | ||||
|     // renew after last expirationCalc time + half of the ttl (in ms) | ||||
|     return renewable ? Math.floor((ttl * 1e3) / 2) + expirationCalcTS : null; | ||||
|   }), | ||||
|  | ||||
|   // returns the key for the token to use | ||||
|   currentTokenName: computed('activeClusterId', 'tokens', 'tokens.[]', function () { | ||||
|     const regex = new RegExp(this.activeClusterId); | ||||
|     return this.tokens.find((key) => regex.test(key)); | ||||
|   }), | ||||
|  | ||||
|   currentToken: computed('currentTokenName', function () { | ||||
|     const name = this.currentTokenName; | ||||
|     const data = name && this.getTokenData(name); | ||||
|     // data.token is undefined so that's why it returns current token undefined | ||||
|     return name && data ? data.token : null; | ||||
|   }), | ||||
|  | ||||
|   authData: computed('currentTokenName', function () { | ||||
|     const token = this.currentTokenName; | ||||
|     if (!token) { | ||||
|       return; | ||||
|     } | ||||
|     const backend = this.backendFromTokenName(token); | ||||
|     const stored = this.getTokenData(token); | ||||
|  | ||||
|     return assign(stored, { | ||||
|       backend: BACKENDS.findBy('type', backend), | ||||
|     }); | ||||
|   }), | ||||
|  | ||||
|   init() { | ||||
|     this._super(...arguments); | ||||
|     this.checkForRootToken(); | ||||
|   }, | ||||
|  | ||||
|   clusterAdapter() { | ||||
|     return getOwner(this).lookup('adapter:cluster'); | ||||
|   }, | ||||
|  | ||||
|   generateTokenName({ backend, clusterId }, policies) { | ||||
|     return (policies || []).includes('root') | ||||
|       ? `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}${clusterId}` | ||||
| @@ -83,7 +165,7 @@ export default Service.extend({ | ||||
|   }, | ||||
|  | ||||
|   setCluster(clusterId) { | ||||
|     this.set('activeCluster', clusterId); | ||||
|     this.set('activeClusterId', clusterId); | ||||
|   }, | ||||
|  | ||||
|   ajax(url, method, options) { | ||||
| @@ -196,7 +278,7 @@ export default Service.extend({ | ||||
|     tokenName = this.generateTokenName( | ||||
|       { | ||||
|         backend, | ||||
|         clusterId: (options && options.clusterId) || this.activeCluster, | ||||
|         clusterId: (options && options.clusterId) || this.activeClusterId, | ||||
|       }, | ||||
|       resp.policies | ||||
|     ); | ||||
| @@ -231,33 +313,6 @@ export default Service.extend({ | ||||
|     return this.storage(token).removeItem(token); | ||||
|   }, | ||||
|  | ||||
|   tokenExpirationDate: computed('currentTokenName', 'expirationCalcTS', function () { | ||||
|     const tokenName = this.currentTokenName; | ||||
|     if (!tokenName) { | ||||
|       return; | ||||
|     } | ||||
|     const { tokenExpirationEpoch } = this.getTokenData(tokenName); | ||||
|     const expirationDate = new Date(0); | ||||
|     return tokenExpirationEpoch ? expirationDate.setUTCMilliseconds(tokenExpirationEpoch) : null; | ||||
|   }), | ||||
|  | ||||
|   get tokenExpired() { | ||||
|     const expiration = this.tokenExpirationDate; | ||||
|     return expiration ? this.now() >= expiration : null; | ||||
|   }, | ||||
|  | ||||
|   renewAfterEpoch: computed('currentTokenName', 'expirationCalcTS', function () { | ||||
|     const tokenName = this.currentTokenName; | ||||
|     const { expirationCalcTS } = this; | ||||
|     const data = this.getTokenData(tokenName); | ||||
|     if (!tokenName || !data || !expirationCalcTS) { | ||||
|       return null; | ||||
|     } | ||||
|     const { ttl, renewable } = data; | ||||
|     // renew after last expirationCalc time + half of the ttl (in ms) | ||||
|     return renewable ? Math.floor((ttl * 1e3) / 2) + expirationCalcTS : null; | ||||
|   }), | ||||
|  | ||||
|   renew() { | ||||
|     const tokenName = this.currentTokenName; | ||||
|     const currentlyRenewing = this.isRenewing; | ||||
| @@ -422,32 +477,6 @@ export default Service.extend({ | ||||
|     this.set('tokens', tokenNames); | ||||
|   }, | ||||
|  | ||||
|   // returns the key for the token to use | ||||
|   currentTokenName: computed('activeCluster', 'tokens', 'tokens.[]', function () { | ||||
|     const regex = new RegExp(this.activeCluster); | ||||
|     return this.tokens.find((key) => regex.test(key)); | ||||
|   }), | ||||
|  | ||||
|   currentToken: computed('currentTokenName', function () { | ||||
|     const name = this.currentTokenName; | ||||
|     const data = name && this.getTokenData(name); | ||||
|     // data.token is undefined so that's why it returns current token undefined | ||||
|     return name && data ? data.token : null; | ||||
|   }), | ||||
|  | ||||
|   authData: computed('currentTokenName', function () { | ||||
|     const token = this.currentTokenName; | ||||
|     if (!token) { | ||||
|       return; | ||||
|     } | ||||
|     const backend = this.backendFromTokenName(token); | ||||
|     const stored = this.getTokenData(token); | ||||
|  | ||||
|     return assign(stored, { | ||||
|       backend: BACKENDS.findBy('type', backend), | ||||
|     }); | ||||
|   }), | ||||
|  | ||||
|   getOktaNumberChallengeAnswer(nonce, mount) { | ||||
|     const url = `/v1/auth/${mount}/verify/${nonce}`; | ||||
|     return this.ajax(url, 'GET', {}).then( | ||||
|   | ||||
| @@ -95,12 +95,15 @@ export default Service.extend({ | ||||
|     this.set('canViewAll', null); | ||||
|   }, | ||||
|  | ||||
|   hasNavPermission(navItem, routeParams) { | ||||
|   hasNavPermission(navItem, routeParams, requireAll) { | ||||
|     if (routeParams) { | ||||
|       // viewing the entity and groups pages require the list capability, while the others require the default, which is anything other than deny | ||||
|       const capability = routeParams === 'entities' || routeParams === 'groups' ? ['list'] : [null]; | ||||
|  | ||||
|       return this.hasPermission(API_PATHS[navItem][routeParams], capability); | ||||
|       // check that the user has permission to access all (requireAll = true) or any of the routes when array is passed | ||||
|       // useful for hiding nav headings when user does not have access to any of the links | ||||
|       const params = Array.isArray(routeParams) ? routeParams : [routeParams]; | ||||
|       const evalMethod = !Array.isArray(routeParams) || requireAll ? 'every' : 'some'; | ||||
|       return params[evalMethod]((param) => this.hasPermission(API_PATHS[navItem][param], capability)); | ||||
|     } | ||||
|     return Object.values(API_PATHS[navItem]).some((path) => this.hasPermission(path)); | ||||
|   }, | ||||
|   | ||||
| @@ -3,8 +3,10 @@ | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| @import './reset'; | ||||
| @import 'ember-basic-dropdown'; | ||||
| @import 'ember-power-select'; | ||||
| @import '@hashicorp/design-system-components'; | ||||
| @import './core'; | ||||
|  | ||||
| @mixin font-face($name) { | ||||
|   | ||||
| @@ -3,25 +3,26 @@ | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| $console-close-height: 35px; | ||||
|  | ||||
| .console-ui-panel { | ||||
|   background: linear-gradient(to right, #191a1c, #1b212d); | ||||
|   background: var(--token-color-palette-neutral-700); | ||||
|   width: -moz-available; | ||||
|   width: -webkit-fill-available; | ||||
|   height: 0; | ||||
|   left: 0; | ||||
|   position: fixed; | ||||
|   min-height: 0; | ||||
|   overflow: scroll; | ||||
|   right: 0; | ||||
|   top: 4rem; | ||||
|   overflow: auto; | ||||
|   position: fixed; | ||||
|   bottom: 0; | ||||
|   transition: min-height $speed $easing, transform $speed ease-in; | ||||
|   will-change: transform, min-height; | ||||
|   -webkit-overflow-scrolling: touch; | ||||
|   width: 100vw; | ||||
|   z-index: 199; | ||||
|  | ||||
|   .button { | ||||
|     background: transparent; | ||||
|     border: none; | ||||
|     color: $grey; | ||||
|     color: $white; | ||||
|     min-width: 0; | ||||
|     padding: 0 $size-8; | ||||
|  | ||||
| @@ -40,8 +41,8 @@ | ||||
|   font-size: 14px; | ||||
|   font-weight: $font-weight-semibold; | ||||
|   justify-content: flex-end; | ||||
|   min-height: 100%; | ||||
|   padding: $size-8 $size-8 $size-4; | ||||
|   min-height: calc(100% - $console-close-height); // account for close button that is sticky positioned | ||||
|   padding: $size-8 $size-8 $size-5; | ||||
|   transition: justify-content $speed ease-in; | ||||
|  | ||||
|   pre, | ||||
| @@ -78,16 +79,17 @@ | ||||
|  | ||||
|   input { | ||||
|     background-color: rgba($black, 0.5); | ||||
|     border: 0; | ||||
|     border: 1px solid var(--token-color-palette-neutral-500); | ||||
|     border-radius: 2px; | ||||
|     caret-color: $white; | ||||
|     color: $white; | ||||
|     flex: 1 1 auto; | ||||
|     font-family: $family-monospace; | ||||
|     font-size: 16px; | ||||
|     font-weight: $font-weight-bold; | ||||
|     margin-left: -$size-10; | ||||
|     outline: none; | ||||
|     padding: $size-10; | ||||
|     margin-right: $spacing-xs; | ||||
|     transition: background-color $speed; | ||||
|   } | ||||
| } | ||||
| @@ -125,31 +127,9 @@ | ||||
|  | ||||
| .panel-open .console-ui-panel.fullscreen { | ||||
|   bottom: 0; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   min-height: 100vh; | ||||
| } | ||||
|  | ||||
| .panel-open { | ||||
|   .navbar, | ||||
|   .navbar-sections { | ||||
|     transition: transform $speed ease-in; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .panel-open.panel-fullscreen { | ||||
|   .navbar, | ||||
|   .navbar-sections { | ||||
|     @include from($mobile) { | ||||
|       transform: translateY(-100px); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| header .navbar, | ||||
| header .navbar-sections { | ||||
|   z-index: 200; | ||||
|   transform: translateY(0); | ||||
|   will-change: transform; | ||||
|   width: 100%; | ||||
| } | ||||
|  | ||||
| .console-spinner.control { | ||||
| @@ -168,12 +148,14 @@ header .navbar-sections { | ||||
| } | ||||
|  | ||||
| .console-close-button { | ||||
|   position: absolute; | ||||
|   top: -3.25rem; | ||||
|   right: $spacing-xs; | ||||
|   position: sticky; | ||||
|   top: $spacing-xs; | ||||
|   height: $console-close-height; | ||||
|   display: flex; | ||||
|   justify-content: flex-end; | ||||
|   z-index: 210; | ||||
|  | ||||
|   @include from($mobile) { | ||||
|     display: none; | ||||
|   button { | ||||
|     margin-right: $spacing-xs; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
|   animation: env-banner-color-rotate 8s infinite linear alternate; | ||||
|   color: $white; | ||||
|   margin-top: -20px; | ||||
|   margin-bottom: 6px; | ||||
|  | ||||
|   .hs-icon { | ||||
|     margin: 0; | ||||
|   | ||||
| @@ -74,3 +74,11 @@ | ||||
|   width: 32px; | ||||
|   height: 32px; | ||||
| } | ||||
|  | ||||
| .brand-icon-large { | ||||
|   width: 62px; | ||||
| } | ||||
|  | ||||
| .error-icon { | ||||
|   width: 48px; | ||||
| } | ||||
|   | ||||
| @@ -5,65 +5,33 @@ | ||||
|  | ||||
| .namespace-picker { | ||||
|   position: relative; | ||||
|   color: $white; | ||||
|   color: var(--token-color-palette-neutral-300); | ||||
|   display: flex; | ||||
|   fill: $white; | ||||
|   padding: $spacing-xxs $spacing-xs; | ||||
|   width: 100%; | ||||
|  | ||||
|   @include from($mobile) { | ||||
|     margin-left: -$spacing-xs; | ||||
|     padding: $spacing-xxs 0 $spacing-xxs $spacing-s; | ||||
|     width: auto; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .namespace-picker.no-namespaces { | ||||
|   border: none; | ||||
|   padding-right: 0; | ||||
| } | ||||
|  | ||||
| .namespace-picker-trigger { | ||||
|   align-items: center; | ||||
|   display: flex; | ||||
|   flex: 1 1 auto; | ||||
|   height: 2rem; | ||||
|   justify-content: space-between; | ||||
|   padding: 0; | ||||
|   text-align: left; | ||||
|  | ||||
|   @include from($mobile) { | ||||
|     height: auto; | ||||
|     padding: $spacing-xs $spacing-m; | ||||
|   margin-right: $spacing-xxs; | ||||
| } | ||||
|  | ||||
|   .is-status-chevron { | ||||
|     transform: rotate(-90deg); | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       transform: rotate(0deg); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.ember-basic-dropdown-trigger--below .is-status-chevron { | ||||
|     transform: rotate(0deg); | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       transform: rotate(180deg); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| .namespace-name { | ||||
|   display: inline-block; | ||||
|   flex: 1 1 auto; | ||||
|   font-size: 1rem; | ||||
|   margin: 0 $spacing-xs; | ||||
|   margin-left: $spacing-xs; | ||||
| } | ||||
|  | ||||
|   @include from($mobile) { | ||||
|     margin-left: $size-10; | ||||
|   } | ||||
| } | ||||
| .namespace-picker-content { | ||||
|   width: $drawer-width - ($spacing-xs * 2); | ||||
|   width: 250px; | ||||
|   max-height: 300px; | ||||
|   overflow: auto; | ||||
|   border-radius: $radius; | ||||
| @@ -72,10 +40,6 @@ | ||||
|   &.ember-basic-dropdown-content { | ||||
|     background: $white; | ||||
|   } | ||||
|  | ||||
|   @include from($mobile) { | ||||
|     width: $drawer-width; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .namespace-picker-content .level-left { | ||||
| @@ -95,6 +59,14 @@ | ||||
|  | ||||
| .namespace-manage-link { | ||||
|   border-top: 1px solid rgba($black, 0.1); | ||||
|  | ||||
|   .level-left { | ||||
|     font-weight: $font-weight-bold; | ||||
|     font-size: 14px; | ||||
|   } | ||||
|   .level-right { | ||||
|     margin-right: 10px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .namespace-list { | ||||
| @@ -112,6 +84,11 @@ | ||||
| .namespace-link.is-current { | ||||
|   margin-top: $size-8; | ||||
|   margin-right: -$size-10; | ||||
|  | ||||
|   svg { | ||||
|     margin-top: 2px; | ||||
|     color: var(--token-color-border-strong); | ||||
|   } | ||||
| } | ||||
|  | ||||
| .leaf-panel { | ||||
| @@ -127,9 +104,11 @@ | ||||
|   bottom: 0; | ||||
|   z-index: 1; | ||||
| } | ||||
|  | ||||
| .leaf-panel-left { | ||||
|   transform: translateX(-$drawer-width); | ||||
| } | ||||
|  | ||||
| .leaf-panel-adding, | ||||
| .leaf-panel-current { | ||||
|   position: relative; | ||||
| @@ -137,6 +116,7 @@ | ||||
|     margin-bottom: 4px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .animated-list { | ||||
|   .leaf-panel-exiting, | ||||
|   .leaf-panel-adding { | ||||
| @@ -144,6 +124,7 @@ | ||||
|     z-index: 20; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .leaf-panel-adding { | ||||
|   z-index: 100; | ||||
| } | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|   } | ||||
|  | ||||
|   .title { | ||||
|     margin-top: $size-1; | ||||
|     margin-top: $size-2; | ||||
|   } | ||||
|  | ||||
|   .title-with-icon { | ||||
|   | ||||
| @@ -132,16 +132,6 @@ | ||||
|   } | ||||
| } | ||||
|  | ||||
| .status-menu-content { | ||||
|   margin-top: 8px; | ||||
|  | ||||
|   .box { | ||||
|     @include until($mobile) { | ||||
|       width: $drawer-width - ($spacing-xs * 2); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .ember-basic-dropdown-content { | ||||
|   background-color: transparent; | ||||
|  | ||||
|   | ||||
| @@ -3,107 +3,44 @@ | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| .is-sidebar { | ||||
|   border-right: $base-border; | ||||
|   display: flex; | ||||
|   flex: 1 1 auto; | ||||
|   margin: 0.75rem 0.75rem 0.75rem 0; | ||||
|   padding: 0 0 0 0.75rem; | ||||
|  | ||||
|   @include until($mobile) { | ||||
|     background-color: $white; | ||||
|     bottom: 0; | ||||
|     left: -1.5rem; | ||||
|     margin: 0; | ||||
|     max-width: $drawer-width; | ||||
|     padding: $spacing-m 0 0; | ||||
|     position: absolute; | ||||
|     right: $size-2; | ||||
|     transform: translateX(-100%); | ||||
|     transition: transform $speed; | ||||
|     top: 0; | ||||
|     z-index: 5; | ||||
|   } | ||||
|  | ||||
|   &.is-active { | ||||
|     @include until($mobile) { | ||||
|       transform: translateX(0); | ||||
|     } | ||||
|  | ||||
|     .menu-toggle { | ||||
|       left: auto; | ||||
|       right: $size-10; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .menu-toggle { | ||||
|     color: $blue; | ||||
|     cursor: pointer; | ||||
|     display: none; | ||||
|     margin-left: $size-10; | ||||
|     left: 100%; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|  | ||||
|     @include until($mobile) { | ||||
|       display: block; | ||||
|     } | ||||
|  | ||||
|     .button { | ||||
|       min-width: 0; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .menu { | ||||
|     flex: 1 1 auto; | ||||
|     padding-top: 5.25rem; | ||||
|     position: relative; | ||||
|  | ||||
|     @include until($mobile) { | ||||
|       padding-top: $size-6; | ||||
|     } | ||||
|   } | ||||
| .sidebar-user-menu { | ||||
|   align-self: center; | ||||
|  | ||||
|   .popup-menu-content { | ||||
|     .menu-label { | ||||
|     color: $grey-light; | ||||
|       color: $black; | ||||
|       font-size: 14px; | ||||
|       font-weight: $font-weight-bold; | ||||
|     font-size: $size-8; | ||||
|     line-height: 1; | ||||
|     margin-bottom: $size-8; | ||||
|     padding-left: $size-5; | ||||
|       text-transform: unset; | ||||
|     } | ||||
|     .token-alert { | ||||
|       padding: $spacing-xs; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .menu-list { | ||||
|     border-top: $base-border; | ||||
|     padding: $size-9 0; | ||||
| .link-status { | ||||
|   height: 40px; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   font-size: $size-7; | ||||
|   font-weight: $font-weight-semibold; | ||||
|  | ||||
|     @include until($mobile) { | ||||
|       padding-top: $size-4; | ||||
|     } | ||||
|  | ||||
|     li { | ||||
|       a { | ||||
|         &.active { | ||||
|           border-right: 4px solid $blue; | ||||
|           color: $blue; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   &.connected { | ||||
|     background-color: var(--token-color-surface-action); | ||||
|     color: var(--token-color-foreground-action-active); | ||||
|  | ||||
|     a { | ||||
|       color: $grey-dark; | ||||
|       padding-left: $size-5; | ||||
|       transition: 250ms border-width; | ||||
|       color: var(--token-color-foreground-action-active); | ||||
|     } | ||||
|   } | ||||
|   &.warning { | ||||
|     background-color: var(--token-color-surface-warning); | ||||
|     color: var(--token-color-palette-amber-300); | ||||
|  | ||||
|       &.active { | ||||
|         border-right: 4px solid $blue; | ||||
|       } | ||||
|     } | ||||
|     // TODO will be removed with the navbar work. Reminder to remove the var $fullhd as this is the only use case. | ||||
|     .tag { | ||||
|       @include from($fullhd) { | ||||
|         float: right; | ||||
|       } | ||||
|     a { | ||||
|       color: var(--token-color-palette-amber-300); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| .navbar-brand .splash-page-logo { | ||||
| .splash-page-logo { | ||||
|   padding: $spacing-xs $spacing-s $spacing-xs $spacing-l; | ||||
|  | ||||
|   @include from($mobile) { | ||||
|   | ||||
| @@ -1,24 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| .status-indicator-button { | ||||
|   &[data-status='good'] { | ||||
|     .status-indicator-color { | ||||
|       color: $green-light; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &[data-status='mixed'] { | ||||
|     .status-indicator-color { | ||||
|       color: $yellow; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &[data-status='bad'] { | ||||
|     .status-indicator-color { | ||||
|       color: $red; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -40,13 +40,12 @@ | ||||
|       &.is-active { | ||||
|         border-bottom: 2px solid $blue; | ||||
|         color: $blue; | ||||
|       } | ||||
|       // solves for tabs on secret engines | ||||
|       > a &.active { | ||||
|         border-bottom: 2px solid $blue; | ||||
| 
 | ||||
|         > button { | ||||
|           color: $blue; | ||||
|         } | ||||
|       // solves for tabs on auth mounts | ||||
|       } | ||||
|       // solves for tabs on auth mounts & secrets engines | ||||
|       > a { | ||||
|         &.active { | ||||
|           color: $blue; | ||||
| @@ -36,7 +36,6 @@ | ||||
| @import './core/lists'; | ||||
| @import './core/menu'; | ||||
| @import './core/message'; | ||||
| @import './core/navbar'; | ||||
| @import './core/progress'; | ||||
| @import './core/select'; | ||||
| @import './core/switch'; | ||||
| @@ -66,7 +65,7 @@ | ||||
| @import './components/control-group'; | ||||
| @import './components/diff-version-selector'; | ||||
| @import './components/doc-link'; | ||||
| @import './components/empty-state'; | ||||
| @import './components/empty-state-component'; | ||||
| @import './components/env-banner'; | ||||
| @import './components/features-selection'; | ||||
| @import './components/form-section'; | ||||
| @@ -85,7 +84,7 @@ | ||||
| @import './components/loader'; | ||||
| @import './components/login-form'; | ||||
| @import './components/masked-input'; | ||||
| @import './components/modal'; | ||||
| @import './components/modal-component.scss'; | ||||
| @import './components/namespace-picker'; | ||||
| @import './components/namespace-reminder'; | ||||
| @import './components/navigate-input'; | ||||
| @@ -112,8 +111,7 @@ | ||||
| @import './components/sidebar'; | ||||
| @import './components/splash-page'; | ||||
| @import './components/stat-text'; | ||||
| @import './components/status-menu'; | ||||
| @import './components/tabs'; | ||||
| @import './components/tabs-component'; | ||||
| @import './components/text-file'; | ||||
| @import './components/token-expire-warning'; | ||||
| @import './components/toolbar'; | ||||
|   | ||||
| @@ -15,12 +15,6 @@ | ||||
|     margin: 0; | ||||
|   } | ||||
|  | ||||
|   .is-sidebar + .column & { | ||||
|     @include until($mobile) { | ||||
|       margin-left: $size-2; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ul, | ||||
|   ol { | ||||
|     align-items: center; | ||||
|   | ||||
| @@ -11,10 +11,9 @@ | ||||
| } | ||||
|  | ||||
| .page-container { | ||||
|   min-height: calc(100vh - 4rem); | ||||
|   min-height: 100vh; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   margin-top: 4rem; | ||||
|   justify-content: flex-end; | ||||
| } | ||||
|  | ||||
| @@ -39,30 +38,7 @@ | ||||
| .container { | ||||
|   flex-grow: 1; | ||||
|   margin: 0 auto; | ||||
|   max-width: 1024px; | ||||
|   position: relative; | ||||
|   width: auto; | ||||
| } | ||||
|  | ||||
| @media screen and (min-width: 1024px) { | ||||
|   .container { | ||||
|     max-width: 960px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media screen and (max-width: 1215px) { | ||||
|   .container.is-widescreen:not(.is-max-desktop) { | ||||
|     max-width: 1152px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media screen and (min-width: 1216px) { | ||||
|   .container:not(.is-max-desktop) { | ||||
|     max-width: 1152px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media screen and (min-width: 1408px) { | ||||
|   .container:not(.is-max-desktop):not(.is-max-widescreen) { | ||||
|     max-width: 1344px; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,309 +0,0 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| .navbar { | ||||
|   left: 0; | ||||
|   position: fixed; | ||||
|   right: 0; | ||||
|   top: 0; | ||||
|   @include from($mobile) { | ||||
|     display: block; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-status { | ||||
|   height: 40px; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   font-size: $size-7; | ||||
|   font-weight: $font-weight-semibold; | ||||
|  | ||||
|   &.connected { | ||||
|     background-color: $ui-gray-800; | ||||
|     color: $ui-gray-300; | ||||
|  | ||||
|     a { | ||||
|       color: #c2c5cb; | ||||
|     } | ||||
|   } | ||||
|   &.warning { | ||||
|     background-color: #fcf6ea; | ||||
|     color: #975b06; | ||||
|  | ||||
|     a { | ||||
|       color: #975b06; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-actions { | ||||
|   background-color: $black; | ||||
|   display: flex; | ||||
|   height: 4rem; | ||||
|   justify-content: flex-start; | ||||
|   padding: $spacing-xs $spacing-s $spacing-xs 0; | ||||
| } | ||||
|  | ||||
| .navbar-brand { | ||||
|   align-items: stretch; | ||||
|   background: $grey; | ||||
|   border-radius: 0 $radius-large $radius-large 0; | ||||
|   display: flex; | ||||
|   margin-right: $spacing-s; | ||||
|   min-height: auto; | ||||
|   position: relative; | ||||
|   z-index: 203; | ||||
|  | ||||
|   .navbar-item { | ||||
|     align-items: center; | ||||
|     background-color: transparent; | ||||
|     display: flex; | ||||
|     padding: $spacing-xs $spacing-l; | ||||
|  | ||||
|     &:hover, | ||||
|     &.is-active { | ||||
|       background-color: transparent; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-drawer-toggle { | ||||
|   font-size: $size-6; | ||||
|   color: $grey; | ||||
|   cursor: pointer; | ||||
|   font-weight: $font-weight-semibold; | ||||
|   margin-left: -$spacing-s; | ||||
|   padding: $spacing-xs $spacing-xxs; | ||||
|   background: none; | ||||
|   border: none; | ||||
|  | ||||
|   .navbar-drawer & { | ||||
|     position: absolute; | ||||
|     top: $spacing-xs; | ||||
|     right: $spacing-xxs; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-drawer-overlay { | ||||
|   height: 100vh; | ||||
|   left: 0; | ||||
|   pointer-events: none; | ||||
|   position: fixed; | ||||
|   right: 0; | ||||
|   top: 0; | ||||
|   transition: background-color $speed, opacity $speed; | ||||
|   will-change: background-color, opacity; | ||||
|   z-index: -1; | ||||
|  | ||||
|   &.is-active { | ||||
|     background-color: rgba($black, 0.25); | ||||
|     pointer-events: all; | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       background-color: transparent; | ||||
|       pointer-events: none; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-sections, | ||||
| .navbar-sections li, | ||||
| .navbar-drawer-scroll, | ||||
| .navbar-drawer-scroll > * { | ||||
|   @include from($mobile) { | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-sections { | ||||
|   a { | ||||
|     color: $grey-light; | ||||
|     display: block; | ||||
|     font-weight: $font-weight-semibold; | ||||
|     line-height: 1.3; | ||||
|     padding: $spacing-xs $spacing-m; | ||||
|     text-decoration: none; | ||||
|     transition: background-color $speed, color $speed; | ||||
|     will-change: background-color, color; | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       border-radius: $radius; | ||||
|       display: inline-block; | ||||
|       padding: $spacing-xxs $spacing-s; | ||||
|     } | ||||
|  | ||||
|     &.is-active { | ||||
|       background-color: $ui-gray-800; | ||||
|       color: $white; | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       color: $white; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-end { | ||||
|   margin-left: auto; | ||||
| } | ||||
|  | ||||
| .navbar-item { | ||||
|   padding: $spacing-xs; | ||||
| } | ||||
|  | ||||
| .navbar-separator { | ||||
|   background-color: $ui-gray-700; | ||||
|   height: 1px; | ||||
|   margin: $spacing-xs 0; | ||||
|   width: 100%; | ||||
|  | ||||
|   @include from($mobile) { | ||||
|     height: $spacing-l; | ||||
|     margin: 0 $spacing-s; | ||||
|     width: 1px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-drawer { | ||||
|   flex: 1 1 auto; | ||||
|  | ||||
|   @include until($mobile) { | ||||
|     background-color: $ui-gray-900; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     height: 100vh; | ||||
|     left: 0; | ||||
|     padding: 4rem 0 $spacing-m; | ||||
|     position: fixed; | ||||
|     top: 0; | ||||
|     transform: translateX(-100%); | ||||
|     transition: box-shadow $speed, transform $speed-slow; | ||||
|     width: $drawer-width; | ||||
|     will-change: transform, box-shadow; | ||||
|     z-index: 201; | ||||
|   } | ||||
|  | ||||
|   &.is-active { | ||||
|     @include until($mobile) { | ||||
|       box-shadow: 5px 0 10px rgba($black, 0.36); | ||||
|       transform: translateX(0); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .navbar-item .button { | ||||
|     color: $grey-light; | ||||
|     display: flex; | ||||
|     font-size: 1rem; | ||||
|     height: auto; | ||||
|     justify-content: flex-start; | ||||
|     text-align: left; | ||||
|     width: 100%; | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       display: inline-flex; | ||||
|       height: $spacing-l; | ||||
|       width: auto; | ||||
|     } | ||||
|  | ||||
|     &.popup-open, | ||||
|     &.ember-basic-dropdown-trigger--below { | ||||
|       color: $white; | ||||
|  | ||||
|       .is-status-chevron { | ||||
|         transform: rotate(0deg); | ||||
|  | ||||
|         @include from($mobile) { | ||||
|           transform: rotate(180deg); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .is-status-chevron { | ||||
|       transform: rotate(270deg); | ||||
|  | ||||
|       @include from($mobile) { | ||||
|         transform: rotate(0deg); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .button .icon, | ||||
|   .button .icon:first-child:not(:last-child) { | ||||
|     flex: 0; | ||||
|     margin: 0 $spacing-xs 0 0; | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       margin: -$spacing-xxs; | ||||
|       margin-right: 0; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .status-menu-label { | ||||
|     flex: 1 1 auto; | ||||
|     line-height: 1; | ||||
|   } | ||||
|  | ||||
|   .nav-console-button .status-menu-label, | ||||
|   .nav-user-button .status-menu-label { | ||||
|     flex: 1 1 auto; | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       display: none; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .nav-user-button .icon { | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| .nav-user-button.may-expire .icon:first-of-type::after { | ||||
|   content: ''; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   height: 6px; | ||||
|   width: 6px; | ||||
|   border-radius: 50%; | ||||
|   background: $yellow; | ||||
| } | ||||
|  | ||||
| .navbar-drawer-scroll { | ||||
|   overflow: auto; | ||||
|   height: 100%; | ||||
|   -webkit-overflow-scrolling: touch; | ||||
|  | ||||
|   &::before { | ||||
|     background-image: linear-gradient(to bottom, $ui-gray-900, rgba($ui-gray-900, 0)); | ||||
|     content: ''; | ||||
|     height: $spacing-xs; | ||||
|     left: 0; | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|     top: 4rem; | ||||
|     z-index: 1; | ||||
|  | ||||
|     @include from($mobile) { | ||||
|       display: none; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .navbar-drawer .ember-basic-dropdown-content { | ||||
|   @include until($mobile) { | ||||
|     position: relative; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // responsive css | ||||
| @media screen and (min-width: 1024px) { | ||||
|   .navbar-item, | ||||
|   .navbar-link { | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|   } | ||||
| } | ||||
| @@ -95,7 +95,7 @@ | ||||
|       content: ''; | ||||
|       height: $size-8; | ||||
|       position: absolute; | ||||
|       top: $size-8/ 5; | ||||
|       top: calc($size-8 / 5); | ||||
|       width: $size-8 * 2; | ||||
|     } | ||||
|     &::after { | ||||
| @@ -106,7 +106,7 @@ | ||||
|       height: $size-8 * 0.8; | ||||
|       left: 0; | ||||
|       position: absolute; | ||||
|       top: $size-8/ 4; | ||||
|       top: calc($size-8 / 4); | ||||
|       transform: translateX(0.15rem); | ||||
|       transition: all 0.25s ease-out; | ||||
|       width: $size-8 * 0.8; | ||||
|   | ||||
| @@ -74,7 +74,7 @@ | ||||
|       padding-left: $size-8 * 2.5; | ||||
|       margin: 0 0.25rem; | ||||
|       &::before { | ||||
|         top: $size-8 / 5; | ||||
|         top: calc($size-8 / 5); | ||||
|         height: $size-8; | ||||
|         width: $size-8 * 2; | ||||
|       } | ||||
| @@ -83,7 +83,7 @@ | ||||
|         height: $size-8 * 0.8; | ||||
|         transform: translateX(0.15rem); | ||||
|         left: 0; | ||||
|         top: $size-8/ 4; | ||||
|         top: calc($size-8 / 4); | ||||
|       } | ||||
|     } | ||||
|     &:checked + label::after { | ||||
|   | ||||
| @@ -34,6 +34,10 @@ | ||||
|   padding-bottom: $spacing-s; | ||||
| } | ||||
|  | ||||
| .has-bottom-padding-l { | ||||
|   padding-bottom: $spacing-l; | ||||
| } | ||||
|  | ||||
| .has-top-padding-s { | ||||
|   padding-top: $spacing-s; | ||||
| } | ||||
|   | ||||
| @@ -79,6 +79,10 @@ | ||||
|   text-align: center !important; | ||||
| } | ||||
|  | ||||
| .has-line-height-1 { | ||||
|   line-height: 1; | ||||
| } | ||||
|  | ||||
| // Text color or styling | ||||
| .is-help { | ||||
|   font-size: $size-8; | ||||
|   | ||||
							
								
								
									
										6
									
								
								ui/app/styles/reset.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ui/app/styles/reset.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| // reset for HDS | ||||
| *, | ||||
| *::before, | ||||
| *::after { | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| @@ -6,12 +6,12 @@ | ||||
| /* General sizing in rem values used largely for text sizing.*/ | ||||
| $size-1: 3rem; // 48px, same as $spacing-xxl | ||||
| $size-2: 2.5rem; // 40px | ||||
| $size-3: (24/14) + 0rem; // ~1.714rem ~27px | ||||
| $size-3: calc(24 / 14) + 0rem; // ~1.714rem ~27px | ||||
| $size-4: 1.5rem; // 24px same as $spacing-l | ||||
| $size-5: 1.25rem; // 20px | ||||
| $size-6: 1rem; // 16px same as $spacing-m | ||||
| $size-7: (13/14) + 0rem; // ~.929rem ~15px | ||||
| $size-8: (12/14) + 0rem; // ~.857rem  ~13.7px | ||||
| $size-7: calc(13 / 14) + 0rem; // ~.929rem ~15px | ||||
| $size-8: calc(12 / 14) + 0rem; // ~.857rem  ~13.7px | ||||
| $size-9: 0.75rem; // 12px same as $spacing-s | ||||
| $size-10: 0.5rem; // 8px same as $spacing-xs | ||||
| $size-11: 0.25rem; // 4px same as spacing-xxs | ||||
|   | ||||
| @@ -84,6 +84,6 @@ | ||||
|  | ||||
| @mixin vault-block { | ||||
|   &:not(:last-child) { | ||||
|     margin-bottom: (5/14) + 0rem; | ||||
|     margin-bottom: calc(5 / 14) + 0rem; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| <Sidebar::Frame @showSidebar={{this.auth.isActiveSession}}> | ||||
|   <div class="page-container"> | ||||
|     {{outlet}} | ||||
|   </div> | ||||
| </Sidebar::Frame> | ||||
| @@ -1,6 +1,6 @@ | ||||
| {{#unless this.selectedAuthIsPath}} | ||||
|   <div class="box has-slim-padding is-shadowless"> | ||||
|     <ToggleButton @isOpen={{this.isOpen}} @onClick={{fn (mut this.isOpen)}} /> | ||||
|     <ToggleButton @isOpen={{this.isOpen}} @onClick={{fn (mut this.isOpen)}} data-test-auth-form-options-toggle /> | ||||
|     {{#if this.isOpen}} | ||||
|       <div class="field"> | ||||
|         <label for="custom-path" class="is-label"> | ||||
| @@ -14,6 +14,7 @@ | ||||
|             class="input" | ||||
|             value={{@customPath}} | ||||
|             oninput={{action @onPathChange value="target.value"}} | ||||
|             data-test-auth-form-mount-path | ||||
|           /> | ||||
|         </div> | ||||
|         <AlertInline | ||||
|   | ||||
| @@ -1,81 +0,0 @@ | ||||
| <Confirm as |c|> | ||||
|   <div class="popup-menu-content"> | ||||
|     <div class="box"> | ||||
|       <div class="menu-label"> | ||||
|         {{this.auth.authData.displayName}} | ||||
|       </div> | ||||
|       <nav class="menu"> | ||||
|         <ul class="menu-list"> | ||||
|           {{#if this.auth.allowExpiration}} | ||||
|             <li class="action"> | ||||
|               <AlertBanner | ||||
|                 @type="warning" | ||||
|                 @message="We've stopped auto-renewing your token due to inactivity. | ||||
|                 It will expire in {{date-from-now this.auth.tokenExpirationDate interval=1000 hideSuffix=true}}. | ||||
|                 On {{date-format this.auth.tokenExpirationDate 'MMMM do yyyy, h:mm:ss a'}}" | ||||
|               /> | ||||
|             </li> | ||||
|           {{/if}} | ||||
|           {{#if this.hasEntityId}} | ||||
|             <li class="action"> | ||||
|               <LinkTo @route="vault.cluster.mfa-setup" data-test-status-link="mfa"> | ||||
|                 Multi-factor authentication | ||||
|               </LinkTo> | ||||
|             </li> | ||||
|           {{/if}} | ||||
|           <li class="action"> | ||||
|             <CopyButton | ||||
|               @clipboardText={{this.auth.currentToken}} | ||||
|               class="link" | ||||
|               @buttonType="button" | ||||
|               @success={{action (set-flash-message "Token copied!")}} | ||||
|             > | ||||
|               Copy token | ||||
|             </CopyButton> | ||||
|           </li> | ||||
|           {{#if (is-before (now interval=1000) this.auth.tokenExpirationDate)}} | ||||
|             {{#if this.auth.authData.renewable}} | ||||
|               <li class="action"> | ||||
|                 <button type="button" {{on "click" this.renewToken}} class="link button {{if this.isRenewing 'is-loading'}}"> | ||||
|                   Renew token | ||||
|                 </button> | ||||
|               </li> | ||||
|               <li class="action"> | ||||
|                 <c.Message | ||||
|                   @id={{get this.auth "authData.displayName"}} | ||||
|                   @title={{concat "Revoke " (get this.auth "authData.displayName") "?"}} | ||||
|                   @onConfirm={{action "revokeToken"}} | ||||
|                   @message="You will not be able to log in again with this token." | ||||
|                   @triggerText="Revoke token" | ||||
|                   @confirmButtonText="Revoke" | ||||
|                 /> | ||||
|               </li> | ||||
|             {{else}} | ||||
|               <li class="action text-right"> | ||||
|                 <c.Message | ||||
|                   @id={{get this.auth "authData.displayName"}} | ||||
|                   @title={{concat "Revoke " (get this.auth "authData.displayName") "?"}} | ||||
|                   @onConfirm={{action "revokeToken"}} | ||||
|                   @message="You will not be able to log in again with this token." | ||||
|                   @triggerText="Revoke token" | ||||
|                   @confirmButtonText="Revoke" | ||||
|                 /> | ||||
|               </li> | ||||
|             {{/if}} | ||||
|           {{/if}} | ||||
|           <li class="action"> | ||||
|             <LinkTo | ||||
|               @route="vault.cluster.logout" | ||||
|               @model={{@activeClusterName}} | ||||
|               id="logout" | ||||
|               class="is-destroy" | ||||
|               {{on "click" @onLinkClick}} | ||||
|             > | ||||
|               Sign out | ||||
|             </LinkTo> | ||||
|           </li> | ||||
|         </ul> | ||||
|       </nav> | ||||
|     </div> | ||||
|   </div> | ||||
| </Confirm> | ||||
| @@ -1,9 +1,5 @@ | ||||
| <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")}} | ||||
|     @htmlTag="button" | ||||
|   > | ||||
|   <D.Trigger data-test-calendar-widget-trigger class={{concat "toolbar-link" (if D.isOpen " is-active")}} @htmlTag="button"> | ||||
|     {{date-format this.startDate "MMM yyyy"}} | ||||
|     - | ||||
|     {{date-format this.endDate "MMM yyyy"}} | ||||
|   | ||||
| @@ -1,150 +0,0 @@ | ||||
| <div class="popup-menu-content"> | ||||
|   <div class="box"> | ||||
|     {{#unless this.version.isOSS}} | ||||
|       {{#if (and this.activeCluster.unsealed this.auth.currentToken)}} | ||||
|         {{#if @cluster.dr.isSecondary}} | ||||
|           {{#if (has-permission "status" routeParams="replication")}} | ||||
|             <nav class="menu" aria-label="replication secondary menu"> | ||||
|               <p class="menu-label">Replication</p> | ||||
|               <ul> | ||||
|                 {{#if @cluster.anyReplicationEnabled}} | ||||
|                   <li> | ||||
|                     <LinkTo | ||||
|                       @route="vault.cluster.replication-dr-promote.details" | ||||
|                       @disabled={{not this.auth.currentToken}} | ||||
|                       {{on "click" @onLinkClick}} | ||||
|                     > | ||||
|                       <ReplicationModeSummary @mode="dr" @display="menu" @cluster={{@cluster}} /> | ||||
|                     </LinkTo> | ||||
|                   </li> | ||||
|                 {{/if}} | ||||
|               </ul> | ||||
|             </nav> | ||||
|             <hr /> | ||||
|           {{/if}} | ||||
|         {{else}} | ||||
|           {{#if (has-permission "status" routeParams="replication")}} | ||||
|             <nav class="menu" aria-label="replication menu"> | ||||
|               <p class="menu-label">Replication</p> | ||||
|               <ul> | ||||
|                 {{#if @cluster.anyReplicationEnabled}} | ||||
|                   <li> | ||||
|                     <LinkTo | ||||
|                       @route="vault.cluster.replication.mode.index" | ||||
|                       @model="dr" | ||||
|                       @disabled={{not this.auth.currentToken}} | ||||
|                       {{on "click" @onLinkClick}} | ||||
|                     > | ||||
|                       <ReplicationModeSummary @mode="dr" @display="menu" @cluster={{@cluster}} /> | ||||
|                     </LinkTo> | ||||
|                   </li> | ||||
|                   <li> | ||||
|                     {{#if (has-feature "Performance Replication")}} | ||||
|                       <LinkTo | ||||
|                         @route="vault.cluster.replication.mode.index" | ||||
|                         @model="performance" | ||||
|                         @disabled={{not this.auth.currentToken}} | ||||
|                         {{on "click" @onLinkClick}} | ||||
|                       > | ||||
|                         <ReplicationModeSummary @mode="performance" @display="menu" @cluster={{@cluster}} @tagName="span" /> | ||||
|                       </LinkTo> | ||||
|                     {{else}} | ||||
|                       <ReplicationModeSummary @mode="performance" @display="menu" @cluster={{@cluster}} @class="menu-item" /> | ||||
|                     {{/if}} | ||||
|                   </li> | ||||
|                 {{else}} | ||||
|                   <li> | ||||
|                     <LinkTo @route="vault.cluster.replication" {{on "click" @onLinkClick}}> | ||||
|                       <div class="level is-mobile"> | ||||
|                         <span class="level-left">Enable</span> | ||||
|                         <Icon @name="plus-circle" class="has-text-grey-light level-right" /> | ||||
|                       </div> | ||||
|                     </LinkTo> | ||||
|                   </li> | ||||
|                 {{/if}} | ||||
|               </ul> | ||||
|             </nav> | ||||
|             <hr /> | ||||
|           {{/if}} | ||||
|         {{/if}} | ||||
|       {{/if}} | ||||
|     {{/unless}} | ||||
|     <nav class="menu" aria-label="server menu"> | ||||
|       <div class="menu-label"> | ||||
|         Server | ||||
|       </div> | ||||
|       <ul class="menu-list"> | ||||
|         <li class="action"> | ||||
|           {{#if this.activeCluster.unsealed}} | ||||
|             {{#if (and (has-permission "status" routeParams="seal") (not @cluster.dr.isSecondary))}} | ||||
|               <LinkTo @route="vault.cluster.settings.seal" @model={{@cluster.name}} {{on "click" @onLinkClick}}> | ||||
|                 <div class="level is-mobile"> | ||||
|                   <span class="level-left">Unsealed</span> | ||||
|                   <Icon @name="check-circle" class="has-text-success level-right" /> | ||||
|                 </div> | ||||
|               </LinkTo> | ||||
|             {{else}} | ||||
|               <span class="menu-item"> | ||||
|                 <div class="level is-mobile"> | ||||
|                   <span class="level-left">Unsealed</span> | ||||
|                   <Icon @name="check-circle" class="has-text-success level-right" /> | ||||
|                 </div> | ||||
|               </span> | ||||
|             {{/if}} | ||||
|           {{else}} | ||||
|             <span class="menu-item"> | ||||
|               <div class="level is-mobile"> | ||||
|                 <span class="level-left has-text-danger">Sealed</span> | ||||
|                 <Icon @name="x-circle" class="has-text-danger level-right" /> | ||||
|               </div> | ||||
|             </span> | ||||
|           {{/if}} | ||||
|         </li> | ||||
|       </ul> | ||||
|       {{#if | ||||
|         (and | ||||
|           (or | ||||
|             (and this.version.features (has-permission "status" routeParams="license")) | ||||
|             (and @cluster.usingRaft (has-permission "status" routeParams="raft")) | ||||
|           ) | ||||
|           (not @cluster.dr.isSecondary) | ||||
|         ) | ||||
|       }} | ||||
|         <ul class="menu-list"> | ||||
|           {{#if (and this.version.features (has-permission "status" routeParams="license") (not @cluster.dr.isSecondary))}} | ||||
|             <li class="action"> | ||||
|               <LinkTo @route="vault.cluster.license" @model={{this.activeCluster.name}} {{on "click" @onLinkClick}}> | ||||
|                 <div class="level is-mobile"> | ||||
|                   <span class="level-left">License</span> | ||||
|                   <Chevron class="has-text-grey-light level-right" /> | ||||
|                 </div> | ||||
|               </LinkTo> | ||||
|             </li> | ||||
|           {{/if}} | ||||
|           {{#if (and @cluster.usingRaft (has-permission "status" routeParams="raft"))}} | ||||
|             <li class="action"> | ||||
|               <LinkTo @route="vault.cluster.storage" @model={{this.activeCluster.name}} {{on "click" @onLinkClick}}> | ||||
|                 <div class="level is-mobile"> | ||||
|                   <span class="level-left">Raft Storage</span> | ||||
|                   <Chevron class="has-text-grey-light level-right" /> | ||||
|                 </div> | ||||
|               </LinkTo> | ||||
|             </li> | ||||
|           {{/if}} | ||||
|         </ul> | ||||
|       {{/if}} | ||||
|       {{#if (and (has-permission "clients" routeParams="activity") (not @cluster.dr.isSecondary) this.auth.currentToken)}} | ||||
|         <ul class="menu-list"> | ||||
|           <li class="action"> | ||||
|             <LinkTo @route="vault.cluster.clients.dashboard" {{on "click" @onLinkClick}}> | ||||
|               <div class="level is-mobile"> | ||||
|                 <span class="level-left">Client count</span> | ||||
|                 <Chevron class="has-text-grey-light level-right" /> | ||||
|               </div> | ||||
|             </LinkTo> | ||||
|           </li> | ||||
|         </ul> | ||||
|       {{/if}} | ||||
|     </nav> | ||||
|   </div> | ||||
| </div> | ||||
| @@ -5,7 +5,7 @@ | ||||
|     <Chevron /> | ||||
|   {{/if}} | ||||
|   <input onkeyup={{action "handleKeyUp"}} value={{this.value}} autocomplete="off" spellcheck="false" /> | ||||
|   <ToolTip @horizontalPosition="auto-right" @verticalPosition={{if this.isFullscreen "above" "below"}} as |d|> | ||||
|   <ToolTip @horizontalPosition="auto-right" @verticalPosition="above" as |d|> | ||||
|     <d.Trigger> | ||||
|       <button | ||||
|         type="button" | ||||
|   | ||||
| @@ -1,2 +1,4 @@ | ||||
| {{! using Icon here instead of Chevron because two nested tagless components results in a rendered line break between the tags breaking the layout in the <pre> }} | ||||
| <p class="console-ui-command is-font-mono"><Icon @name="chevron-right" />{{@content}}</p> | ||||
| <div class="is-flex-center"> | ||||
|   <Icon @name="chevron-right" /> | ||||
|   <pre class="console-ui-command">{{@content}}</pre> | ||||
| </div> | ||||
| @@ -1,6 +1,8 @@ | ||||
| <button type="button" class="button is-ghost console-close-button" {{action "closeConsole"}}> | ||||
| <div class="console-close-button"> | ||||
|   <button type="button" class="button is-ghost" {{action "closeConsole"}} data-test-console-panel-close> | ||||
|     <Icon @name="x" aria-label="Close console" /> | ||||
|   </button> | ||||
| </div> | ||||
| <div class="console-ui-panel-content"> | ||||
|   <div class="content has-bottom-margin-l"> | ||||
|     <p class="has-text-grey is-font-mono"> | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| <span class={{@class}}> | ||||
|   {{#if (has-block)}} | ||||
|     {{yield}} | ||||
|   {{else}} | ||||
|     {{@text}} | ||||
|   {{/if}} | ||||
| </span> | ||||
| @@ -1,5 +1,5 @@ | ||||
| {{#if (and this.state this.version.isEnterprise)}} | ||||
|   <div class="navbar-status {{if (eq this.state 'connected') 'connected' 'warning'}}"> | ||||
|   <div class="link-status {{if (eq this.state 'connected') 'connected' 'warning'}}"> | ||||
|     <Icon @name="info" /> | ||||
|     <p data-test-link-status> | ||||
|       {{#if (eq this.state "connected")}} | ||||
|   | ||||
| @@ -1,21 +0,0 @@ | ||||
| <aside class="menu"> | ||||
|   {{#if this.title}} | ||||
|     <p class="menu-label"> | ||||
|       {{this.title}} | ||||
|     </p> | ||||
|   {{/if}} | ||||
|   <ul class="menu-list"> | ||||
|     {{yield}} | ||||
|   </ul> | ||||
|   <div class="menu-toggle"> | ||||
|     {{#if this.isActive}} | ||||
|       <button type="button" class="button is-ghost" {{action "closeMenu"}}> | ||||
|         <Icon @name="x" aria-label="Close menu" /> | ||||
|       </button> | ||||
|     {{else}} | ||||
|       <button type="button" class="button is-ghost has-text-grey-light" {{action "openMenu"}}> | ||||
|         <Icon @name="more-vertical" aria-label="Open menu" /> | ||||
|       </button> | ||||
|     {{/if}} | ||||
|   </div> | ||||
| </aside> | ||||
| @@ -1,37 +1,18 @@ | ||||
| {{#if (and (not this.accessibleNamespaces.length) this.inRootNamespace)}} | ||||
|   <div class="namespace-picker no-namespaces"> | ||||
|     {{! Just yield the logo if they're in the root namespace and only have access to it }} | ||||
|     {{yield}} | ||||
|   </div> | ||||
| {{else}} | ||||
|   <div class="namespace-picker"> | ||||
|     <BasicDropdown @horizontalPosition="auto-left" @verticalPosition="below" as |D|> | ||||
| <div class="namespace-picker" ...attributes> | ||||
|   <BasicDropdown @horizontalPosition="left" @verticalPosition="above" as |D|> | ||||
|     <D.Trigger | ||||
|       @htmlTag="button" | ||||
|       class="button is-transparent namespace-picker-trigger has-current-color" | ||||
|       data-test-namespace-toggle | ||||
|     > | ||||
|         {{yield}} | ||||
|         {{#if this.namespaceDisplay}} | ||||
|       <div class="is-flex-center"> | ||||
|         <Icon @name="org" /> | ||||
|         <span class="namespace-name">{{this.namespaceDisplay}}</span> | ||||
|         {{else}} | ||||
|           <span class="namespace-name is-hidden-tablet">/ (Root)</span> | ||||
|         {{/if}} | ||||
|         <Chevron @direction="down" @class="has-text-white auto-width is-status-chevron" /> | ||||
|       </div> | ||||
|       <Icon @name="caret" /> | ||||
|     </D.Trigger> | ||||
|     <D.Content @defaultClass="namespace-picker-content"> | ||||
|       <div class="is-mobile level-left"> | ||||
|           {{#unless this.isUserRootNamespace}} | ||||
|             <NamespaceLink | ||||
|               @targetNamespace={{or | ||||
|                 (object-at (dec 2 this.menuLeaves.length) this.lastMenuLeaves) | ||||
|                 this.auth.authData.userRootNamespace | ||||
|               }} | ||||
|               @class="namespace-link is-current button is-ghost icon" | ||||
|             > | ||||
|               <Chevron @direction="left" @class="has-text-grey" /> | ||||
|             </NamespaceLink> | ||||
|           {{/unless}} | ||||
|         <h5 class="list-header">Current namespace</h5> | ||||
|       </div> | ||||
|       <div class="namespace-header-bar level is-mobile"> | ||||
| @@ -41,9 +22,6 @@ | ||||
|               <span class="level-left" data-test-current-namespace> | ||||
|                 {{if this.namespacePath (concat this.namespacePath "/") "root"}} | ||||
|               </span> | ||||
|                 <span class="level-right"> | ||||
|                   <Icon @name="check-circle" class="has-text-success" /> | ||||
|                 </span> | ||||
|             </div> | ||||
|           </header> | ||||
|         </div> | ||||
| @@ -56,7 +34,7 @@ | ||||
|                 (object-at (dec 2 this.menuLeaves.length) this.lastMenuLeaves) | ||||
|                 this.auth.authData.userRootNamespace | ||||
|               }} | ||||
|                 @class="namespace-link is-current button is-ghost icon" | ||||
|               @class="namespace-link is-current button is-transparent icon" | ||||
|             > | ||||
|               <Chevron @direction="left" @class="has-text-grey" /> | ||||
|             </NamespaceLink> | ||||
| @@ -66,12 +44,13 @@ | ||||
|         {{#if (includes "" this.lastMenuLeaves)}} | ||||
|           {{! leaf is '' which is the root namespace, and then we need to iterate the root leaves }} | ||||
|           <div class="leaf-panel {{if (eq '' this.currentLeaf) 'leaf-panel-current' 'leaf-panel-left'}} "> | ||||
|               {{~#each this.rootLeaves as |rootLeaf|}} | ||||
|             {{#each this.rootLeaves as |rootLeaf|}} | ||||
|               <NamespaceLink @targetNamespace={{rootLeaf}} @class="namespace-link" @showLastSegment={{true}} /> | ||||
|               {{/each~}} | ||||
|             {{/each}} | ||||
|           </div> | ||||
|         {{/if}} | ||||
|         {{#each this.lastMenuLeaves as |leaf|}} | ||||
|           {{#if leaf}} | ||||
|             <div | ||||
|               class="leaf-panel | ||||
|                 {{if (eq leaf this.currentLeaf) 'leaf-panel-current' 'leaf-panel-left'}} | ||||
| @@ -79,32 +58,38 @@ | ||||
|                 {{if (and (not this.isAdding) (eq leaf this.changedLeaf)) 'leaf-panel-exiting'}} | ||||
|                 " | ||||
|             > | ||||
|               {{~#each-in (get this.namespaceTree leaf) as |leafName|}} | ||||
|               {{#each-in (get this.namespaceTree leaf) as |leafName|}} | ||||
|                 <NamespaceLink | ||||
|                   @targetNamespace={{concat leaf "/" leafName}} | ||||
|                   @class="namespace-link" | ||||
|                   @showLastSegment={{true}} | ||||
|                 /> | ||||
|               {{/each-in~}} | ||||
|               {{/each-in}} | ||||
|             </div> | ||||
|           {{/if}} | ||||
|         {{/each}} | ||||
|         {{#if this.canList}} | ||||
|           <div class="leaf-panel leaf-panel-current"> | ||||
|             <div class="level"> | ||||
|               <span class="level-left"> | ||||
|                 <LinkTo @route="vault.cluster.access.namespaces" class="is-block namespace-link namespace-manage-link"> | ||||
|                 <div class="level is-mobile"> | ||||
|                   <span class="level-left">Manage namespaces</span> | ||||
|                   Manage Namespaces | ||||
|                 </LinkTo> | ||||
|               </span> | ||||
|               <span class="level-right"> | ||||
|                     <button type="button" class="button is-ghost icon" onclick={{action "refreshNamespaceList"}}> | ||||
|                 <button | ||||
|                   type="button" | ||||
|                   class="button is-ghost icon has-right-margin-m" | ||||
|                   data-test-refresh-namespaces | ||||
|                   onclick={{action "refreshNamespaceList"}} | ||||
|                 > | ||||
|                   <Icon @name="reload" class="has-text-grey" /> | ||||
|                 </button> | ||||
|               </span> | ||||
|             </div> | ||||
|               </LinkTo> | ||||
|           </div> | ||||
|         {{/if}} | ||||
|       </div> | ||||
|     </D.Content> | ||||
|   </BasicDropdown> | ||||
| </div> | ||||
|   <div class="navbar-separator"></div> | ||||
| {{/if}} | ||||
| @@ -1,42 +0,0 @@ | ||||
| <nav class="navbar"> | ||||
|   <LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} /> | ||||
|  | ||||
|   <div class="navbar-actions"> | ||||
|     <div class="navbar-brand" data-test-navheader-home> | ||||
|       {{yield (hash home=(component "nav-header/home"))}} | ||||
|     </div> | ||||
|  | ||||
|     {{#unless this.navDrawerOpen}} | ||||
|       <button type="button" class="navbar-drawer-toggle is-hidden-tablet" {{action "toggleNavDrawer"}}> | ||||
|         <Icon @name="more-vertical" /> | ||||
|         Menu | ||||
|       </button> | ||||
|     {{/unless}} | ||||
|  | ||||
|     {{#unless this.hideLinks}} | ||||
|       <div class="navbar-drawer{{if this.navDrawerOpen ' is-active'}}"> | ||||
|         <div class="navbar-drawer-scroll"> | ||||
|           <div data-test-navheader-main> | ||||
|             {{yield (hash main=(component "nav-header/main") closeDrawer=(action "toggleNavDrawer" false))}} | ||||
|           </div> | ||||
|           <div class="navbar-end" data-test-navheader-items> | ||||
|             {{yield (hash items=(component "nav-header/items") closeDrawer=(action "toggleNavDrawer" false))}} | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         {{#if this.navDrawerOpen}} | ||||
|           <button class="navbar-drawer-toggle is-hidden-tablet" type="button" {{action "toggleNavDrawer" false}}> | ||||
|             <Icon @name="x" /> | ||||
|           </button> | ||||
|         {{/if}} | ||||
|       </div> | ||||
|     {{/unless}} | ||||
|  | ||||
|     <div | ||||
|       class="navbar-drawer-overlay{{if this.navDrawerOpen ' is-active'}}" | ||||
|       role="button" | ||||
|       onclick={{action "toggleNavDrawer" (not this.navDrawerOpen)}} | ||||
|     ></div> | ||||
|   </div> | ||||
| </nav> | ||||
| <Console::UiPanel @isFullscreen={{this.consoleFullscreen}} /> | ||||
| @@ -8,6 +8,9 @@ | ||||
|   </PageHeader> | ||||
|   <div class="box is-sideless has-background-white-bis has-text-grey has-text-centered"> | ||||
|     <p>Sorry, we were unable to find any content at <code>{{or this.model.path this.path}}</code>.</p> | ||||
|     <p>Double check the url or go back <HomeLink @text="home" />.</p> | ||||
|     <p> | ||||
|       Double check the url or | ||||
|       <ExternalLink @href="/" @sameTab={{true}}>go back home</ExternalLink>. | ||||
|     </p> | ||||
|   </div> | ||||
| </div> | ||||
| @@ -1,17 +1,3 @@ | ||||
| {{#if this.showTruncatedNavBar}} | ||||
|   <NavHeader as |Nav|> | ||||
|     <Nav.home> | ||||
|       <HomeLink @class="navbar-item splash-page-logo has-text-white"> | ||||
|         <LogoEdition /> | ||||
|       </HomeLink> | ||||
|     </Nav.home> | ||||
|     <Nav.items> | ||||
|       <div class="navbar-item status-indicator-button" data-status={{if this.activeCluster.unsealed "good" "bad"}}> | ||||
|         <StatusMenu @label="Status" @onLinkClick={{Nav.closeDrawer}} /> | ||||
|       </div> | ||||
|     </Nav.items> | ||||
|   </NavHeader> | ||||
| {{/if}} | ||||
| {{! bypass container styling }} | ||||
| {{#if @hasAltContent}} | ||||
|   {{yield (hash altContent=(component "splash-page/splash-content"))}} | ||||
|   | ||||
| @@ -1,23 +0,0 @@ | ||||
| <BasicDropdown @horizontalPosition="auto-left" @verticalPosition="below" @renderInPlace={{this.media.isMobile}} as |d|> | ||||
|   <d.Trigger | ||||
|     @htmlTag={{if (eq this.type "replication") "span" "button"}} | ||||
|     class={{if (eq this.type "replication") "" "button is-transparent"}} | ||||
|   > | ||||
|     <span class="status-indicator-color"> | ||||
|       <Icon @name={{this.glyphName}} aria-label={{@ariaLabel}} /> | ||||
|     </span> | ||||
|     <div class="status-menu-label"> | ||||
|       {{@label}} | ||||
|     </div> | ||||
|     <Chevron @direction="down" class="has-text-white is-status-chevron" /> | ||||
|   </d.Trigger> | ||||
|   <d.Content @defaultClass={{concat "status-menu-content status-menu-content-" this.type}}> | ||||
|     {{#if (eq this.type "user")}} | ||||
|       {{#if (and this.currentCluster.cluster.name this.auth.currentToken)}} | ||||
|         <AuthInfo @activeClusterName={{this.currentCluster.cluster.name}} @onLinkClick={{fn this.onLinkClick null}} /> | ||||
|       {{/if}} | ||||
|     {{else}} | ||||
|       <ClusterInfo @cluster={{this.currentCluster.cluster}} @onLinkClick={{fn this.onLinkClick d}} /> | ||||
|     {{/if}} | ||||
|   </d.Content> | ||||
| </BasicDropdown> | ||||
| @@ -7,9 +7,9 @@ | ||||
|     HashiCorp | ||||
|   </span> | ||||
|   <span> | ||||
|     <ExternalLink @href={{changelog-url-for this.activeCluster.leaderNode.version}} class="link has-text-grey"> | ||||
|     <ExternalLink @href={{changelog-url-for this.auth.activeCluster.leaderNode.version}} class="link has-text-grey"> | ||||
|       Vault | ||||
|       {{this.activeCluster.leaderNode.version}} | ||||
|       {{this.auth.activeCluster.leaderNode.version}} | ||||
|     </ExternalLink> | ||||
|   </span> | ||||
|   {{#if (is-version "OSS")}} | ||||
| @@ -34,4 +34,3 @@ | ||||
|     </div> | ||||
|   </div> | ||||
| {{/if}} | ||||
| <div id="modal-wormhole"></div> | ||||
| @@ -1,113 +1,5 @@ | ||||
| {{#if this.showNav}} | ||||
|   <NavHeader data-test-header-with-nav @class={{if this.consoleOpen "panel-open"}} as |Nav|> | ||||
|     <Nav.home> | ||||
|       <HomeLink @class="navbar-item has-text-white has-current-color-fill"> | ||||
|         <Icon @name="vault-logo" /> | ||||
|       </HomeLink> | ||||
|     </Nav.home> | ||||
|     <Nav.main> | ||||
|       <ul class="navbar-sections {{if (has-feature 'Namespaces') 'with-ns-picker'}}"> | ||||
|         {{#if (has-feature "Namespaces")}} | ||||
|           <li> | ||||
|             <NamespacePicker @class="navbar-item" @namespace={{this.namespaceQueryParam}} /> | ||||
|           </li> | ||||
|         {{/if}} | ||||
|         <li> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.secrets" | ||||
|             @current-when="vault.cluster.secrets vault.cluster.settings.mount-secret-backend vault.cluster.settings.configure-secret-backend" | ||||
|             class={{if (is-active-route "vault.cluster.secrets") "is-active"}} | ||||
|             {{on "click" Nav.closeDrawer}} | ||||
|             data-test-navbar-item="secrets" | ||||
|           > | ||||
|             Secrets | ||||
|           </LinkTo> | ||||
|         </li> | ||||
|         {{#if (has-permission "access")}} | ||||
|           <li> | ||||
|             <LinkTo | ||||
|               @route={{get (route-params-for "access") "route"}} | ||||
|               @models={{get (route-params-for "access") "models"}} | ||||
|               @current-when="vault.cluster.access vault.cluster.settings.auth" | ||||
|               class={{if (is-active-route "vault.cluster.access") "is-active"}} | ||||
|               {{on "click" Nav.closeDrawer}} | ||||
|               data-test-navbar-item="access" | ||||
|             > | ||||
|               Access | ||||
|             </LinkTo> | ||||
|           </li> | ||||
|         {{/if}} | ||||
|         {{#if (has-permission "policies")}} | ||||
|           <li> | ||||
|             <LinkTo | ||||
|               @route="vault.cluster.policies" | ||||
|               @models={{get (route-params-for "policies") "models"}} | ||||
|               @current-when="vault.cluster.policies vault.cluster.policy" | ||||
|               class={{if (is-active-route (array "vault.cluster.policies" "vault.cluster.policy")) "is-active"}} | ||||
|               {{on "click" Nav.closeDrawer}} | ||||
|               data-test-navbar-item="policies" | ||||
|             > | ||||
|               Policies | ||||
|             </LinkTo> | ||||
|           </li> | ||||
|         {{/if}} | ||||
|         {{#if (has-permission "tools")}} | ||||
|           <li> | ||||
|             <LinkTo | ||||
|               @route="vault.cluster.tools.tool" | ||||
|               @models={{get (route-params-for "tools") "models"}} | ||||
|               class={{if (is-active-route "vault.cluster.tools") "is-active"}} | ||||
|               {{on "click" Nav.closeDrawer}} | ||||
|             > | ||||
|               Tools | ||||
|             </LinkTo> | ||||
|           </li> | ||||
|         {{/if}} | ||||
|       </ul> | ||||
|     </Nav.main> | ||||
|     <Nav.items> | ||||
|       <div class="navbar-separator is-hidden-tablet"></div> | ||||
|       {{! template-lint-disable block-indentation }} | ||||
|       {{#if this.namespaceService.inRootNamespace}} | ||||
|         <div class="navbar-item status-indicator-button" data-status={{if this.activeCluster.unsealed "good" "bad"}}> | ||||
|           <StatusMenu @label="Status" @onLinkClick={{action Nav.closeDrawer}} /> | ||||
|         </div> | ||||
|         <div class="navbar-separator is-hidden-mobile"></div> | ||||
|       {{else if (and | ||||
|         (has-permission "clients" routeParams="activity") (not this.cluster.dr.isSecondary) this.auth.currentToken | ||||
|       )}} | ||||
|         <div class="navbar-sections"> | ||||
|           <div class={{if (is-active-route "vault.cluster.clients") "is-active"}}> | ||||
|             <LinkTo @route="vault.cluster.clients.dashboard" data-test-navbar-item="metrics"> | ||||
|               Client count | ||||
|             </LinkTo> | ||||
|           </div> | ||||
|         </div> | ||||
|       {{/if}} | ||||
|       {{! template-lint-enable block-indentation }} | ||||
|       <div class="navbar-item"> | ||||
|         <button | ||||
|           type="button" | ||||
|           class="button is-transparent nav-console-button{{if this.consoleOpen ' popup-open'}}" | ||||
|           {{action (queue (action "toggleConsole") (action Nav.closeDrawer))}} | ||||
|           data-test-console-toggle | ||||
|         > | ||||
|           <Icon @name="terminal-screen" /> | ||||
|           <div class="status-menu-label"> | ||||
|             Console | ||||
|           </div> | ||||
|           <Chevron @direction="down" class="has-text-white is-status-chevron" /> | ||||
|         </button> | ||||
|       </div> | ||||
|       <div | ||||
|         class="navbar-item nav-user-button {{if this.auth.allowExpiration 'may-expire'}}" | ||||
|         data-test-allow-expiration={{this.auth.allowExpiration}} | ||||
|       > | ||||
|         <StatusMenu @type="user" @label="User" @onLinkClick={{action Nav.closeDrawer}} /> | ||||
|       </div> | ||||
|     </Nav.items> | ||||
|   </NavHeader> | ||||
| {{/if}} | ||||
| <Sidebar::Nav::Cluster /> | ||||
|  | ||||
| <LicenseBanners | ||||
|   @expiry={{this.activeCluster.licenseExpiry}} | ||||
|   @autoloaded={{eq this.activeCluster.licenseState "autoloaded"}} | ||||
| @@ -128,10 +20,8 @@ | ||||
|     </FlashMessage> | ||||
|   {{/each}} | ||||
| </div> | ||||
| {{#if this.currentlyLoading}} | ||||
|   <LogoSplash /> | ||||
| {{else}} | ||||
|   {{#if this.showNav}} | ||||
|  | ||||
| {{#if this.auth.isActiveSession}} | ||||
|   <section class="section"> | ||||
|     <div class="container is-widescreen"> | ||||
|       <TokenExpireWarning @expirationDate={{this.auth.tokenExpirationDate}}> | ||||
| @@ -142,4 +32,3 @@ | ||||
| {{else}} | ||||
|   {{outlet}} | ||||
| {{/if}} | ||||
| {{/if}} | ||||
| @@ -1,75 +1,2 @@ | ||||
| <div class="columns"> | ||||
|   <MenuSidebar @title="Access" @class="is-3" @data-test-sidebar={{true}}> | ||||
|     {{#if (has-permission "access" routeParams="methods")}} | ||||
|       <li> | ||||
|         <LinkTo | ||||
|           @route="vault.cluster.access.methods" | ||||
|           data-test-link={{true}} | ||||
|           @current-when="vault.cluster.access.methods vault.cluster.access.method" | ||||
|         > | ||||
|           Auth Methods | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (has-permission "access" routeParams="mfa")}} | ||||
|       <li> | ||||
|         <LinkTo | ||||
|           @route="vault.cluster.access.mfa.methods" | ||||
|           @current-when="vault.cluster.access.mfa.methods vault.cluster.access.mfa.enforcements vault.cluster.access.mfa.index" | ||||
|           data-test-link="mfa" | ||||
|         > | ||||
|           Multi-factor authentication | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (has-permission "access" routeParams="entities")}} | ||||
|       <li> | ||||
|         <LinkTo @route="vault.cluster.access.identity" @model="entities" data-test-link={{true}}> | ||||
|           Entities | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (has-permission "access" routeParams="groups")}} | ||||
|       <li> | ||||
|         <LinkTo @route="vault.cluster.access.identity" @model="groups" data-test-link={{true}}> | ||||
|           Groups | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (has-permission "access" routeParams="leases")}} | ||||
|       <li> | ||||
|         <LinkTo @route="vault.cluster.access.leases" data-test-link={{true}}> | ||||
|           Leases | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (and (has-feature "Namespaces") (has-permission "access" routeParams="namespaces"))}} | ||||
|       <li> | ||||
|         <LinkTo @route="vault.cluster.access.namespaces" data-test-link={{true}}> | ||||
|           Namespaces | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (and (has-feature "Control Groups") (has-permission "access" routeParams="control-groups"))}} | ||||
|       <li> | ||||
|         <LinkTo | ||||
|           @route="vault.cluster.access.control-groups" | ||||
|           data-test-link={{true}} | ||||
|           @current-when="vault.cluster.access.control-groups vault.cluster.access.control-group-accessor vault.cluster.access.control-groups-configure" | ||||
|         > | ||||
|           Control Groups | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (has-permission "access" routeParams="oidc")}} | ||||
|       <li> | ||||
|         <LinkTo @route="vault.cluster.access.oidc" data-test-link="oidc"> | ||||
|           OIDC Provider | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|   </MenuSidebar> | ||||
|   <div class="column is-9"> | ||||
| <Sidebar::Nav::Access /> | ||||
| {{outlet}} | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										1
									
								
								ui/app/templates/vault/cluster/access/loading.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								ui/app/templates/vault/cluster/access/loading.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <LayoutLoading /> | ||||
| @@ -22,6 +22,11 @@ | ||||
|         <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}} /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="is-flex-row"> | ||||
|         {{#if this.mfaAuthData}} | ||||
|           <button type="button" class="icon-button" {{on "click" (fn (mut this.mfaAuthData) null)}}> | ||||
|   | ||||
| @@ -23,8 +23,5 @@ | ||||
|     </ul> | ||||
|   </nav> | ||||
| </div> | ||||
| {{#if this.currentlyLoading}} | ||||
|   <LayoutLoading /> | ||||
| {{else}} | ||||
|  | ||||
| {{outlet}} | ||||
| {{/if}} | ||||
							
								
								
									
										1
									
								
								ui/app/templates/vault/cluster/clients/loading.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								ui/app/templates/vault/cluster/clients/loading.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <LayoutLoading /> | ||||
| @@ -1,51 +1,2 @@ | ||||
| {{#if | ||||
|   (and | ||||
|     (has-feature "Sentinel") (or (has-permission "policies" routeParams="rgp") (has-permission "policies" routeParams="egp")) | ||||
|   ) | ||||
| }} | ||||
|   <div class="columns"> | ||||
|     <MenuSidebar @title="Policies" @class="is-3" @data-test-sidebar={{true}}> | ||||
|       {{#if (has-permission "policies" routeParams="acl")}} | ||||
|         <li> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.policies" | ||||
|             @model="acl" | ||||
|             data-test-link={{true}} | ||||
|             class={{if (is-active-route "vault.cluster.policies" "acl") "is-active"}} | ||||
|           > | ||||
|             ACL Policies | ||||
|           </LinkTo> | ||||
|         </li> | ||||
|       {{/if}} | ||||
|       {{#if (has-permission "policies" routeParams="rgp")}} | ||||
|         <li> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.policies" | ||||
|             @model="rgp" | ||||
|             data-test-link={{true}} | ||||
|             class={{if (is-active-route "vault.cluster.policies" "rgp") "is-active"}} | ||||
|           > | ||||
|             Role Governing Policies | ||||
|           </LinkTo> | ||||
|         </li> | ||||
|       {{/if}} | ||||
|       {{#if (has-permission "policies" routeParams="egp")}} | ||||
|         <li> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.policies" | ||||
|             @model="egp" | ||||
|             data-test-link={{true}} | ||||
|             class={{if (is-active-route "vault.cluster.policies" "egp") "is-active"}} | ||||
|           > | ||||
|             Endpoint Governing Policies | ||||
|           </LinkTo> | ||||
|         </li> | ||||
|       {{/if}} | ||||
|     </MenuSidebar> | ||||
|     <div class="column is-9"> | ||||
| <Sidebar::Nav::Policies /> | ||||
| {{outlet}} | ||||
|     </div> | ||||
|   </div> | ||||
| {{else}} | ||||
|   {{outlet}} | ||||
| {{/if}} | ||||
| @@ -1,45 +1,2 @@ | ||||
| <div class="columns"> | ||||
|   <MenuSidebar @title="Policies" @class="is-3" @data-test-sidebar={{true}}> | ||||
|     {{#if (has-permission "policies" routeParams="acl")}} | ||||
|       <li> | ||||
|         <LinkTo | ||||
|           @route="vault.cluster.policies" | ||||
|           @model="acl" | ||||
|           data-test-link={{true}} | ||||
|           class={{if (is-active-route "vault.cluster.policy" "acl") "is-active"}} | ||||
|         > | ||||
|           ACL Policies | ||||
|         </LinkTo> | ||||
|       </li> | ||||
|     {{/if}} | ||||
|     {{#if (has-feature "Sentinel")}} | ||||
|       {{#if (has-permission "policies" routeParams="rgp")}} | ||||
|         <li> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.policies" | ||||
|             @model="rgp" | ||||
|             data-test-link={{true}} | ||||
|             class={{if (is-active-route "vault.cluster.policy" "rgp") "is-active"}} | ||||
|           > | ||||
|             Role Governing Policies | ||||
|           </LinkTo> | ||||
|         </li> | ||||
|       {{/if}} | ||||
|       {{#if (has-permission "policies" routeParams="egp")}} | ||||
|         <li> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.policies" | ||||
|             @model="egp" | ||||
|             data-test-link={{true}} | ||||
|             class={{if (is-active-route "vault.cluster.policy" "egp") "is-active"}} | ||||
|           > | ||||
|             Endpoint Governing Policies | ||||
|           </LinkTo> | ||||
|         </li> | ||||
|       {{/if}} | ||||
|     {{/if}} | ||||
|   </MenuSidebar> | ||||
|   <div class="column is-9"> | ||||
| <Sidebar::Nav::Policies /> | ||||
| {{outlet}} | ||||
|   </div> | ||||
| </div> | ||||
| @@ -56,7 +56,7 @@ | ||||
|           {{#if this.model.secret}} | ||||
|             <LinkTo @route="vault.cluster.secrets.backend.list-root">Navigate back to the root</LinkTo>. | ||||
|           {{else}} | ||||
|             <HomeLink>Go back home</HomeLink>. | ||||
|             <LinkTo @route="vault.cluster">Go back home</LinkTo>. | ||||
|           {{/if}} | ||||
|         </p> | ||||
|       {{/if}} | ||||
|   | ||||
| @@ -1,21 +1,2 @@ | ||||
| <div class="columns"> | ||||
|   <MenuSidebar @title="Tools" @class="is-3"> | ||||
|     {{#each (tools-actions) as |supportedAction|}} | ||||
|       {{#if (has-permission "tools" routeParams=supportedAction)}} | ||||
|         <li> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.tools.tool" | ||||
|             @model={{supportedAction}} | ||||
|             class={{if (eq supportedAction this.selectedAction) "is-active"}} | ||||
|             data-test-tools-action-link={{supportedAction}} | ||||
|           > | ||||
|             {{capitalize supportedAction}} | ||||
|           </LinkTo> | ||||
|         </li> | ||||
|       {{/if}} | ||||
|     {{/each}} | ||||
|   </MenuSidebar> | ||||
|   <div class="column is-9"> | ||||
| <Sidebar::Nav::Tools /> | ||||
| <ToolActionsForm @selectedAction={{this.selectedAction}} /> | ||||
|   </div> | ||||
| </div> | ||||
| @@ -1,16 +1,4 @@ | ||||
| {{#if this.showLicenseError}} | ||||
|   <NavHeader as |Nav|> | ||||
|     <Nav.home> | ||||
|       <HomeLink @class="navbar-item splash-page-logo has-text-white"> | ||||
|         <LogoEdition /> | ||||
|       </HomeLink> | ||||
|     </Nav.home> | ||||
|     <Nav.items> | ||||
|       <div class="navbar-item status-indicator-button" data-status={{if this.activeCluster.unsealed "good" "bad"}}> | ||||
|         <StatusMenu @label="Status" @onLinkClick={{action Nav.closeDrawer}} /> | ||||
|       </div> | ||||
|     </Nav.items> | ||||
|   </NavHeader> | ||||
|   <div class="section is-flex-v-centered-tablet is-flex-1 is-fullwidth"> | ||||
|     <div class="columns is-centered is-gapless is-fullwidth"> | ||||
|       <EmptyState | ||||
|   | ||||
| @@ -1,34 +1,45 @@ | ||||
| <NavHeader data-test-header-without-nav as |Nav|> | ||||
|   <Nav.home> | ||||
|     <HomeLink @class="navbar-item splash-page-logo"> | ||||
|       <LogoEdition /> | ||||
|     </HomeLink> | ||||
|   </Nav.home> | ||||
| </NavHeader> | ||||
| <section class="section"> | ||||
|   <div class="container"> | ||||
|     {{#if (eq this.model.httpStatus 404)}} | ||||
|       <NotFound @model={{this.model}} /> | ||||
|     {{else}} | ||||
|       <PageHeader as |p|> | ||||
|         <p.levelLeft> | ||||
|           <h1 class="title is-3 has-text-grey"> | ||||
| <div class="is-flex-1 is-flex-v-centered"> | ||||
|   <div class="empty-state-content"> | ||||
|     <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-center"> | ||||
|       <div class="error-icon"> | ||||
|         <Icon @name="help" @size="24" class="has-text-grey" @stretched={{true}} /> | ||||
|       </div> | ||||
|       <div class="has-left-margin-s"> | ||||
|         <h1 class="is-size-4 has-text-semibold has-text-grey has-line-height-1"> | ||||
|           {{#if (eq this.model.httpStatus 403)}} | ||||
|             Not authorized | ||||
|           {{else if (eq this.model.httpStatus 404)}} | ||||
|             Page not found | ||||
|           {{else}} | ||||
|             Error | ||||
|           {{/if}} | ||||
|         </h1> | ||||
|         </p.levelLeft> | ||||
|       </PageHeader> | ||||
|       <BlockError> | ||||
|         {{#if this.model.message}} | ||||
|           <p>{{this.model.message}}</p> | ||||
|         {{/if}} | ||||
|         {{#each this.model.errors as |error|}} | ||||
|           <p>{{error}}</p> | ||||
|         {{/each}} | ||||
|       </BlockError> | ||||
|     {{/if}} | ||||
|         <p class="has-text-grey is-size-8">Error {{this.model.httpStatus}}</p> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <p class="has-text-grey has-top-margin-m has-bottom-padding-l has-border-bottom-light" data-test-error-description> | ||||
|       {{#if (eq this.model.httpStatus 404)}} | ||||
|         Sorry, we were unable to find any content at that URL. Double check it or go back home. | ||||
|       {{else}} | ||||
|         {{this.model.message}} | ||||
|         {{join ". " this.model.errors}} | ||||
|       {{/if}} | ||||
|     </p> | ||||
|  | ||||
|     <div class="is-flex-between has-top-margin-s"> | ||||
|       <ExternalLink @href="/" @sameTab={{true}} class="is-no-underline is-flex-center has-text-semibold"> | ||||
|         <Chevron @direction="left" /> | ||||
|         Go home | ||||
|       </ExternalLink> | ||||
|       <DocLink @path="/vault/api-docs#http-status-codes"> | ||||
|         Learn more | ||||
|       </DocLink> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| </section> | ||||
| @@ -1,14 +1 @@ | ||||
| <header> | ||||
|   <nav class="navbar is-grouped-split"> | ||||
|     <div class="navbar-brand"> | ||||
|       <HomeLink @class="navbar-item has-text-white has-current-color-fill"> | ||||
|         <Icon @name="vault-logo" /> | ||||
|       </HomeLink> | ||||
|     </div> | ||||
|   </nav> | ||||
| </header> | ||||
| <section class="section"> | ||||
|   <div class="container is-widescreen"> | ||||
| <NotFound @model={{this.model}} /> | ||||
|   </div> | ||||
| </section> | ||||
| @@ -8,7 +8,6 @@ | ||||
|  | ||||
| const EmberApp = require('ember-cli/lib/broccoli/ember-app'); | ||||
| const config = require('./config/environment')(); | ||||
| const nodeSass = require('node-sass'); | ||||
|  | ||||
| const environment = EmberApp.env(); | ||||
| const isProd = environment === 'production'; | ||||
| @@ -44,9 +43,18 @@ const appConfig = { | ||||
|     enabled: !isProd, | ||||
|   }, | ||||
|   sassOptions: { | ||||
|     implementation: nodeSass, | ||||
|     sourceMap: false, | ||||
|     onlyIncluded: true, | ||||
|     precision: 4, | ||||
|     includePaths: [ | ||||
|       './node_modules/@hashicorp/design-system-components/app/styles', | ||||
|       './node_modules/@hashicorp/design-system-tokens/dist/products/css', | ||||
|     ], | ||||
|   }, | ||||
|   minifyCSS: { | ||||
|     options: { | ||||
|       advanced: false, | ||||
|     }, | ||||
|   }, | ||||
|   autoprefixer: { | ||||
|     enabled: isTest || isProd, | ||||
|   | ||||
| @@ -1,18 +1,3 @@ | ||||
| {{! DR Secondary has a different Nav Header with access only to the Status menu }} | ||||
| {{#if this.isSecondary}} | ||||
|   <NavHeader as |Nav|> | ||||
|     <Nav.home> | ||||
|       <HomeLink @class="navbar-item splash-page-logo has-text-white"> | ||||
|         <LogoEdition /> | ||||
|       </HomeLink> | ||||
|     </Nav.home> | ||||
|     <Nav.items> | ||||
|       <div class="navbar-item status-indicator-button" data-status={{if this.data.unsealed "good" "bad"}}> | ||||
|         <StatusMenu @label="Status" @onLinkClick={{action Nav.closeDrawer}} /> | ||||
|       </div> | ||||
|     </Nav.items> | ||||
|   </NavHeader> | ||||
| {{/if}} | ||||
| <PageHeader as |p|> | ||||
|   <p.top> | ||||
|     {{! template-lint-configure simple-unless "warn" }} | ||||
| @@ -55,14 +40,10 @@ | ||||
|         </ul> | ||||
|       {{else}} | ||||
|         <ul> | ||||
|           <LinkTo @route="vault.cluster.replication-dr-promote.details" @activeClass="is-active"> | ||||
|           <LinkTo @route="vault.cluster.replication-dr-promote.details"> | ||||
|             Details | ||||
|           </LinkTo> | ||||
|           <LinkTo | ||||
|             @route="vault.cluster.replication-dr-promote" | ||||
|             @activeClass="is-active" | ||||
|             @current-when="vault.cluster.replication-dr-promote.index" | ||||
|           > | ||||
|           <LinkTo @route="vault.cluster.replication-dr-promote" @current-when="vault.cluster.replication-dr-promote.index"> | ||||
|             Manage | ||||
|           </LinkTo> | ||||
|         </ul> | ||||
|   | ||||
| @@ -98,7 +98,12 @@ | ||||
|     </div> | ||||
|     <div class="level-right"> | ||||
|       {{#if this.replicationDisabled}} | ||||
|         <LinkTo @route="mode.index" @models={{array this.cluster.name this.mode}} class="button is-primary"> | ||||
|         <LinkTo | ||||
|           @route="mode.index" | ||||
|           @models={{array this.cluster.name this.mode}} | ||||
|           class="button is-primary" | ||||
|           data-test-replication-promote-secondary | ||||
|         > | ||||
|           Enable | ||||
|         </LinkTo> | ||||
|       {{else}} | ||||
|   | ||||
| @@ -21,8 +21,7 @@ export default Route.extend(ClusterRoute, { | ||||
|   }, | ||||
|  | ||||
|   model() { | ||||
|     const activeClusterId = this.auth.activeCluster; | ||||
|     return this.store.peekRecord('cluster', activeClusterId); | ||||
|     return this.auth.activeCluster; | ||||
|   }, | ||||
|  | ||||
|   afterModel(model) { | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| <section class="section"> | ||||
|   <div class="container is-widescreen"> | ||||
|     {{#if this.model.replicationIsInitializing}} | ||||
|       <nav class="navbar"></nav> | ||||
|       <LayoutLoading /> | ||||
|     {{else}} | ||||
|       {{#if (eq this.model.mode "unsupported")}} | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| <section class="section"> | ||||
|   <div class="container is-widescreen"> | ||||
|     {{#if (and (eq this.model.drMode "secondary") (eq this.model.drModeInit "primary"))}} | ||||
|       <nav class="navbar" aria-label="loading nav"></nav> | ||||
|       <LayoutLoading /> | ||||
|     {{else}} | ||||
|       {{#if this.model.replicationAttrs.replicationEnabled}} | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
|  * SPDX-License-Identifier: MPL-2.0 | ||||
|  */ | ||||
|  | ||||
| import modifyPassthroughResponse from '../helpers/modify-passthrough-response'; | ||||
|  | ||||
| export const statuses = [ | ||||
|   'connected', | ||||
|   'disconnected since 2022-09-21T11:25:02.196835-07:00; error: unable to establish a connection with HCP', | ||||
| @@ -13,22 +15,6 @@ export const statuses = [ | ||||
| let index = null; | ||||
|  | ||||
| export default function (server) { | ||||
|   const handleResponse = (req, props) => { | ||||
|     const xhr = req.passthrough(); | ||||
|     xhr.onreadystatechange = () => { | ||||
|       if (xhr.readyState === 4 && xhr.status < 300) { | ||||
|         // XMLHttpRequest response prop only has a getter -- redefine as writable and set value | ||||
|         Object.defineProperty(xhr, 'response', { | ||||
|           writable: true, | ||||
|           value: JSON.stringify({ | ||||
|             ...JSON.parse(xhr.responseText), | ||||
|             ...props, | ||||
|           }), | ||||
|         }); | ||||
|       } | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
|   server.get('sys/seal-status', (schema, req) => { | ||||
|     // return next status from statuses array | ||||
|     if (index === null || index === statuses.length - 1) { | ||||
| @@ -36,8 +22,8 @@ export default function (server) { | ||||
|     } else { | ||||
|       index++; | ||||
|     } | ||||
|     return handleResponse(req, { hcp_link_status: statuses[index] }); | ||||
|     return modifyPassthroughResponse(req, { hcp_link_status: statuses[index] }); | ||||
|   }); | ||||
|   // enterprise only feature initially | ||||
|   server.get('sys/health', (schema, req) => handleResponse(req, { version: '1.12.0-dev1+ent' })); | ||||
|   server.get('sys/health', (schema, req) => modifyPassthroughResponse(req, { version: '1.12.0-dev1+ent' })); | ||||
| } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Jordan Reimer
					Jordan Reimer