diff --git a/addons/rose/addon/components/rose/anonymous.js b/addons/rose/addon/components/rose/anonymous.js index 74592a562..c5321f53a 100644 --- a/addons/rose/addon/components/rose/anonymous.js +++ b/addons/rose/addon/components/rose/anonymous.js @@ -6,5 +6,5 @@ import layout from '../../templates/components/rose/anonymous'; * that yield simple contextual components, such as layouts yielding regions. */ export default Component.extend({ - layout + layout, }); diff --git a/addons/rose/addon/components/rose/layout/centered.js b/addons/rose/addon/components/rose/layout/centered.js new file mode 100644 index 000000000..ddebf0b09 --- /dev/null +++ b/addons/rose/addon/components/rose/layout/centered.js @@ -0,0 +1,7 @@ +import Component from '@ember/component'; +import layout from '../../../templates/components/rose/layout/centered'; + +export default Component.extend({ + layout, + tagName: '', +}); diff --git a/addons/rose/addon/components/rose/layout/centered.stories.mdx b/addons/rose/addon/components/rose/layout/centered.stories.mdx new file mode 100644 index 000000000..c3bd62fd3 --- /dev/null +++ b/addons/rose/addon/components/rose/layout/centered.stories.mdx @@ -0,0 +1,25 @@ +import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@storybook/addon-actions'; + + + +# Layout|Centered + +A left and right centered layout/positioning component. + +```htmlbars + + Centered + +``` + + + {{ + template: hbs` + + Centered + + `, + }} + diff --git a/addons/rose/addon/components/rose/message.js b/addons/rose/addon/components/rose/message.js new file mode 100644 index 000000000..89ac42ee5 --- /dev/null +++ b/addons/rose/addon/components/rose/message.js @@ -0,0 +1,7 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/rose/message'; + +export default Component.extend({ + layout, + tagName: '', +}); diff --git a/addons/rose/addon/components/rose/message.stories.mdx b/addons/rose/addon/components/rose/message.stories.mdx new file mode 100644 index 000000000..89447aca1 --- /dev/null +++ b/addons/rose/addon/components/rose/message.stories.mdx @@ -0,0 +1,84 @@ +import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@storybook/addon-actions'; +import { text, boolean, select } from '@storybook/addon-knobs'; + + + +# Message + +A message component containing a title, description, and nav links. +Title can be configured with optional icon and subtitle. + + + {{ + template: hbs` + + + {{description}} + + + + Go back + + + Need help? + + + `, + context: { + title: text('title', 'Page not found'), + subtitle: text('subtitle', 'Error 404'), + description: text('description', 'Sorry, we could not find the page you were looking for. Navigate to another page with the links below.'), + icon: select( + 'icon', + { + 'alert-circle-outline': 'alert-circle-outline', + 'help-circle-outline': 'help-circle-outline', + 'disabled': 'disabled', + }, + 'help-circle-outline' + ), + }, + }} + + +```html + + + You must be granted permissions to view this data. Ask your administrator + if you think you should have access. + + + + Go back + + Need help? + +``` + +Message can be configured without icons and navigation links. + + + {{ + template: hbs` + + + {{description}} + + + `, + context: { + title: text('title', 'No logs received yet'), + description: text('description', 'Sorry, we could not find the page you were looking for. Navigate to another page with the links below.'), + }, + }} + + +```html + + + Description. + + +``` diff --git a/addons/rose/addon/styles/rose/_index.scss b/addons/rose/addon/styles/rose/_index.scss index b812b9f98..b3ddfcdd1 100644 --- a/addons/rose/addon/styles/rose/_index.scss +++ b/addons/rose/addon/styles/rose/_index.scss @@ -1,5 +1,4 @@ @import 'reset'; @import 'variables'; @import 'typography'; -@import 'icon'; @import 'components'; diff --git a/addons/rose/addon/styles/rose/_icon.scss b/addons/rose/addon/styles/rose/components/_icon.scss similarity index 89% rename from addons/rose/addon/styles/rose/_icon.scss rename to addons/rose/addon/styles/rose/components/_icon.scss index 08d4c6438..451af3823 100644 --- a/addons/rose/addon/styles/rose/_icon.scss +++ b/addons/rose/addon/styles/rose/components/_icon.scss @@ -1,4 +1,4 @@ -@use 'variables/sizing'; +@use '../variables/sizing'; .rose-icon { --size: #{sizing.rems(l)}; diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss index f63f9bdf0..948a7b735 100644 --- a/addons/rose/addon/styles/rose/components/_index.scss +++ b/addons/rose/addon/styles/rose/components/_index.scss @@ -2,6 +2,7 @@ @import 'form'; @import 'header'; @import 'layout'; +@import 'icon'; @import 'notification'; @import 'card'; @import 'cards'; @@ -9,3 +10,4 @@ @import 'separator'; @import 'nav'; @import 'table'; +@import 'message'; diff --git a/addons/rose/addon/styles/rose/components/_message.scss b/addons/rose/addon/styles/rose/components/_message.scss new file mode 100644 index 000000000..7aa35e13c --- /dev/null +++ b/addons/rose/addon/styles/rose/components/_message.scss @@ -0,0 +1,78 @@ +@use '../utilities/type'; +@use '../variables/sizing'; +@use '../variables/media'; + +.rose-message { + --message-color: var(--ui-gray-starker-1); + display: flex; + flex-direction: column; + color: var(--message-color); + width: sizing.rems(l) * 15; //16 rems/320 px + margin-bottom: sizing.rems(l); + + // Icons always need tweaking + .rose-icon { + --size: #{sizing.rems(m)}; + > svg { + vertical-align: text-bottom; + } + } + + .rose-message-header { + display: grid; + grid-template-areas: + 'icon title' + 'icon subtitle'; + grid-auto-columns: auto 1fr; + margin-bottom: sizing.rems(m); + + .rose-icon { + // targeting 40px + --size: #{sizing.rems(xl) + sizing.rems(xxs)}; + grid-area: icon; + margin-right: sizing.rems(s); + } + + .rose-message-title { + @include type.type(l, semibold); + grid-area: title; + margin: 0; + } + + .rose-message-subtitle { + @include type.type(s, normal); + grid-area: subtitle; + margin: 0; + } + } + + .rose-message-body { + display: grid; + grid-template-areas: + 'description description' + 'left-link right-link'; + + .rose-message-description { + grid-area: description; + margin-bottom: sizing.rems(l); + } + + .rose-message-link { + @include type.type(s, semibold); + grid-area: left-link; + color: var(--action); + + text-decoration: none; + + & + .rose-message-link { + grid-area: right-link; + text-align: right; + color: var(--message-color); + } + + &:hover { + text-decoration: underline; + } + } + } +} diff --git a/addons/rose/addon/styles/rose/components/layout/_centered.scss b/addons/rose/addon/styles/rose/components/layout/_centered.scss new file mode 100644 index 000000000..10844c8ae --- /dev/null +++ b/addons/rose/addon/styles/rose/components/layout/_centered.scss @@ -0,0 +1,6 @@ +@use '../../variables/sizing'; + +.rose-layout-centered { + display: flex; + justify-content: center; +} diff --git a/addons/rose/addon/styles/rose/components/layout/_global.scss b/addons/rose/addon/styles/rose/components/layout/_global.scss index 2ae679873..894f83410 100644 --- a/addons/rose/addon/styles/rose/components/layout/_global.scss +++ b/addons/rose/addon/styles/rose/components/layout/_global.scss @@ -24,7 +24,6 @@ grid-area: navigation; border-right: sizing.rems(xxxxs) solid var(--ui-border-subtler-1); } - } @media (max-width: media.width(small)) { diff --git a/addons/rose/addon/styles/rose/components/layout/_index.scss b/addons/rose/addon/styles/rose/components/layout/_index.scss index 02f7021b2..1ed6d8338 100644 --- a/addons/rose/addon/styles/rose/components/layout/_index.scss +++ b/addons/rose/addon/styles/rose/components/layout/_index.scss @@ -1,2 +1,3 @@ @import 'global'; @import 'page'; +@import 'centered'; diff --git a/addons/rose/addon/templates/components/rose/layout/centered.hbs b/addons/rose/addon/templates/components/rose/layout/centered.hbs new file mode 100644 index 000000000..5d32a4b3b --- /dev/null +++ b/addons/rose/addon/templates/components/rose/layout/centered.hbs @@ -0,0 +1,3 @@ +
+ {{yield}} +
diff --git a/addons/rose/addon/templates/components/rose/message.hbs b/addons/rose/addon/templates/components/rose/message.hbs new file mode 100644 index 000000000..1dd3157f0 --- /dev/null +++ b/addons/rose/addon/templates/components/rose/message.hbs @@ -0,0 +1,27 @@ +
+ + {{#if @title}} +
+ {{#if @icon}} + + {{/if}} + +

{{@title}}

+ + {{#if @subtitle}} +

{{@subtitle}}

+ {{/if}} +
+ {{/if}} + +
+ {{yield (hash + description=(component "rose/anonymous" tagName="p" class="rose-message-description") + link=(component "link-to" class="rose-message-link") + )}} +
+ +
diff --git a/addons/rose/app/components/rose/anonymous.js b/addons/rose/app/components/rose/anonymous.js index 56df9e7f7..5117bad96 100644 --- a/addons/rose/app/components/rose/anonymous.js +++ b/addons/rose/app/components/rose/anonymous.js @@ -1 +1 @@ -export { default } from 'rose/components/rose/anonymous'; \ No newline at end of file +export { default } from 'rose/components/rose/anonymous'; diff --git a/addons/rose/app/components/rose/layout/centered.js b/addons/rose/app/components/rose/layout/centered.js new file mode 100644 index 000000000..ff3274fee --- /dev/null +++ b/addons/rose/app/components/rose/layout/centered.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/layout/centered'; diff --git a/addons/rose/app/components/rose/message.js b/addons/rose/app/components/rose/message.js new file mode 100644 index 000000000..914d08e41 --- /dev/null +++ b/addons/rose/app/components/rose/message.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/message'; diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs index bb8ae24db..dde1dea20 100644 --- a/addons/rose/tests/dummy/app/templates/index.hbs +++ b/addons/rose/tests/dummy/app/templates/index.hbs @@ -1,7 +1,8 @@ - - Level 1 - Level 2 - Level 3 - Level 4 - Level 5 - + + + + Namespaces allow you to create distinct areas with their own separate policies, mounts, tokens, and identity entities. + + Add a namespace + + diff --git a/addons/rose/tests/dummy/app/templates/playground.hbs b/addons/rose/tests/dummy/app/templates/playground.hbs index 634ea800c..3d4559c6b 100644 --- a/addons/rose/tests/dummy/app/templates/playground.hbs +++ b/addons/rose/tests/dummy/app/templates/playground.hbs @@ -229,5 +229,60 @@ + + + Level 1 + Level 2 + Level 3 + Level 4 + Level 5 + + + + + You must be granted permissions to view this data. + Ask your administrator if you think you should have access. + + + + Go back + + Need help? + + + + + Sorry, we couldn't find the page you were looking for. You can go back or to one of the links below. + + + + Go back + + Need help? + + + + + We ran into a problem and could not continue. You can ask your administrator or try again later. + + + + Try Again + + Need help? + + + + + Your logs will appear here when we start receiving them. If your server is currently outputting logs, check your configuration. + + + + + + Namespaces allow you to create distinct areas with their own separate policies, mounts, tokens, and identity entities. + + Add a namespace + diff --git a/addons/rose/tests/integration/components/rose/anonymous-test.js b/addons/rose/tests/integration/components/rose/anonymous-test.js index 01e171a52..d5b65eac0 100644 --- a/addons/rose/tests/integration/components/rose/anonymous-test.js +++ b/addons/rose/tests/integration/components/rose/anonymous-test.js @@ -3,10 +3,10 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, find } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; -module('Integration | Component | rose/anonymous', function(hooks) { +module('Integration | Component | rose/anonymous', function (hooks) { setupRenderingTest(hooks); - test('it renders', async function(assert) { + test('it renders', async function (assert) { await render(hbs` {{#rose/anonymous tagName='main'}} template block text diff --git a/addons/rose/tests/integration/components/rose/layout/centered-test.js b/addons/rose/tests/integration/components/rose/layout/centered-test.js new file mode 100644 index 000000000..1900a1a5a --- /dev/null +++ b/addons/rose/tests/integration/components/rose/layout/centered-test.js @@ -0,0 +1,30 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | rose/layout/centered', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs``); + assert.ok(find('section')); + assert.ok(find('.rose-layout-centered')); + assert.notOk(find('.rose-layout-centered-body')); + }); + + test('it renders with attributes', async function (assert) { + await render(hbs``); + assert.ok(find('#layout')); + }); + + test('it renders with content', async function (assert) { + await render(hbs` + Layout content + `); + assert.equal( + find('.rose-layout-centered').textContent.trim(), + 'Layout content' + ); + }); +}); diff --git a/addons/rose/tests/integration/components/rose/message-test.js b/addons/rose/tests/integration/components/rose/message-test.js new file mode 100644 index 000000000..e14d74f2e --- /dev/null +++ b/addons/rose/tests/integration/components/rose/message-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | rose/message', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + assert.expect(5); + await render(hbs` + + Description + Link + + `); + assert.ok(find('.rose-message')); + assert.equal(find('.rose-message-title').textContent.trim(), 'Title'); + assert.equal(find('.rose-message-subtitle').textContent.trim(), 'Subtitle'); + assert.equal( + find('.rose-message-description').textContent.trim(), + 'Description' + ); + assert.equal(find('.rose-message-link').textContent.trim(), 'Link'); + }); +}); diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs index 1d845501b..d164728eb 100644 --- a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs +++ b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs @@ -5,7 +5,7 @@ -

{{t "resources.host-catalogs"}}

+

{{t 'resources.host-catalogs'}}

@@ -17,18 +17,33 @@ -
    - {{#each @model as |hostCatalog|}} -
  • - - {{hostCatalog.name}} - -
  • - {{/each}} -
+

{{t 'descriptions.host-catalogs'}}

+ + + + + {{t 'form.label.name'}} + {{t 'form.label.type'}} + {{t 'form.label.description'}} + + + + {{#each @model as |hostCatalog|}} + + + + {{hostCatalog.name}} + + + {{hostCatalog.type}} + {{hostCatalog.description}} + + {{/each}} + +
diff --git a/ui/core/tests/acceptance/host-catalogs-test.js b/ui/core/tests/acceptance/host-catalogs-test.js index acdacafa7..7a54340b3 100644 --- a/ui/core/tests/acceptance/host-catalogs-test.js +++ b/ui/core/tests/acceptance/host-catalogs-test.js @@ -23,7 +23,7 @@ module('Acceptance | host catalogs', function(hooks) { this.server.createList('project', 1); this.server.createList('host-catalog', 1); await visit('/orgs/1/projects/1/host-catalogs'); - await click('main ul li:first-child a'); + await click('main .rose-table-cell:first-child a'); await a11yAudit(); assert.equal(currentURL(), '/orgs/1/projects/1/host-catalogs/1'); }); diff --git a/ui/core/tests/acceptance/projects-test.js b/ui/core/tests/acceptance/projects-test.js index 612da5ffb..af00df26d 100644 --- a/ui/core/tests/acceptance/projects-test.js +++ b/ui/core/tests/acceptance/projects-test.js @@ -17,6 +17,26 @@ module('Acceptance | projects', function (hooks) { assert.equal(currentURL(), '/orgs/1/projects'); }); + test('can create new projects', async function (assert) { + assert.expect(3); + assert.equal(this.server.db.projects.length, 0); + await visit('/orgs/1/projects/new'); + await fillIn('[name="name"]', 'random string'); + await click('[type="submit"]'); + assert.equal(currentURL(), '/orgs/1/projects/1'); + assert.equal(this.server.db.projects.length, 1); + }); + + test('can cancel create new projects', async function (assert) { + assert.expect(3); + assert.equal(this.server.db.projects.length, 0); + await visit('/orgs/1/projects/new'); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.equal(currentURL(), '/orgs/1/projects'); + assert.equal(this.server.db.projects.length, 0); + }); + test('saving a new project with invalid fields displays error messages', async function (assert) { assert.expect(2); this.server.post('/orgs/:org_id/projects', () => { @@ -53,6 +73,34 @@ module('Acceptance | projects', function (hooks) { ); }); + test('can save changes to existing project', async function (assert) { + assert.expect(2); + this.server.createList('project', 1, {name: 'MyProject'}); + await visit('/orgs/1/projects/1'); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="submit"]'); + assert.equal(currentURL(), '/orgs/1/projects/1'); + assert.equal(this.server.db.projects[0].name, 'random string'); + }); + + test('can cancel changes to existing project', async function (assert) { + assert.expect(1); + this.server.createList('project', 1, {name: 'MyProject'}); + await visit('/orgs/1/projects/1'); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.equal(find('[name="name"]').value, 'MyProject'); + }); + + test('can delete project', async function(assert) { + assert.expect(2); + this.server.createList('project', 1); + assert.equal(this.server.db.projects.length, 1); + await visit('/orgs/1/projects/1'); + await click('.rose-button-warning'); + assert.equal(this.server.db.projects.length, 0); + }); + test('saving an existing project with invalid fields displays error messages', async function (assert) { assert.expect(2); this.server.createList('project', 1); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index a6cb7e4c1..4b51a1dc2 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -12,6 +12,8 @@ titles: new: New overview: Overview settings: Settings +descriptions: + host-catalogs: Overview of collections of hosts and host sets. states: info: Info success: Success @@ -21,6 +23,7 @@ form: label: name: Name description: Description + type: Type dev: default-org: Default Org menu-item: Menu item