Use updated cache daemon (#2475)
* feat: 🎸 Handle truncated cache daemon response
This commit is contained in:
1 parent
ace65eab3a
commit
97258f7525
15 files changed
+468
-478
No files matched your search
@@ -7,6 +7,7 @@ import sinon from 'sinon';
|
||||
import { typeOf } from '@ember/utils';
|
||||
import { resourceNames } from 'api/handlers/cache-daemon-handler';
|
||||
import { singularize } from 'ember-inflector';
|
||||
import { underscore } from '@ember/string';
|
||||
|
||||
/**
|
||||
* This test helper can be used to help setup your sinon stubs in your tests.
|
||||
@@ -69,16 +70,18 @@ export default function setupStubs(hooks) {
|
||||
.withArgs('searchCacheDaemon')
|
||||
.onCall(i)
|
||||
.returns({
|
||||
[resourceName]: models.map((model) => {
|
||||
// Use internal serializer to serialize the model correctly
|
||||
// according to our mirage serializers
|
||||
const modelData =
|
||||
this.server.serializerOrRegistry.serialize(model);
|
||||
[underscore(resourceNames[singularize(resourceName)])]: models.map(
|
||||
(model) => {
|
||||
// Use internal serializer to serialize the model correctly
|
||||
// according to our mirage serializers
|
||||
const modelData =
|
||||
this.server.serializerOrRegistry.serialize(model);
|
||||
|
||||
// Serialize the data properly to standard JSON as that is what
|
||||
// we're expecting from the cache daemon response
|
||||
return JSON.parse(JSON.stringify(modelData));
|
||||
}),
|
||||
// Serialize the data properly to standard JSON as that is what
|
||||
// we're expecting from the cache daemon response
|
||||
return JSON.parse(JSON.stringify(modelData));
|
||||
},
|
||||
),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { inject as service } from '@ember/service';
|
||||
import { getOwner, setOwner } from '@ember/application';
|
||||
import { generateMQLExpression } from '../utils/mql-query';
|
||||
import { paginateResults } from '../utils/paginate-results';
|
||||
import { underscore } from '@ember/string';
|
||||
|
||||
/**
|
||||
* Not all types are yet supported by the cache daemon so we'll
|
||||
@@ -94,12 +95,14 @@ export default class CacheDaemonHandler {
|
||||
const sessionData = this.session.data?.authenticated;
|
||||
const auth_token_id = sessionData?.id;
|
||||
const token = sessionData?.token;
|
||||
const resourceName = resourceNames[type];
|
||||
|
||||
remainingQuery = {
|
||||
...remainingQuery,
|
||||
query: searchQuery,
|
||||
auth_token_id,
|
||||
token,
|
||||
resource: resourceNames[type],
|
||||
resource: resourceName,
|
||||
};
|
||||
|
||||
let cacheDaemonResults = {};
|
||||
@@ -130,7 +133,7 @@ export default class CacheDaemonHandler {
|
||||
}
|
||||
} else {
|
||||
__electronLog?.error(
|
||||
'Failed to search cache daemon; falling back to search controller',
|
||||
`Failed to search cache daemon for ${resourceName}. Error:`,
|
||||
e.message,
|
||||
);
|
||||
|
||||
@@ -138,10 +141,7 @@ export default class CacheDaemonHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Currently returns with a singular top level field with resource name
|
||||
// e.g. { targets: [...] } or { sessions: [..] }
|
||||
// So this just unwraps to the array, or undefined
|
||||
const [results] = Object.values(cacheDaemonResults);
|
||||
const results = cacheDaemonResults[underscore(resourceName)];
|
||||
const payload = { items: paginateResults(results, page, pageSize) };
|
||||
|
||||
const schema = store.modelFor(type);
|
||||
@@ -167,7 +167,15 @@ export default class CacheDaemonHandler {
|
||||
// This isn't conventional but is better than returning an ArrayProxy
|
||||
// or EmberArray since the ember store query method asserts it has to be an array
|
||||
// so we can't just return an object.
|
||||
records.meta = { totalItems: results?.length ?? 0 };
|
||||
// Also include whether the results are incomplete or is still refreshing
|
||||
// as part of the response
|
||||
records.meta = {
|
||||
totalItems: results?.length ?? 0,
|
||||
isLoadIncomplete: cacheDaemonResults.incomplete ?? false,
|
||||
// Sometimes the refresh status returns an error status if it takes too long but it's still refreshing
|
||||
isRefreshing: cacheDaemonResults.refresh_status !== 'not-refreshing',
|
||||
};
|
||||
|
||||
return records;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
gap: 0.5rem;
|
||||
|
||||
.filter-tag {
|
||||
@@ -27,18 +26,16 @@
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.hds-segmented-group {
|
||||
> * {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hds-segmented-group {
|
||||
.hds-dropdown-toggle-button__text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.hds-table {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.search-filtering-toolbar {
|
||||
display: flex;
|
||||
|
||||
|
||||
@@ -24,3 +24,6 @@ loading:
|
||||
title: Loading all items...
|
||||
description: All of your {resource} are still loading. Please wait while we finish loading everything on screen.
|
||||
generic-description: All of your resources are still loading. Please wait while we finish loading everything on screen.
|
||||
incomplete-loading:
|
||||
limit: We've reached the result limit. Please narrow down your search or filters if the result you're looking for is not shown.
|
||||
refreshing: We're still currently loading the results. Please wait a moment before refreshing again.
|
||||
@@ -54,19 +54,6 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns targets that are associated will all sessions the user has access to
|
||||
* @returns {[TargetModel]}
|
||||
*/
|
||||
get availableTargets() {
|
||||
const uniqueSessionTargetIds = new Set(
|
||||
this.model.allSessions.map((session) => session.target_id),
|
||||
);
|
||||
return this.model.allTargets.filter((target) =>
|
||||
uniqueSessionTargetIds.has(target.id),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all status types for sessions
|
||||
* @returns {[object]}
|
||||
@@ -98,7 +85,7 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
|
||||
get filters() {
|
||||
return {
|
||||
allFilters: {
|
||||
targets: this.availableTargets,
|
||||
targets: this.model.associatedTargets,
|
||||
status: this.sessionStatusOptions,
|
||||
scopes: this.availableScopes,
|
||||
},
|
||||
|
||||
@@ -49,15 +49,6 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
|
||||
|
||||
// =methods
|
||||
|
||||
get showFilters() {
|
||||
return (
|
||||
this.model.allTargets.length ||
|
||||
this.types.length ||
|
||||
this.search ||
|
||||
this.availableSessions.length
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if model is empty but we have a search term or filters
|
||||
* @returns {boolean}
|
||||
@@ -85,13 +76,8 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
|
||||
* @returns {[ScopeModel]}
|
||||
*/
|
||||
get availableScopes() {
|
||||
const uniqueTargetScopeIds = new Set(
|
||||
this.model.allTargets.map((target) => target.scope.id),
|
||||
);
|
||||
|
||||
return this.model.projects.filter((project) =>
|
||||
uniqueTargetScopeIds.has(project.id),
|
||||
);
|
||||
// TODO: Make use of implicit scopes or when scopes are added to cache daemon
|
||||
return this.model.projects;
|
||||
}
|
||||
|
||||
get availableSessionOptions() {
|
||||
|
||||
@@ -8,9 +8,10 @@ import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import {
|
||||
STATUS_SESSION_ACTIVE,
|
||||
STATUS_SESSION_PENDING,
|
||||
STATUS_SESSION_CANCELING,
|
||||
STATUS_SESSION_PENDING,
|
||||
} from 'api/models/session';
|
||||
import chunk from 'lodash/chunk';
|
||||
|
||||
export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -44,7 +45,6 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
};
|
||||
|
||||
allSessions;
|
||||
allTargets;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -65,7 +65,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
/**
|
||||
* Loads queried sessions, the total number of sessions, all sessions,
|
||||
* all targets and all projects for filtering options for the current user.
|
||||
* @return {Promise<{sessions: [SessionModel], projects: [ScopeModel], allSessions: [SessionModel], allTargets: [TargetModel], totalItems: number}>}
|
||||
* @return {Promise<{sessions: [SessionModel], projects: [ScopeModel], allSessions: [SessionModel], associatedTargets: [TargetModel], totalItems: number}>}
|
||||
*/
|
||||
async model({ targets, status, scopes, page, pageSize }) {
|
||||
const projects = this.modelFor('scopes.scope.projects');
|
||||
@@ -97,6 +97,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
page,
|
||||
pageSize,
|
||||
force_refresh: true,
|
||||
max_result_set_size: -1,
|
||||
};
|
||||
if (orgScope.isOrg && scopes.length === 0) {
|
||||
queryOptions.filter = orgFilter;
|
||||
@@ -104,36 +105,62 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
const sessions = await this.store.query('session', queryOptions);
|
||||
const totalItems = sessions.meta?.totalItems;
|
||||
|
||||
// Query all sessions and all targets for defining filtering values if entering route for the first time
|
||||
// Query all sessions and associated targets for defining filtering values if entering route for the first time
|
||||
if (!this.allSessions?.length) {
|
||||
await this.getAllSessions(orgScope, scopes, orgFilter);
|
||||
}
|
||||
if (!this.allTargets?.length) {
|
||||
await this.getAllTargets(orgScope, orgFilter);
|
||||
}
|
||||
const associatedTargets = await this.getAssociatedTargets(
|
||||
orgScope,
|
||||
orgFilter,
|
||||
);
|
||||
|
||||
return {
|
||||
sessions,
|
||||
projects,
|
||||
allSessions: this.allSessions,
|
||||
allTargets: this.allTargets,
|
||||
associatedTargets,
|
||||
totalItems,
|
||||
isCacheDaemonRunning: await this.ipc.invoke('isCacheDaemonRunning'),
|
||||
};
|
||||
}
|
||||
|
||||
async getAllTargets(orgScope, orgFilter) {
|
||||
const allTargetsQuery = {
|
||||
scope_id: orgScope.id,
|
||||
recursive: true,
|
||||
force_refresh: true,
|
||||
};
|
||||
if (orgScope.isOrg) {
|
||||
allTargetsQuery.filter = orgFilter;
|
||||
async getAssociatedTargets(orgScope) {
|
||||
if (!this.allSessions) {
|
||||
return [];
|
||||
}
|
||||
this.allTargets = await this.store.query('target', allTargetsQuery, {
|
||||
pushToStore: false,
|
||||
});
|
||||
|
||||
const uniqueTargetIds = [
|
||||
...new Set(this.allSessions.map((session) => session.target_id)),
|
||||
];
|
||||
// Chunk target IDs in case there's a lot of sessions as cache daemon syntax can't take too many
|
||||
const chunkedTargetIds = chunk(uniqueTargetIds, 50);
|
||||
|
||||
const associatedTargetsPromises = chunkedTargetIds.map((targetIds) =>
|
||||
this.store.query(
|
||||
'target',
|
||||
{
|
||||
scope_id: orgScope.id,
|
||||
recursive: true,
|
||||
force_refresh: true,
|
||||
query: {
|
||||
filters: {
|
||||
id: {
|
||||
logicalOperator: 'or',
|
||||
values: targetIds.map((targetId) => ({
|
||||
equals: targetId,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pushToStore: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const targetsArray = await Promise.all(associatedTargetsPromises);
|
||||
return targetsArray.flat();
|
||||
}
|
||||
|
||||
async getAllSessions(orgScope, scopes, orgFilter) {
|
||||
@@ -146,6 +173,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
},
|
||||
},
|
||||
force_refresh: true,
|
||||
max_result_set_size: -1,
|
||||
};
|
||||
if (orgScope.isOrg && scopes.length === 0) {
|
||||
allSessionsQuery.filter = orgFilter;
|
||||
@@ -182,8 +210,8 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
const orgScope = this.modelFor('scopes.scope');
|
||||
const orgFilter = `"/item/scope/parent_scope_id" == "${orgScope.id}"`;
|
||||
|
||||
await this.getAllTargets(orgScope, orgFilter);
|
||||
await this.getAllSessions(orgScope, [], orgFilter);
|
||||
await this.getAssociatedTargets(orgScope);
|
||||
|
||||
// Prime the store by searching for orgs only in case there are new org scopes;
|
||||
// otherwise we won't be able to correctly peek the org scopes for their display name in the UI.
|
||||
|
||||
@@ -49,8 +49,6 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
},
|
||||
};
|
||||
|
||||
allTargets;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
@@ -79,8 +77,12 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
* projects for filtering options.
|
||||
*
|
||||
* @returns {Promise<{totalItems: number, targets: [TargetModel], projects: [ScopeModel], allTargets: [TargetModel] }> }
|
||||
* @return {Promise<{totalItems: number, targets: [TargetModel], projects: [ScopeModel],
|
||||
* isCacheDaemonRunning: boolean, isLoadIncomplete: boolean, isRefreshing: boolean}>}
|
||||
*/
|
||||
async model({ search, scopes, availableSessions, types, page, pageSize }) {
|
||||
const isCacheRunningPromise = this.ipc.invoke('isCacheDaemonRunning');
|
||||
|
||||
const orgScope = this.modelFor('scopes.scope');
|
||||
const projects = this.modelFor('scopes.scope.projects');
|
||||
|
||||
@@ -96,15 +98,7 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
filters.type.push({ equals: type });
|
||||
});
|
||||
|
||||
const aliasPromise = this.store.query('alias', {
|
||||
scope_id: 'global',
|
||||
force_refresh: true,
|
||||
});
|
||||
const allTargetsPromise = !this.allTargets?.length
|
||||
? this.makeAllTargetsQuery(orgScope, orgFilter)
|
||||
: Promise.resolve();
|
||||
|
||||
const sessions = await this.makeSessionQuery(orgScope, scopes, orgFilter);
|
||||
const sessions = await this.getSessions(orgScope, scopes, orgFilter);
|
||||
this.addActiveSessionFilters(filters, availableSessions, sessions);
|
||||
|
||||
const query = {
|
||||
@@ -119,20 +113,28 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
query.filter = orgFilter;
|
||||
}
|
||||
let targets = await this.store.query('target', query);
|
||||
const totalItems = targets.meta?.totalItems;
|
||||
const { totalItems, isLoadIncomplete, isRefreshing } = targets.meta;
|
||||
// Filter out targets to which users do not have the connect ability
|
||||
targets = targets.filter((target) =>
|
||||
this.can.can('connect target', target),
|
||||
);
|
||||
|
||||
const allTargets = await allTargetsPromise;
|
||||
if (!this.allTargets?.length) {
|
||||
// Filter out targets to which users do not have the connect ability
|
||||
this.allTargets = allTargets.filter((target) =>
|
||||
target.authorized_actions.includes('authorize-session'),
|
||||
);
|
||||
}
|
||||
const aliasPromise = this.store.query('alias', {
|
||||
scope_id: 'global',
|
||||
force_refresh: true,
|
||||
query: {
|
||||
filters: {
|
||||
destination_id: {
|
||||
logicalOperator: 'or',
|
||||
values: targets.map((target) => ({
|
||||
equals: target.id,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Load the aliases for the targets on the current page
|
||||
try {
|
||||
await aliasPromise;
|
||||
} catch (e) {
|
||||
@@ -144,13 +146,14 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
return {
|
||||
targets,
|
||||
projects,
|
||||
allTargets: this.allTargets,
|
||||
totalItems,
|
||||
isCacheDaemonRunning: await this.ipc.invoke('isCacheDaemonRunning'),
|
||||
isLoadIncomplete,
|
||||
isRefreshing,
|
||||
isCacheDaemonRunning: await isCacheRunningPromise,
|
||||
};
|
||||
}
|
||||
|
||||
async makeSessionQuery(orgScope, scopes, orgFilter) {
|
||||
async getSessions(orgScope, scopes, orgFilter) {
|
||||
// Retrieve all sessions so that the session and activeSessions getters
|
||||
// in the target model always retrieve the most up-to-date sessions.
|
||||
const sessionQuery = {
|
||||
@@ -166,6 +169,7 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
},
|
||||
},
|
||||
force_refresh: true,
|
||||
max_result_set_size: -1,
|
||||
};
|
||||
if (orgScope.isOrg && scopes.length === 0) {
|
||||
sessionQuery.filter = orgFilter;
|
||||
@@ -173,27 +177,6 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
return this.store.query('session', sessionQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the targets but only load them once when entering the route. Ideally we would lazy load it when needed
|
||||
* but this can be revisited in the future.
|
||||
* @param orgScope
|
||||
* @param orgFilter
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async makeAllTargetsQuery(orgScope, orgFilter) {
|
||||
// Query all targets for defining filtering values if entering route for first time
|
||||
const query = {
|
||||
scope_id: orgScope.id,
|
||||
recursive: true,
|
||||
force_refresh: true,
|
||||
};
|
||||
if (orgScope.isOrg) {
|
||||
query.filter = orgFilter;
|
||||
}
|
||||
const options = { pushToStore: false };
|
||||
return this.store.query('target', query, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the filters for active sessions to the filter object.
|
||||
* @param filters
|
||||
@@ -255,14 +238,6 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
|
||||
@action
|
||||
async refreshAll() {
|
||||
const orgScope = this.modelFor('scopes.scope');
|
||||
const orgFilter = `"/item/scope/parent_scope_id" == "${orgScope.id}"`;
|
||||
const allTargets = await this.makeAllTargetsQuery(orgScope, orgFilter);
|
||||
// Filter out targets to which users do not have the connect ability
|
||||
this.allTargets = allTargets.filter((target) =>
|
||||
target.authorized_actions.includes('authorize-session'),
|
||||
);
|
||||
|
||||
// Prime the store by searching for only orgs in case there are new org scopes;
|
||||
// otherwise we won't be able to correctly peek the org scopes for their display name in the UI.
|
||||
await this.store.query('scope', {});
|
||||
|
||||
@@ -3,119 +3,118 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{#if (or @model.sessions @model.allSessions)}}
|
||||
{{#if @model.isCacheDaemonRunning}}
|
||||
<div class='search-filtering-toolbar'>
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='target'
|
||||
@toggleText={{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='status'
|
||||
@toggleText={{t 'form.status.label'}}
|
||||
@itemOptions={{this.sessionStatusOptions}}
|
||||
@checkedItems={{this.status}}
|
||||
@applyFilter={{fn this.applyFilter 'status'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='scope'
|
||||
@toggleText={{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>
|
||||
<span>
|
||||
<ToolbarRefresher @onClick={{route-action 'refreshAll'}} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{/if}}
|
||||
{{#if @model.sessions}}
|
||||
<Hds::Table
|
||||
@model={{this.sortedSessions}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.started.label'))
|
||||
(hash label=(t 'form.session_id.label'))
|
||||
(hash label=(t 'resources.target.title'))
|
||||
(hash label=(t 'form.proxy_url.label'))
|
||||
(hash label=(t 'form.status.label'))
|
||||
(hash
|
||||
label=(t 'titles.actions')
|
||||
isVisuallyHidden=true
|
||||
align='right'
|
||||
width='60px'
|
||||
)
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<time datetime={{format-date-iso B.data.created_time}}>
|
||||
{{format-date-iso-human B.data.created_time}}
|
||||
</time>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<div class='link-copy-button-container'>
|
||||
{{#if B.data.isAvailable}}
|
||||
<Hds::Link::Inline
|
||||
@route='scopes.scope.projects.sessions.session.index'
|
||||
@model={{B.data.id}}
|
||||
data-test-session-detail-link={{B.data.id}}
|
||||
{{#if @model.isCacheDaemonRunning}}
|
||||
<div class='search-filtering-toolbar'>
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='target'
|
||||
@toggleText={{t 'resources.target.title'}}
|
||||
@itemOptions={{this.model.associatedTargets}}
|
||||
@checkedItems={{this.targets}}
|
||||
@applyFilter={{fn this.applyFilter 'targets'}}
|
||||
@isSearchable={{true}}
|
||||
@listPosition='bottom-left'
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='status'
|
||||
@toggleText={{t 'form.status.label'}}
|
||||
@itemOptions={{this.sessionStatusOptions}}
|
||||
@checkedItems={{this.status}}
|
||||
@applyFilter={{fn this.applyFilter 'status'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='scope'
|
||||
@toggleText={{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}}
|
||||
>
|
||||
{{B.data.id}}
|
||||
</Hds::Link::Inline>
|
||||
{{else}}
|
||||
<Hds::Text::Body data-test-session-id-copy={{B.data.id}}>
|
||||
{{B.data.id}}
|
||||
</Hds::Text::Body>
|
||||
{{/if}}
|
||||
<Hds::Copy::Button
|
||||
@text={{B.data.id}}
|
||||
@isIconOnly={{true}}
|
||||
@textToCopy={{B.data.id}}
|
||||
data-test-session-id-copy={{B.data.id}}
|
||||
/>
|
||||
</div>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{B.data.target.displayName}}
|
||||
</B.Td>
|
||||
{{!
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<span>
|
||||
<ToolbarRefresher @onClick={{route-action 'refreshAll'}} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{/if}}
|
||||
{{#if @model.sessions}}
|
||||
<Hds::Table
|
||||
@model={{this.sortedSessions}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.started.label'))
|
||||
(hash label=(t 'form.session_id.label'))
|
||||
(hash label=(t 'resources.target.title'))
|
||||
(hash label=(t 'form.proxy_url.label'))
|
||||
(hash label=(t 'form.status.label'))
|
||||
(hash
|
||||
label=(t 'titles.actions')
|
||||
isVisuallyHidden=true
|
||||
align='right'
|
||||
width='60px'
|
||||
)
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<time datetime={{format-date-iso B.data.created_time}}>
|
||||
{{format-date-iso-human B.data.created_time}}
|
||||
</time>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<div class='link-copy-button-container'>
|
||||
{{#if B.data.isAvailable}}
|
||||
<Hds::Link::Inline
|
||||
@route='scopes.scope.projects.sessions.session.index'
|
||||
@model={{B.data.id}}
|
||||
data-test-session-detail-link={{B.data.id}}
|
||||
>
|
||||
{{B.data.id}}
|
||||
</Hds::Link::Inline>
|
||||
{{else}}
|
||||
<Hds::Text::Body data-test-session-id-copy={{B.data.id}}>
|
||||
{{B.data.id}}
|
||||
</Hds::Text::Body>
|
||||
{{/if}}
|
||||
<Hds::Copy::Button
|
||||
@text={{B.data.id}}
|
||||
@isIconOnly={{true}}
|
||||
@textToCopy={{B.data.id}}
|
||||
data-test-session-id-copy={{B.data.id}}
|
||||
/>
|
||||
</div>
|
||||
</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
|
||||
@@ -126,50 +125,49 @@
|
||||
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}}
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{B.data.proxy}}
|
||||
@color='secondary'
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<SessionStatus @model={{B.data}} />
|
||||
</B.Td>
|
||||
<B.Td @align='right' width='60px'>
|
||||
{{#if (can 'cancel session' B.data)}}
|
||||
<Hds::Button
|
||||
@text={{t 'actions.cancel'}}
|
||||
@icon='x'
|
||||
@isIconOnly={{true}}
|
||||
<B.Td>
|
||||
{{#if B.data.isAvailable}}
|
||||
{{#if B.data.proxy}}
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{B.data.proxy}}
|
||||
@color='secondary'
|
||||
{{on 'click' (route-action 'cancelSession' B.data)}}
|
||||
data-test-session-cancel-button={{B.data.id}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<SessionStatus @model={{B.data}} />
|
||||
</B.Td>
|
||||
<B.Td @align='right' width='60px'>
|
||||
{{#if (can 'cancel session' B.data)}}
|
||||
<Hds::Button
|
||||
@text={{t 'actions.cancel'}}
|
||||
@icon='x'
|
||||
@isIconOnly={{true}}
|
||||
@color='secondary'
|
||||
{{on 'click' (route-action 'cancelSession' B.data)}}
|
||||
data-test-session-cancel-button={{B.data.id}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
/>
|
||||
{{else if @model.allSessions}}
|
||||
<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=(t 'resources.session.title_plural')
|
||||
}}
|
||||
/>
|
||||
{{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=(t 'resources.session.title_plural')
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
</Hds::ApplicationState>
|
||||
{{else}}
|
||||
<Hds::ApplicationState data-test-no-sessions as |A|>
|
||||
<A.Header @title={{t 'resources.session.messages.none-friendly.title'}} />
|
||||
|
||||
@@ -3,191 +3,199 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{#if this.showFilters}}
|
||||
{{#if @model.isCacheDaemonRunning}}
|
||||
<div class='search-filtering-toolbar'>
|
||||
<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='active-sessions'
|
||||
@toggleText={{t 'resources.target.titles.active-sessions'}}
|
||||
@itemOptions={{this.availableSessionOptions}}
|
||||
@checkedItems={{this.availableSessions}}
|
||||
@applyFilter={{fn this.applyFilter 'availableSessions'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='type'
|
||||
@toggleText={{t 'form.type.label'}}
|
||||
@itemOptions={{this.targetTypeOptions}}
|
||||
@checkedItems={{this.types}}
|
||||
@applyFilter={{fn this.applyFilter 'types'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='scope'
|
||||
@toggleText={{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>
|
||||
<span>
|
||||
<ToolbarRefresher @onClick={{route-action 'refreshAll'}} />
|
||||
</span>
|
||||
</div>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.targets}}
|
||||
<Hds::Table
|
||||
@model={{@model.targets}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.name.label'))
|
||||
(hash label=(t 'resources.alias.title_plural'))
|
||||
(hash label=(t 'resources.target.titles.active-sessions'))
|
||||
(hash label=(t 'form.type.label'))
|
||||
(hash label=(t 'resources.project.title'))
|
||||
(hash label=(t 'titles.actions') isVisuallyHidden=true align='right')
|
||||
}}
|
||||
@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 data-test-target-aliases={{B.data.id}}>
|
||||
{{truncate-list 'actions.more' B.data.associatedAliases}}
|
||||
</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>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{B.data.project.id}}
|
||||
@color='secondary'
|
||||
data-test-target-project-id={{B.data.project.id}}
|
||||
/>
|
||||
</div>
|
||||
{{#if B.data.project.name}}
|
||||
<Hds::Badge @color='neutral' @text={{B.data.project.name}} />
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td @align='right'>
|
||||
<div {{style display='inline-block'}}>
|
||||
{{#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}}
|
||||
</div>
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
/>
|
||||
|
||||
{{/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=(t 'resources.target.title_plural')
|
||||
}}
|
||||
{{#if @model.isCacheDaemonRunning}}
|
||||
<div class='search-filtering-toolbar'>
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.TextInput
|
||||
@value={{this.search}}
|
||||
@type='search'
|
||||
placeholder={{t 'actions.search'}}
|
||||
aria-label={{t 'actions.search'}}
|
||||
{{on 'input' this.handleSearchInput}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='active-sessions'
|
||||
@toggleText={{t 'resources.target.titles.active-sessions'}}
|
||||
@itemOptions={{this.availableSessionOptions}}
|
||||
@checkedItems={{this.availableSessions}}
|
||||
@applyFilter={{fn this.applyFilter 'availableSessions'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='type'
|
||||
@toggleText={{t 'form.type.label'}}
|
||||
@itemOptions={{this.targetTypeOptions}}
|
||||
@checkedItems={{this.types}}
|
||||
@applyFilter={{fn this.applyFilter 'types'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
name='scope'
|
||||
@toggleText={{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>
|
||||
<span>
|
||||
<ToolbarRefresher @onClick={{route-action 'refreshAll'}} />
|
||||
</span>
|
||||
</div>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{#if @model.isLoadIncomplete}}
|
||||
<Hds::Alert @color='warning' @type='compact' as |A|>
|
||||
<A.Description>{{t 'states.incomplete-loading.limit'}}</A.Description>
|
||||
</Hds::Alert>
|
||||
{{/if}}
|
||||
{{#if @model.isRefreshing}}
|
||||
<Hds::Alert @color='warning' @type='compact' as |A|>
|
||||
<A.Description>{{t
|
||||
'states.incomplete-loading.refreshing'
|
||||
}}</A.Description>
|
||||
</Hds::Alert>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if this.noTargets}}
|
||||
|
||||
{{#if @model.targets}}
|
||||
<Hds::Table
|
||||
@model={{@model.targets}}
|
||||
@columns={{array
|
||||
(hash label=(t 'form.name.label'))
|
||||
(hash label=(t 'resources.alias.title_plural'))
|
||||
(hash label=(t 'resources.target.titles.active-sessions'))
|
||||
(hash label=(t 'form.type.label'))
|
||||
(hash label=(t 'resources.project.title'))
|
||||
(hash label=(t 'titles.actions') isVisuallyHidden=true align='right')
|
||||
}}
|
||||
@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 data-test-target-aliases={{B.data.id}}>
|
||||
{{truncate-list 'actions.more' B.data.associatedAliases}}
|
||||
</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>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{B.data.project.id}}
|
||||
@color='secondary'
|
||||
data-test-target-project-id={{B.data.project.id}}
|
||||
/>
|
||||
</div>
|
||||
{{#if B.data.project.name}}
|
||||
<Hds::Badge @color='neutral' @text={{B.data.project.name}} />
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td @align='right'>
|
||||
<div {{style display='inline-block'}}>
|
||||
{{#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}}
|
||||
</div>
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{@model.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
/>
|
||||
{{/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=(t 'resources.target.title_plural')
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{else 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'}} />
|
||||
|
||||
@@ -60,7 +60,7 @@ const unixSocketRequest = (options, reqBody) =>
|
||||
if (statusCode < 200 || statusCode >= 400) {
|
||||
reject({
|
||||
statusCode,
|
||||
message: parsedResponse?.api_error ?? parsedResponse?.error,
|
||||
message: parsedResponse?.message ?? parsedResponse?.error,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
|
||||
test('visiting empty sessions', async function (assert) {
|
||||
this.server.db.sessions.remove();
|
||||
this.stubCacheDaemonSearch('sessions', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'sessions');
|
||||
await visit(urls.projects);
|
||||
|
||||
await click(`[href="${urls.sessions}"]`);
|
||||
@@ -371,14 +371,14 @@ module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
'sessions',
|
||||
'sessions',
|
||||
'targets',
|
||||
'aliases',
|
||||
'targets',
|
||||
'sessions',
|
||||
'targets',
|
||||
'aliases',
|
||||
{
|
||||
resource: 'sessions',
|
||||
func: () => [instances.session2],
|
||||
},
|
||||
'targets',
|
||||
);
|
||||
await visit(urls.globalSessions);
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
});
|
||||
|
||||
test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) {
|
||||
@@ -207,7 +207,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
test('visiting targets list view with no targets', async function (assert) {
|
||||
this.server.db.targets.remove();
|
||||
this.server.db.sessions.remove();
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
|
||||
await visit(urls.projects);
|
||||
|
||||
@@ -219,7 +219,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
test('user cannot navigate to a target without proper authorization', async function (assert) {
|
||||
instances.target.authorized_actions =
|
||||
instances.target.authorized_actions.filter((item) => item !== 'read');
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
|
||||
await visit(urls.projects);
|
||||
|
||||
@@ -248,7 +248,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
instances.target.authorized_actions.filter(
|
||||
(item) => item !== 'authorize-session',
|
||||
);
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
|
||||
await visit(urls.projects);
|
||||
|
||||
@@ -281,7 +281,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
test('user can connect without target read permissions', async function (assert) {
|
||||
instances.target.authorized_actions =
|
||||
instances.target.authorized_actions.filter((item) => item !== 'read');
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
session_id: instances.session.id,
|
||||
@@ -307,7 +307,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
instances.target.authorized_actions.filter((item) => item !== 'read');
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').rejects();
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
await visit(urls.projects);
|
||||
@@ -348,8 +348,6 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
|
||||
test('user can cancel a session from inside target sessions flyout', async function (assert) {
|
||||
this.stubCacheDaemonSearch(
|
||||
'aliases',
|
||||
'targets',
|
||||
'sessions',
|
||||
'targets',
|
||||
'aliases',
|
||||
@@ -358,6 +356,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
func: () => [],
|
||||
},
|
||||
'targets',
|
||||
'aliases',
|
||||
);
|
||||
await visit(urls.projects);
|
||||
|
||||
@@ -387,7 +386,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
test('user cannot cancel a session from inside target sessions flyout without permissions', async function (assert) {
|
||||
instances.session.authorized_actions =
|
||||
instances.session.authorized_actions.filter((item) => item !== 'cancel');
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
await visit(urls.projects);
|
||||
|
||||
await click(`[href="${urls.targets}"]`);
|
||||
@@ -425,7 +424,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
test('user can navigate to session details from sessions table in flyout without permissions', async function (assert) {
|
||||
instances.session.authorized_actions =
|
||||
instances.session.authorized_actions.filter((item) => item !== 'read');
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
await visit(urls.projects);
|
||||
|
||||
await click(`[href="${urls.targets}"]`);
|
||||
@@ -447,8 +446,6 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
|
||||
test('user can change org scope and only targets for that org will be displayed', async function (assert) {
|
||||
this.stubCacheDaemonSearch(
|
||||
'aliases',
|
||||
'targets',
|
||||
'sessions',
|
||||
'targets',
|
||||
'aliases',
|
||||
@@ -457,6 +454,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
resource: 'targets',
|
||||
func: () => [instances.target],
|
||||
},
|
||||
'aliases',
|
||||
);
|
||||
|
||||
await visit(urls.scopes.global);
|
||||
|
||||
@@ -130,7 +130,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
});
|
||||
|
||||
test('user can connect to a target with an address', async function (assert) {
|
||||
|
||||
@@ -143,7 +143,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
});
|
||||
|
||||
test('visiting index', async function (assert) {
|
||||
@@ -213,8 +213,6 @@ module('Acceptance | scopes', function (hooks) {
|
||||
test('can navigate among org scopes via header navigation', async function (assert) {
|
||||
assert.expect(3);
|
||||
this.stubCacheDaemonSearch(
|
||||
'aliases',
|
||||
'targets',
|
||||
'sessions',
|
||||
'targets',
|
||||
'aliases',
|
||||
@@ -226,6 +224,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
'aliases',
|
||||
'sessions',
|
||||
'targets',
|
||||
'aliases',
|
||||
);
|
||||
await visit(urls.targets);
|
||||
|
||||
@@ -264,7 +263,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
assert.expect(1);
|
||||
this.server.db.targets.remove();
|
||||
this.server.db.sessions.remove();
|
||||
this.stubCacheDaemonSearch('aliases', 'targets', 'sessions', 'targets');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
|
||||
|
||||
await visit(urls.targets);
|
||||
|
||||
|
||||
Reference in new issue
Block a user