Merge branch 'develop' into chore-logomark
This commit is contained in:
19 files changed
+222
-81
No files matched your search
@@ -64,7 +64,7 @@ export default class PasswordAuthenticator extends BaseAuthenticator {
|
||||
case 404:
|
||||
return reject();
|
||||
}
|
||||
return resolve(data);
|
||||
return resolve(this.normalizeData(data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +93,25 @@ export default class PasswordAuthenticator extends BaseAuthenticator {
|
||||
const authEndpointURL = this.buildAuthEndpointURL(options);
|
||||
const response = await fetch(authEndpointURL, { method: 'post', body });
|
||||
const json = await response.json();
|
||||
return response.status < 400 ? resolve(json) : reject();
|
||||
return response.status < 400
|
||||
? resolve(this.normalizeData(json, login_name))
|
||||
: reject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the auth data. Adds convenience booleans depending on the
|
||||
* scope within which the session is authenticated: isGlobal, isOrg.
|
||||
* If a `username` is provided, appends this to the data.
|
||||
* @param {object} data
|
||||
* @param {string} username
|
||||
* @return {object}
|
||||
*/
|
||||
normalizeData(data, username) {
|
||||
// Add booleans indicated the scope type
|
||||
data.isGlobal = (data?.scope?.type === 'global');
|
||||
data.isOrg = (data?.scope?.type === 'org');
|
||||
if (username) data.username = username;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,17 +4,30 @@
|
||||
@route={{if @route @route "index"}}
|
||||
@model={{@model}}
|
||||
>
|
||||
{{#if @title}}
|
||||
<h2 class="rose-card-link-title">{{@title}}</h2>
|
||||
{{/if}}
|
||||
<div class="rose-card-link-header">
|
||||
{{#if @title}}
|
||||
<h2 class="rose-card-link-title">
|
||||
{{#if @icon}}
|
||||
<Rose::Icon
|
||||
@name={{@icon}}
|
||||
@size="medium" />
|
||||
{{/if}}
|
||||
<span class="rose-card-link-title-text">{{@title}}</span>
|
||||
</h2>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if @subtitle}}
|
||||
<p class="rose-card-link-subtitle">{{@subtitle}}</p>
|
||||
{{/if}}
|
||||
<div class="rose-card-link-body">
|
||||
{{#if @description}}
|
||||
<p class="rose-card-link-description">{{@description}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if @description}}
|
||||
<p class="rose-card-link-description">{{@description}}</p>
|
||||
{{/if}}
|
||||
<div class="rose-card-link-footer">
|
||||
{{#if @id}}
|
||||
<p class="rose-card-link-footer-id">{{@id}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</LinkTo>
|
||||
|
||||
{{yield}}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
...attributes
|
||||
class="rose-nav-sidebar"
|
||||
id={{this.id}}
|
||||
title={{@title}}
|
||||
aria-labelledby={{if @title (concat "title-" this.id)}}>
|
||||
|
||||
{{#if @title}}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
@use 'sass:map';
|
||||
@use '../../variables/sizing';
|
||||
@use '../../variables/typography';
|
||||
@use '../../utilities/type';
|
||||
|
||||
$xxxs: sizing.rems(xxxs); // 2
|
||||
|
||||
.rose-card-link {
|
||||
@include type.type(s);
|
||||
border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
|
||||
@@ -9,19 +13,21 @@
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
margin: 0 sizing.rems(m) sizing.rems(m) 0;
|
||||
box-shadow: 0 $xxxs $xxxs rgba(var(--stark-components), var(--opacity-4)),
|
||||
0 $xxxs $xxxs rgba(var(--stark-components), var(--opacity-4));
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: var(--ui-gray-subtler-6);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: sizing.rems(s);
|
||||
// TODO: Figure out better method of defining card dimensions
|
||||
min-height: sizing.rems(m) * 6; //6 rems
|
||||
width: sizing.rems(m) * 12; //12 rems
|
||||
min-height: sizing.rems(m) * 8; //6 rems
|
||||
width: sizing.rems(m) * 16; //12 rems
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -42,27 +48,78 @@
|
||||
border-color: var(--info);
|
||||
}
|
||||
|
||||
.rose-card-link-title {
|
||||
$truncate-line-count: 3;
|
||||
@include type.type(m, semibold);
|
||||
color: var(--stark);
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: $truncate-line-count;
|
||||
overflow: hidden;
|
||||
.rose-card-link-header,
|
||||
.rose-card-link-body,
|
||||
.rose-card-link-footer {
|
||||
padding-left: sizing.rems(m);
|
||||
padding-right: sizing.rems(m);
|
||||
}
|
||||
|
||||
.rose-card-link-subtitle {
|
||||
color: var(--ui-gray);
|
||||
.rose-card-link-header {
|
||||
padding-top: sizing.rems(m);
|
||||
}
|
||||
.rose-card-link-footer {
|
||||
margin-top: sizing.rems(xs) + sizing.rems(xxs);
|
||||
padding-top: sizing.rems(xs) * 2;
|
||||
padding-bottom: sizing.rems(xs) * 2;
|
||||
}
|
||||
|
||||
.rose-card-link-footer {
|
||||
border-top: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
|
||||
}
|
||||
|
||||
.rose-card-link-title {
|
||||
@include type.type(m, semibold);
|
||||
display: flex;
|
||||
color: var(--stark);
|
||||
margin: 0;
|
||||
margin-bottom: sizing.rems(xs);
|
||||
|
||||
.rose-icon {
|
||||
margin-right: sizing.rems(xs);
|
||||
|
||||
svg {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.rose-card-link-title-text {
|
||||
flex-grow: 1;
|
||||
height: typography.line-height(m) * 2rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.rose-card-link-description {
|
||||
margin: auto 0 0;
|
||||
color: var(--ui-gray);
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
height: typography.line-height(m) * 2rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rose-card-link-footer-id {
|
||||
@include type.type(xs);
|
||||
color: var(--ui-gray);
|
||||
margin: 0;
|
||||
|
||||
&::before {
|
||||
content: "ID";
|
||||
color: var(--stark);
|
||||
padding-right: sizing.rems(xs);
|
||||
}
|
||||
}
|
||||
|
||||
// .rose-card-link-description {
|
||||
// margin: auto 0 0;
|
||||
// color: var(--ui-gray);
|
||||
// white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
// text-overflow: ellipsis;
|
||||
// }
|
||||
}
|
||||
@@ -6,8 +6,7 @@
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
// Targeting min-width of ~200
|
||||
min-width: (sizing.rems(m)) * 10;
|
||||
min-width: (sizing.rems(m)) * 13;
|
||||
border: 0;
|
||||
padding: 0 sizing.rems(s);
|
||||
text-decoration: none;
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
// Targeting min-width of ~200
|
||||
min-width: (sizing.rems(m)) * 10;
|
||||
min-width: (sizing.rems(m)) * 13;
|
||||
border: 0;
|
||||
padding: 0 sizing.rems(s);
|
||||
text-decoration: none;
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
display: grid;
|
||||
grid-template-areas: 'brand nav utilities';
|
||||
grid-template-columns: 0.01fr auto 1fr;
|
||||
background: var(--black) linear-gradient(90deg, #191a1c 0%, #1b212d 100%);
|
||||
background: var(--black);
|
||||
box-shadow: inset 0 -#{sizing.rems(xxxxs)} 0 var(--ui-border-subtler-1);
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: media.width(small)) {
|
||||
|
||||
@@ -3,10 +3,20 @@
|
||||
|
||||
.rose-nav-sidebar {
|
||||
$threePX: sizing.rems(xxxs) + sizing.rems(xxxxs);
|
||||
$yPadding: sizing.rems(xs);
|
||||
|
||||
padding-top: $yPadding;
|
||||
padding-bottom: $yPadding;
|
||||
border-bottom: sizing.rems(xxxxs) solid var(--ui-border-subtler-1);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
// TODO revisit the title
|
||||
.rose-nav-title {
|
||||
@include type.type(xs, semibold);
|
||||
display: block;
|
||||
display: none;
|
||||
color: var(--ui-gray);
|
||||
text-transform: uppercase;
|
||||
$lineHeight: 20 / 12;
|
||||
@@ -29,6 +39,7 @@
|
||||
&.active {
|
||||
color: var(--action);
|
||||
border-color: currentColor;
|
||||
background-color: var(--action-subtler-1h);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
// poles
|
||||
--subtle: #{darken(color(ui-gray, 900), 3%)};
|
||||
--subtle: var(--black);
|
||||
--stark: #{color(ui-gray, 100)};
|
||||
--stark-components: 255, 255, 255;
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ module('Integration | Component | rose/card/link', function (hooks) {
|
||||
await render(hbs`<Rose::Card::Link />`);
|
||||
assert.ok(find('article'));
|
||||
assert.ok(find('.rose-card-link'));
|
||||
assert.notOk(find('.rose-card-link-title'));
|
||||
assert.notOk(find('.rose-card-link-subtitle'));
|
||||
assert.notOk(find('.rose-card-link-description'));
|
||||
assert.ok(find('.rose-card-link-header'));
|
||||
assert.ok(find('.rose-card-link-body'));
|
||||
assert.ok(find('.rose-card-link-footer'));
|
||||
});
|
||||
|
||||
test('it renders with @title', async function (assert) {
|
||||
@@ -24,22 +24,27 @@ module('Integration | Component | rose/card/link', function (hooks) {
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with @subtitle', async function (assert) {
|
||||
await render(hbs`<Rose::Card::Link @subtitle="card subtitle" />`);
|
||||
assert.equal(
|
||||
find('.rose-card-link-subtitle').textContent.trim(),
|
||||
'card subtitle'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with @description', async function (assert) {
|
||||
await render(hbs`<Rose::Card::Link @description="card description" />`);
|
||||
assert.equal(
|
||||
find('.rose-card-link-description').textContent.trim(),
|
||||
find('.rose-card-link-body').textContent.trim(),
|
||||
'card description'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with @id', async function (assert) {
|
||||
await render(hbs`<Rose::Card::Link @id="card_id" />`);
|
||||
assert.equal(
|
||||
find('.rose-card-link-footer-id').textContent.trim(),
|
||||
'card_id'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with @icon', async function (assert) {
|
||||
await render(hbs`<Rose::Card::Link @title="foo" @icon="alert-circle-outline" />`);
|
||||
assert.ok(find('.rose-icon'));
|
||||
});
|
||||
|
||||
test('it renders with attributes', async function (assert) {
|
||||
await render(hbs`<Rose::Card::Link id="card" />`);
|
||||
assert.ok(find('#card'));
|
||||
|
||||
@@ -38,6 +38,8 @@ export default class ScopesScopeRoute extends Route {
|
||||
* Set scope in application controller
|
||||
*/
|
||||
afterModel() {
|
||||
const orgScopes = this.modelFor('scopes').filterBy('isOrg', true);
|
||||
this.controllerFor('application').set('orgScopes', orgScopes);
|
||||
const scope = this.modelFor('scopes.scope');
|
||||
this.controllerFor('application').set('scope', scope);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,17 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
|
||||
// =methods
|
||||
|
||||
beforeModel() {
|
||||
if (this.session.isAuthenticated) {
|
||||
const scope = this.modelFor('scopes.scope');
|
||||
if (scope.isGlobal) {
|
||||
this.transitionTo('scopes.scope.orgs');
|
||||
} else {
|
||||
this.transitionTo('scopes.scope.projects');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all auth methods for the current scope, along with the current
|
||||
* scope and all scopes (for org navigation).
|
||||
@@ -28,15 +39,4 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
// }])
|
||||
});
|
||||
}
|
||||
|
||||
redirect() {
|
||||
if (this.session.isAuthenticated) {
|
||||
const scope = this.modelFor('scopes.scope');
|
||||
if (scope.isGlobal) {
|
||||
this.transitionTo('scopes.scope.orgs');
|
||||
} else {
|
||||
this.transitionTo('scopes.scope.projects');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
}
|
||||
|
||||
.rose-nav-sidebar {
|
||||
margin-bottom: sizing.rems(l);
|
||||
margin-top: sizing.rems(xxl);
|
||||
|
||||
& + .rose-nav-sidebar {
|
||||
@@ -67,11 +66,17 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rose-dropdown {
|
||||
.rose-icon {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
&::after {
|
||||
animation: gradient 1s linear infinite;
|
||||
background: repeating-linear-gradient(to right, var(--subtle), var(--subtle), var(--info), var(--subtle), var(--subtle));
|
||||
background-position: 0 100%;
|
||||
background-position: -100% 0;
|
||||
background-size: 200% auto;
|
||||
content: ' ';
|
||||
height: 3px;
|
||||
@@ -81,7 +86,7 @@
|
||||
top: 100%;
|
||||
|
||||
@keyframes gradient {
|
||||
0% { background-position: 0 0; }
|
||||
0% { background-position: -100% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
}
|
||||
@@ -90,9 +95,9 @@
|
||||
|
||||
.copyable {
|
||||
.copyable-button {
|
||||
margin: 0;
|
||||
padding: 0 sizing.rems(xxs);
|
||||
position: relative;
|
||||
top: -2px;
|
||||
padding: 0 sizing.rems(xxs);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,35 @@
|
||||
<layout.header>
|
||||
<Rose::Header class="{{if (is-loading) " loading"}}" as |header|>
|
||||
<header.brand @logo="logo-boundary" @text={{app-name}} />
|
||||
|
||||
{{#if this.session.isAuthenticated}}
|
||||
<header.nav as |nav|>
|
||||
{{#if this.scope.isOrg}}
|
||||
<nav.link @route="scopes.scope" @model={{this.scope}}>{{this.scope.displayName}}</nav.link>
|
||||
{{/if}}
|
||||
</header.nav>
|
||||
{{#if this.scope.isOrg}}
|
||||
<header.nav as |nav|>
|
||||
<nav.dropdown
|
||||
@text={{this.scope.displayName}}
|
||||
@icon="boundary-icons/org" as |dropdown|
|
||||
>
|
||||
{{#if this.session.data.authenticated.isGlobal}}
|
||||
<dropdown.link @route="scopes.scope" @model="global">
|
||||
{{t "titles.global"}}
|
||||
</dropdown.link>
|
||||
{{#each this.orgScopes as |scope|}}
|
||||
<dropdown.link @route="scopes.scope" @model={{scope.id}}>
|
||||
{{scope.displayName}}
|
||||
</dropdown.link>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<dropdown.link @route="scopes.scope" @model={{this.scope.id}}>
|
||||
{{this.scope.displayName}}
|
||||
</dropdown.link>
|
||||
{{/if}}
|
||||
</nav.dropdown>
|
||||
</header.nav>
|
||||
{{/if}}
|
||||
|
||||
<header.utilities as |utilities|>
|
||||
<utilities.dropdown
|
||||
@text="user@example.net"
|
||||
@iconOnly={{true}}
|
||||
@text={{this.session.data.authenticated.username}}
|
||||
@icon="boundary-icons/user-profile" as |dropdown|
|
||||
>
|
||||
{{!-- <dropdown.link @route="account.change-password">
|
||||
@@ -29,7 +47,9 @@
|
||||
</dropdown.button>
|
||||
</utilities.dropdown>
|
||||
</header.utilities>
|
||||
|
||||
{{/if}}
|
||||
|
||||
</Rose::Header>
|
||||
</layout.header>
|
||||
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
{{#each @model as |org|}}
|
||||
<cards.link
|
||||
@title={{org.name}}
|
||||
@subtitle={{org.id}}
|
||||
@description={{org.description}}
|
||||
@id={{org.id}}
|
||||
@icon="boundary-icons/org"
|
||||
@route="scopes.scope"
|
||||
@model={{org.id}}
|
||||
/>
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
{{#each @model as |project|}}
|
||||
<cards.link
|
||||
@title={{project.name}}
|
||||
@subtitle={{project.id}}
|
||||
@description={{project.description}}
|
||||
@id={{project.id}}
|
||||
@icon="boundary-icons/project"
|
||||
@route="scopes.scope.projects.project"
|
||||
@model={{project.id}}
|
||||
/>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import factory from '../generated/factories/org';
|
||||
|
||||
export default factory.extend();
|
||||
@@ -1,3 +0,0 @@
|
||||
import factory from '../generated/factories/project';
|
||||
|
||||
export default factory.extend();
|
||||
@@ -5,6 +5,16 @@ export default factory.extend({
|
||||
|
||||
type: 'global',
|
||||
|
||||
/**
|
||||
* Generates realistic-ish IDs while still being deterministic.
|
||||
*/
|
||||
id(i) {
|
||||
let id = 'tv6fv3yz8p';
|
||||
if (i % 3) id = 'xk1i4dk50v';
|
||||
if (i % 2) id = 's9n819zgko';
|
||||
return `s_${id}${i}`;
|
||||
},
|
||||
|
||||
withChildren: trait({
|
||||
afterCreate(record, server) {
|
||||
if (record.type === 'global') {
|
||||
|
||||
Reference in new issue
Block a user