Skip to content

Commit 5e2f690

Browse files
committed
feat: 🎸 preferred clients section on settings page (#3034)
* feat: 🎸 preferred clients section on settings page * chore: πŸ€– update mock ipc with new handlers * refactor: πŸ’‘ moved windows rdp protocol to const * refactor: πŸ’‘ moved rdp calls to app route * test: πŸ’ fixed failings tests * refactor: πŸ’‘ addressed comments * fix: πŸ› fixed failing tests * refactor: πŸ’‘ add mock rdp service calls to tests * refactor: πŸ’‘ addressed comments * test: πŸ’ add additional tests * refactor: πŸ’‘ fixed test failure
1 parent 9f8c09b commit 5e2f690

File tree

19 files changed

+487
-31
lines changed

19 files changed

+487
-31
lines changed

β€Žaddons/api/mirage/scenarios/ipc.jsβ€Ž

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,20 @@ export default function initializeMockIPC(server, config) {
248248
resumeClientAgent() {}
249249
hasRunningSessions() {}
250250
stopAll() {}
251+
getRdpClients() {
252+
return ['windows-app', 'none', 'mstsc'];
253+
}
254+
getPreferredRdpClient() {
255+
return 'windows-app';
256+
}
257+
getRecommendedRdpClient() {
258+
return {
259+
name: 'windows-app',
260+
link: 'https://apps.apple.com/us/app/windows-app/id1295203466',
261+
};
262+
}
263+
setPreferredRdpClient() {}
264+
launchRdpClient() {}
251265
}
252266

253267
/**

β€Žaddons/core/translations/en-us.yamlβ€Ž

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,21 @@ settings:
142142
alerts:
143143
cache-daemon: There may be a problem with the cache daemon
144144
client-agent: There may be a problem with the client agent
145+
preferred-clients:
146+
title: Preferred Clients
147+
description: Select the applications for Boundary to use when opening these target types.
148+
table:
149+
header:
150+
protocol: Protocol
151+
client: Client
152+
data:
153+
protocols:
154+
windows-rdp: Windows RDP
155+
clients:
156+
none: None
157+
mstsc: Remote Desktop Connection (mstsc)
158+
windows-app: Windows App
159+
none-detected: 'None detected. We recommend <a href={rdpClientLink} target="_blank" rel="noopener noreferrer">{rdpClientName}</a>.'
145160
worker-filter-generator:
146161
title: Filter generator
147162
description: Choose what you want to format into a filter.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: BUSL-1.1
4+
}}
5+
6+
<SettingsCard
7+
@header={{t 'settings.preferred-clients.title'}}
8+
@icon='external-link'
9+
@description={{t 'settings.preferred-clients.description'}}
10+
>
11+
<:body>
12+
<Hds::Table
13+
class='full-width'
14+
@model={{this.protocolClients}}
15+
@columns={{array
16+
(hash
17+
label=(t 'settings.preferred-clients.table.header.protocol')
18+
width='50%'
19+
)
20+
(hash
21+
label=(t 'settings.preferred-clients.table.header.client') width='50%'
22+
)
23+
}}
24+
>
25+
<:body as |B|>
26+
<B.Tr>
27+
<B.Td>{{t
28+
(concat
29+
'settings.preferred-clients.table.data.protocols.'
30+
B.data.protocolType
31+
)
32+
}}
33+
</B.Td>
34+
<B.Td>
35+
{{#if this.showRecommendedRdpClient}}
36+
<Hds::Text::Body data-test-recommended-rdp-client>
37+
{{t
38+
'settings.preferred-clients.table.data.clients.none-detected'
39+
rdpClientLink=this.rdp.recommendedRdpClient.link
40+
rdpClientName=(t
41+
(concat
42+
'settings.preferred-clients.table.data.clients.'
43+
this.rdp.recommendedRdpClient.name
44+
)
45+
)
46+
htmlSafe=true
47+
}}
48+
</Hds::Text::Body>
49+
{{else}}
50+
<Hds::Form::Select::Field
51+
@width='100%'
52+
{{on 'change' B.data.updateClient}}
53+
data-test-select-preferred-rdp-client
54+
as |F|
55+
>
56+
<F.Options>
57+
{{#each B.data.clients as |client|}}
58+
<option
59+
value={{client}}
60+
selected={{eq B.data.preferredClient client}}
61+
>
62+
{{t
63+
(concat
64+
'settings.preferred-clients.table.data.clients.'
65+
client
66+
)
67+
}}
68+
</option>
69+
{{/each}}
70+
</F.Options>
71+
</Hds::Form::Select::Field>
72+
{{/if}}
73+
</B.Td>
74+
</B.Tr>
75+
</:body>
76+
</Hds::Table>
77+
</:body>
78+
</SettingsCard>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Component from '@glimmer/component';
7+
import { action } from '@ember/object';
8+
import { service } from '@ember/service';
9+
10+
const PROTOCOL_WINDOWS_RDP = 'windows-rdp';
11+
12+
export default class SettingsCardPreferredClientsComponent extends Component {
13+
// =services
14+
@service rdp;
15+
16+
// =getters
17+
18+
/**
19+
* Returns the list of protocols and their available clients
20+
* @returns {Array<Object>}
21+
*/
22+
get protocolClients() {
23+
return [
24+
{
25+
protocolType: PROTOCOL_WINDOWS_RDP,
26+
clients: this.rdp.rdpClients,
27+
preferredClient: this.rdp.preferredRdpClient,
28+
updateClient: this.updatePreferredRDPClient,
29+
},
30+
];
31+
}
32+
33+
get showRecommendedRdpClient() {
34+
return (
35+
this.rdp.rdpClients.length === 1 && this.rdp.rdpClients[0] === 'none'
36+
);
37+
}
38+
39+
// =methods
40+
41+
/**
42+
* Updates the preferred RDP client
43+
* @param value
44+
* @return {Promise<void>}
45+
*/
46+
@action
47+
async updatePreferredRDPClient({ target: { value } }) {
48+
await this.rdp.setPreferredRdpClient(value);
49+
}
50+
}

β€Žui/desktop/app/routes/application.jsβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default class ApplicationRoute extends Route {
1616
@service clusterUrl;
1717
@service ipc;
1818
@service intl;
19+
@service rdp;
1920

2021
// =attributes
2122

@@ -52,6 +53,9 @@ export default class ApplicationRoute extends Route {
5253

5354
await this.session.loadAuthenticatedAccount();
5455
}
56+
57+
// initialize RDP service with rdp client data
58+
await this.rdp.initialize();
5559
}
5660

5761
/**

β€Žui/desktop/app/services/rdp.jsβ€Ž

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,23 @@ import Service from '@ember/service';
77
import { service } from '@ember/service';
88
import { tracked } from '@glimmer/tracking';
99

10+
export const RDP_CLIENT_NONE = 'none';
11+
export const RDP_CLIENT_WINDOWS_APP = 'windows-app';
12+
export const RDP_CLIENT_MSTSC = 'mstsc';
13+
export const RDP_CLIENT_WINDOWS_APP_LINK =
14+
'https://apps.apple.com/us/app/windows-app/id1295203466';
15+
export const RDP_CLIENT_MSTSC_LINK =
16+
'https://learn.microsoft.com/windows-server/remote/remote-desktop-services/remotepc/uninstall-remote-desktop-connection';
17+
1018
const { __electronLog } = globalThis;
19+
const macRecommendedRdpClient = {
20+
name: RDP_CLIENT_WINDOWS_APP,
21+
link: RDP_CLIENT_WINDOWS_APP_LINK,
22+
};
23+
const windowsRecommendedRdpClient = {
24+
name: RDP_CLIENT_MSTSC,
25+
link: RDP_CLIENT_MSTSC_LINK,
26+
};
1127

1228
export default class RdpService extends Service {
1329
// =services
@@ -19,17 +35,21 @@ export default class RdpService extends Service {
1935
/**
2036
* The preferred RDP client set by the user.
2137
* @type {string|null}
22-
* @private
2338
*/
2439
@tracked preferredRdpClient = null;
2540

2641
/**
2742
* The list of available RDP clients fetched from the main process.
2843
* @type {Array<String>}
29-
* @private
3044
*/
3145
@tracked rdpClients = [];
3246

47+
/**
48+
* The recommended RDP client based on platform when only 'none' is available.
49+
* @type {Object|null}
50+
*/
51+
@tracked recommendedRdpClient = null;
52+
3353
// =attributes
3454

3555
/**
@@ -40,12 +60,21 @@ export default class RdpService extends Service {
4060
*/
4161
get isPreferredRdpClientSet() {
4262
return (
43-
this.preferredRdpClient !== null && this.preferredRdpClient !== 'none'
63+
this.preferredRdpClient !== null &&
64+
this.preferredRdpClient !== RDP_CLIENT_NONE
4465
);
4566
}
4667

4768
// =methods
4869

70+
async initialize() {
71+
await Promise.all([
72+
this.getRdpClients(),
73+
this.getPreferredRdpClient(),
74+
this.getRecommendedRdpClient(),
75+
]);
76+
}
77+
4978
/**
5079
* Fetches the list of available RDP clients from the main process.
5180
*/
@@ -60,11 +89,35 @@ export default class RdpService extends Service {
6089
} catch (error) {
6190
__electronLog?.error('Failed to fetch RDP clients', error.message);
6291
// default to 'none' option if it fails
63-
this.rdpClients = ['none'];
92+
this.rdpClients = [RDP_CLIENT_NONE];
6493
return this.rdpClients;
6594
}
6695
}
6796

97+
/**
98+
* Gets the recommended RDP client based on OS platform.
99+
*/
100+
async getRecommendedRdpClient() {
101+
try {
102+
// return cached recommended client if already set
103+
if (this.recommendedRdpClient !== null) {
104+
return this.recommendedRdpClient;
105+
}
106+
const { isWindows, isMac } = await this.ipc.invoke('checkOS');
107+
if (isWindows) {
108+
this.recommendedRdpClient = windowsRecommendedRdpClient;
109+
} else if (isMac) {
110+
this.recommendedRdpClient = macRecommendedRdpClient;
111+
}
112+
} catch (error) {
113+
__electronLog?.error(
114+
'Failed to set recommended RDP client',
115+
error.message,
116+
);
117+
this.recommendedRdpClient = null;
118+
}
119+
}
120+
68121
/**
69122
* Fetches the preferred RDP client from the main process.
70123
* @returns {string} The preferred RDP client
@@ -83,7 +136,7 @@ export default class RdpService extends Service {
83136
error.message,
84137
);
85138
// default to 'none' if it fails
86-
this.preferredRdpClient = 'none';
139+
this.preferredRdpClient = RDP_CLIENT_NONE;
87140
return this.preferredRdpClient;
88141
}
89142
}
@@ -101,7 +154,7 @@ export default class RdpService extends Service {
101154
} catch (error) {
102155
__electronLog?.error('Failed to set preferred RDP client', error.message);
103156
// set to 'none' if it fails
104-
this.preferredRdpClient = 'none';
157+
this.preferredRdpClient = RDP_CLIENT_NONE;
105158
}
106159
}
107160

β€Žui/desktop/app/templates/scopes/scope/projects/settings/index.hbsβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
<SettingsCard::Server @model={{@model}} />
88
<SettingsCard::Application @model={{@model}} @toggle={{this.toggleTheme}} />
99
<SettingsCard::ClientAgent @model={{@model}} />
10-
<SettingsCard::Logs @model={{@model}} />
10+
<SettingsCard::Logs @model={{@model}} />
11+
<SettingsCard::PreferredClients />

β€Žui/desktop/electron-app/src/index.jsβ€Ž

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,17 @@ const createWindow = async (partition, closeWindowCB) => {
164164
// Opens external links in the host default browser.
165165
// We allow developer.hashicorp.com domain to open on an external window
166166
// and releases.hashicorp.com domain to download the desktop app or
167-
// link to the release page for the desktop app.
167+
// link to the release page for the desktop app or
168+
// apps.apple.com domain to open the app store page to download the Windows App or
169+
// learn.microsoft.com domain to open the documentation for installing Remote Desktop Connection.
168170
browserWindow.webContents.setWindowOpenHandler(({ url }) => {
169171
if (
170172
isLocalhost(url) ||
171173
url.startsWith('https://developer.hashicorp.com/') ||
172174
url.startsWith('https://releases.hashicorp.com/boundary-desktop/') ||
173-
url.startsWith('https://support.hashicorp.com/hc/en-us')
175+
url.startsWith('https://support.hashicorp.com/hc/en-us') ||
176+
url.startsWith('https://learn.microsoft.com/') ||
177+
url.startsWith('https://apps.apple.com/')
174178
) {
175179
shell.openExternal(url);
176180
}

0 commit comments

Comments
Β (0)