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