feat: 🎸 Add loading indicators for initial loads of resources (#2074)
* feat: 🎸 Add loading indicators for initial loads of resources * refactor: 💡 Use HDS application state component ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12025
This commit is contained in:
1 parent
16a054519f
commit
ba0c7d5683
12 files changed
+532
-499
No files matched your search
@@ -20,3 +20,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search & Filtering
|
||||
.search-filtering {
|
||||
.hds-application-state {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.hds-segmented-group {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.hds-dropdown-toggle-button__text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.hds-table {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Flyout with table
|
||||
.flyout-with-table {
|
||||
.copyable-content a,
|
||||
button {
|
||||
color: var(--token-color-foreground-primary);
|
||||
}
|
||||
}
|
||||
@@ -14,3 +14,6 @@ active-public: Public
|
||||
recording: Recording
|
||||
completed: Completed
|
||||
failed: Failed
|
||||
loading:
|
||||
title: Loading all items...
|
||||
description: All of your {resource} are still loading. Please wait while we finish loading everything on screen.
|
||||
@@ -8,7 +8,6 @@ import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { A } from '@ember/array';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { loading } from 'ember-loading';
|
||||
|
||||
export default class ApplicationRoute extends Route {
|
||||
// =services
|
||||
@@ -78,17 +77,6 @@ export default class ApplicationRoute extends Route {
|
||||
this.invalidateSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into ember-loading to kick off loading indicator in the
|
||||
* application template.
|
||||
* @return {boolean} always returns true
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
loading() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the session if a 401 error occurs and returns false to
|
||||
* prevent further error handling.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -40,6 +41,20 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Event to determine whether the loading template should be shown.
|
||||
* Only show the loading template during initial loads or when transitioning
|
||||
* from different routes. Don't show it when a user is just searching or
|
||||
* filtering on the same page as it can be jarring.
|
||||
* @param transition
|
||||
* @returns {boolean}
|
||||
*/
|
||||
@action
|
||||
loading(transition) {
|
||||
const from = transition.from?.name;
|
||||
return from !== 'scopes.scope.projects.sessions.index';
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads queried sessions, the total number of sessions, all sessions,
|
||||
* all targets and all projects for filtering options for the current user.
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
STATUS_SESSION_ACTIVE,
|
||||
STATUS_SESSION_PENDING,
|
||||
} from 'api/models/session';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -51,6 +52,20 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Event to determine whether the loading template should be shown.
|
||||
* Only show the loading template during initial loads or when transitioning
|
||||
* from different routes. Don't show it when a user is just searching or
|
||||
* filtering on the same page as it can be jarring.
|
||||
* @param transition
|
||||
* @returns {boolean}
|
||||
*/
|
||||
@action
|
||||
loading(transition) {
|
||||
const from = transition.from?.name;
|
||||
return from !== 'scopes.scope.projects.targets.index';
|
||||
}
|
||||
|
||||
/**
|
||||
* If arriving here unauthenticated, redirect to index for further processing.
|
||||
*/
|
||||
|
||||
@@ -684,30 +684,3 @@ $chevron-down: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231
|
||||
color: var(--token-color-foreground-faint);
|
||||
}
|
||||
}
|
||||
|
||||
// Search & Filtering
|
||||
.search-filtering {
|
||||
.hds-application-state {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.hds-segmented-group {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.hds-dropdown-toggle-button__text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.hds-table {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Flyout with table
|
||||
.flyout-with-table {
|
||||
.copyable-content a,
|
||||
button {
|
||||
color: var(--token-color-foreground-primary);
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,16 @@
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
{{outlet}}
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.session.title_plural'}}
|
||||
<DocLink @doc='sessions' @iconSize='large' />
|
||||
</h2>
|
||||
<p>{{t 'resources.session.description'}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.body class='search-filtering'>
|
||||
{{outlet}}
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
@@ -3,191 +3,175 @@
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.session.title_plural'}}
|
||||
<DocLink @doc='sessions' @iconSize='large' />
|
||||
</h2>
|
||||
<p>{{t 'resources.session.description'}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.body class='search-filtering'>
|
||||
{{#if (or @model.sessions @model.allSessions)}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.title'}}
|
||||
@itemOptions={{this.availableTargets}}
|
||||
@checkedItems={{this.targets}}
|
||||
@applyFilter={{fn this.applyFilter 'targets'}}
|
||||
@isSearchable={{true}}
|
||||
@listPosition='bottom-left'
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.status.label'}}
|
||||
@itemOptions={{this.sessionStatusOptions}}
|
||||
@checkedItems={{this.status}}
|
||||
@applyFilter={{fn this.applyFilter 'status'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{#if @model.sessions}}
|
||||
<Hds::Table
|
||||
@model={{this.sortedSessions}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.id.label'))
|
||||
(hash label=(t 'resources.target.title'))
|
||||
(hash label=(t 'form.proxy_url.label'))
|
||||
(hash label=(t 'form.started.label'))
|
||||
(hash label=(t 'form.status.label'))
|
||||
(hash label='')
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
{{#if B.data.isAvailable}}
|
||||
<Hds::Badge
|
||||
@text={{t 'resources.session.title'}}
|
||||
@icon='entry-point'
|
||||
@isIconOnly={{true}}
|
||||
@color='success'
|
||||
/>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<LinkTo
|
||||
@route='scopes.scope.projects.sessions.session.index'
|
||||
@model={{B.data.id}}
|
||||
data-test-session-detail-link
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.id}}</Hds::Text::Code>
|
||||
</LinkTo>
|
||||
</Copyable>
|
||||
{{else}}
|
||||
<Hds::Badge
|
||||
@text={{t 'resources.session.title'}}
|
||||
@icon='entry-point'
|
||||
@isIconOnly={{true}}
|
||||
/>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code
|
||||
data-test-session-id-copy
|
||||
>{{B.data.id}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{B.data.target.displayName}}
|
||||
</B.Td>
|
||||
{{!
|
||||
Two if's here because:
|
||||
1) Local proxy info is only relevant if the session is
|
||||
active or pending (aka isAvailable). Once the session
|
||||
is canceled or in the process thereof, the local proxy
|
||||
may no longer be used.
|
||||
2) The session actually has proxy information associated
|
||||
with it. It's possible for the user to start sessions
|
||||
from other devices (or even another tab), in which case we
|
||||
won't have the local proxy info available here.
|
||||
}}
|
||||
<B.Td>
|
||||
{{#if B.data.isAvailable}}
|
||||
{{#if B.data.proxy}}
|
||||
<Copyable
|
||||
@text={{B.data.proxy}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.proxy}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<time datetime={{format-date-iso B.data.created_time}}>
|
||||
{{format-date-iso-human B.data.created_time}}
|
||||
</time>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<SessionStatus @model={{B.data}} />
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if (can 'cancel session' B.data)}}
|
||||
<Hds::Button
|
||||
@text={{t 'actions.cancel'}}
|
||||
@color='secondary'
|
||||
{{on 'click' (route-action 'cancelSession' B.data)}}
|
||||
data-test-session-cancel-button
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::ApplicationState data-test-no-session-results as |A|>
|
||||
<A.Header @title={{t 'titles.no-results-found'}} />
|
||||
<A.Body
|
||||
@text={{t 'descriptions.no-filter-results' resource='sessions'}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<Hds::ApplicationState data-test-no-sessions as |A|>
|
||||
<A.Header
|
||||
@title={{t 'resources.session.messages.none-friendly.title'}}
|
||||
/>
|
||||
<A.Body
|
||||
@text={{t 'resources.session.messages.none-friendly.description'}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
{{#if (or @model.sessions @model.allSessions)}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.title'}}
|
||||
@itemOptions={{this.availableTargets}}
|
||||
@checkedItems={{this.targets}}
|
||||
@applyFilter={{fn this.applyFilter 'targets'}}
|
||||
@isSearchable={{true}}
|
||||
@listPosition='bottom-left'
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.status.label'}}
|
||||
@itemOptions={{this.sessionStatusOptions}}
|
||||
@checkedItems={{this.status}}
|
||||
@applyFilter={{fn this.applyFilter 'status'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{#if @model.sessions}}
|
||||
<Hds::Table
|
||||
@model={{this.sortedSessions}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.id.label'))
|
||||
(hash label=(t 'resources.target.title'))
|
||||
(hash label=(t 'form.proxy_url.label'))
|
||||
(hash label=(t 'form.started.label'))
|
||||
(hash label=(t 'form.status.label'))
|
||||
(hash label='')
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
{{#if B.data.isAvailable}}
|
||||
<Hds::Badge
|
||||
@text={{t 'resources.session.title'}}
|
||||
@icon='entry-point'
|
||||
@isIconOnly={{true}}
|
||||
@color='success'
|
||||
/>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<LinkTo
|
||||
@route='scopes.scope.projects.sessions.session.index'
|
||||
@model={{B.data.id}}
|
||||
data-test-session-detail-link
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.id}}</Hds::Text::Code>
|
||||
</LinkTo>
|
||||
</Copyable>
|
||||
{{else}}
|
||||
<Hds::Badge
|
||||
@text={{t 'resources.session.title'}}
|
||||
@icon='entry-point'
|
||||
@isIconOnly={{true}}
|
||||
/>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code
|
||||
data-test-session-id-copy
|
||||
>{{B.data.id}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{B.data.target.displayName}}
|
||||
</B.Td>
|
||||
{{!
|
||||
Two if's here because:
|
||||
1) Local proxy info is only relevant if the session is
|
||||
active or pending (aka isAvailable). Once the session
|
||||
is canceled or in the process thereof, the local proxy
|
||||
may no longer be used.
|
||||
2) The session actually has proxy information associated
|
||||
with it. It's possible for the user to start sessions
|
||||
from other devices (or even another tab), in which case we
|
||||
won't have the local proxy info available here.
|
||||
}}
|
||||
<B.Td>
|
||||
{{#if B.data.isAvailable}}
|
||||
{{#if B.data.proxy}}
|
||||
<Copyable
|
||||
@text={{B.data.proxy}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.proxy}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<time datetime={{format-date-iso B.data.created_time}}>
|
||||
{{format-date-iso-human B.data.created_time}}
|
||||
</time>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<SessionStatus @model={{B.data}} />
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if (can 'cancel session' B.data)}}
|
||||
<Hds::Button
|
||||
@text={{t 'actions.cancel'}}
|
||||
@color='secondary'
|
||||
{{on 'click' (route-action 'cancelSession' B.data)}}
|
||||
data-test-session-cancel-button
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::ApplicationState data-test-no-session-results as |A|>
|
||||
<A.Header @title={{t 'titles.no-results-found'}} />
|
||||
<A.Body
|
||||
@text={{t 'descriptions.no-filter-results' resource='sessions'}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<Hds::ApplicationState data-test-no-sessions as |A|>
|
||||
<A.Header @title={{t 'resources.session.messages.none-friendly.title'}} />
|
||||
<A.Body
|
||||
@text={{t 'resources.session.messages.none-friendly.description'}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header @title={{t 'states.loading.title'}} @icon='loading' />
|
||||
<A.Body
|
||||
@text={{t
|
||||
'states.loading.description'
|
||||
resource=(t 'resources.session.title_plural')
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
@@ -5,4 +5,16 @@
|
||||
|
||||
{{page-title (t 'resources.target.title_plural')}}
|
||||
|
||||
{{outlet}}
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.target.title_plural'}}
|
||||
<DocLink @doc='targets' @iconSize='large' />
|
||||
</h2>
|
||||
<p>{{t 'resources.target.description'}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.body class='search-filtering'>
|
||||
{{outlet}}
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
@@ -3,278 +3,264 @@
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
{{#if this.showFilters}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.TextInput
|
||||
@value={{this.search}}
|
||||
@type='search'
|
||||
placeholder={{t 'actions.search'}}
|
||||
aria-label={{t 'actions.search'}}
|
||||
{{on 'input' this.handleSearchInput}}
|
||||
/>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.titles.active-sessions'}}
|
||||
@itemOptions={{this.availableSessionOptions}}
|
||||
@checkedItems={{this.availableSessions}}
|
||||
@applyFilter={{fn this.applyFilter 'availableSessions'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.type.label'}}
|
||||
@itemOptions={{this.targetTypeOptions}}
|
||||
@checkedItems={{this.types}}
|
||||
@applyFilter={{fn this.applyFilter 'types'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
data-test-checkbox={{project.name}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{#if @model.targets}}
|
||||
<Hds::Table
|
||||
@model={{@model.targets}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.name.label'))
|
||||
(hash label=(t 'form.id.label'))
|
||||
(hash label=(t 'resources.target.titles.active-sessions'))
|
||||
(hash label=(t 'form.type.label'))
|
||||
(hash label=(t 'resources.project.title'))
|
||||
(hash label='')
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<span class='hds-font-weight-semibold'>
|
||||
{{#if (can 'read model' B.data)}}
|
||||
<LinkTo
|
||||
data-test-visit-target={{B.data.id}}
|
||||
@route='scopes.scope.projects.targets.target'
|
||||
@model={{B.data.id}}
|
||||
@query={{hash isConnecting=false}}
|
||||
>
|
||||
{{B.data.displayName}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{B.data.displayName}}
|
||||
{{/if}}
|
||||
{{#if B.data.description}}
|
||||
<p>{{B.data.description}}</p>
|
||||
{{/if}}
|
||||
</span>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.id}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if B.data.isActive}}
|
||||
<Hds::Button
|
||||
data-test-targets-sessions-flyout-button={{B.data.id}}
|
||||
@text={{t 'actions.yes'}}
|
||||
@color='tertiary'
|
||||
@icon='info'
|
||||
{{on 'click' (fn this.selectTarget B.data)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if B.data.type}}
|
||||
<Hds::Badge
|
||||
@text={{t (concat 'resources.target.types.' B.data.type)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<div>
|
||||
<Copyable
|
||||
@text={{B.data.project.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.project.id}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
</div>
|
||||
{{#if B.data.project.name}}
|
||||
<Hds::Badge @color='neutral' @text={{B.data.project.name}} />
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if (can 'connect target' B.data)}}
|
||||
{{#if (can 'read target' B.data)}}
|
||||
<Hds::Button
|
||||
data-test-targets-connect-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.connect'}}
|
||||
@color='secondary'
|
||||
@route='scopes.scope.projects.targets.target'
|
||||
@model={{B.data.id}}
|
||||
@query={{hash isConnecting=true}}
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::Button
|
||||
data-test-targets-connect-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.connect'}}
|
||||
@color='secondary'
|
||||
{{on 'click' (fn this.quickConnect B.data)}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
/>
|
||||
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.target.title_plural'}}
|
||||
<DocLink @doc='targets' @iconSize='large' />
|
||||
</h2>
|
||||
<p>{{t 'resources.target.description'}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.body class='search-filtering'>
|
||||
{{#if this.showFilters}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.TextInput
|
||||
@value={{this.search}}
|
||||
@type='search'
|
||||
placeholder={{t 'actions.search'}}
|
||||
aria-label={{t 'actions.search'}}
|
||||
{{on 'input' this.handleSearchInput}}
|
||||
/>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.titles.active-sessions'}}
|
||||
@itemOptions={{this.availableSessionOptions}}
|
||||
@checkedItems={{this.availableSessions}}
|
||||
@applyFilter={{fn this.applyFilter 'availableSessions'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.type.label'}}
|
||||
@itemOptions={{this.targetTypeOptions}}
|
||||
@checkedItems={{this.types}}
|
||||
@applyFilter={{fn this.applyFilter 'types'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
data-test-checkbox={{project.name}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{#if @model.targets}}
|
||||
<Hds::Table
|
||||
@model={{@model.targets}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.name.label'))
|
||||
(hash label=(t 'form.id.label'))
|
||||
(hash label=(t 'resources.target.titles.active-sessions'))
|
||||
(hash label=(t 'form.type.label'))
|
||||
(hash label=(t 'resources.project.title'))
|
||||
(hash label='')
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<span class='hds-font-weight-semibold'>
|
||||
{{#if (can 'read model' B.data)}}
|
||||
<LinkTo
|
||||
data-test-visit-target={{B.data.id}}
|
||||
@route='scopes.scope.projects.targets.target'
|
||||
@model={{B.data.id}}
|
||||
@query={{hash isConnecting=false}}
|
||||
>
|
||||
{{B.data.displayName}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{B.data.displayName}}
|
||||
{{/if}}
|
||||
{{#if B.data.description}}
|
||||
<p>{{B.data.description}}</p>
|
||||
{{/if}}
|
||||
</span>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
{{/if}}
|
||||
{{#if this.noResults}}
|
||||
<Hds::ApplicationState data-test-no-target-results as |A|>
|
||||
<A.Header @title={{t 'titles.no-results-found'}} />
|
||||
<A.Body
|
||||
@text={{t
|
||||
(if
|
||||
this.search
|
||||
'descriptions.no-search-results'
|
||||
'descriptions.no-filter-results'
|
||||
)
|
||||
query=this.search
|
||||
resource='targets'
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if this.noTargets}}
|
||||
<Hds::ApplicationState data-test-no-targets as |A|>
|
||||
<A.Header @title={{t 'resources.target.messages.none.title'}} />
|
||||
<A.Body @text={{t 'resources.target.messages.none.description'}} />
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
{{#if (and this.selectedTarget this.selectedTarget.isActive)}}
|
||||
<Hds::Flyout
|
||||
@onClose={{fn this.selectTarget null}}
|
||||
class='flyout-with-table'
|
||||
data-test-targets-sessions-flyout
|
||||
as |M|
|
||||
>
|
||||
<M.Header>
|
||||
{{t
|
||||
'resources.target.titles.sessions-flyout'
|
||||
targetDisplayName=this.selectedTarget.displayName
|
||||
}}
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<Hds::Table
|
||||
@model={{this.sortedTargetSessions}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.id.label'))
|
||||
(hash label=(t 'form.started.label'))
|
||||
(hash label=(t 'form.proxy_url.label'))
|
||||
(hash label='')
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<LinkTo
|
||||
@route='scopes.scope.projects.sessions.session.index'
|
||||
@model={{B.data.id}}
|
||||
data-test-targets-session-detail-link={{B.data.id}}
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.id}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if B.data.isActive}}
|
||||
<Hds::Button
|
||||
data-test-targets-sessions-flyout-button={{B.data.id}}
|
||||
@text={{t 'actions.yes'}}
|
||||
@color='tertiary'
|
||||
@icon='info'
|
||||
{{on 'click' (fn this.selectTarget B.data)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if B.data.type}}
|
||||
<Hds::Badge
|
||||
@text={{t (concat 'resources.target.types.' B.data.type)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<div>
|
||||
<Copyable
|
||||
@text={{B.data.project.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.project.id}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
</div>
|
||||
{{#if B.data.project.name}}
|
||||
<Hds::Badge @color='neutral' @text={{B.data.project.name}} />
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if (can 'connect target' B.data)}}
|
||||
{{#if (can 'read target' B.data)}}
|
||||
<Hds::Button
|
||||
data-test-targets-connect-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.connect'}}
|
||||
@color='secondary'
|
||||
@route='scopes.scope.projects.targets.target'
|
||||
@model={{B.data.id}}
|
||||
@query={{hash isConnecting=true}}
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::Button
|
||||
data-test-targets-connect-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.connect'}}
|
||||
@color='secondary'
|
||||
{{on 'click' (fn this.quickConnect B.data)}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
</LinkTo>
|
||||
</Copyable>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<time datetime={{format-date-iso B.data.created_time}}>
|
||||
{{format-date-iso-human B.data.created_time}}
|
||||
</time>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{B.data.proxy}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if (can 'cancel session' B.data)}}
|
||||
<Hds::Button
|
||||
data-test-session-flyout-cancel-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.end'}}
|
||||
@icon='x'
|
||||
@isIconOnly={{true}}
|
||||
@color='secondary'
|
||||
{{on 'click' (fn this.cancelSession B.data)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
{{#if this.showFlyoutViewMoreLink}}
|
||||
<Hds::Link::Standalone
|
||||
@text={{t 'resources.target.actions.view-more-sessions'}}
|
||||
@route='scopes.scope.projects.sessions'
|
||||
@query={{this.viewMoreLinkQueryParams}}
|
||||
@icon='arrow-right'
|
||||
@iconPosition='trailing'
|
||||
/>
|
||||
|
||||
{{/if}}
|
||||
{{#if this.noResults}}
|
||||
<Hds::ApplicationState data-test-no-target-results as |A|>
|
||||
<A.Header @title={{t 'titles.no-results-found'}} />
|
||||
<A.Body
|
||||
@text={{t
|
||||
(if
|
||||
this.search
|
||||
'descriptions.no-search-results'
|
||||
'descriptions.no-filter-results'
|
||||
)
|
||||
query=this.search
|
||||
resource='targets'
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if this.noTargets}}
|
||||
<Hds::ApplicationState data-test-no-targets as |A|>
|
||||
<A.Header @title={{t 'resources.target.messages.none.title'}} />
|
||||
<A.Body @text={{t 'resources.target.messages.none.description'}} />
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
{{#if (and this.selectedTarget this.selectedTarget.isActive)}}
|
||||
<Hds::Flyout
|
||||
@onClose={{fn this.selectTarget null}}
|
||||
class='flyout-with-table'
|
||||
data-test-targets-sessions-flyout
|
||||
as |M|
|
||||
>
|
||||
<M.Header>
|
||||
{{t
|
||||
'resources.target.titles.sessions-flyout'
|
||||
targetDisplayName=this.selectedTarget.displayName
|
||||
}}
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<Hds::Table
|
||||
@model={{this.sortedTargetSessions}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.id.label'))
|
||||
(hash label=(t 'form.started.label'))
|
||||
(hash label=(t 'form.proxy_url.label'))
|
||||
(hash label='')
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<Copyable
|
||||
@text={{B.data.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<LinkTo
|
||||
@route='scopes.scope.projects.sessions.session.index'
|
||||
@model={{B.data.id}}
|
||||
data-test-targets-session-detail-link={{B.data.id}}
|
||||
>
|
||||
<Hds::Text::Code>{{B.data.id}}</Hds::Text::Code>
|
||||
</LinkTo>
|
||||
</Copyable>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<time datetime={{format-date-iso B.data.created_time}}>
|
||||
{{format-date-iso-human B.data.created_time}}
|
||||
</time>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{B.data.proxy}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if (can 'cancel session' B.data)}}
|
||||
<Hds::Button
|
||||
data-test-session-flyout-cancel-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.end'}}
|
||||
@icon='x'
|
||||
@isIconOnly={{true}}
|
||||
@color='secondary'
|
||||
{{on 'click' (fn this.cancelSession B.data)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
{{#if this.showFlyoutViewMoreLink}}
|
||||
<Hds::Link::Standalone
|
||||
@text={{t 'resources.target.actions.view-more-sessions'}}
|
||||
@route='scopes.scope.projects.sessions'
|
||||
@query={{this.viewMoreLinkQueryParams}}
|
||||
@icon='arrow-right'
|
||||
@iconPosition='trailing'
|
||||
/>
|
||||
{{/if}}
|
||||
</M.Body>
|
||||
</Hds::Flyout>
|
||||
{{/if}}
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
</M.Body>
|
||||
</Hds::Flyout>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header @title={{t 'states.loading.title'}} @icon='loading' />
|
||||
<A.Body
|
||||
@text={{t
|
||||
'states.loading.description'
|
||||
resource=(t 'resources.target.title_plural')
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
Reference in new issue
Block a user