Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/policyBlocked/browser/sessionsPolicyBlocked.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/sessionsPolicyBlocked.css';
7
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
8
import { $, addDisposableGenericMouseDownListener, append, EventType, addDisposableListener, getWindow } from '../../../../base/browser/dom.js';
9
import { localize } from '../../../../nls.js';
10
import { Button } from '../../../../base/browser/ui/button/button.js';
11
import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js';
12
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
13
import { IProductService } from '../../../../platform/product/common/productService.js';
14
import { URI } from '../../../../base/common/uri.js';
15
import { ICommandService } from '../../../../platform/commands/common/commands.js';
16
17
export const enum SessionsBlockedReason {
18
AgentDisabled = 'agentDisabled',
19
/** Transient loading state — blocks UI but shows only a progress bar. */
20
Loading = 'loading',
21
/** Signed in but not in an approved org — must switch accounts. */
22
AccountPolicyGate = 'accountPolicyGate',
23
}
24
25
export interface ISessionsBlockedOverlayOptions {
26
readonly reason: SessionsBlockedReason;
27
readonly approvedOrganizations?: readonly string[];
28
readonly accountName?: string;
29
}
30
31
/**
32
* Full-window impassable overlay shown when the Agents app is blocked.
33
*/
34
export class SessionsPolicyBlockedOverlay extends Disposable {
35
36
private readonly overlay: HTMLElement;
37
38
constructor(
39
container: HTMLElement,
40
options: ISessionsBlockedOverlayOptions,
41
@ICommandService private readonly commandService: ICommandService,
42
@IOpenerService private readonly openerService: IOpenerService,
43
@IProductService private readonly productService: IProductService,
44
) {
45
super();
46
47
this.overlay = append(container, $('.sessions-policy-blocked-overlay'));
48
this.overlay.setAttribute('role', 'dialog');
49
this.overlay.setAttribute('aria-modal', 'true');
50
this.overlay.tabIndex = -1;
51
this.overlay.focus();
52
this._register(toDisposable(() => this.overlay.remove()));
53
54
const card = append(this.overlay, $('.sessions-policy-blocked-card'));
55
56
this._register(addDisposableListener(getWindow(this.overlay), EventType.KEY_DOWN, (e: KeyboardEvent) => {
57
if (card.contains(e.target as Node)) {
58
return;
59
}
60
e.preventDefault();
61
e.stopPropagation();
62
}, true));
63
64
this._register(addDisposableGenericMouseDownListener(this.overlay, e => {
65
if (e.target === this.overlay) {
66
e.preventDefault();
67
e.stopPropagation();
68
}
69
}));
70
71
append(card, $('div.sessions-policy-blocked-logo'));
72
73
switch (options.reason) {
74
case SessionsBlockedReason.AgentDisabled:
75
this._renderAgentDisabled(card);
76
break;
77
case SessionsBlockedReason.Loading:
78
this._renderLoading(card);
79
break;
80
case SessionsBlockedReason.AccountPolicyGate:
81
this._renderAccountPolicyGate(card, options);
82
break;
83
}
84
}
85
86
private _renderAgentDisabled(card: HTMLElement): void {
87
this.overlay.setAttribute('aria-label', localize('policyBlocked.aria', "Agents disabled by organization policy"));
88
89
append(card, $('h2', undefined, localize('policyBlocked.title', "Agents Disabled")));
90
91
const description = append(card, $('p'));
92
append(description, document.createTextNode(localize('policyBlocked.description', "Your organization has disabled Agents via policy.")));
93
append(description, document.createTextNode(' '));
94
const learnMore = append(description, $('a.sessions-policy-blocked-link')) as HTMLAnchorElement;
95
learnMore.textContent = localize('policyBlocked.learnMore', "Learn more");
96
learnMore.href = 'https://aka.ms/VSCode/Agents/docs';
97
this._register(addDisposableListener(learnMore, EventType.CLICK, (e) => {
98
e.preventDefault();
99
this.openerService.open(URI.parse('https://aka.ms/VSCode/Agents/docs'));
100
}));
101
102
const button = this._register(new Button(card, { ...defaultButtonStyles, secondary: true }));
103
button.label = localize('policyBlocked.openVSCode', "Open VS Code");
104
this._register(button.onDidClick(() => this._openVSCode()));
105
}
106
107
private _renderLoading(card: HTMLElement): void {
108
this.overlay.setAttribute('aria-label', localize('loading.aria', "Loading"));
109
append(card, $('div.sessions-policy-blocked-progress-bar', undefined,
110
$('div.sessions-policy-blocked-progress-bar-fill')
111
));
112
}
113
114
private _renderAccountPolicyGate(card: HTMLElement, options: ISessionsBlockedOverlayOptions): void {
115
this.overlay.setAttribute('aria-label', localize('accountGate.aria', "Sign-in required by organization policy"));
116
117
append(card, $('h2', undefined, localize('accountGate.title', "Sign-In Required")));
118
119
const description = append(card, $('p'));
120
if (options.accountName) {
121
append(description, document.createTextNode(
122
localize('accountGate.descriptionWithAccount', "The account \"{0}\" is not a member of an approved organization. Sign into an approved GitHub account to use Agents.", options.accountName)
123
));
124
} else {
125
append(description, document.createTextNode(
126
localize('accountGate.descriptionNoAccount', "Sign in with a GitHub account from an approved organization to use Agents.")
127
));
128
}
129
130
const approvedOrgs = options.approvedOrganizations ?? [];
131
const hasConcreteOrgs = approvedOrgs.length > 0 && !approvedOrgs.includes('*');
132
if (hasConcreteOrgs) {
133
const orgSection = append(card, $('div.sessions-policy-blocked-orgs'));
134
append(orgSection, $('p.sessions-policy-blocked-orgs-label', undefined,
135
localize('accountGate.approvedOrgs', "Approved organizations:")
136
));
137
const orgList = append(orgSection, $('ul'));
138
for (const org of approvedOrgs) {
139
append(orgList, $('li', undefined, org));
140
}
141
}
142
143
const footer = append(card, $('p.sessions-policy-blocked-footer'));
144
append(footer, document.createTextNode(localize('accountGate.contactAdmin', "Contact your administrator for more information.")));
145
append(footer, document.createTextNode(' '));
146
const learnMore = append(footer, $('a.sessions-policy-blocked-link')) as HTMLAnchorElement;
147
learnMore.textContent = localize('accountGate.learnMore', "Learn more");
148
learnMore.href = 'https://code.visualstudio.com/docs/enterprise/overview';
149
this._register(addDisposableListener(learnMore, EventType.CLICK, (e) => {
150
e.preventDefault();
151
this.openerService.open(URI.parse('https://code.visualstudio.com/docs/enterprise/overview'));
152
}));
153
154
const signInButton = this._register(new Button(card, { ...defaultButtonStyles }));
155
signInButton.label = localize('accountGate.signIn', "Sign In");
156
this._register(signInButton.onDidClick(() => {
157
this.commandService.executeCommand('workbench.action.agenticSignIn');
158
}));
159
}
160
161
private _openVSCode(): void {
162
const scheme = this.productService.parentPolicyConfig?.urlProtocol ?? this.productService.urlProtocol;
163
this.openerService.open(URI.from({ scheme, query: 'windowId=_blank' }), { openExternal: true });
164
}
165
}
166
167