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:
parent
00e43b88b4
commit
c84d267c61
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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -29,3 +29,12 @@ package-lock.json
|
|||
|
||||
# broccoli-debug
|
||||
/DEBUG/
|
||||
|
||||
# yarn
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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: '',
|
||||
});
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
margin-right: $spacing-xxs;
|
||||
}
|
||||
|
||||
.namespace-name {
|
||||
display: inline-block;
|
||||
flex: 1 1 auto;
|
||||
font-size: 1rem;
|
||||
margin: 0 $spacing-xs;
|
||||
|
||||
@include from($mobile) {
|
||||
margin-left: $size-10;
|
||||
}
|
||||
margin-left: $spacing-xs;
|
||||
}
|
||||
|
||||
.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;
|
||||
.sidebar-user-menu {
|
||||
align-self: center;
|
||||
|
||||
@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);
|
||||
.popup-menu-content {
|
||||
.menu-label {
|
||||
color: $black;
|
||||
font-size: 14px;
|
||||
font-weight: $font-weight-bold;
|
||||
text-transform: unset;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
color: $grey-light;
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: $size-8;
|
||||
line-height: 1;
|
||||
margin-bottom: $size-8;
|
||||
padding-left: $size-5;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
border-top: $base-border;
|
||||
padding: $size-9 0;
|
||||
|
||||
@include until($mobile) {
|
||||
padding-top: $size-4;
|
||||
}
|
||||
|
||||
li {
|
||||
a {
|
||||
&.active {
|
||||
border-right: 4px solid $blue;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $grey-dark;
|
||||
padding-left: $size-5;
|
||||
transition: 250ms border-width;
|
||||
|
||||
&.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;
|
||||
}
|
||||
.token-alert {
|
||||
padding: $spacing-xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.link-status {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: $size-7;
|
||||
font-weight: $font-weight-semibold;
|
||||
|
||||
&.connected {
|
||||
background-color: var(--token-color-surface-action);
|
||||
color: var(--token-color-foreground-action-active);
|
||||
|
||||
a {
|
||||
color: var(--token-color-foreground-action-active);
|
||||
}
|
||||
}
|
||||
&.warning {
|
||||
background-color: var(--token-color-surface-warning);
|
||||
color: var(--token-color-palette-amber-300);
|
||||
|
||||
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;
|
||||
|
||||
> button {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
// solves for tabs on secret engines
|
||||
> a &.active {
|
||||
border-bottom: 2px solid $blue;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
height: 2.25em;
|
||||
justify-content: flex-start;
|
||||
min-width: auto;
|
||||
padding-bottom: calc(0.375em -1px);
|
||||
padding-bottom: calc(0.375em - 1px);
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
padding-top: calc(0.375em -1px);
|
||||
padding-top: calc(0.375em - 1px);
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 @@
|
|||
<div class="page-container">
|
||||
{{outlet}}
|
||||
</div>
|
||||
<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"}}>
|
||||
<Icon @name="x" aria-label="Close console" />
|
||||
</button>
|
||||
<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,25 +1,32 @@
|
|||
{{#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|>
|
||||
<D.Trigger
|
||||
@htmlTag="button"
|
||||
class="button is-transparent namespace-picker-trigger has-current-color"
|
||||
data-test-namespace-toggle
|
||||
>
|
||||
{{yield}}
|
||||
{{#if this.namespaceDisplay}}
|
||||
<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" />
|
||||
</D.Trigger>
|
||||
<D.Content @defaultClass="namespace-picker-content">
|
||||
<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
|
||||
>
|
||||
<div class="is-flex-center">
|
||||
<Icon @name="org" />
|
||||
<span class="namespace-name">{{this.namespaceDisplay}}</span>
|
||||
</div>
|
||||
<Icon @name="caret" />
|
||||
</D.Trigger>
|
||||
<D.Content @defaultClass="namespace-picker-content">
|
||||
<div class="is-mobile level-left">
|
||||
<h5 class="list-header">Current namespace</h5>
|
||||
</div>
|
||||
<div class="namespace-header-bar level is-mobile">
|
||||
<div class="level-left">
|
||||
<header>
|
||||
<div class="level is-mobile namespace-link">
|
||||
<span class="level-left" data-test-current-namespace>
|
||||
{{if this.namespacePath (concat this.namespacePath "/") "root"}}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
<div class="namespace-list {{if this.isAnimating 'animated-list'}}">
|
||||
<div class="is-mobile level-left">
|
||||
{{#unless this.isUserRootNamespace}}
|
||||
<NamespaceLink
|
||||
|
@ -27,51 +34,23 @@
|
|||
(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>
|
||||
{{/unless}}
|
||||
<h5 class="list-header">Current namespace</h5>
|
||||
<h5 class="list-header">Namespaces</h5>
|
||||
</div>
|
||||
<div class="namespace-header-bar level is-mobile">
|
||||
<div class="level-left">
|
||||
<header>
|
||||
<div class="level is-mobile namespace-link">
|
||||
<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>
|
||||
{{#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|}}
|
||||
<NamespaceLink @targetNamespace={{rootLeaf}} @class="namespace-link" @showLastSegment={{true}} />
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="namespace-list {{if this.isAnimating 'animated-list'}}">
|
||||
<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">Namespaces</h5>
|
||||
</div>
|
||||
{{#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|}}
|
||||
<NamespaceLink @targetNamespace={{rootLeaf}} @class="namespace-link" @showLastSegment={{true}} />
|
||||
{{/each~}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#each this.lastMenuLeaves as |leaf|}}
|
||||
{{/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~}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{#if this.canList}}
|
||||
<div class="leaf-panel leaf-panel-current">
|
||||
<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>
|
||||
<span class="level-right">
|
||||
<button type="button" class="button is-ghost icon" onclick={{action "refreshNamespaceList"}}>
|
||||
<Icon @name="reload" class="has-text-grey" />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</LinkTo>
|
||||
{{/each-in}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</D.Content>
|
||||
</BasicDropdown>
|
||||
</div>
|
||||
<div class="navbar-separator"></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">
|
||||
Manage Namespaces
|
||||
</LinkTo>
|
||||
</span>
|
||||
<span class="level-right">
|
||||
<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>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</D.Content>
|
||||
</BasicDropdown>
|
||||
</div>
|
|
@ -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,18 +20,15 @@
|
|||
</FlashMessage>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if this.currentlyLoading}}
|
||||
<LogoSplash />
|
||||
|
||||
{{#if this.auth.isActiveSession}}
|
||||
<section class="section">
|
||||
<div class="container is-widescreen">
|
||||
<TokenExpireWarning @expirationDate={{this.auth.tokenExpirationDate}}>
|
||||
{{outlet}}
|
||||
</TokenExpireWarning>
|
||||
</div>
|
||||
</section>
|
||||
{{else}}
|
||||
{{#if this.showNav}}
|
||||
<section class="section">
|
||||
<div class="container is-widescreen">
|
||||
<TokenExpireWarning @expirationDate={{this.auth.tokenExpirationDate}}>
|
||||
{{outlet}}
|
||||
</TokenExpireWarning>
|
||||
</div>
|
||||
</section>
|
||||
{{else}}
|
||||
{{outlet}}
|
||||
{{/if}}
|
||||
{{outlet}}
|
||||
{{/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">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
<Sidebar::Nav::Access />
|
||||
{{outlet}}
|
|
@ -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}}
|
||||
|
||||
{{outlet}}
|
|
@ -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">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{outlet}}
|
||||
{{/if}}
|
||||
<Sidebar::Nav::Policies />
|
||||
{{outlet}}
|
|
@ -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">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
<Sidebar::Nav::Policies />
|
||||
{{outlet}}
|
|
@ -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">
|
||||
<ToolActionsForm @selectedAction={{this.selectedAction}} />
|
||||
</div>
|
||||
</div>
|
||||
<Sidebar::Nav::Tools />
|
||||
<ToolActionsForm @selectedAction={{this.selectedAction}} />
|
|
@ -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">
|
||||
{{#if (eq this.model.httpStatus 403)}}
|
||||
Not authorized
|
||||
{{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}}
|
||||
<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 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>
|
||||
</section>
|
||||
</div>
|
|
@ -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>
|
||||
<NotFound @model={{this.model}} />
|
|
@ -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}}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue