mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
Sidebar Navigation (#19296)
* Add Helios Design System Components (#19278) * adds hds dependency * updates reset import path * sets minifyCSS advanced option to false * Remove node-sass (#19376) * removes node-sass and fixes sass compilation * fixes active tab li class * Sidebar Navigation Components (#19446) * links ember-shared-components addon and imports styles * adds sidebar frame and nav components * updates HcNav component name to HcAppFrame and adds sidebar UserMenu component * adds tests for sidebar components * fixes tests * updates user menu styling * fixes typos in nav cluster component * changes padding value in sidebar stylesheet to use variable * Replace and remove old nav components with new ones (#19447) * links ember-shared-components addon and imports styles * adds sidebar frame and nav components * updates activeCluster on auth service and adds activeSession prop for sidebar visibility * replaces old nav components with new ones in templates * fixes sidebar visibility issue and updates user menu label class * removes NavHeader usage * adds clients index route to redirect to dashboard * removes unused HcAppFrame footer block and reduces page header top margin * Nav component cleanup (#19681) * removes nav-header components * removes navbar styling * removes status-menu component and styles * removes cluster and auth info components * removes menu-sidebar component and styling * fixes tests * Console Panel Updates (#19741) * updates console panel styling * adds test for opening and closing the console panel * updates console panel background color to use hds token * adds right margin to console panel input * updates link-status banner styling * updates hc nav components to new API * Namespace Picker Updates (#19753) * updates namespace-picker * updates namespace picker menu styling * adds bottom margin to env banner * updates class order on namespace picker link * restores manage namespaces refresh icon * removes manage namespaces nav icon * removes home link component (#20027) * Auth and Error View Updates (#19749) * adds vault logo to auth page * updates top level error template * updates loading substate handling and moves policies link from access to cluster nav (#20033) * moves console panel to bottom of viewport (#20183) * HDS Sidebar Nav Components (#20197) * updates nav components to hds * upgrades project yarn version to 3.5 * fixes issues in app frame component * updates sidenav actions to use icon button component * Sidebar navigation acceptance tests (#20270) * adds sidebar navigation acceptance tests and fixes other test failures * console panel styling tweaks * bumps addon version * remove and ignore yarn install-state file * fixes auth service and console tests * moves classes from deleted files after bulma merge * fixes sass syntax errors blocking build * cleans up dart sass deprecation warnings * adds changelog entry * hides namespace picker when sidebar nav panel is minimized * style tweaks * fixes sidebar nav tests * bumps hds addon to latest version and removes style override * updates modify-passthrough-response helper * updates sidebar nav tests * mfa-setup test fix attempt * fixes cluster mfa setup test * remove deprecated yarn ignore-optional flag from makefile * removes another instance of yarn ignore-optional and updates ui readme * removes unsupported yarn verbose flag from ci-helper * hides nav headings when user does not have access to any sub links * removes unused optional deps and moves lint-staged to dev deps * updates has-permission helper and permissions service tests * fixes issue with console panel not filling container width
This commit is contained in:
2
Makefile
2
Makefile
@@ -179,7 +179,7 @@ static-assets-dir:
|
||||
|
||||
install-ui-dependencies:
|
||||
@echo "--> Installing JavaScript assets"
|
||||
@cd ui && yarn --ignore-optional
|
||||
@cd ui && yarn
|
||||
|
||||
test-ember: install-ui-dependencies
|
||||
@echo "--> Running ember tests"
|
||||
|
||||
3
changelog/19296.txt
Normal file
3
changelog/19296.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:feature
|
||||
**Sidebar Navigation in UI**: A new sidebar navigation panel has been added in the UI to replace the top navigation bar.
|
||||
```
|
||||
@@ -132,9 +132,9 @@ function build_ui() {
|
||||
mkdir -p http/web_ui
|
||||
popd
|
||||
pushd "$repo_root/ui"
|
||||
yarn install --ignore-optional
|
||||
yarn install
|
||||
npm rebuild node-sass
|
||||
yarn --verbose run build
|
||||
yarn run build
|
||||
popd
|
||||
}
|
||||
|
||||
|
||||
9
ui/.gitignore
vendored
9
ui/.gitignore
vendored
@@ -29,3 +29,12 @@ package-lock.json
|
||||
|
||||
# broccoli-debug
|
||||
/DEBUG/
|
||||
|
||||
# yarn
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
147221
ui/.yarn/releases/yarn-1.19.1.js
vendored
147221
ui/.yarn/releases/yarn-1.19.1.js
vendored
File diff suppressed because one or more lines are too long
175350
ui/.yarn/releases/yarn-1.22.19.js
vendored
175350
ui/.yarn/releases/yarn-1.22.19.js
vendored
File diff suppressed because one or more lines are too long
873
ui/.yarn/releases/yarn-3.5.0.cjs
vendored
Executable file
873
ui/.yarn/releases/yarn-3.5.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
3
ui/.yarnrc.yml
Normal file
3
ui/.yarnrc.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.5.0.cjs
|
||||
@@ -39,12 +39,6 @@ You will need the following things properly installed on your computer.
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [Ember CLI](https://cli.emberjs.com/release/)
|
||||
* [Google Chrome](https://google.com/chrome/)
|
||||
- [lint-staged\*](https://www.npmjs.com/package/lint-staged)
|
||||
|
||||
\* lint-staged is an optional dependency - running `yarn` will install it.
|
||||
If don't want optional dependencies installed you can run `yarn --ignore-optional`. If you've ignored the optional deps
|
||||
previously and want to install them, you have to tell yarn to refetch all deps by
|
||||
running `yarn --force`.
|
||||
|
||||
In order to enforce the same version of `yarn` across installs, the `yarn` binary is included in the repo
|
||||
in the `.yarn/releases` folder. To update to a different version of `yarn`, use the `yarn policies set-version VERSION` command. For more information on this, see the [documentation](https://yarnpkg.com/en/docs/cli/policies).
|
||||
|
||||
@@ -42,7 +42,7 @@ export default ApplicationAdapter.extend({
|
||||
}
|
||||
} catch (error) {
|
||||
// no path means this was an error on listing
|
||||
if (!query.path) {
|
||||
if (!query.path || !mountModel) {
|
||||
throw error;
|
||||
}
|
||||
// control groups will throw a 403 permission denied error. If this happens return the mountModel
|
||||
|
||||
@@ -22,7 +22,7 @@ export default ApplicationAdapter.extend({
|
||||
// concerns and we only want to send "list" to the server
|
||||
query(store, type, query) {
|
||||
let { backend, id } = query;
|
||||
return this.ajax(this._url(backend, id), 'GET', { data: { list: true } }).then(resp => {
|
||||
return this.ajax(this._url(backend, id), 'GET', { data: { list: true } }).then((resp) => {
|
||||
resp.id = id;
|
||||
resp.backend = backend;
|
||||
return resp;
|
||||
@@ -36,7 +36,7 @@ export default ApplicationAdapter.extend({
|
||||
|
||||
queryRecord(store, type, query) {
|
||||
let { backend, id } = query;
|
||||
return this.ajax(this._url(backend, id), 'GET').then(resp => {
|
||||
return this.ajax(this._url(backend, id), 'GET').then((resp) => {
|
||||
resp.id = id;
|
||||
resp.backend = backend;
|
||||
return resp;
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
/**
|
||||
* @module ClusterInfo
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <ClusterInfo @cluster={{cluster}} @onLinkClick={{action}} />
|
||||
* ```
|
||||
*
|
||||
* @param {object} cluster - details of the current cluster, passed from the parent.
|
||||
* @param {Function} onLinkClick - parent action which determines the behavior on link click
|
||||
*/
|
||||
export default class ClusterInfoComponent extends Component {
|
||||
@service auth;
|
||||
@service store;
|
||||
@service version;
|
||||
|
||||
get activeCluster() {
|
||||
return this.store.peekRecord('cluster', this.auth.activeCluster);
|
||||
}
|
||||
|
||||
transitionToRoute() {
|
||||
this.router.transitionTo(...arguments);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
/**
|
||||
* @module HomeLink
|
||||
* `HomeLink` is a span that contains either the text `home` or the `LogoEdition` component.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <HomeLink @class="navbar-item splash-page-logo">
|
||||
* <LogoEdition />
|
||||
* </HomeLink>
|
||||
* ```
|
||||
* @param {string} class - Classes attached to the the component.
|
||||
* @param {string} text - Text displayed instead of logo.
|
||||
*
|
||||
* @see {@link https://github.com/hashicorp/vault/search?l=Handlebars&q=HomeLink|Uses of HomeLink}
|
||||
* @see {@link https://github.com/hashicorp/vault/blob/main/ui/app/components/home-link.js|HomeLink Source Code}
|
||||
*/
|
||||
|
||||
export default class HomeLink extends Component {
|
||||
get text() {
|
||||
return 'home';
|
||||
}
|
||||
|
||||
get computedClasses() {
|
||||
return this.classNames.join(' ');
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ['column', 'is-sidebar'],
|
||||
classNameBindings: ['isActive:is-active'],
|
||||
isActive: false,
|
||||
actions: {
|
||||
openMenu() {
|
||||
this.set('isActive', true);
|
||||
},
|
||||
closeMenu() {
|
||||
this.set('isActive', false);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -155,7 +155,9 @@ export default Component.extend({
|
||||
|
||||
namespaceDisplay: computed('namespacePath', 'accessibleNamespaces', 'accessibleNamespaces.[]', function () {
|
||||
const namespace = this.namespacePath;
|
||||
if (!namespace) return '';
|
||||
if (!namespace) {
|
||||
return 'root';
|
||||
}
|
||||
const parts = namespace?.split('/');
|
||||
return parts[parts.length - 1];
|
||||
}),
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
router: service(),
|
||||
currentCluster: service(),
|
||||
'data-test-navheader': true,
|
||||
attributeBindings: ['data-test-navheader'],
|
||||
classNameBindings: 'consoleFullscreen:panel-fullscreen',
|
||||
tagName: 'header',
|
||||
navDrawerOpen: false,
|
||||
consoleFullscreen: false,
|
||||
hideLinks: computed('router.currentRouteName', function () {
|
||||
const currentRoute = this.router.currentRouteName;
|
||||
if ('vault.cluster.oidc-provider' === currentRoute) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
actions: {
|
||||
toggleNavDrawer(isOpen) {
|
||||
if (isOpen !== undefined) {
|
||||
return this.set('navDrawerOpen', isOpen);
|
||||
}
|
||||
this.toggleProperty('navDrawerOpen');
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
||||
51
ui/app/components/sidebar/frame.hbs
Normal file
51
ui/app/components/sidebar/frame.hbs
Normal file
@@ -0,0 +1,51 @@
|
||||
<Hds::AppFrame @hasSidebar={{@showSidebar}} @hasHeader={{false}} @hasFooter={{false}} as |Frame|>
|
||||
<Frame.Sidebar data-test-sidebar-nav>
|
||||
<Hds::SideNav @isResponsive={{true}} @hasA11yRefocus={{true}} @a11yRefocusSkipTo="app-main-content">
|
||||
<:header>
|
||||
<Hds::SideNav::Header>
|
||||
<:logo>
|
||||
<Hds::SideNav::Header::HomeLink
|
||||
@icon="vault"
|
||||
@route="vault.cluster"
|
||||
@model={{this.currentCluster.cluster.name}}
|
||||
@ariaLabel="home link"
|
||||
data-test-sidebar-logo
|
||||
/>
|
||||
</:logo>
|
||||
<:actions>
|
||||
<Hds::SideNav::Header::IconButton
|
||||
@icon="terminal-screen"
|
||||
@ariaLabel="Console toggle"
|
||||
data-test-console-toggle
|
||||
{{on "click" (fn (mut this.console.isOpen) (not this.console.isOpen))}}
|
||||
/>
|
||||
<Sidebar::UserMenu />
|
||||
</:actions>
|
||||
</Hds::SideNav::Header>
|
||||
</:header>
|
||||
|
||||
{{! this block is where the Hds::SideNav::Portal components render into }}
|
||||
<:body>
|
||||
<Hds::SideNav::Portal::Target aria-label="sidebar navigation links" />
|
||||
</:body>
|
||||
|
||||
<:footer>
|
||||
{{#if (has-feature "Namespaces")}}
|
||||
<NamespacePicker
|
||||
@namespace={{this.clusterController.namespaceQueryParam}}
|
||||
class="hds-side-nav-hide-when-minimized"
|
||||
/>
|
||||
{{/if}}
|
||||
</:footer>
|
||||
</Hds::SideNav>
|
||||
</Frame.Sidebar>
|
||||
<Frame.Main id="app-main-content">
|
||||
{{! outlet for app content }}
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} />
|
||||
{{yield}}
|
||||
<div data-test-console-panel class={{if this.console.isOpen "panel-open"}}>
|
||||
<Console::UiPanel @isFullscreen={{this.consoleFullscreen}} />
|
||||
</div>
|
||||
</Frame.Main>
|
||||
</Hds::AppFrame>
|
||||
9
ui/app/components/sidebar/frame.js
Normal file
9
ui/app/components/sidebar/frame.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class SidebarNavComponent extends Component {
|
||||
@service currentCluster;
|
||||
@service console;
|
||||
@controller('vault.cluster') clusterController;
|
||||
}
|
||||
65
ui/app/components/sidebar/nav/access.hbs
Normal file
65
ui/app/components/sidebar/nav/access.hbs
Normal file
@@ -0,0 +1,65 @@
|
||||
<Hds::SideNav::Portal @ariaLabel="Access Navigation Links" data-test-sidebar-nav-panel="Access" as |Nav|>
|
||||
<Nav.BackLink
|
||||
@route="vault.cluster"
|
||||
@current-when={{false}}
|
||||
@icon="arrow-left"
|
||||
@text="Back to main navigation"
|
||||
data-test-sidebar-nav-link="Back to main navigation"
|
||||
/>
|
||||
|
||||
{{#if (has-permission "access" routeParams=(array "methods" "mfa" "oidc"))}}
|
||||
<Nav.Title data-test-sidebar-nav-heading="Authentication">Authentication</Nav.Title>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="methods")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.access.methods"
|
||||
@current-when="vault.cluster.access.methods vault.cluster.access.method"
|
||||
@text="Authentication methods"
|
||||
data-test-sidebar-nav-link="Authentication methods"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="mfa")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.access.mfa.methods"
|
||||
@current-when="vault.cluster.access.mfa.methods vault.cluster.access.mfa.enforcements vault.cluster.access.mfa.index"
|
||||
@text="Multi-factor authentication"
|
||||
data-test-sidebar-nav-link="Multi-factor authentication"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="oidc")}}
|
||||
<Nav.Link @route="vault.cluster.access.oidc" @text="OIDC" data-test-sidebar-nav-link="OIDC" />
|
||||
{{/if}}
|
||||
|
||||
{{#if (and (has-feature "Control Groups") (has-permission "access" routeParams="control-groups"))}}
|
||||
<Nav.Title data-test-sidebar-nav-heading="Access Control">Access Control</Nav.Title>
|
||||
<Nav.Link
|
||||
@route="vault.cluster.access.control-groups"
|
||||
@current-when="vault.cluster.access.control-groups vault.cluster.access.control-group-accessor vault.cluster.access.control-groups-configure"
|
||||
@text="Control Groups"
|
||||
data-test-sidebar-nav-link="Control Groups"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (has-permission "access" routeParams=(array "namespaces" "groups" "entities"))}}
|
||||
<Nav.Title data-test-sidebar-nav-heading="Organization">Organization</Nav.Title>
|
||||
{{/if}}
|
||||
{{#if (and (has-feature "Namespaces") (has-permission "access" routeParams="namespaces"))}}
|
||||
<Nav.Link @route="vault.cluster.access.namespaces" @text="Namespaces" data-test-sidebar-nav-link="Namespaces" />
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="groups")}}
|
||||
<Nav.Link @route="vault.cluster.access.identity" @model="groups" @text="Groups" data-test-sidebar-nav-link="Groups" />
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="entities")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.access.identity"
|
||||
@model="entities"
|
||||
@text="Entities"
|
||||
data-test-sidebar-nav-link="Entities"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (has-permission "access" routeParams="leases")}}
|
||||
<Nav.Title data-test-sidebar-nav-heading="Administration">Administration</Nav.Title>
|
||||
<Nav.Link @route="vault.cluster.access.leases" @text="Leases" data-test-sidebar-nav-link="Leases" />
|
||||
{{/if}}
|
||||
</Hds::SideNav::Portal>
|
||||
98
ui/app/components/sidebar/nav/cluster.hbs
Normal file
98
ui/app/components/sidebar/nav/cluster.hbs
Normal file
@@ -0,0 +1,98 @@
|
||||
<Hds::SideNav::Portal @ariaLabel="Cluster Navigation Links" data-test-sidebar-nav-panel="Cluster" as |Nav|>
|
||||
<Nav.Title data-test-sidebar-nav-heading="Vault">Vault</Nav.Title>
|
||||
|
||||
<Nav.Link
|
||||
@route="vault.cluster.secrets"
|
||||
@current-when="vault.cluster.secrets vault.cluster.settings.mount-secret-backend vault.cluster.settings.configure-secret-backend"
|
||||
@text="Secrets engines"
|
||||
data-test-sidebar-nav-link="Secrets engines"
|
||||
/>
|
||||
{{#if (has-permission "access")}}
|
||||
<Nav.Link
|
||||
@route={{get (route-params-for "access") "route"}}
|
||||
@models={{get (route-params-for "access") "models"}}
|
||||
@current-when="vault.cluster.access vault.cluster.settings.auth"
|
||||
@text="Access"
|
||||
@hasSubItems={{true}}
|
||||
data-test-sidebar-nav-link="Access"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (has-permission "policies")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.policies"
|
||||
@models={{get (route-params-for "policies") "models"}}
|
||||
@text="Policies"
|
||||
@hasSubItems={{true}}
|
||||
data-test-sidebar-nav-link="Policies"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (has-permission "tools")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.tools.tool"
|
||||
@models={{get (route-params-for "tools") "models"}}
|
||||
@text="Tools"
|
||||
@hasSubItems={{true}}
|
||||
data-test-sidebar-nav-link="Tools"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if
|
||||
(and this.version.isEnterprise this.cluster.anyReplicationEnabled (has-permission "status" routeParams="replication"))
|
||||
}}
|
||||
<Nav.Title data-test-sidebar-nav-heading="Replication">Replication</Nav.Title>
|
||||
<Nav.Link
|
||||
@route="vault.cluster.replication.mode.index"
|
||||
@model="dr"
|
||||
@text="DR Primary"
|
||||
data-test-sidebar-nav-link="DR Primary"
|
||||
/>
|
||||
|
||||
{{#if (has-feature "Performance Replication")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.replication.mode.index"
|
||||
@model="performance"
|
||||
@text="Performance Secondary"
|
||||
data-test-sidebar-nav-link="Performance Secondary"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if
|
||||
(or
|
||||
(has-permission "status" routeParams=(array "replication" "raft" "license" "seal"))
|
||||
(has-permission "clients" routeParams="activity")
|
||||
)
|
||||
}}
|
||||
<Nav.Title data-test-sidebar-nav-heading="Monitoring">Monitoring</Nav.Title>
|
||||
{{/if}}
|
||||
{{#if (and this.version.isEnterprise (has-permission "status" routeParams="replication"))}}
|
||||
<Nav.Link @route="vault.cluster.replication.index" @text="Replication" data-test-sidebar-nav-link="Replication" />
|
||||
{{/if}}
|
||||
{{#if (and this.cluster.usingRaft (has-permission "status" routeParams="raft"))}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.storage"
|
||||
@model={{this.cluster.name}}
|
||||
@text="Raft Storage"
|
||||
data-test-sidebar-nav-link="Raft Storage"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and (has-permission "clients" routeParams="activity") (not this.cluster.dr.isSecondary))}}
|
||||
<Nav.Link @route="vault.cluster.clients" @text="Client count" data-test-sidebar-nav-link="Client count" />
|
||||
{{/if}}
|
||||
{{#if (and this.version.features (has-permission "status" routeParams="license") (not this.cluster.dr.isSecondary))}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.license"
|
||||
@model={{this.cluster.name}}
|
||||
@text="License"
|
||||
data-test-sidebar-nav-link="License"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and (has-permission "status" routeParams="seal") (not this.cluster.dr.isSecondary))}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.settings.seal"
|
||||
@model={{this.cluster.name}}
|
||||
@text="Seal Vault"
|
||||
data-test-sidebar-nav-link="Seal Vault"
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::SideNav::Portal>
|
||||
12
ui/app/components/sidebar/nav/cluster.js
Normal file
12
ui/app/components/sidebar/nav/cluster.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class SidebarNavClusterComponent extends Component {
|
||||
@service currentCluster;
|
||||
@service version;
|
||||
@service auth;
|
||||
|
||||
get cluster() {
|
||||
return this.currentCluster.cluster;
|
||||
}
|
||||
}
|
||||
39
ui/app/components/sidebar/nav/policies.hbs
Normal file
39
ui/app/components/sidebar/nav/policies.hbs
Normal file
@@ -0,0 +1,39 @@
|
||||
<Hds::SideNav::Portal @ariaLabel="Policies Navigation Links" data-test-sidebar-nav-panel="Policies" as |Nav|>
|
||||
<Nav.BackLink
|
||||
@route="vault.cluster"
|
||||
@current-when={{false}}
|
||||
@icon="arrow-left"
|
||||
@text="Back to main navigation"
|
||||
data-test-sidebar-nav-link="Back to main navigation"
|
||||
/>
|
||||
|
||||
<Nav.Title data-test-sidebar-nav-heading="Policies">Policies</Nav.Title>
|
||||
|
||||
{{#if (has-permission "policies" routeParams="acl")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.policies"
|
||||
@model="acl"
|
||||
@current-when="vault.cluster.policies vault.cluster.policy"
|
||||
@text="ACL Policies"
|
||||
data-test-sidebar-nav-link="ACL Policies"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="rgp"))}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.policies"
|
||||
@model="rgp"
|
||||
@current-when="vault.cluster.policies vault.cluster.policy"
|
||||
@text="Role-Governing Policies"
|
||||
data-test-sidebar-nav-link="Role-Governing Policies"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="egp"))}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.policies"
|
||||
@model="egp"
|
||||
@current-when="vault.cluster.policies vault.cluster.policy"
|
||||
@text="Endpoint Governing Policies"
|
||||
data-test-sidebar-nav-link="Endpoint Governing Policies"
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::SideNav::Portal>
|
||||
22
ui/app/components/sidebar/nav/tools.hbs
Normal file
22
ui/app/components/sidebar/nav/tools.hbs
Normal file
@@ -0,0 +1,22 @@
|
||||
<Hds::SideNav::Portal @ariaLabel="Tools Navigation Links" data-test-sidebar-nav-panel="Tools" as |Nav|>
|
||||
<Nav.BackLink
|
||||
@route="vault.cluster"
|
||||
@current-when={{false}}
|
||||
@icon="arrow-left"
|
||||
@text="Back to main navigation"
|
||||
data-test-sidebar-nav-link="Back to main navigation"
|
||||
/>
|
||||
|
||||
<Nav.Title data-test-sidebar-nav-heading="Tools">Tools</Nav.Title>
|
||||
|
||||
{{#each (tools-actions) as |supportedAction|}}
|
||||
{{#if (has-permission "tools" routeParams=supportedAction)}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.tools.tool"
|
||||
@model={{supportedAction}}
|
||||
@text={{capitalize supportedAction}}
|
||||
data-test-sidebar-nav-link={{capitalize supportedAction}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</Hds::SideNav::Portal>
|
||||
83
ui/app/components/sidebar/user-menu.hbs
Normal file
83
ui/app/components/sidebar/user-menu.hbs
Normal file
@@ -0,0 +1,83 @@
|
||||
<BasicDropdown
|
||||
@horizontalPosition="right"
|
||||
@verticalPosition="below"
|
||||
@renderInPlace={{true}}
|
||||
class="sidebar-user-menu"
|
||||
data-test-user-menu
|
||||
as |Dropdown|
|
||||
>
|
||||
<Dropdown.Trigger data-test-user-menu-trigger>
|
||||
<Hds::SideNav::Header::IconButton @icon="user" @ariaLabel="User menu" />
|
||||
</Dropdown.Trigger>
|
||||
<Dropdown.Content>
|
||||
<Confirm as |c|>
|
||||
<div class="popup-menu-content" data-test-user-menu-content>
|
||||
<div class="box">
|
||||
<div class="menu-label">
|
||||
{{capitalize this.auth.authData.displayName}}
|
||||
</div>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if this.auth.allowExpiration}}
|
||||
<li class="token-alert is-flex" data-test-user-menu-item="token alert">
|
||||
<span><Icon @name="alert-triangle-fill" class="has-text-highlight" /></span>
|
||||
<span class="is-size-8 has-text-semibold">
|
||||
We've stopped auto-renewing your token due to inactivity. It will expire on
|
||||
{{date-format this.auth.tokenExpirationDate "MMMM do yyyy, h:mm:ss a"}}.
|
||||
</span>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if this.hasEntityId}}
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.mfa-setup" data-test-user-menu-item="mfa">
|
||||
Multi-factor authentication
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li class="action">
|
||||
<CopyButton
|
||||
@clipboardText={{this.auth.currentToken}}
|
||||
class="link"
|
||||
@buttonType="button"
|
||||
@success={{action (set-flash-message "Token copied!")}}
|
||||
>
|
||||
Copy token
|
||||
</CopyButton>
|
||||
</li>
|
||||
{{#if (is-before (now interval=1000) this.auth.tokenExpirationDate)}}
|
||||
{{#if this.auth.authData.renewable}}
|
||||
<li class="action">
|
||||
<button
|
||||
type="button"
|
||||
{{on "click" this.renewToken}}
|
||||
class="link button {{if this.isRenewing 'is-loading'}}"
|
||||
data-test-user-menu-item="renew token"
|
||||
>
|
||||
Renew token
|
||||
</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li class="action">
|
||||
<c.Message
|
||||
@id={{get this.auth "authData.displayName"}}
|
||||
@title={{concat "Revoke " (get this.auth "authData.displayName") "?"}}
|
||||
@onConfirm={{action "revokeToken"}}
|
||||
@message="You will not be able to log in again with this token."
|
||||
@triggerText="Revoke token"
|
||||
@confirmButtonText="Revoke"
|
||||
data-test-user-menu-item="revoke token"
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.logout" @model={{this.currentCluster.cluster.name}} id="logout">
|
||||
Log out
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</Confirm>
|
||||
</Dropdown.Content>
|
||||
</BasicDropdown>
|
||||
@@ -1,27 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { later } from '@ember/runloop';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
* @module AuthInfo
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <AuthInfo @activeClusterName={{cluster.name}} @onLinkClick={{action "onLinkClick"}} />
|
||||
* ```
|
||||
*
|
||||
* @param {string} activeClusterName - name of the current cluster, passed from the parent.
|
||||
* @param {Function} onLinkClick - parent action which determines the behavior on link click
|
||||
*/
|
||||
export default class AuthInfoComponent extends Component {
|
||||
export default class SidebarUserMenuComponent extends Component {
|
||||
@service auth;
|
||||
@service currentCluster;
|
||||
@service router;
|
||||
|
||||
@tracked fakeRenew = false;
|
||||
@@ -29,7 +14,7 @@ export default class AuthInfoComponent extends Component {
|
||||
get hasEntityId() {
|
||||
// root users will not have an entity_id because they are not associated with an entity.
|
||||
// in order to use the MFA end user setup they need an entity_id
|
||||
return !!this.auth.authData.entity_id;
|
||||
return !!this.auth.authData?.entity_id;
|
||||
}
|
||||
|
||||
get isRenewing() {
|
||||
@@ -31,8 +31,4 @@ export default class SplashPage extends Component {
|
||||
// default is true unless showTruncatedNavBar is defined as false
|
||||
return this.args.showTruncatedNavBar === false ? false : true;
|
||||
}
|
||||
|
||||
get activeCluster() {
|
||||
return this.store.peekRecord('cluster', this.auth.activeCluster);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { next } from '@ember/runloop';
|
||||
|
||||
/**
|
||||
* @module StatusMenu
|
||||
* StatusMenu component is the drop down menu on the main navigation.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <StatusMenu @label='user' @onLinkClick={{action Nav.closeDrawer}}/>
|
||||
* ```
|
||||
* @param {string} [ariaLabel] - aria label for the status icon.
|
||||
* @param {string} [label] - label for the status menu.
|
||||
* @param {string} [type] - determines where the component is being used. e.g. replication, auth, etc.
|
||||
* @param {function} [onLinkClick] - function to handle click on the nested links under content.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class StatusMenu extends Component {
|
||||
@service currentCluster;
|
||||
@service auth;
|
||||
@service media;
|
||||
@service router;
|
||||
|
||||
get type() {
|
||||
return this.args.type || 'cluster';
|
||||
}
|
||||
|
||||
get glyphName() {
|
||||
return this.type === 'user' ? 'user' : 'circle-dot';
|
||||
}
|
||||
|
||||
@action
|
||||
onLinkClick(dropdown) {
|
||||
if (dropdown) {
|
||||
// strange issue where closing dropdown triggers full transition which redirects to auth screen in production builds
|
||||
// closing dropdown in next tick of run loop fixes it
|
||||
next(() => dropdown.actions.close());
|
||||
}
|
||||
this.args.onLinkClick();
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,10 @@
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from '../config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
env: config.environment,
|
||||
auth: service(),
|
||||
store: service(),
|
||||
activeCluster: computed('auth.activeCluster', function () {
|
||||
const id = this.auth.activeCluster;
|
||||
return id ? this.store.peekRecord('cluster', id) : null;
|
||||
}),
|
||||
activeClusterName: computed('activeCluster', function () {
|
||||
const activeCluster = this.activeCluster;
|
||||
return activeCluster ? activeCluster.get('name') : null;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from '../config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
@@ -20,12 +19,4 @@ export default Controller.extend({
|
||||
env: config.environment,
|
||||
auth: service(),
|
||||
store: service(),
|
||||
activeCluster: computed('auth.activeCluster', function () {
|
||||
const id = this.auth.activeCluster;
|
||||
return id ? this.store.peekRecord('cluster', id) : null;
|
||||
}),
|
||||
activeClusterName: computed('activeCluster', function () {
|
||||
const activeCluster = this.activeCluster;
|
||||
return activeCluster ? activeCluster.get('name') : null;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import Controller from '@ember/controller';
|
||||
import { observer, computed } from '@ember/object';
|
||||
import { observer } from '@ember/object';
|
||||
export default Controller.extend({
|
||||
auth: service(),
|
||||
store: service(),
|
||||
@@ -36,35 +36,7 @@ export default Controller.extend({
|
||||
}),
|
||||
|
||||
consoleOpen: alias('console.isOpen'),
|
||||
|
||||
activeCluster: computed('auth.activeCluster', function () {
|
||||
return this.store.peekRecord('cluster', this.auth.activeCluster);
|
||||
}),
|
||||
|
||||
activeClusterName: computed('activeCluster', function () {
|
||||
const activeCluster = this.activeCluster;
|
||||
return activeCluster ? activeCluster.get('name') : null;
|
||||
}),
|
||||
|
||||
showNav: computed(
|
||||
'router.currentRouteName',
|
||||
'activeClusterName',
|
||||
'auth.currentToken',
|
||||
'activeCluster.{dr.isSecondary,needsInit,sealed}',
|
||||
function () {
|
||||
if (this.activeCluster.dr?.isSecondary || this.activeCluster.needsInit || this.activeCluster.sealed) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.activeClusterName &&
|
||||
this.auth.currentToken &&
|
||||
this.router.currentRouteName !== 'vault.cluster.auth'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
),
|
||||
activeCluster: alias('auth.activeCluster'),
|
||||
|
||||
actions: {
|
||||
toggleConsole() {
|
||||
|
||||
@@ -20,9 +20,9 @@ export default Helper.extend({
|
||||
),
|
||||
|
||||
compute([route], params) {
|
||||
const { routeParams } = params;
|
||||
const { routeParams, requireAll } = params;
|
||||
const permissions = this.permissions;
|
||||
|
||||
return permissions.hasNavPermission(route, routeParams);
|
||||
return permissions.hasNavPermission(route, routeParams, requireAll);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -135,18 +135,5 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
||||
}
|
||||
return true;
|
||||
},
|
||||
loading(transition) {
|
||||
const isSameRoute = transition.from?.name === transition.to?.name;
|
||||
if (isSameRoute || Ember.testing) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line ember/no-controller-access-in-routes
|
||||
const controller = this.controllerFor('vault.cluster');
|
||||
controller.set('currentlyLoading', true);
|
||||
|
||||
transition.finally(function () {
|
||||
controller.set('currentlyLoading', false);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -35,16 +35,6 @@ export default class ClientsRoute extends Route {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
async loading(transition) {
|
||||
// eslint-disable-next-line ember/no-controller-access-in-routes
|
||||
const controller = this.controllerFor(this.routeName);
|
||||
controller.set('currentlyLoading', true);
|
||||
transition.promise.finally(function () {
|
||||
controller.set('currentlyLoading', false);
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
deactivate() {
|
||||
// when navigating away from parent route, delete manually inputted license start date
|
||||
|
||||
10
ui/app/routes/vault/cluster/clients/index.js
Normal file
10
ui/app/routes/vault/cluster/clients/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ClientsIndexRoute extends Route {
|
||||
@service router;
|
||||
|
||||
redirect() {
|
||||
this.router.transitionTo('vault.cluster.clients.dashboard');
|
||||
}
|
||||
}
|
||||
@@ -26,20 +26,24 @@ export { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX };
|
||||
|
||||
export default Service.extend({
|
||||
permissions: service(),
|
||||
store: service(),
|
||||
router: service(),
|
||||
namespaceService: service('namespace'),
|
||||
|
||||
IDLE_TIMEOUT: 3 * 60e3,
|
||||
expirationCalcTS: null,
|
||||
isRenewing: false,
|
||||
mfaErrors: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.checkForRootToken();
|
||||
get tokenExpired() {
|
||||
const expiration = this.tokenExpirationDate;
|
||||
return expiration ? this.now() >= expiration : null;
|
||||
},
|
||||
|
||||
clusterAdapter() {
|
||||
return getOwner(this).lookup('adapter:cluster');
|
||||
get activeCluster() {
|
||||
return this.activeClusterId ? this.store.peekRecord('cluster', this.activeClusterId) : null;
|
||||
},
|
||||
|
||||
// eslint-disable-next-line
|
||||
tokens: computed({
|
||||
get() {
|
||||
@@ -50,6 +54,84 @@ export default Service.extend({
|
||||
},
|
||||
}),
|
||||
|
||||
isActiveSession: computed(
|
||||
'router.currentRouteName',
|
||||
'currentToken',
|
||||
'activeCluster.{dr.isSecondary,needsInit,sealed,name}',
|
||||
function () {
|
||||
if (this.activeCluster) {
|
||||
if (this.activeCluster.dr?.isSecondary || this.activeCluster.needsInit || this.activeCluster.sealed) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.activeCluster.name &&
|
||||
this.currentToken &&
|
||||
this.router.currentRouteName !== 'vault.cluster.auth'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
),
|
||||
|
||||
tokenExpirationDate: computed('currentTokenName', 'expirationCalcTS', function () {
|
||||
const tokenName = this.currentTokenName;
|
||||
if (!tokenName) {
|
||||
return;
|
||||
}
|
||||
const { tokenExpirationEpoch } = this.getTokenData(tokenName);
|
||||
const expirationDate = new Date(0);
|
||||
return tokenExpirationEpoch ? expirationDate.setUTCMilliseconds(tokenExpirationEpoch) : null;
|
||||
}),
|
||||
|
||||
renewAfterEpoch: computed('currentTokenName', 'expirationCalcTS', function () {
|
||||
const tokenName = this.currentTokenName;
|
||||
const { expirationCalcTS } = this;
|
||||
const data = this.getTokenData(tokenName);
|
||||
if (!tokenName || !data || !expirationCalcTS) {
|
||||
return null;
|
||||
}
|
||||
const { ttl, renewable } = data;
|
||||
// renew after last expirationCalc time + half of the ttl (in ms)
|
||||
return renewable ? Math.floor((ttl * 1e3) / 2) + expirationCalcTS : null;
|
||||
}),
|
||||
|
||||
// returns the key for the token to use
|
||||
currentTokenName: computed('activeClusterId', 'tokens', 'tokens.[]', function () {
|
||||
const regex = new RegExp(this.activeClusterId);
|
||||
return this.tokens.find((key) => regex.test(key));
|
||||
}),
|
||||
|
||||
currentToken: computed('currentTokenName', function () {
|
||||
const name = this.currentTokenName;
|
||||
const data = name && this.getTokenData(name);
|
||||
// data.token is undefined so that's why it returns current token undefined
|
||||
return name && data ? data.token : null;
|
||||
}),
|
||||
|
||||
authData: computed('currentTokenName', function () {
|
||||
const token = this.currentTokenName;
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const backend = this.backendFromTokenName(token);
|
||||
const stored = this.getTokenData(token);
|
||||
|
||||
return assign(stored, {
|
||||
backend: BACKENDS.findBy('type', backend),
|
||||
});
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.checkForRootToken();
|
||||
},
|
||||
|
||||
clusterAdapter() {
|
||||
return getOwner(this).lookup('adapter:cluster');
|
||||
},
|
||||
|
||||
generateTokenName({ backend, clusterId }, policies) {
|
||||
return (policies || []).includes('root')
|
||||
? `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}${clusterId}`
|
||||
@@ -83,7 +165,7 @@ export default Service.extend({
|
||||
},
|
||||
|
||||
setCluster(clusterId) {
|
||||
this.set('activeCluster', clusterId);
|
||||
this.set('activeClusterId', clusterId);
|
||||
},
|
||||
|
||||
ajax(url, method, options) {
|
||||
@@ -196,7 +278,7 @@ export default Service.extend({
|
||||
tokenName = this.generateTokenName(
|
||||
{
|
||||
backend,
|
||||
clusterId: (options && options.clusterId) || this.activeCluster,
|
||||
clusterId: (options && options.clusterId) || this.activeClusterId,
|
||||
},
|
||||
resp.policies
|
||||
);
|
||||
@@ -231,33 +313,6 @@ export default Service.extend({
|
||||
return this.storage(token).removeItem(token);
|
||||
},
|
||||
|
||||
tokenExpirationDate: computed('currentTokenName', 'expirationCalcTS', function () {
|
||||
const tokenName = this.currentTokenName;
|
||||
if (!tokenName) {
|
||||
return;
|
||||
}
|
||||
const { tokenExpirationEpoch } = this.getTokenData(tokenName);
|
||||
const expirationDate = new Date(0);
|
||||
return tokenExpirationEpoch ? expirationDate.setUTCMilliseconds(tokenExpirationEpoch) : null;
|
||||
}),
|
||||
|
||||
get tokenExpired() {
|
||||
const expiration = this.tokenExpirationDate;
|
||||
return expiration ? this.now() >= expiration : null;
|
||||
},
|
||||
|
||||
renewAfterEpoch: computed('currentTokenName', 'expirationCalcTS', function () {
|
||||
const tokenName = this.currentTokenName;
|
||||
const { expirationCalcTS } = this;
|
||||
const data = this.getTokenData(tokenName);
|
||||
if (!tokenName || !data || !expirationCalcTS) {
|
||||
return null;
|
||||
}
|
||||
const { ttl, renewable } = data;
|
||||
// renew after last expirationCalc time + half of the ttl (in ms)
|
||||
return renewable ? Math.floor((ttl * 1e3) / 2) + expirationCalcTS : null;
|
||||
}),
|
||||
|
||||
renew() {
|
||||
const tokenName = this.currentTokenName;
|
||||
const currentlyRenewing = this.isRenewing;
|
||||
@@ -422,32 +477,6 @@ export default Service.extend({
|
||||
this.set('tokens', tokenNames);
|
||||
},
|
||||
|
||||
// returns the key for the token to use
|
||||
currentTokenName: computed('activeCluster', 'tokens', 'tokens.[]', function () {
|
||||
const regex = new RegExp(this.activeCluster);
|
||||
return this.tokens.find((key) => regex.test(key));
|
||||
}),
|
||||
|
||||
currentToken: computed('currentTokenName', function () {
|
||||
const name = this.currentTokenName;
|
||||
const data = name && this.getTokenData(name);
|
||||
// data.token is undefined so that's why it returns current token undefined
|
||||
return name && data ? data.token : null;
|
||||
}),
|
||||
|
||||
authData: computed('currentTokenName', function () {
|
||||
const token = this.currentTokenName;
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const backend = this.backendFromTokenName(token);
|
||||
const stored = this.getTokenData(token);
|
||||
|
||||
return assign(stored, {
|
||||
backend: BACKENDS.findBy('type', backend),
|
||||
});
|
||||
}),
|
||||
|
||||
getOktaNumberChallengeAnswer(nonce, mount) {
|
||||
const url = `/v1/auth/${mount}/verify/${nonce}`;
|
||||
return this.ajax(url, 'GET', {}).then(
|
||||
|
||||
@@ -95,12 +95,15 @@ export default Service.extend({
|
||||
this.set('canViewAll', null);
|
||||
},
|
||||
|
||||
hasNavPermission(navItem, routeParams) {
|
||||
hasNavPermission(navItem, routeParams, requireAll) {
|
||||
if (routeParams) {
|
||||
// viewing the entity and groups pages require the list capability, while the others require the default, which is anything other than deny
|
||||
const capability = routeParams === 'entities' || routeParams === 'groups' ? ['list'] : [null];
|
||||
|
||||
return this.hasPermission(API_PATHS[navItem][routeParams], capability);
|
||||
// check that the user has permission to access all (requireAll = true) or any of the routes when array is passed
|
||||
// useful for hiding nav headings when user does not have access to any of the links
|
||||
const params = Array.isArray(routeParams) ? routeParams : [routeParams];
|
||||
const evalMethod = !Array.isArray(routeParams) || requireAll ? 'every' : 'some';
|
||||
return params[evalMethod]((param) => this.hasPermission(API_PATHS[navItem][param], capability));
|
||||
}
|
||||
return Object.values(API_PATHS[navItem]).some((path) => this.hasPermission(path));
|
||||
},
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
@import './reset';
|
||||
@import 'ember-basic-dropdown';
|
||||
@import 'ember-power-select';
|
||||
@import '@hashicorp/design-system-components';
|
||||
@import './core';
|
||||
|
||||
@mixin font-face($name) {
|
||||
|
||||
@@ -3,25 +3,26 @@
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
$console-close-height: 35px;
|
||||
|
||||
.console-ui-panel {
|
||||
background: linear-gradient(to right, #191a1c, #1b212d);
|
||||
background: var(--token-color-palette-neutral-700);
|
||||
width: -moz-available;
|
||||
width: -webkit-fill-available;
|
||||
height: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
min-height: 0;
|
||||
overflow: scroll;
|
||||
right: 0;
|
||||
top: 4rem;
|
||||
overflow: auto;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
transition: min-height $speed $easing, transform $speed ease-in;
|
||||
will-change: transform, min-height;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
width: 100vw;
|
||||
z-index: 199;
|
||||
|
||||
.button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: $grey;
|
||||
color: $white;
|
||||
min-width: 0;
|
||||
padding: 0 $size-8;
|
||||
|
||||
@@ -40,8 +41,8 @@
|
||||
font-size: 14px;
|
||||
font-weight: $font-weight-semibold;
|
||||
justify-content: flex-end;
|
||||
min-height: 100%;
|
||||
padding: $size-8 $size-8 $size-4;
|
||||
min-height: calc(100% - $console-close-height); // account for close button that is sticky positioned
|
||||
padding: $size-8 $size-8 $size-5;
|
||||
transition: justify-content $speed ease-in;
|
||||
|
||||
pre,
|
||||
@@ -78,16 +79,17 @@
|
||||
|
||||
input {
|
||||
background-color: rgba($black, 0.5);
|
||||
border: 0;
|
||||
border: 1px solid var(--token-color-palette-neutral-500);
|
||||
border-radius: 2px;
|
||||
caret-color: $white;
|
||||
color: $white;
|
||||
flex: 1 1 auto;
|
||||
font-family: $family-monospace;
|
||||
font-size: 16px;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-left: -$size-10;
|
||||
outline: none;
|
||||
padding: $size-10;
|
||||
margin-right: $spacing-xs;
|
||||
transition: background-color $speed;
|
||||
}
|
||||
}
|
||||
@@ -125,31 +127,9 @@
|
||||
|
||||
.panel-open .console-ui-panel.fullscreen {
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.panel-open {
|
||||
.navbar,
|
||||
.navbar-sections {
|
||||
transition: transform $speed ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-open.panel-fullscreen {
|
||||
.navbar,
|
||||
.navbar-sections {
|
||||
@include from($mobile) {
|
||||
transform: translateY(-100px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header .navbar,
|
||||
header .navbar-sections {
|
||||
z-index: 200;
|
||||
transform: translateY(0);
|
||||
will-change: transform;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.console-spinner.control {
|
||||
@@ -168,12 +148,14 @@ header .navbar-sections {
|
||||
}
|
||||
|
||||
.console-close-button {
|
||||
position: absolute;
|
||||
top: -3.25rem;
|
||||
right: $spacing-xs;
|
||||
position: sticky;
|
||||
top: $spacing-xs;
|
||||
height: $console-close-height;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
z-index: 210;
|
||||
|
||||
@include from($mobile) {
|
||||
display: none;
|
||||
button {
|
||||
margin-right: $spacing-xs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
animation: env-banner-color-rotate 8s infinite linear alternate;
|
||||
color: $white;
|
||||
margin-top: -20px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.hs-icon {
|
||||
margin: 0;
|
||||
|
||||
@@ -74,3 +74,11 @@
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.brand-icon-large {
|
||||
width: 62px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
@@ -5,65 +5,33 @@
|
||||
|
||||
.namespace-picker {
|
||||
position: relative;
|
||||
color: $white;
|
||||
color: var(--token-color-palette-neutral-300);
|
||||
display: flex;
|
||||
fill: $white;
|
||||
padding: $spacing-xxs $spacing-xs;
|
||||
width: 100%;
|
||||
|
||||
@include from($mobile) {
|
||||
margin-left: -$spacing-xs;
|
||||
padding: $spacing-xxs 0 $spacing-xxs $spacing-s;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.namespace-picker.no-namespaces {
|
||||
border: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.namespace-picker-trigger {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
height: 2rem;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
@include from($mobile) {
|
||||
height: auto;
|
||||
padding: $spacing-xs $spacing-m;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
6
ui/app/styles/reset.scss
Normal file
6
ui/app/styles/reset.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
// reset for HDS
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -6,12 +6,12 @@
|
||||
/* General sizing in rem values used largely for text sizing.*/
|
||||
$size-1: 3rem; // 48px, same as $spacing-xxl
|
||||
$size-2: 2.5rem; // 40px
|
||||
$size-3: (24/14) + 0rem; // ~1.714rem ~27px
|
||||
$size-3: calc(24 / 14) + 0rem; // ~1.714rem ~27px
|
||||
$size-4: 1.5rem; // 24px same as $spacing-l
|
||||
$size-5: 1.25rem; // 20px
|
||||
$size-6: 1rem; // 16px same as $spacing-m
|
||||
$size-7: (13/14) + 0rem; // ~.929rem ~15px
|
||||
$size-8: (12/14) + 0rem; // ~.857rem ~13.7px
|
||||
$size-7: calc(13 / 14) + 0rem; // ~.929rem ~15px
|
||||
$size-8: calc(12 / 14) + 0rem; // ~.857rem ~13.7px
|
||||
$size-9: 0.75rem; // 12px same as $spacing-s
|
||||
$size-10: 0.5rem; // 8px same as $spacing-xs
|
||||
$size-11: 0.25rem; // 4px same as spacing-xxs
|
||||
|
||||
@@ -84,6 +84,6 @@
|
||||
|
||||
@mixin vault-block {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: (5/14) + 0rem;
|
||||
margin-bottom: calc(5 / 14) + 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<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")}}
|
||||
@@ -33,5 +33,4 @@
|
||||
<Icon @name="pencil-tool" /><Icon @name="git-branch" />
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div id="modal-wormhole"></div>
|
||||
{{/if}}
|
||||
@@ -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}}
|
||||
1
ui/app/templates/vault/cluster/access/loading.hbs
Normal file
1
ui/app/templates/vault/cluster/access/loading.hbs
Normal file
@@ -0,0 +1 @@
|
||||
<LayoutLoading />
|
||||
@@ -22,6 +22,11 @@
|
||||
<LogoEdition aria-label="Sign in with Hashicorp Vault" />
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="is-flex-v-centered has-bottom-margin-xxl">
|
||||
<div class="brand-icon-large">
|
||||
<Icon @name="vault" @size="24" @stretched={{true}} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-flex-row">
|
||||
{{#if this.mfaAuthData}}
|
||||
<button type="button" class="icon-button" {{on "click" (fn (mut this.mfaAuthData) null)}}>
|
||||
|
||||
@@ -23,8 +23,5 @@
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{#if this.currentlyLoading}}
|
||||
<LayoutLoading />
|
||||
{{else}}
|
||||
{{outlet}}
|
||||
{{/if}}
|
||||
|
||||
{{outlet}}
|
||||
1
ui/app/templates/vault/cluster/clients/loading.hbs
Normal file
1
ui/app/templates/vault/cluster/clients/loading.hbs
Normal file
@@ -0,0 +1 @@
|
||||
<LayoutLoading />
|
||||
@@ -1,51 +1,2 @@
|
||||
{{#if
|
||||
(and
|
||||
(has-feature "Sentinel") (or (has-permission "policies" routeParams="rgp") (has-permission "policies" routeParams="egp"))
|
||||
)
|
||||
}}
|
||||
<div class="columns">
|
||||
<MenuSidebar @title="Policies" @class="is-3" @data-test-sidebar={{true}}>
|
||||
{{#if (has-permission "policies" routeParams="acl")}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.policies"
|
||||
@model="acl"
|
||||
data-test-link={{true}}
|
||||
class={{if (is-active-route "vault.cluster.policies" "acl") "is-active"}}
|
||||
>
|
||||
ACL Policies
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "policies" routeParams="rgp")}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.policies"
|
||||
@model="rgp"
|
||||
data-test-link={{true}}
|
||||
class={{if (is-active-route "vault.cluster.policies" "rgp") "is-active"}}
|
||||
>
|
||||
Role Governing Policies
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "policies" routeParams="egp")}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.policies"
|
||||
@model="egp"
|
||||
data-test-link={{true}}
|
||||
class={{if (is-active-route "vault.cluster.policies" "egp") "is-active"}}
|
||||
>
|
||||
Endpoint Governing Policies
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
</MenuSidebar>
|
||||
<div class="column is-9">
|
||||
{{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
Reference in New Issue
Block a user