Llb/side nav refactor (#2763)
* feat: 🎸 scope picker component (#2646) * feat: 🎸 scope picker component --------- Co-authored-by: lisbet-alvarez <lisbet-alvarez@users.noreply.github.com> Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com> * refactor: 💡 use hds side nav w/ hds frame instead of rose (#2688) * refactor: 💡 use hds side nav w/ hds frame instead of rose * test: 💍 clean up selectors * fix: 🐛 specificity of style override * refactor: 💡 use hds side nav list instead of target and portal * refactor: 💡 revert to using portal component & update scss * docs: ✏️ update comment * refactor: 💡 use custom class names for containers * refactor: 💡 pr feedback (use @hasSidebar arg) * refactor: 💡 fix onboarding page & fix settings title * refactor: 💡 remove unneeded template file (pr feedback) * test: 💍 fix tests due to merge conflicts * test: 💍 add missing test selector due to merge conflict * test: 💍 fix more broken tests due to merge conflicts * chore: 🤖 update yarn.lock & update hds version to 4.18.1 * chore: 🤖 fix e2e test cases due to side nav refactor (#2751) * chore: 🤖 fix e2e test cases due to side nav refactor * test: 💍 fix e2e test for desktop --------- Co-authored-by: lisbet-alvarez <lisbet-alvarez@users.noreply.github.com> Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
This commit is contained in:
76 files changed
+1621
-711
No files matched your search
@@ -18,9 +18,7 @@ titles:
|
||||
home: Home
|
||||
iam: IAM
|
||||
new: New
|
||||
resources: Resources
|
||||
details: Details
|
||||
general: General
|
||||
overview: Overview
|
||||
settings: Settings
|
||||
empty-set: No Items Yet
|
||||
@@ -46,6 +44,11 @@ titles:
|
||||
days: '{days, plural, =1 {# day} other {# days}}'
|
||||
workers: Workers
|
||||
no-worker-filter: No worker filter added
|
||||
scope-level: '{scope} Level'
|
||||
scope-iam: '{scope} IAM'
|
||||
project-actions: Project Actions
|
||||
user-menu: User Menu
|
||||
back-link: 'Back to {scope}'
|
||||
descriptions:
|
||||
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
||||
cluster-url-initialization: To get started, please enter your cluster URL
|
||||
@@ -57,6 +60,9 @@ descriptions:
|
||||
provider: This is the provider for this host catalog. The format of the host set filters are specific to each provider.
|
||||
no-search-results: Sorry, we weren't able to find any {resource} for "{query}".
|
||||
no-filter-results: Sorry, we weren’t able to find any {resource} within the filtered parameters.
|
||||
and-more: ...and more
|
||||
view-all-orgs: 'View all orgs ({total})'
|
||||
view-all-projects: 'View all projects ({total})'
|
||||
questions:
|
||||
delete-confirm: Are you sure you want to delete this resource?
|
||||
remove-confirm: Are you sure you want to remove this association?
|
||||
|
||||
@@ -53,3 +53,20 @@ form {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is a temporary fix that ensures changing the color theme won't alter the dropdown colors.
|
||||
.app-frame-sidebar-style-overrides .side-nav-style-overrides {
|
||||
.hds-dropdown-toggle-button,
|
||||
.hds-dropdown-toggle-icon {
|
||||
background: var(--token-side-nav-color-surface-primary);
|
||||
|
||||
&:hover {
|
||||
background: var(--token-side-nav-color-surface-interactive-hover);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
background: var(--token-side-nav-color-surface-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
@use '../utilities/type';
|
||||
@use '../variables/media';
|
||||
|
||||
$l: sizing.rems(l); // 16
|
||||
$l: sizing.rems(l); // 24
|
||||
$m: sizing.rems(m); // 16
|
||||
$s: sizing.rems(s); // 12
|
||||
$xs: sizing.rems(xs); // 8
|
||||
@@ -36,11 +36,22 @@ $xxxxs: sizing.rems(xxxxs); // 1
|
||||
border-bottom: $xxxxs solid var(--header-border-color);
|
||||
border-top-left-radius: $xxxs;
|
||||
border-top-right-radius: $xxxs;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: $xs;
|
||||
|
||||
h2 {
|
||||
margin: $m 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rose-frame-body {
|
||||
$body-padding: $l;
|
||||
|
||||
padding: $body-padding;
|
||||
|
||||
p {
|
||||
margin-bottom: $m;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@embroider/test-setup": "^2.1.1",
|
||||
"@hashicorp/design-system-components": "^4.13.0",
|
||||
"@hashicorp/design-system-components": "^4.18.1",
|
||||
"codemirror": "5.65.7",
|
||||
"ember-auto-import": "^2.10.0",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
|
||||
@@ -15,10 +15,13 @@ export class AliasesPage extends BaseResourcePage {
|
||||
* @param {string} targetId ID of the target
|
||||
* @returns Name of the alias
|
||||
*/
|
||||
async createAliasForTarget(alias, targetId) {
|
||||
async createAliasForTarget(alias, targetId, orgName) {
|
||||
const aliasName = 'Alias ' + nanoid();
|
||||
|
||||
await this.page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
await this.page
|
||||
.getByRole('link', { name: `Back to ${orgName}`, exact: true })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'Orgs' }).click();
|
||||
await this.page.getByRole('link', { name: 'Aliases' }).click();
|
||||
await expect(
|
||||
this.page
|
||||
|
||||
@@ -16,7 +16,7 @@ export class AuthMethodsPage extends BaseResourcePage {
|
||||
async createPasswordAuthMethod() {
|
||||
const authMethodName = 'Auth Method ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
.click();
|
||||
await this.page.getByRole('button', { name: 'New' }).click();
|
||||
@@ -43,7 +43,7 @@ export class AuthMethodsPage extends BaseResourcePage {
|
||||
async createOidcAuthMethod(issuer, clientId, clientSecret, boundaryAddr) {
|
||||
const authMethodName = 'Auth Method ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
.click();
|
||||
await this.page.getByRole('button', { name: 'New' }).click();
|
||||
@@ -130,7 +130,7 @@ export class AuthMethodsPage extends BaseResourcePage {
|
||||
*/
|
||||
async makeAuthMethodPrimary() {
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
.click();
|
||||
await this.page.getByLabel('Manage').click();
|
||||
|
||||
@@ -17,7 +17,7 @@ export class CredentialStoresPage extends BaseResourcePage {
|
||||
async createStaticCredentialStore() {
|
||||
const credentialStoreName = 'Credential Store ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Credential Stores' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -47,7 +47,7 @@ export class CredentialStoresPage extends BaseResourcePage {
|
||||
async createVaultCredentialStore(vaultAddr, clientToken) {
|
||||
const credentialStoreName = 'Credential Store ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Credential Stores' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -84,7 +84,7 @@ export class CredentialStoresPage extends BaseResourcePage {
|
||||
) {
|
||||
const credentialStoreName = 'Credential Store ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Credential Stores' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
|
||||
@@ -16,7 +16,7 @@ export class GroupsPage extends BaseResourcePage {
|
||||
async createGroup() {
|
||||
const groupName = 'Group ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Groups' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
|
||||
@@ -16,7 +16,7 @@ export class HostCatalogsPage extends BaseResourcePage {
|
||||
async createHostCatalog() {
|
||||
const hostCatalogName = 'Host Catalog ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Host Catalogs' })
|
||||
.click();
|
||||
await expect(
|
||||
|
||||
@@ -18,7 +18,10 @@ export class LoginPage extends BaseResourcePage {
|
||||
await this.page.getByLabel('Password', { exact: true }).fill(password);
|
||||
await this.page.getByRole('button', { name: 'Sign In' }).click();
|
||||
await expect(
|
||||
this.page.getByRole('navigation', { name: 'General' }),
|
||||
this.page.getByRole('navigation', {
|
||||
// TODO: Uncomment line below once both UIs have been refactored to use HDS side nav.
|
||||
// name: 'Application local navigation',
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(this.page.getByText(loginName)).toBeEnabled();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export class OrgsPage extends BaseResourcePage {
|
||||
async createOrg() {
|
||||
const orgName = 'Org ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Orgs' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New Org' }).click();
|
||||
|
||||
@@ -16,7 +16,7 @@ export class ProjectsPage extends BaseResourcePage {
|
||||
async createProject() {
|
||||
const projectName = 'Project ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Projects' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
|
||||
@@ -16,7 +16,7 @@ export class RolesPage extends BaseResourcePage {
|
||||
const roleName = 'Role ' + nanoid();
|
||||
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Roles' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New Role' }).click();
|
||||
|
||||
@@ -14,7 +14,7 @@ export class SessionsPage extends BasePage {
|
||||
*/
|
||||
async waitForSessionToBeVisible(targetName) {
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Sessions' })
|
||||
.click();
|
||||
let i = 0;
|
||||
|
||||
@@ -17,7 +17,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
async createTarget(port) {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -44,7 +44,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
async createTargetWithAddress(address, port) {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -73,7 +73,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
async createTargetWithAddressAndAlias(address, port, alias) {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -109,7 +109,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
async createTcpTargetWithAddressEnt(address, port) {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -140,7 +140,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
async createSshTargetEnt(port) {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -172,7 +172,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
async createSshTargetWithAddressEnt(address, port) {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -206,7 +206,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
async createSshTargetWithAddressAndAlias(address, port, alias) {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -349,7 +349,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
*/
|
||||
async addBrokeredCredentialsToTarget(targetName, credentialName) {
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await expect(
|
||||
@@ -397,7 +397,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
*/
|
||||
async addInjectedCredentialsToTarget(targetName, credentialName) {
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await expect(
|
||||
|
||||
@@ -16,7 +16,7 @@ export class UsersPage extends BaseResourcePage {
|
||||
async createUser() {
|
||||
const userName = 'User ' + nanoid();
|
||||
await this.page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Users' })
|
||||
.click();
|
||||
await this.page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
|
||||
@@ -92,7 +92,7 @@ test.describe('Aliases (Enterprise)', () => {
|
||||
|
||||
// Clear destination from alias
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
@@ -270,10 +270,10 @@ test.describe('Aliases (Enterprise)', () => {
|
||||
|
||||
alias = 'example.alias.' + nanoid();
|
||||
const aliasesPage = new AliasesPage(page);
|
||||
await aliasesPage.createAliasForTarget(alias, targetId);
|
||||
await aliasesPage.createAliasForTarget(alias, targetId, orgName);
|
||||
connect = await boundaryCli.connectSshToAlias(alias);
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Orgs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
|
||||
@@ -75,7 +75,7 @@ test.describe('Aliases', () => {
|
||||
|
||||
// Clear destination from alias
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
@@ -230,7 +230,7 @@ test.describe('Aliases', () => {
|
||||
|
||||
alias = 'example.alias.' + nanoid();
|
||||
const aliasesPage = new AliasesPage(page);
|
||||
await aliasesPage.createAliasForTarget(alias, targetId);
|
||||
await aliasesPage.createAliasForTarget(alias, targetId, orgName);
|
||||
await page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Orgs' })).toBeVisible();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
|
||||
@@ -52,7 +52,7 @@ test(
|
||||
const orgsPage = new OrgsPage(page);
|
||||
orgName = await orgsPage.createOrg();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
.click();
|
||||
await page.getByRole('button', { name: 'New' }).click();
|
||||
@@ -191,7 +191,7 @@ test(
|
||||
// View the LDAP account and verify account attributes
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: ldapAuthMethodName }).click();
|
||||
@@ -280,7 +280,7 @@ test(
|
||||
|
||||
// View the User account and verify attributes
|
||||
await page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Users' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: userName }).click();
|
||||
|
||||
@@ -32,7 +32,6 @@ test(
|
||||
async ({
|
||||
page,
|
||||
context,
|
||||
baseURL,
|
||||
controllerAddr,
|
||||
adminAuthMethodId,
|
||||
adminLoginName,
|
||||
@@ -52,7 +51,7 @@ test(
|
||||
userName,
|
||||
password,
|
||||
email,
|
||||
baseURL,
|
||||
controllerAddr,
|
||||
);
|
||||
policyName = authPolicyName;
|
||||
|
||||
@@ -71,7 +70,7 @@ test(
|
||||
issuer,
|
||||
clientId,
|
||||
clientSecret,
|
||||
baseURL,
|
||||
controllerAddr,
|
||||
);
|
||||
|
||||
// Change OIDC Auth Method state to active-public
|
||||
@@ -134,9 +133,7 @@ test(
|
||||
).toBeVisible();
|
||||
|
||||
// Log back in as an admin
|
||||
// WORKAROUND: Currently, users logging using OIDC don't have a username
|
||||
// displayed in the UI, so there's no simple locator to access this menu.
|
||||
await page.locator('details').filter({ hasText: 'Sign Out' }).click();
|
||||
await page.getByRole('button', { name: 'User Menu' }).click();
|
||||
await page.getByRole('button', { name: 'Sign Out' }).click();
|
||||
await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible();
|
||||
await loginPage.login(adminLoginName, adminPassword);
|
||||
@@ -152,7 +149,7 @@ test(
|
||||
.getByText(orgName),
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: oidcAuthMethodName }).click();
|
||||
@@ -241,7 +238,7 @@ test(
|
||||
|
||||
// View the User account and verify attributes
|
||||
await page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Users' })
|
||||
.click();
|
||||
await page
|
||||
|
||||
@@ -75,7 +75,7 @@ test(
|
||||
|
||||
// Set a new password on the account
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Orgs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
@@ -85,7 +85,7 @@ test(
|
||||
.getByText(orgName),
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: authMethodName }).click();
|
||||
|
||||
@@ -49,7 +49,7 @@ test.describe('AWS', () => {
|
||||
// Create host catalog
|
||||
const hostCatalogName = 'Host Catalog ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Host Catalogs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -164,7 +164,7 @@ test.describe('AWS', () => {
|
||||
await hostCatalogsPage.createHostCatalog();
|
||||
const newHostSetName = await hostCatalogsPage.createHostSet();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
|
||||
@@ -49,7 +49,7 @@ test.describe('AWS', () => {
|
||||
// Create host catalog
|
||||
const hostCatalogName = 'Host Catalog ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Host Catalogs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -164,7 +164,7 @@ test.describe('AWS', () => {
|
||||
await hostCatalogsPage.createHostCatalog();
|
||||
const newHostSetName = await hostCatalogsPage.createHostSet();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
|
||||
@@ -42,7 +42,7 @@ test(
|
||||
|
||||
// Navigate to orgs page when done
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Orgs' })
|
||||
.click();
|
||||
await expect(page.getByRole('heading', { name: 'Orgs' })).toBeVisible();
|
||||
|
||||
@@ -34,7 +34,7 @@ test(
|
||||
await page.getByRole('link', { name: org.name }).click();
|
||||
await page.getByRole('link', { name: project.name }).click();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await expect(
|
||||
|
||||
@@ -62,6 +62,9 @@ test(
|
||||
const projectName = await projectsPage.createProject();
|
||||
|
||||
// Create storage bucket
|
||||
await page
|
||||
.getByRole('link', { name: `Back to ${orgName}`, exact: true })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
const storageBucketsPage = new StorageBucketsPage(page);
|
||||
const storageBucketName = await storageBucketsPage.createStorageBucketAws(
|
||||
@@ -107,6 +110,9 @@ test(
|
||||
await targetsPage.enableSessionRecording(storageBucketName);
|
||||
|
||||
// Create storage policy in org scope: keep session recordings forever
|
||||
await page
|
||||
.getByRole('link', { name: `Back to ${orgName}`, exact: true })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Orgs' })).toBeVisible();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
@@ -146,9 +152,12 @@ test(
|
||||
await boundaryCli.waitForSessionRecording(storageBucket.id);
|
||||
|
||||
// Play back session recording
|
||||
await page
|
||||
.getByRole('link', { name: `Back to ${orgName}`, exact: true })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Session Recordings', exact: true })
|
||||
.click();
|
||||
await page
|
||||
|
||||
@@ -115,6 +115,9 @@ test(
|
||||
await targetsPage.enableSessionRecording(storageBucketName);
|
||||
|
||||
// Create storage policy in org scope: keep session recordings forever
|
||||
await page
|
||||
.getByRole('link', { name: `Back to ${orgName}`, exact: true })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Orgs' })).toBeVisible();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
@@ -154,9 +157,12 @@ test(
|
||||
await boundaryCli.waitForSessionRecording(storageBucket.id);
|
||||
|
||||
// Play back session recording
|
||||
await page
|
||||
.getByRole('link', { name: `Back to ${orgName}`, exact: true })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Session Recordings', exact: true })
|
||||
.click();
|
||||
await page
|
||||
|
||||
@@ -190,7 +190,7 @@ test(
|
||||
|
||||
// Create another host set
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Host Catalogs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: hostCatalogName }).click();
|
||||
|
||||
@@ -49,7 +49,7 @@ test(
|
||||
|
||||
// Create another host set
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Host Catalogs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: hostCatalogName }).click();
|
||||
|
||||
@@ -14,7 +14,7 @@ test(
|
||||
|
||||
await page.goto('/');
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Workers' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
|
||||
@@ -17,7 +17,7 @@ test(
|
||||
|
||||
await page.goto('/');
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Workers' })
|
||||
.click();
|
||||
await expect(
|
||||
@@ -102,7 +102,7 @@ test(
|
||||
|
||||
// Check tag in worker dialog
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Workers' })
|
||||
.click();
|
||||
await expect(
|
||||
|
||||
@@ -14,7 +14,7 @@ test(
|
||||
|
||||
await page.goto('/');
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('navigation', { name: 'Application local navigation' })
|
||||
.getByRole('link', { name: 'Workers' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{{#if (can 'addPrincipals role' @model)}}
|
||||
<dd.Interactive
|
||||
@route='scopes.scope.roles.role.add-principals'
|
||||
data-test-manage-dropdown-principals
|
||||
data-test-manage-role-principals
|
||||
>
|
||||
{{t 'resources.role.principal.actions.add-principals'}}
|
||||
</dd.Interactive>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Hds::Dropdown
|
||||
@enableCollisionDetection={{true}}
|
||||
@matchToggleWidth={{true}}
|
||||
{{! HDS provided class to automatically fade in/out }}
|
||||
class='hds-side-nav-hide-when-minimized scope-picker'
|
||||
as |D|
|
||||
>
|
||||
{{#let this.currentScope as |scope|}}
|
||||
<D.ToggleButton
|
||||
@text={{scope.name}}
|
||||
@icon={{scope.icon}}
|
||||
class='scope-picker__toggle-button'
|
||||
/>
|
||||
{{/let}}
|
||||
|
||||
<D.Checkmark
|
||||
@icon='globe'
|
||||
@route='scopes.scope'
|
||||
@model='global'
|
||||
@selected={{this.scope.org.isGlobal}}
|
||||
>
|
||||
{{t 'titles.global'}}
|
||||
</D.Checkmark>
|
||||
|
||||
{{#if this.scope.orgsList}}
|
||||
<D.Separator />
|
||||
|
||||
<D.Title @text={{t 'resources.org.title_plural'}} />
|
||||
{{/if}}
|
||||
|
||||
{{#each this.truncatedOrgsList as |org|}}
|
||||
<D.Checkmark
|
||||
@icon='org'
|
||||
@route='scopes.scope'
|
||||
@model={{org.id}}
|
||||
@selected={{and (eq this.scope.org.id org.id) (not this.scope.project)}}
|
||||
data-test-scope-picker-org-item
|
||||
>
|
||||
{{org.displayName}}
|
||||
</D.Checkmark>
|
||||
{{/each}}
|
||||
|
||||
{{#if (gt this.scope.orgsList.length 5)}}
|
||||
<D.Description
|
||||
@text={{t 'descriptions.and-more'}}
|
||||
data-test-scope-picker-org-and-more-text
|
||||
/>
|
||||
|
||||
<D.Interactive
|
||||
@route='scopes.scope.scopes'
|
||||
@model='global'
|
||||
data-test-scope-picker-org-count
|
||||
>{{t
|
||||
'descriptions.view-all-orgs'
|
||||
total=this.scope.orgsList.length
|
||||
}}</D.Interactive>
|
||||
{{/if}}
|
||||
|
||||
{{! All project scopes in org are only loaded when current scope is of project type. }}
|
||||
{{#if this.scope.project}}
|
||||
<D.Title @text={{t 'resources.project.title_plural'}} class='indentation' />
|
||||
|
||||
{{#each this.truncatedProjectsList as |project|}}
|
||||
<D.Checkmark
|
||||
@icon='grid'
|
||||
@route='scopes.scope'
|
||||
@model={{project.id}}
|
||||
@selected={{eq this.scope.project.id project.id}}
|
||||
class='indentation'
|
||||
data-test-scope-picker-project-item
|
||||
>
|
||||
{{project.displayName}}
|
||||
</D.Checkmark>
|
||||
{{/each}}
|
||||
|
||||
{{#if (gt this.scope.projectsList.length 5)}}
|
||||
<D.Description
|
||||
@text={{t 'descriptions.and-more'}}
|
||||
class='indentation'
|
||||
data-test-scope-picker-project-and-more-text
|
||||
/>
|
||||
|
||||
<D.Interactive
|
||||
@route='scopes.scope.scopes'
|
||||
@model={{this.scope.org.id}}
|
||||
class='indentation'
|
||||
data-test-scope-picker-project-count
|
||||
>{{t
|
||||
'descriptions.view-all-projects'
|
||||
total=this.scope.projectsList.length
|
||||
}}</D.Interactive>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
|
||||
export default class ScopePickerIndexComponent extends Component {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
@service scope;
|
||||
@service session;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Returns display name and icon for current scope.
|
||||
* @type {object}
|
||||
*/
|
||||
get currentScope() {
|
||||
if (this.scope.project) {
|
||||
return { name: this.scope.project.displayName, icon: 'grid' };
|
||||
} else if (this.scope.org.isOrg) {
|
||||
return { name: this.scope.org.displayName, icon: 'org' };
|
||||
} else {
|
||||
return { name: this.intl.t('titles.global'), icon: 'globe' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first five orgs ordered by most recently updated.
|
||||
* @type {[ScopeModel]}
|
||||
*/
|
||||
get truncatedOrgsList() {
|
||||
return orderBy(this.scope.orgsList, 'updated_time', 'desc').slice(0, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first five projects ordered by most recently updated.
|
||||
* @type {[ScopeModel]}
|
||||
*/
|
||||
get truncatedProjectsList() {
|
||||
return orderBy(this.scope.projectsList, 'updated_time', 'desc').slice(0, 5);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export default class ApplicationController extends Controller {
|
||||
@service features;
|
||||
@service featureEdition;
|
||||
@service flashMessages;
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Returns available themes
|
||||
@@ -39,6 +40,13 @@ export default class ApplicationController extends Controller {
|
||||
return THEMES;
|
||||
}
|
||||
|
||||
get showSideNav() {
|
||||
return (
|
||||
this.router.currentRouteName.startsWith('scopes.scope') &&
|
||||
this.session.isAuthenticated
|
||||
);
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default class ScopesIndexController extends Controller {
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
}
|
||||
@@ -10,4 +10,5 @@ export default class ScopesScopeController extends Controller {
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
@service scope;
|
||||
}
|
||||
@@ -15,9 +15,14 @@ export default class AccountRoute extends Route {
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* If arriving here unauthenticated, redirect to index for further processing.
|
||||
* If arriving here unauthenticated, redirect to index for further processing
|
||||
* else redirect to change-password route since account route is empty.
|
||||
*/
|
||||
beforeModel() {
|
||||
if (!this.session.isAuthenticated) this.router.transitionTo('index');
|
||||
if (!this.session.isAuthenticated) {
|
||||
this.router.transitionTo('index');
|
||||
} else {
|
||||
this.router.transitionTo('account.change-password');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,29 +72,5 @@ export default class ScopesScopeRoute extends Route {
|
||||
this.scope.project = selectedProject;
|
||||
this.scope.orgsList = orgs;
|
||||
this.scope.projectsList = projects;
|
||||
this.scopes = { orgs, projects, selectedOrg, selectedProject };
|
||||
// Update the controller (if exists), since setupController is only
|
||||
// called once the first time the route is activated. It is not called
|
||||
// again on route refreshes.
|
||||
/* eslint-disable-next-line ember/no-controller-access-in-routes */
|
||||
if (this.controller) this.setControllerProperties(this.scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the scopes hash to the controller context (see `afterModel`).
|
||||
* @param {Controller} controller
|
||||
*/
|
||||
setupController(/* controller */) {
|
||||
super.setupController(...arguments);
|
||||
this.setControllerProperties(this.scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the controller's `scopes`.
|
||||
* @param {array} scopes
|
||||
*/
|
||||
setControllerProperties(scopes) {
|
||||
/* eslint-disable-next-line ember/no-controller-access-in-routes */
|
||||
this.controller.setProperties({ scopes });
|
||||
}
|
||||
}
|
||||
@@ -173,8 +173,6 @@
|
||||
|
||||
// Onboarding styles
|
||||
.create-resources {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: sizing.rems(xl) * 15;
|
||||
|
||||
.create-resources-form {
|
||||
@@ -185,7 +183,6 @@
|
||||
|
||||
.hds-form-group {
|
||||
margin-top: sizing.rems(l);
|
||||
justify-content: center;
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
@@ -1083,3 +1080,15 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// scope picker
|
||||
.scope-picker {
|
||||
&__toggle-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
li:has(.indentation),
|
||||
li.indentation {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
@@ -5,32 +5,26 @@
|
||||
|
||||
{{page-title (t 'actions.change-password')}}
|
||||
|
||||
<Rose::Layout::Sidebar as |sidebarLayout|>
|
||||
<sidebarLayout.body>
|
||||
<main>
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Layout::Page as |page|>
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Layout::Page as |page|>
|
||||
|
||||
<page.header>
|
||||
<h2>{{t 'actions.change-password'}}</h2>
|
||||
</page.header>
|
||||
<page.body>
|
||||
<Rose::Frame>
|
||||
<:header>
|
||||
<Hds::Icon @name='boundary-color' @size='24' @isInline={{true}} />
|
||||
<Hds::Text::Display @tag='h2' @size='300' @weight='bold'>{{t
|
||||
'actions.change-password'
|
||||
}}</Hds::Text::Display>
|
||||
</:header>
|
||||
<:body>
|
||||
<Form::Account::Password::ChangePassword
|
||||
@model={{@model}}
|
||||
@submit={{fn this.changePassword @model}}
|
||||
@cancel={{fn this.cancel @model}}
|
||||
/>
|
||||
</:body>
|
||||
</Rose::Frame>
|
||||
</page.body>
|
||||
|
||||
<page.body>
|
||||
<Form::Account::Password::ChangePassword
|
||||
@model={{@model}}
|
||||
@submit={{fn this.changePassword @model}}
|
||||
@cancel={{fn this.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
</Rose::Layout::Centered>
|
||||
</main>
|
||||
</sidebarLayout.body>
|
||||
<sidebarLayout.footer>
|
||||
<Hds::AppFooter @theme={{this.session.data.theme}} as |AF|>
|
||||
<AF.Link @href={{(doc-url)}}>{{t 'titles.documentation'}}</AF.Link>
|
||||
<AF.LegalLinks />
|
||||
</Hds::AppFooter>
|
||||
</sidebarLayout.footer>
|
||||
</Rose::Layout::Sidebar>
|
||||
</Rose::Layout::Page>
|
||||
</Rose::Layout::Centered>
|
||||
@@ -5,113 +5,116 @@
|
||||
|
||||
{{page-title (app-name)}}
|
||||
|
||||
<Rose::Layout::Global as |layout|>
|
||||
<Hds::AppFrame @hasHeader={{false}} @hasSidebar={{this.showSideNav}} as |Frame|>
|
||||
<Frame.Sidebar class='app-frame-sidebar-style-overrides'>
|
||||
<Hds::SideNav class='side-nav-style-overrides'>
|
||||
<:header>
|
||||
<Hds::SideNav::Header>
|
||||
<:logo>
|
||||
<Hds::SideNav::Header::HomeLink
|
||||
@icon='boundary'
|
||||
@color='var(--token-color-boundary-brand)'
|
||||
@ariaLabel={{(app-name)}}
|
||||
@route='index'
|
||||
/>
|
||||
</:logo>
|
||||
<:actions>
|
||||
{{#if (feature-flag 'dev-edition-toggle')}}
|
||||
<Hds::Dropdown @enableCollisionDetection={{true}} as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon='layers'
|
||||
@text={{t
|
||||
'titles.feature-edition'
|
||||
edition=this.featureEdition.edition
|
||||
}}
|
||||
data-test-dev-edition-toggle
|
||||
/>
|
||||
|
||||
<layout.header>
|
||||
<Rose::Header class={{if (is-loading) ' loading'}} as |header|>
|
||||
<header.brand @logo='logo-app' @text={{(app-name)}} />
|
||||
|
||||
{{#if this.session.isAuthenticated}}
|
||||
<header.nav>
|
||||
<HeaderNav />
|
||||
</header.nav>
|
||||
|
||||
<header.utilities as |utilities|>
|
||||
<utilities.dropdown
|
||||
@text={{this.session.data.authenticated.username}}
|
||||
@icon='flight-icons/svg/user-circle-16'
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.link @route='account.change-password'>
|
||||
{{t 'actions.change-password'}}
|
||||
</dropdown.link>
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.button {{on 'click' this.invalidateSession}}>
|
||||
{{t 'actions.deauthenticate'}}
|
||||
</dropdown.button>
|
||||
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.section @title={{t 'actions.toggle-color-theme'}}>
|
||||
<Hds::Form::Radio::Group
|
||||
@name='theme'
|
||||
class='dropdown-radio-btn'
|
||||
as |G|
|
||||
>
|
||||
{{#each this.themes as |theme|}}
|
||||
<G.RadioField
|
||||
@value={{theme.value}}
|
||||
checked={{eq this.session.data.theme theme.value}}
|
||||
{{on 'change' (fn this.toggleTheme theme.value)}}
|
||||
as |F|
|
||||
<dd.Title @text={{t 'actions.toggle-feature-edition'}} />
|
||||
{{#each this.featureEdition.editions as |edition|}}
|
||||
<dd.Radio
|
||||
@value={{edition}}
|
||||
checked={{eq this.featureEdition.edition edition}}
|
||||
{{on 'change' (fn this.toggleEdition edition)}}
|
||||
>
|
||||
<F.Label>{{t (concat 'themes.' theme.label)}}</F.Label>
|
||||
</G.RadioField>
|
||||
{{edition}}
|
||||
</dd.Radio>
|
||||
{{/each}}
|
||||
</Hds::Form::Radio::Group>
|
||||
</dropdown.section>
|
||||
<dd.Separator />
|
||||
|
||||
</utilities.dropdown>
|
||||
<dd.Title @text={{t 'actions.toggle-licensed-features'}} />
|
||||
{{#each this.featureEdition.licensedFeatures as |feature|}}
|
||||
<dd.Checkbox
|
||||
@value={{feature}}
|
||||
checked={{feature-flag feature}}
|
||||
{{on 'change' (fn this.toggleFeature feature)}}
|
||||
>
|
||||
{{feature}}
|
||||
</dd.Checkbox>
|
||||
{{/each}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
|
||||
{{#if (feature-flag 'dev-edition-toggle')}}
|
||||
<utilities.dropdown
|
||||
data-test-dev-edition-toggle
|
||||
@text={{t
|
||||
'titles.feature-edition'
|
||||
edition=this.featureEdition.edition
|
||||
}}
|
||||
@icon='flight-icons/svg/layers-16'
|
||||
as |dropdown|
|
||||
<Hds::Dropdown
|
||||
@enableCollisionDetection={{true}}
|
||||
data-test-side-nav-user-dropdown
|
||||
as |dd|
|
||||
>
|
||||
<dropdown.section @title={{t 'actions.toggle-feature-edition'}}>
|
||||
<Hds::Form::Radio::Group
|
||||
@name='edition'
|
||||
class='dropdown-radio-btn'
|
||||
as |G|
|
||||
{{#if this.session.data.authenticated.username}}
|
||||
<dd.ToggleButton
|
||||
@icon='user'
|
||||
@text={{this.session.data.authenticated.username}}
|
||||
/>
|
||||
{{else}}
|
||||
<dd.ToggleIcon @icon='user' @text={{t 'titles.user-menu'}} />
|
||||
{{/if}}
|
||||
|
||||
<dd.Interactive @route='account.change-password'>{{t
|
||||
'actions.change-password'
|
||||
}}</dd.Interactive>
|
||||
<dd.Separator />
|
||||
|
||||
<dd.Interactive {{on 'click' this.invalidateSession}}>{{t
|
||||
'actions.deauthenticate'
|
||||
}}</dd.Interactive>
|
||||
<dd.Separator />
|
||||
|
||||
<dd.Title @text={{t 'actions.toggle-color-theme'}} />
|
||||
{{#each this.themes as |theme|}}
|
||||
<dd.Radio
|
||||
@value={{theme.value}}
|
||||
checked={{eq this.session.data.theme theme.value}}
|
||||
{{on 'change' (fn this.toggleTheme theme.value)}}
|
||||
>
|
||||
{{#each this.featureEdition.editions as |edition|}}
|
||||
<G.RadioField
|
||||
@value={{edition}}
|
||||
checked={{eq this.featureEdition.edition edition}}
|
||||
{{on 'change' (fn this.toggleEdition edition)}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{edition}}</F.Label>
|
||||
</G.RadioField>
|
||||
{{/each}}
|
||||
</Hds::Form::Radio::Group>
|
||||
{{t (concat 'themes.' theme.label)}}
|
||||
</dd.Radio>
|
||||
{{/each}}
|
||||
</Hds::Dropdown>
|
||||
</:actions>
|
||||
</Hds::SideNav::Header>
|
||||
</:header>
|
||||
|
||||
</dropdown.section>
|
||||
<dropdown.separator />
|
||||
<dropdown.section @title={{t 'actions.toggle-licensed-features'}}>
|
||||
<Rose::Form as |form|>
|
||||
{{#each this.featureEdition.licensedFeatures as |feature|}}
|
||||
<dropdown.item>
|
||||
<form.checkbox
|
||||
@label={{feature}}
|
||||
@checked={{feature-flag feature}}
|
||||
@onChange={{fn this.toggleFeature feature}}
|
||||
/>
|
||||
</dropdown.item>
|
||||
{{/each}}
|
||||
</Rose::Form>
|
||||
</dropdown.section>
|
||||
</utilities.dropdown>
|
||||
{{/if}}
|
||||
<:body>
|
||||
<Hds::SideNav::Portal::Target />
|
||||
</:body>
|
||||
|
||||
</header.utilities>
|
||||
<:footer>
|
||||
<ScopePicker />
|
||||
</:footer>
|
||||
</Hds::SideNav>
|
||||
</Frame.Sidebar>
|
||||
|
||||
{{/if}}
|
||||
|
||||
</Rose::Header>
|
||||
</layout.header>
|
||||
|
||||
<layout.body>
|
||||
<Frame.Main>
|
||||
{{outlet}}
|
||||
</layout.body>
|
||||
</Frame.Main>
|
||||
|
||||
</Rose::Layout::Global>
|
||||
<Frame.Footer>
|
||||
<Hds::AppFooter @theme={{this.session.data.theme}} as |AF|>
|
||||
<AF.Link @href={{(doc-url)}}>{{t 'titles.documentation'}}</AF.Link>
|
||||
<AF.LegalLinks />
|
||||
</Hds::AppFooter>
|
||||
</Frame.Footer>
|
||||
</Hds::AppFrame>
|
||||
|
||||
{{#if this.flashMessages.queue}}
|
||||
<div class='ember-notify-cn'>
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Rose::Layout::Centered class='create-resources'>
|
||||
<Rose::Frame>
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Frame class='create-resources'>
|
||||
<:header>
|
||||
<h2>{{t 'onboarding.add-target.titles.have-a-target'}}</h2>
|
||||
<Hds::Icon @name='boundary-color' @size='24' @isInline={{true}} />
|
||||
<Hds::Text::Display @tag='h2' @size='300' @weight='bold'>{{t
|
||||
'onboarding.add-target.titles.have-a-target'
|
||||
}}</Hds::Text::Display>
|
||||
</:header>
|
||||
<:body>
|
||||
<p>{{t 'onboarding.add-target.descriptions.setup-first-target'}}</p>
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
}}
|
||||
|
||||
{{page-title (t 'titles.success')}}
|
||||
<Rose::Layout::Centered class='create-resources'>
|
||||
<Rose::Frame>
|
||||
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Frame class='create-resources'>
|
||||
<:header>
|
||||
<h2>{{t 'onboarding.success.titles.connect'}}</h2>
|
||||
<Hds::Icon @name='boundary-color' @size='24' @isInline={{true}} />
|
||||
<Hds::Text::Display @tag='h2' @size='300' @weight='bold'>{{t
|
||||
'onboarding.success.titles.connect'
|
||||
}}</Hds::Text::Display>
|
||||
</:header>
|
||||
<:body>
|
||||
{{! We have created }}
|
||||
@@ -27,7 +31,6 @@
|
||||
{{! Download Desktop client }}
|
||||
<div class='create-resources-item'>
|
||||
<span class='create-resources-item-header'>
|
||||
<Hds::Icon @name='boundary-color' @size='24' @isInline={{true}} />
|
||||
<h3>{{t 'onboarding.success.titles.desktop'}}</h3>
|
||||
</span>
|
||||
<p>{{t 'onboarding.success.descriptions.download'}}</p>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{outlet}}
|
||||
|
||||
{{#unless @model}}
|
||||
<Rose::Layout::Sidebar as |sidebarLayout|>
|
||||
<sidebarLayout.body>
|
||||
<main>
|
||||
<Rose::Layout::Centered>
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header
|
||||
@title={{t 'auth.titles.no-scopes'}}
|
||||
@icon='alert-circle'
|
||||
/>
|
||||
<A.Body @text={{t 'auth.descriptions.no-scopes'}} />
|
||||
</Hds::ApplicationState>
|
||||
</Rose::Layout::Centered>
|
||||
</main>
|
||||
</sidebarLayout.body>
|
||||
<sidebarLayout.footer>
|
||||
<Hds::AppFooter @theme={{this.session.data.theme}} as |AF|>
|
||||
<AF.Link @href={{(doc-url)}}>{{t 'titles.documentation'}}</AF.Link>
|
||||
<AF.LegalLinks />
|
||||
</Hds::AppFooter>
|
||||
</sidebarLayout.footer>
|
||||
</Rose::Layout::Sidebar>
|
||||
{{/unless}}
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
{{#if @model.isProject}}
|
||||
{{page-title (t 'resources.org.title_plural')}}
|
||||
{{page-title this.scopes.selectedOrg.displayName}}
|
||||
{{page-title this.scope.org.displayName}}
|
||||
{{page-title (t 'resources.project.title_plural')}}
|
||||
{{page-title @model.displayName}}
|
||||
<Breadcrumbs::Item
|
||||
@text={{this.scopes.selectedOrg.displayName}}
|
||||
@text={{this.scope.org.displayName}}
|
||||
@icon='org'
|
||||
@route='scopes.scope.edit'
|
||||
@model={{this.scopes.selectedOrg.id}}
|
||||
@model={{this.scope.org.id}}
|
||||
/>
|
||||
<Breadcrumbs::Item
|
||||
@text={{@model.displayName}}
|
||||
@@ -33,218 +33,217 @@
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<Rose::Layout::Sidebar as |sidebarLayout|>
|
||||
<Hds::SideNav::Portal as |Nav|>
|
||||
{{#if @model.isGlobal}}
|
||||
<Nav.Title>{{t 'titles.scope-level' scope=(t 'titles.global')}}</Nav.Title>
|
||||
|
||||
{{#if this.session.isAuthenticated}}
|
||||
<sidebarLayout.sidebar>
|
||||
<Nav.Link
|
||||
@icon='org'
|
||||
@text={{t 'resources.org.title_plural'}}
|
||||
@route='scopes.scope.scopes'
|
||||
/>
|
||||
|
||||
{{#if @model.isGlobal}}
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.general'}} as |nav|>
|
||||
<nav.link @route='scopes.scope.scopes'>
|
||||
<Rose::Icon @name='flight-icons/svg/org-16' @size='medium' />
|
||||
{{t 'resources.org.title_plural'}}
|
||||
</nav.link>
|
||||
{{#if (can 'navigate scope' @model collection='session-recordings')}}
|
||||
<Nav.Link
|
||||
@icon='film'
|
||||
@text={{t 'resources.session-recording.title_plural'}}
|
||||
@route='scopes.scope.session-recordings'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'navigate scope' @model collection='session-recordings')}}
|
||||
<nav.link @route='scopes.scope.session-recordings'>
|
||||
<Rose::Icon @name='flight-icons/svg/film-16' @size='medium' />
|
||||
{{t 'resources.session-recording.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate scope' @model collection='storage-buckets')}}
|
||||
<nav.link @route='scopes.scope.storage-buckets'>
|
||||
<Rose::Icon
|
||||
@name='flight-icons/svg/cloud-upload-16'
|
||||
@size='medium'
|
||||
/>
|
||||
{{t 'resources.storage-bucket.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate scope' @model collection='policies')}}
|
||||
<nav.link @route='scopes.scope.policies'>
|
||||
<Rose::Icon
|
||||
@name='flight-icons/svg/hourglass-16'
|
||||
@size='medium'
|
||||
/>
|
||||
{{t 'resources.policy.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate scope' @model collection='aliases')}}
|
||||
<nav.link @route='scopes.scope.aliases'>
|
||||
<Rose::Icon @name='flight-icons/svg/at-sign-16' @size='medium' />
|
||||
{{t 'resources.alias.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if
|
||||
(and
|
||||
(can 'navigate worker' @model collection='workers')
|
||||
(feature-flag 'byow')
|
||||
)
|
||||
}}
|
||||
<nav.link @route='scopes.scope.workers'>
|
||||
<Rose::Icon @name='flight-icons/svg/cpu-16' @size='medium' />
|
||||
{{t 'titles.workers'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
</Rose::Nav::Sidebar>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate scope' @model collection='storage-buckets')}}
|
||||
<Nav.Link
|
||||
@icon='cloud-upload'
|
||||
@text={{t 'resources.storage-bucket.title_plural'}}
|
||||
@route='scopes.scope.storage-buckets'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.isOrg}}
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.general'}} as |nav|>
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<nav.link @route='scopes.scope.scopes' @model='global'>
|
||||
<Rose::Icon @name='flight-icons/svg/org-16' @size='medium' />
|
||||
{{t 'resources.org.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
<nav.link @route='scopes.scope.scopes'>
|
||||
<Rose::Icon @name='flight-icons/svg/grid-16' @size='medium' />
|
||||
{{t 'resources.project.title_plural'}}
|
||||
</nav.link>
|
||||
{{#if (can 'navigate scope' @model collection='policies')}}
|
||||
<Nav.Link
|
||||
@icon='hourglass'
|
||||
@text={{t 'resources.policy.title_plural'}}
|
||||
@route='scopes.scope.policies'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'navigate scope' @model collection='policies')}}
|
||||
<nav.link @route='scopes.scope.policies'>
|
||||
<Rose::Icon
|
||||
@name='flight-icons/svg/hourglass-16'
|
||||
@size='medium'
|
||||
/>
|
||||
{{t 'resources.policy.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
</Rose::Nav::Sidebar>
|
||||
{{#if (can 'navigate scope' @model collection='aliases')}}
|
||||
<Nav.Link
|
||||
@icon='at-sign'
|
||||
@text={{t 'resources.alias.title_plural'}}
|
||||
@route='scopes.scope.aliases'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
{{#if
|
||||
(and
|
||||
(can 'navigate worker' @model collection='workers')
|
||||
(feature-flag 'byow')
|
||||
)
|
||||
}}
|
||||
<Nav.Link
|
||||
@icon='cpu'
|
||||
@text={{t 'titles.workers'}}
|
||||
@route='scopes.scope.workers'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.isProject}}
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.general'}} as |nav|>
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<nav.link @route='scopes.scope.scopes' @model='global'>
|
||||
<Rose::Icon @name='flight-icons/svg/org-16' @size='medium' />
|
||||
{{t 'resources.org.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
<nav.link @route='scopes.scope.scopes' @model={{@model.scopeID}}>
|
||||
<Rose::Icon @name='flight-icons/svg/grid-16' @size='medium' />
|
||||
{{t 'resources.project.title_plural'}}
|
||||
</nav.link>
|
||||
</Rose::Nav::Sidebar>
|
||||
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.resources'}} as |nav|>
|
||||
{{#if (can 'navigate model' @model collection='sessions')}}
|
||||
<nav.link @route='scopes.scope.sessions'>
|
||||
<Rose::Icon
|
||||
@name='flight-icons/svg/entry-point-16'
|
||||
@size='medium'
|
||||
/>
|
||||
{{t 'resources.session.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate model' @model collection='targets')}}
|
||||
<nav.link @route='scopes.scope.targets'>
|
||||
<Rose::Icon @name='flight-icons/svg/target-16' @size='medium' />
|
||||
{{t 'resources.target.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate model' @model collection='host-catalogs')}}
|
||||
<nav.link @route='scopes.scope.host-catalogs'>
|
||||
<Rose::Icon
|
||||
@name='flight-icons/svg/collections-16'
|
||||
@size='medium'
|
||||
/>
|
||||
{{t 'resources.host-catalog.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate model' @model collection='credential-stores')}}
|
||||
<nav.link @route='scopes.scope.credential-stores'>
|
||||
<Rose::Icon @name='flight-icons/svg/key-16' @size='medium' />
|
||||
{{t 'resources.credential-store.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
</Rose::Nav::Sidebar>
|
||||
{{/if}}
|
||||
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.iam'}} as |nav|>
|
||||
{{! Users are available only for org/global scopes }}
|
||||
{{#unless @model.isProject}}
|
||||
{{#if (can 'navigate model' @model collection='users')}}
|
||||
<nav.link @route='scopes.scope.users'>
|
||||
<Rose::Icon @name='flight-icons/svg/users-16' @size='medium' />
|
||||
{{t 'resources.user.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{! Groups and roles are available for all scopes }}
|
||||
{{#if (can 'navigate model' @model collection='groups')}}
|
||||
<nav.link @route='scopes.scope.groups'>
|
||||
<Rose::Icon
|
||||
@name='flight-icons/svg/folder-users-16'
|
||||
@size='medium'
|
||||
/>
|
||||
{{t 'resources.group.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate model' @model collection='roles')}}
|
||||
<nav.link @route='scopes.scope.roles'>
|
||||
<Rose::Icon
|
||||
@name='flight-icons/svg/identity-user-16'
|
||||
@size='medium'
|
||||
/>
|
||||
{{t 'resources.role.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
|
||||
{{! Auth methods are available only for org/global scopes }}
|
||||
{{#unless @model.isProject}}
|
||||
{{#if (can 'navigate model' @model collection='auth-methods')}}
|
||||
<nav.link @route='scopes.scope.auth-methods'>
|
||||
<Rose::Icon @name='flight-icons/svg/lock-16' @size='medium' />
|
||||
{{t 'resources.auth-method.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
</Rose::Nav::Sidebar>
|
||||
|
||||
{{#if (and @model.isGlobal (feature-flag 'ssh-session-recording'))}}
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.settings'}} as |nav|>
|
||||
<nav.link @route='scopes.scope.edit'>
|
||||
{{t 'resources.global.messages.settings.title'}}
|
||||
</nav.link>
|
||||
</Rose::Nav::Sidebar>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.isOrg}}
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.settings'}} as |nav|>
|
||||
<nav.link @route='scopes.scope.edit'>
|
||||
{{t 'resources.org.messages.settings.title'}}
|
||||
</nav.link>
|
||||
</Rose::Nav::Sidebar>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.isProject}}
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.settings'}} as |nav|>
|
||||
<nav.link @route='scopes.scope.edit'>
|
||||
{{t 'resources.project.messages.settings.title'}}
|
||||
</nav.link>
|
||||
</Rose::Nav::Sidebar>
|
||||
{{/if}}
|
||||
|
||||
</sidebarLayout.sidebar>
|
||||
<Nav.Title>{{t 'titles.scope-iam' scope=(t 'titles.global')}}</Nav.Title>
|
||||
{{/if}}
|
||||
|
||||
<sidebarLayout.body>
|
||||
<main>
|
||||
{{outlet}}
|
||||
</main>
|
||||
</sidebarLayout.body>
|
||||
<sidebarLayout.footer>
|
||||
<Hds::AppFooter @theme={{this.session.data.theme}} as |AF|>
|
||||
<AF.Link @href={{(doc-url)}}>{{t 'titles.documentation'}}</AF.Link>
|
||||
<AF.LegalLinks />
|
||||
</Hds::AppFooter>
|
||||
</sidebarLayout.footer>
|
||||
</Rose::Layout::Sidebar>
|
||||
{{#if @model.isOrg}}
|
||||
<Nav.Title>{{t
|
||||
'titles.scope-level'
|
||||
scope=(t 'resources.org.title')
|
||||
}}</Nav.Title>
|
||||
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<Nav.Link
|
||||
@icon='org'
|
||||
@text={{t 'resources.org.title_plural'}}
|
||||
@route='scopes.scope.scopes'
|
||||
@model='global'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<Nav.Link
|
||||
@icon='grid'
|
||||
@text={{t 'resources.project.title_plural'}}
|
||||
@route='scopes.scope.scopes'
|
||||
/>
|
||||
|
||||
{{#if (can 'navigate scope' @model collection='policies')}}
|
||||
<Nav.Link
|
||||
@icon='hourglass'
|
||||
@text={{t 'resources.policy.title_plural'}}
|
||||
@route='scopes.scope.policies'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<Nav.Title>{{t
|
||||
'titles.scope-iam'
|
||||
scope=(t 'resources.org.title')
|
||||
}}</Nav.Title>
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.isProject}}
|
||||
<Nav.BackLink
|
||||
@text={{t 'titles.back-link' scope=this.scope.org.displayName}}
|
||||
@route='scopes.scope.scopes'
|
||||
@model={{@model.scopeID}}
|
||||
/>
|
||||
|
||||
<Nav.Title>{{t 'titles.project-actions'}}</Nav.Title>
|
||||
|
||||
{{#if (can 'navigate model' @model collection='sessions')}}
|
||||
<Nav.Link
|
||||
@icon='entry-point'
|
||||
@text={{t 'resources.session.title_plural'}}
|
||||
@route='scopes.scope.sessions'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'navigate model' @model collection='targets')}}
|
||||
<Nav.Link
|
||||
@icon='target'
|
||||
@text={{t 'resources.target.title_plural'}}
|
||||
@route='scopes.scope.targets'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'navigate model' @model collection='host-catalogs')}}
|
||||
<Nav.Link
|
||||
@icon='collections'
|
||||
@text={{t 'resources.host-catalog.title_plural'}}
|
||||
@route='scopes.scope.host-catalogs'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'navigate model' @model collection='credential-stores')}}
|
||||
<Nav.Link
|
||||
@icon='key'
|
||||
@text={{t 'resources.credential-store.title_plural'}}
|
||||
@route='scopes.scope.credential-stores'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<Nav.Title>{{t
|
||||
'titles.scope-iam'
|
||||
scope=(t 'resources.project.title')
|
||||
}}</Nav.Title>
|
||||
{{/if}}
|
||||
|
||||
{{! Users are available only for org/global scopes }}
|
||||
{{#unless @model.isProject}}
|
||||
{{#if (can 'navigate model' @model collection='users')}}
|
||||
<Nav.Link
|
||||
@icon='users'
|
||||
@text={{t 'resources.user.title_plural'}}
|
||||
@route='scopes.scope.users'
|
||||
/>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{! Groups and roles are available for all scopes }}
|
||||
{{#if (can 'navigate model' @model collection='groups')}}
|
||||
<Nav.Link
|
||||
@icon='folder-users'
|
||||
@text={{t 'resources.group.title_plural'}}
|
||||
@route='scopes.scope.groups'
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate model' @model collection='roles')}}
|
||||
<Nav.Link
|
||||
@icon='identity-user'
|
||||
@text={{t 'resources.role.title_plural'}}
|
||||
@route='scopes.scope.roles'
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{! Auth methods are available only for org/global scopes }}
|
||||
{{#unless @model.isProject}}
|
||||
{{#if (can 'navigate model' @model collection='auth-methods')}}
|
||||
<Nav.Link
|
||||
@icon='lock'
|
||||
@text={{t 'resources.auth-method.title_plural'}}
|
||||
@route='scopes.scope.auth-methods'
|
||||
/>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if (and @model.isGlobal (feature-flag 'ssh-session-recording'))}}
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<Nav.Title>{{t 'titles.settings'}}</Nav.Title>
|
||||
|
||||
<Nav.Link
|
||||
@icon='settings'
|
||||
@text={{t 'resources.global.messages.settings.title'}}
|
||||
@route='scopes.scope.edit'
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.isOrg}}
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<Nav.Title>{{t 'titles.settings'}}</Nav.Title>
|
||||
|
||||
<Nav.Link
|
||||
@icon='settings'
|
||||
@text={{t 'resources.org.messages.settings.title'}}
|
||||
@route='scopes.scope.edit'
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.isProject}}
|
||||
<Nav.Title>{{t 'titles.settings'}}</Nav.Title>
|
||||
|
||||
<Nav.Link
|
||||
@icon='settings'
|
||||
@text={{t 'resources.project.messages.settings.title'}}
|
||||
@route='scopes.scope.edit'
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::SideNav::Portal>
|
||||
|
||||
{{outlet}}
|
||||
@@ -1,23 +0,0 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Rose::Nav::Sidebar @title={{t 'titles.iam'}} as |nav|>
|
||||
<nav.link @route='scopes.scope.users'>
|
||||
<Rose::Icon @name='flight-icons/svg/users-16' @size='medium' />
|
||||
{{t 'resources.user.title_plural'}}
|
||||
</nav.link>
|
||||
<nav.link @route='scopes.scope.groups'>
|
||||
<Rose::Icon @name='flight-icons/svg/folder-users-16' @size='medium' />
|
||||
{{t 'resources.group.title_plural'}}
|
||||
</nav.link>
|
||||
<nav.link @route='scopes.scope.roles'>
|
||||
<Rose::Icon @name='flight-icons/svg/identity-user-16' @size='medium' />
|
||||
{{t 'resources.role.title_plural'}}
|
||||
</nav.link>
|
||||
<nav.link @route='scopes.scope.auth-methods'>
|
||||
<Rose::Icon @name='flight-icons/svg/lock-16' @size='medium' />
|
||||
{{t 'resources.auth-method.title_plural'}}
|
||||
</nav.link>
|
||||
</Rose::Nav::Sidebar>
|
||||
@@ -33,7 +33,7 @@
|
||||
<page.actions>
|
||||
|
||||
{{#if (or perms.canUpdate perms.canDelete)}}
|
||||
<Hds::Dropdown as |dd|>
|
||||
<Hds::Dropdown data-test-manage-alias as |dd|>
|
||||
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||
{{#if (and perms.canUpdate @model.destination_id)}}
|
||||
<dd.Interactive
|
||||
|
||||
@@ -12,13 +12,16 @@
|
||||
|
||||
<Rose::Frame>
|
||||
<:header>
|
||||
<h2>{{t 'actions.authenticate'}}</h2>
|
||||
<Hds::Icon @name='boundary-color' @size='24' @isInline={{true}} />
|
||||
<Hds::Text::Display @tag='h2' @size='300' @weight='bold'>{{t
|
||||
'actions.authenticate'
|
||||
}}</Hds::Text::Display>
|
||||
</:header>
|
||||
<:body>
|
||||
<p>{{t
|
||||
<Hds::Text::Body @tag='p'>{{t
|
||||
'auth.descriptions.authenticating-with-org'
|
||||
name=@model.scope.displayName
|
||||
}}</p>
|
||||
}}</Hds::Text::Body>
|
||||
|
||||
<Rose::Dropdown
|
||||
@text={{t 'actions.choose-different-org'}}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<page.actions>
|
||||
{{#if (or perms.canUpdate perms.canDelete)}}
|
||||
|
||||
<Hds::Dropdown as |dd|>
|
||||
<Hds::Dropdown data-test-manage-target-alias as |dd|>
|
||||
<dd.ToggleButton
|
||||
data-test-manage-dropdown
|
||||
@text={{t 'actions.manage'}}
|
||||
|
||||
@@ -58,22 +58,13 @@ module('Acceptance | accounts | change password', function (hooks) {
|
||||
test('visiting account change password', async function (assert) {
|
||||
await visit(urls.orgScope);
|
||||
|
||||
await click(commonSelectors.SIDEBAR_USER_DROPDOWN);
|
||||
await click(commonSelectors.HREF(urls.changePassword));
|
||||
await a11yAudit();
|
||||
|
||||
assert.strictEqual(currentURL(), urls.changePassword);
|
||||
});
|
||||
|
||||
test('visiting account change password from header', async function (assert) {
|
||||
await visit(urls.orgScope);
|
||||
// Open header utilities dropdown
|
||||
await click(selectors.APP_HEADER_DROPDOWN);
|
||||
// Find first element in dropdown - should be change password link
|
||||
await click(commonSelectors.HREF(urls.changePassword));
|
||||
|
||||
assert.strictEqual(currentURL(), urls.changePassword);
|
||||
});
|
||||
|
||||
test('can change password for account', async function (assert) {
|
||||
assert.expect(2);
|
||||
// TODO: address issue with ICU-15021
|
||||
@@ -145,6 +136,7 @@ module('Acceptance | accounts | change password', function (hooks) {
|
||||
});
|
||||
await visit(urls.orgScope);
|
||||
|
||||
await click(commonSelectors.SIDEBAR_USER_DROPDOWN);
|
||||
await click(commonSelectors.HREF(urls.changePassword));
|
||||
await fillIn(
|
||||
selectors.FIELD_CURRENT_PASSWORD,
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export const APP_HEADER_DROPDOWN =
|
||||
'.rose-header-utilities details:first-child summary';
|
||||
|
||||
export const MANAGE_DROPDOWN_AUTH_METHOD =
|
||||
'[data-test-manage-auth-method] button';
|
||||
export const MANAGE_DROPDOWN_ACCOUNT =
|
||||
|
||||
@@ -17,7 +17,8 @@ module('Acceptance | aliases | delete', function (hooks) {
|
||||
|
||||
let aliasCount;
|
||||
|
||||
const DROPDOWN_BUTTON_SELECTOR = '.hds-dropdown-toggle-icon';
|
||||
const DROPDOWN_BUTTON_SELECTOR =
|
||||
'tbody tr td:last-child .hds-dropdown-toggle-icon';
|
||||
const DELETE_DROPDOWN_SELECTOR =
|
||||
'.hds-dropdown-list-item--color-critical button';
|
||||
|
||||
@@ -35,7 +36,6 @@ module('Acceptance | aliases | delete', function (hooks) {
|
||||
globalScope: null,
|
||||
aliases: null,
|
||||
alias: null,
|
||||
target: null,
|
||||
};
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import * as commonSelectors from 'admin/tests/helpers/selectors';
|
||||
|
||||
module('Acceptance | aliases | list', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -116,7 +117,7 @@ module('Acceptance | aliases | list', function (hooks) {
|
||||
),
|
||||
);
|
||||
assert
|
||||
.dom('[title="General"] a:nth-of-type(1)')
|
||||
.dom(commonSelectors.SIDEBAR_NAV_CONTENT)
|
||||
.doesNotIncludeText(ALIAS_TITLE);
|
||||
|
||||
await visit(urls.aliases);
|
||||
|
||||
@@ -62,7 +62,7 @@ module('Acceptance | aliases | update', function (hooks) {
|
||||
await click(commonSelectors.EDIT_BTN);
|
||||
await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE);
|
||||
await click(commonSelectors.SAVE_BTN);
|
||||
|
||||
|
||||
assert.strictEqual(instances.alias.name, commonSelectors.FIELD_NAME_VALUE);
|
||||
});
|
||||
|
||||
@@ -86,7 +86,6 @@ module('Acceptance | aliases | update', function (hooks) {
|
||||
assert.true(instances.alias.authorized_actions.includes('update'));
|
||||
await visit(urls.globalScope);
|
||||
|
||||
|
||||
await click(commonSelectors.HREF(urls.aliases));
|
||||
await click(commonSelectors.TABLE_FIRST_ROW_ACTION_DROPDOWN);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
TYPE_AUTH_METHOD_PASSWORD,
|
||||
TYPE_AUTH_METHOD_OIDC,
|
||||
} from 'api/models/auth-method';
|
||||
import * as commonSelectors from 'admin/tests/helpers/selectors';
|
||||
|
||||
module('Acceptance | authentication', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -342,8 +343,11 @@ module('Acceptance | authentication', function (hooks) {
|
||||
assert.ok(getRootElement().classList.contains('rose-theme-light'));
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-dark'));
|
||||
|
||||
// open dropdown
|
||||
await click(commonSelectors.SIDEBAR_USER_DROPDOWN);
|
||||
|
||||
// toggle system default
|
||||
await click('[name="theme"][value="system-default-theme"]');
|
||||
await click('[value="system-default-theme"]');
|
||||
|
||||
assert.strictEqual(
|
||||
currentSession().get('data.theme'),
|
||||
@@ -353,14 +357,14 @@ module('Acceptance | authentication', function (hooks) {
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-dark'));
|
||||
|
||||
// toggle dark mode
|
||||
await click('[name="theme"][value="dark"]');
|
||||
await click('[value="dark"]');
|
||||
|
||||
assert.strictEqual(currentSession().get('data.theme'), 'dark');
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-light'));
|
||||
assert.ok(getRootElement().classList.contains('rose-theme-dark'));
|
||||
|
||||
// toggle system default
|
||||
await click('[name="theme"][value="system-default-theme"]');
|
||||
await click('[value="system-default-theme"]');
|
||||
|
||||
assert.strictEqual(
|
||||
currentSession().get('data.theme'),
|
||||
|
||||
@@ -7,17 +7,26 @@ import { module, test } from 'qunit';
|
||||
import { visit, getContext } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
|
||||
module('Acceptance | develop', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
|
||||
let featuresService;
|
||||
const DEV_TOGGLE_SELECTOR = '[data-test-dev-edition-toggle]';
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
hooks.beforeEach(async function () {
|
||||
const { owner } = getContext();
|
||||
featuresService = owner.lookup('service:features');
|
||||
this.server.create('scope', { id: 'global' });
|
||||
this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
await authenticateSession({});
|
||||
});
|
||||
|
||||
test('edition toggle is hidden when `dev-edition-toggle` is false', async function (assert) {
|
||||
|
||||
@@ -90,7 +90,7 @@ module('Acceptance | policies | list', function (hooks) {
|
||||
].includes('create'),
|
||||
);
|
||||
assert
|
||||
.dom(commonSelectors.GENERAL_SIDEBAR_NAV_LINK(urls.policies))
|
||||
.dom(commonSelectors.SIDEBAR_NAV_LINK(urls.policies))
|
||||
.doesNotExist();
|
||||
|
||||
await visit(urls.policies);
|
||||
|
||||
@@ -85,7 +85,7 @@ module('Acceptance | roles | create', function (hooks) {
|
||||
),
|
||||
);
|
||||
assert.dom(selectors.NEW_ROLE_BTN).isVisible();
|
||||
assert.dom(commonSelectors.IAM_SIDEBAR_NAV_LINK(urls.roles)).isVisible();
|
||||
assert.dom(commonSelectors.SIDEBAR_NAV_LINK(urls.roles)).isVisible();
|
||||
assert.dom(commonSelectors.HREF(urls.newRole)).isVisible();
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ module('Acceptance | roles | create', function (hooks) {
|
||||
),
|
||||
);
|
||||
assert.dom(selectors.NEW_ROLE_BTN).doesNotExist();
|
||||
assert.dom(commonSelectors.IAM_SIDEBAR_NAV_LINK(urls.roles)).doesNotExist();
|
||||
assert.dom(commonSelectors.SIDEBAR_NAV_LINK(urls.roles)).doesNotExist();
|
||||
assert.dom(commonSelectors.HREF(urls.newRole)).doesNotExist();
|
||||
});
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ module('Acceptance | roles | principals', function (hooks) {
|
||||
setupMirage(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
|
||||
const MANAGE_DROPDOWN_SELECTOR = '.hds-dropdown-toggle-button';
|
||||
const ADD_PRINCIPALS_SELECTOR = '[data-test-manage-dropdown-principals]';
|
||||
const MANAGE_DROPDOWN_SELECTOR = '[data-test-manage-roles-dropdown] button';
|
||||
const ADD_PRINCIPALS_SELECTOR = '[data-test-manage-role-principals]';
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
@@ -70,7 +70,7 @@ module('Acceptance | roles | principals', function (hooks) {
|
||||
test('principal can be removed from a role', async function (assert) {
|
||||
await visit(urls.rolePrincipals);
|
||||
assert.strictEqual(findAll('tbody tr').length, principalsCount);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.strictEqual(findAll('tbody tr').length, principalsCount - 1);
|
||||
});
|
||||
@@ -99,7 +99,7 @@ module('Acceptance | roles | principals', function (hooks) {
|
||||
});
|
||||
await visit(urls.rolePrincipals);
|
||||
assert.strictEqual(findAll('tbody tr').length, principalsCount);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert
|
||||
.dom(commonSelectors.ALERT_TOAST_BODY)
|
||||
@@ -135,7 +135,7 @@ module('Acceptance | roles | principals', function (hooks) {
|
||||
await visit(urls.rolePrincipals);
|
||||
assert.strictEqual(findAll('tbody tr').length, principalsCount);
|
||||
// Remove a principal to populate association view
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.strictEqual(findAll('tbody tr').length, principalsCount - 1);
|
||||
await click(MANAGE_DROPDOWN_SELECTOR);
|
||||
|
||||
@@ -18,7 +18,7 @@ module('Acceptance | roles | project-scope', function (hooks) {
|
||||
const GRANT_SCOPE_ROW_SELECTOR = (id) =>
|
||||
`tbody [data-test-grant-scope-row="${id}"]`;
|
||||
const TABLE_ROW_SELECTOR = 'tbody tr';
|
||||
const MANAGE_DROPDOWN_SELECTOR = '.hds-dropdown-toggle-button';
|
||||
const MANAGE_DROPDOWN_SELECTOR = '[data-test-manage-roles-dropdown] button';
|
||||
const MANAGE_SCOPES_SELECTOR = '[data-test-manage-dropdown-scopes]';
|
||||
const SEARCH_INPUT_SELECTOR = '.search-filtering [type="search"]';
|
||||
const NO_SCOPES_MSG_SELECTOR = '.role-grant-scopes div';
|
||||
|
||||
@@ -18,10 +18,8 @@ module('Acceptance | scopes', function (hooks) {
|
||||
setupMirage(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
|
||||
const ORG_SCOPES_DROPDOWN_SELECTOR = (url) =>
|
||||
`.rose-header-nav .rose-dropdown [href="${url}"]`;
|
||||
const PROJECT_SCOPES_DROPDOWN_SELECTOR = (url) =>
|
||||
`.rose-header-nav .rose-dropdown + .rose-dropdown [href="${url}"]`;
|
||||
const SCOPES_DROPDOWN_BTN = '.scope-picker button';
|
||||
const SCOPES_DROPDOWN_SELECTOR = (url) => `.scope-picker [href="${url}"]`;
|
||||
const SAVE_BUTTON_SELECTOR = '[type="submit"]';
|
||||
const CANCEL_BUTTON_SELECTOR = '.rose-form-actions [type="button"]';
|
||||
const NAME_FIELD_SELECTOR = '[name="name"]';
|
||||
@@ -118,26 +116,29 @@ module('Acceptance | scopes', function (hooks) {
|
||||
assert.strictEqual(currentURL(), urls.orgScopes);
|
||||
});
|
||||
|
||||
test('can navigate among org scopes via header navigation', async function (assert) {
|
||||
test('can navigate among org scopes via side-nav', async function (assert) {
|
||||
await visit(urls.globalScope);
|
||||
await a11yAudit();
|
||||
|
||||
assert.strictEqual(currentURL(), urls.globalScope);
|
||||
|
||||
await click(ORG_SCOPES_DROPDOWN_SELECTOR(urls.orgScope));
|
||||
await click(SCOPES_DROPDOWN_BTN);
|
||||
await click(SCOPES_DROPDOWN_SELECTOR(urls.orgScope));
|
||||
assert.strictEqual(currentURL(), urls.orgScopes);
|
||||
|
||||
await click(ORG_SCOPES_DROPDOWN_SELECTOR(urls.org2Scope));
|
||||
await click(SCOPES_DROPDOWN_BTN);
|
||||
await click(SCOPES_DROPDOWN_SELECTOR(urls.org2Scope));
|
||||
assert.strictEqual(currentURL(), urls.org2Scopes);
|
||||
});
|
||||
|
||||
test('can navigate among project scopes via header navigation', async function (assert) {
|
||||
test('can navigate among project scopes via side-nav', async function (assert) {
|
||||
await visit(urls.projectScope);
|
||||
await a11yAudit();
|
||||
|
||||
assert.strictEqual(currentURL(), urls.projectScopeEdit);
|
||||
|
||||
await click(PROJECT_SCOPES_DROPDOWN_SELECTOR(urls.project2Scope));
|
||||
await click(SCOPES_DROPDOWN_BTN);
|
||||
await click(SCOPES_DROPDOWN_SELECTOR(urls.project2Scope));
|
||||
|
||||
assert.strictEqual(currentURL(), urls.project2ScopeEdit);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { visit, fillIn, waitFor } from '@ember/test-helpers';
|
||||
import { currentURL, visit, fillIn, waitFor } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
@@ -28,6 +28,7 @@ module('Acceptance | scopes | list', function (hooks) {
|
||||
};
|
||||
|
||||
const urls = {
|
||||
scopes: null,
|
||||
globalScope: null,
|
||||
orgScope: null,
|
||||
};
|
||||
@@ -50,12 +51,20 @@ module('Acceptance | scopes | list', function (hooks) {
|
||||
type: 'project',
|
||||
scope: { id: instances.scopes.org1.id, type: 'org' },
|
||||
});
|
||||
urls.globalScope = `/scopes/global/scopes`;
|
||||
|
||||
urls.scopes = '/scopes';
|
||||
urls.globalScope = '/scopes/global/scopes';
|
||||
urls.orgScope = `/scopes/${instances.scopes.org1.id}/scopes`;
|
||||
await authenticateSession({});
|
||||
});
|
||||
|
||||
test('user can search for a specifc org scope by id', async function (assert) {
|
||||
test('user gets redirected to scopes list view', async function (assert) {
|
||||
await visit(urls.scopes);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.globalScope);
|
||||
});
|
||||
|
||||
test('user can search for a specific org scope by id', async function (assert) {
|
||||
await visit(urls.globalScope);
|
||||
|
||||
assert
|
||||
@@ -102,7 +111,7 @@ module('Acceptance | scopes | list', function (hooks) {
|
||||
.doesNotExist();
|
||||
});
|
||||
|
||||
test('user can search for a specifc project scope by id', async function (assert) {
|
||||
test('user can search for a specific project scope by id', async function (assert) {
|
||||
await visit(urls.orgScope);
|
||||
|
||||
assert
|
||||
|
||||
@@ -134,7 +134,9 @@ module('Acceptance | session recordings | list', function (hooks) {
|
||||
].includes('list'),
|
||||
);
|
||||
assert.dom(`[href="${urls.sessionRecordings}"]`).exists();
|
||||
assert.dom('[title="General"]').includesText(SESSION_RECORDING_TITLE);
|
||||
assert
|
||||
.dom(commonSelectors.SIDEBAR_NAV_CONTENT)
|
||||
.includesText(SESSION_RECORDING_TITLE);
|
||||
|
||||
// Visit session recordings
|
||||
await click(`[href="${urls.sessionRecordings}"]`);
|
||||
@@ -153,7 +155,9 @@ module('Acceptance | session recordings | list', function (hooks) {
|
||||
'session-recordings'
|
||||
].includes('list'),
|
||||
);
|
||||
assert.dom('[title="General"]').doesNotIncludeText(SESSION_RECORDING_TITLE);
|
||||
assert
|
||||
.dom(commonSelectors.SIDEBAR_NAV_CONTENT)
|
||||
.doesNotIncludeText(SESSION_RECORDING_TITLE);
|
||||
assert.dom(`[href="${urls.sessionRecordings}"]`).doesNotExist();
|
||||
});
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ module('Acceptance | sessions | list', function (hooks) {
|
||||
);
|
||||
|
||||
assert
|
||||
.dom(commonSelectors.RESOURCES_SIDEBAR_NAV_LINK(urls.sessions))
|
||||
.dom(commonSelectors.SIDEBAR_NAV_LINK(urls.sessions))
|
||||
.doesNotExist();
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ module('Acceptance | storage-buckets | delete', function (hooks) {
|
||||
let features;
|
||||
let getStorageBucketCount;
|
||||
|
||||
const DROPDOWN_BUTTON_SELECTOR = '.hds-dropdown-toggle-icon';
|
||||
const DROPDOWN_BUTTON_SELECTOR = 'tbody tr td:last-child button';
|
||||
const DELETE_DROPDOWN_SELECTOR = '.hds-dropdown-list-item [type="button"]';
|
||||
const NOTIFICATION_MSG_TEXT = 'Deleted successfully.';
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
|
||||
].includes('create'),
|
||||
);
|
||||
assert
|
||||
.dom(commonSelectors.GENERAL_SIDEBAR_NAV_LINK(urls.storageBuckets))
|
||||
.dom(commonSelectors.SIDEBAR_NAV_LINK(urls.storageBuckets))
|
||||
.doesNotExist();
|
||||
|
||||
// Tests that correct message is displayed when no buckets exist
|
||||
@@ -177,7 +177,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
|
||||
await visit(urls.globalScope);
|
||||
assert.false(featuresService.isEnabled('ssh-session-recording'));
|
||||
assert
|
||||
.dom(commonSelectors.GENERAL_SIDEBAR_NAV_LINK(urls.storageBuckets))
|
||||
.dom(commonSelectors.SIDEBAR_NAV_LINK(urls.storageBuckets))
|
||||
.doesNotExist();
|
||||
});
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) {
|
||||
const availableCredentialsCount = getCredentialCount();
|
||||
await visit(urls.brokeredCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount - 1);
|
||||
await visit(urls.addBrokeredCredentialSources);
|
||||
@@ -327,7 +327,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) {
|
||||
const availableCredentialsCount = getCredentialLibraryCount();
|
||||
await visit(urls.brokeredCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, credentialCount);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.strictEqual(findAll('tbody tr').length, credentialCount - 1);
|
||||
await visit(urls.addBrokeredCredentialSources);
|
||||
@@ -365,7 +365,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) {
|
||||
const count = getCredentialLibraryCount();
|
||||
await visit(urls.brokeredCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, count);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.dom(commonSelectors.ALERT_TOAST_BODY).isVisible();
|
||||
});
|
||||
@@ -389,7 +389,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) {
|
||||
const count = getCredentialCount();
|
||||
await visit(urls.brokeredCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, count);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.dom(commonSelectors.ALERT_TOAST_BODY).isVisible();
|
||||
});
|
||||
|
||||
@@ -130,7 +130,7 @@ module('Acceptance | targets | host-sources', function (hooks) {
|
||||
await visit(urls.target);
|
||||
|
||||
await click(`[href="${urls.targetHostSources}"]`);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
|
||||
assert.strictEqual(getTargetHostSetCount(), targetHostSetCount - 1);
|
||||
@@ -166,7 +166,7 @@ module('Acceptance | targets | host-sources', function (hooks) {
|
||||
await visit(urls.target);
|
||||
|
||||
await click(`[href="${urls.targetHostSources}"]`);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
|
||||
assert.dom('tbody tr').exists({ count: targetHostSetCount });
|
||||
@@ -216,7 +216,7 @@ module('Acceptance | targets | host-sources', function (hooks) {
|
||||
assert.dom('tbody tr').exists({ count: targetHostSetCount });
|
||||
|
||||
// first, remove a target host set (otherwise none would be available to add)
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.dom('tbody tr').exists({ count: targetHostSetCount - 1 });
|
||||
|
||||
|
||||
@@ -331,7 +331,7 @@ module(
|
||||
const credentialLibraryCount = getCredentialLibraryCount();
|
||||
await visit(urls.injectedApplicationCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.strictEqual(
|
||||
findAll('tbody tr').length,
|
||||
@@ -353,7 +353,7 @@ module(
|
||||
const credentialCount = getCredentialCount();
|
||||
await visit(urls.injectedApplicationCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, credentialCount);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.strictEqual(findAll('tbody tr').length, credentialCount - 1);
|
||||
await visit(urls.addInjectedApplicationCredentialSources);
|
||||
@@ -390,7 +390,7 @@ module(
|
||||
const count = getCredentialLibraryCount();
|
||||
await visit(urls.injectedApplicationCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, count);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.dom(commonSelectors.ALERT_TOAST_BODY).isVisible();
|
||||
});
|
||||
@@ -416,7 +416,7 @@ module(
|
||||
const count = getCredentialCount();
|
||||
await visit(urls.injectedApplicationCredentialSources);
|
||||
assert.strictEqual(findAll('tbody tr').length, count);
|
||||
await click('.hds-dropdown-toggle-icon');
|
||||
await click('tbody tr td:last-child .hds-dropdown-toggle-icon');
|
||||
await click('tbody tr .hds-dropdown-list-item button');
|
||||
assert.dom(commonSelectors.ALERT_TOAST_BODY).isVisible();
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target';
|
||||
import { STATUS_SESSION_ACTIVE } from 'api/models/session';
|
||||
import * as commonSelectors from 'admin/tests/helpers/selectors';
|
||||
|
||||
module('Acceptance | targets | list', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -122,9 +123,7 @@ module('Acceptance | targets | list', function (hooks) {
|
||||
'create',
|
||||
),
|
||||
);
|
||||
assert
|
||||
.dom('[title="Resources"] a:nth-of-type(2)')
|
||||
.doesNotIncludeText('Targets');
|
||||
assert.dom(commonSelectors.SIDEBAR_NAV_CONTENT).doesNotIncludeText('Targets');
|
||||
});
|
||||
|
||||
test('user can navigate to index with only create action', async function (assert) {
|
||||
|
||||
@@ -17,7 +17,7 @@ module('Acceptance | targets | manage-alias', function (hooks) {
|
||||
let aliasResourceTwo, aliasResourceOne, getAliasCount;
|
||||
const ALIASES_SIDEBAR = '.target-sidebar-aliases';
|
||||
const ALIASES_SIDEBAR_LIST = '.link-list-item';
|
||||
const DROPDOWN_ACTION = '.hds-dropdown-toggle-button__text';
|
||||
const DROPDOWN_ACTION = '[data-test-manage-target-alias] button';
|
||||
const CLEAR_BTN_SELECTOR = '.hds-dropdown-list-item--color-action button';
|
||||
const DELETE_BTN_SELECTOR = '.hds-dropdown-list-item--color-critical button';
|
||||
const LINK_TO_NEW_ALIAS = '.target-sidebar-aliases .hds-button';
|
||||
|
||||
@@ -35,11 +35,10 @@ export const PAGE_MESSAGE_LINK =
|
||||
|
||||
export const HREF = (url) => `[href="${url}"]`;
|
||||
|
||||
export const GENERAL_SIDEBAR_NAV_LINK = (url) =>
|
||||
`[title="General"] a[href="${url}"]`;
|
||||
export const RESOURCES_SIDEBAR_NAV_LINK = (url) =>
|
||||
`[title="Resources"] a[href="${url}"]`;
|
||||
export const IAM_SIDEBAR_NAV_LINK = (url) => `[title="IAM"] a[href="${url}"]`;
|
||||
export const SIDEBAR_NAV_LINK = (url) => `.hds-side-nav__content a[href="${url}"]`;
|
||||
export const SIDEBAR_NAV_CONTENT = '.hds-side-nav__content';
|
||||
export const SIDEBAR_USER_DROPDOWN =
|
||||
'[data-test-side-nav-user-dropdown] button';
|
||||
|
||||
export const ALERT_TOAST = '[data-test-toast-notification]';
|
||||
export const ALERT_TOAST_BODY =
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'admin/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { click } from '@ember/test-helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
|
||||
module('Integration | Component | scope-picker/index', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupIntl(hooks, 'en-us');
|
||||
|
||||
const SCOPE_PICKER_DROPDOWN = '.scope-picker button';
|
||||
const SCOPE_PICKER_DROPDOWN_ICON = '.hds-dropdown-toggle-button__icon svg';
|
||||
const ORG_ADD_MORE_TEXT = '[data-test-scope-picker-org-and-more-text]';
|
||||
const ORG_LIST_ITEM = '[data-test-scope-picker-org-item]';
|
||||
const ORG_LIST_COUNT = '[data-test-scope-picker-org-count]';
|
||||
const PROJECT_LIST_ITEM = '[data-test-scope-picker-project-item]';
|
||||
const PROJECT_ADD_MORE_TEXT =
|
||||
'[data-test-scope-picker-project-and-more-text]';
|
||||
const PROJECT_LIST_COUNT = '[data-test-scope-picker-project-count]';
|
||||
const SELECTED_SCOPE_CHECK = (id) => `[href="/scopes/${id}"] .hds-icon-check`;
|
||||
|
||||
let global;
|
||||
let scopeService;
|
||||
let store;
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
store = this.owner.lookup('service:store');
|
||||
scopeService = this.owner.lookup('service:scope');
|
||||
|
||||
this.server.create('scope', { id: 'global' });
|
||||
global = await store.findRecord('scope', 'global');
|
||||
});
|
||||
|
||||
test('it renders correct content when there are more than five orgs', async function (assert) {
|
||||
const orgs = this.server.createList('scope', 6, {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
this.server.createList('scope', 2, {
|
||||
type: 'project',
|
||||
scope: { id: orgs[0].id, type: 'org' },
|
||||
});
|
||||
scopeService.org = global;
|
||||
scopeService.orgsList = await store.query('scope', { scope_id: 'global' });
|
||||
scopeService.projectsList = await store.query('scope', {
|
||||
scope_id: orgs[0].id,
|
||||
});
|
||||
|
||||
await render(hbs`<ScopePicker />`);
|
||||
await click(SCOPE_PICKER_DROPDOWN);
|
||||
|
||||
assert.dom(ORG_ADD_MORE_TEXT).isVisible();
|
||||
// Scope picker should always render five or less orgs.
|
||||
assert.dom(ORG_LIST_ITEM).isVisible({ count: 5 });
|
||||
assert.dom(ORG_LIST_COUNT).includesText(`(${orgs.length})`);
|
||||
// Project scopes do not render when a project hasn't been selected.
|
||||
assert.dom(PROJECT_LIST_ITEM).doesNotExist();
|
||||
});
|
||||
|
||||
test('it renders correct content when there are five or less orgs', async function (assert) {
|
||||
const orgs = this.server.createList('scope', 4, {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
this.server.createList('scope', 2, {
|
||||
type: 'project',
|
||||
scope: { id: orgs[0].id, type: 'org' },
|
||||
});
|
||||
scopeService.org = global;
|
||||
scopeService.orgsList = await store.query('scope', { scope_id: 'global' });
|
||||
scopeService.projectsList = await store.query('scope', {
|
||||
scope_id: orgs[0].id,
|
||||
});
|
||||
|
||||
await render(hbs`<ScopePicker />`);
|
||||
await click(SCOPE_PICKER_DROPDOWN);
|
||||
|
||||
assert.dom(ORG_ADD_MORE_TEXT).doesNotExist();
|
||||
// Scope picker should always render five or less orgs.
|
||||
assert.dom(ORG_LIST_ITEM).isVisible({ count: 4 });
|
||||
assert.dom(ORG_LIST_COUNT).doesNotExist();
|
||||
// Project scopes do not render when a project hasn't been selected.
|
||||
assert.dom(PROJECT_LIST_ITEM).doesNotExist();
|
||||
});
|
||||
|
||||
test('it renders correct content when there are more than five projects', async function (assert) {
|
||||
const orgs = this.server.createList('scope', 2, {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
this.server.createList('scope', 6, {
|
||||
type: 'project',
|
||||
scope: { id: orgs[0].id, type: 'org' },
|
||||
});
|
||||
const projects = await store.query('scope', { scope_id: orgs[0].id });
|
||||
scopeService.org = global;
|
||||
scopeService.project = projects[0];
|
||||
scopeService.orgsList = await store.query('scope', { scope_id: 'global' });
|
||||
scopeService.projectsList = projects;
|
||||
|
||||
await render(hbs`<ScopePicker />`);
|
||||
await click(SCOPE_PICKER_DROPDOWN);
|
||||
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN).hasText(scopeService.project.displayName);
|
||||
assert.dom(PROJECT_ADD_MORE_TEXT).isVisible();
|
||||
// Scope picker should always render five or less projects.
|
||||
assert.dom(PROJECT_LIST_ITEM).isVisible({ count: 5 });
|
||||
assert.dom(PROJECT_LIST_ITEM).hasClass('indentation');
|
||||
assert.dom(PROJECT_LIST_COUNT).includesText(`(${projects.length})`);
|
||||
assert.dom(PROJECT_LIST_COUNT).hasClass('indentation');
|
||||
assert.dom(ORG_LIST_ITEM).isVisible({ count: orgs.length });
|
||||
});
|
||||
|
||||
test('it renders correct content when there are five or less projects', async function (assert) {
|
||||
const orgs = this.server.createList('scope', 5, {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
this.server.createList('scope', 4, {
|
||||
type: 'project',
|
||||
scope: { id: orgs[0].id, type: 'org' },
|
||||
});
|
||||
const projects = await store.query('scope', { scope_id: orgs[0].id });
|
||||
scopeService.org = global;
|
||||
scopeService.project = projects[0];
|
||||
scopeService.orgsList = await store.query('scope', { scope_id: 'global' });
|
||||
scopeService.projectsList = projects;
|
||||
|
||||
await render(hbs`<ScopePicker />`);
|
||||
await click(SCOPE_PICKER_DROPDOWN);
|
||||
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN).hasText(scopeService.project.displayName);
|
||||
assert.dom(PROJECT_ADD_MORE_TEXT).doesNotExist();
|
||||
// Scope picker should always render five or less projects.
|
||||
assert.dom(PROJECT_LIST_ITEM).isVisible({ count: projects.length });
|
||||
assert.dom(PROJECT_LIST_ITEM).hasClass('indentation');
|
||||
assert.dom(PROJECT_LIST_COUNT).doesNotExist();
|
||||
assert.dom(ORG_LIST_ITEM).isVisible({ count: orgs.length });
|
||||
});
|
||||
|
||||
test('it renders correct dropdown button text and icon when selected scope is global type', async function (assert) {
|
||||
this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
scopeService.org = global;
|
||||
scopeService.orgsList = await store.query('scope', { scope_id: 'global' });
|
||||
|
||||
await render(hbs`<ScopePicker />`);
|
||||
await click(SCOPE_PICKER_DROPDOWN);
|
||||
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN).hasText('Global');
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN_ICON).hasClass('hds-icon-globe');
|
||||
assert.dom(SELECTED_SCOPE_CHECK('global')).isVisible();
|
||||
});
|
||||
|
||||
test('it renders correct dropdown button text and icon when selected scope is org type', async function (assert) {
|
||||
this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
|
||||
scopeService.orgsList = await store.query('scope', { scope_id: 'global' });
|
||||
scopeService.org = scopeService.orgsList[0];
|
||||
|
||||
await render(hbs`<ScopePicker />`);
|
||||
await click(SCOPE_PICKER_DROPDOWN);
|
||||
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN).hasText(scopeService.org.displayName);
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN_ICON).hasClass('hds-icon-org');
|
||||
assert.dom(SELECTED_SCOPE_CHECK(scopeService.org.id)).isVisible();
|
||||
});
|
||||
|
||||
test('it renders correct dropdown button text and icon when selected scope is project type', async function (assert) {
|
||||
const org = this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
this.server.createList('scope', 2, {
|
||||
type: 'project',
|
||||
scope: { id: org.id, type: 'org' },
|
||||
});
|
||||
const projects = await store.query('scope', { scope_id: org.id });
|
||||
scopeService.org = global;
|
||||
scopeService.project = projects[0];
|
||||
scopeService.orgsList = await store.query('scope', { scope_id: 'global' });
|
||||
scopeService.projectsList = projects;
|
||||
|
||||
await render(hbs`<ScopePicker />`);
|
||||
await click(SCOPE_PICKER_DROPDOWN);
|
||||
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN).hasText(scopeService.project.displayName);
|
||||
assert.dom(SCOPE_PICKER_DROPDOWN_ICON).hasClass('hds-icon-grid');
|
||||
assert.dom(SELECTED_SCOPE_CHECK(scopeService.project.id)).isVisible();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user