From 0f5ceb2f1db6b67a4fe3552a47e3075192977ed5 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 18 May 2021 15:06:45 +0200 Subject: [PATCH] Add context menu for tunnel protocol Fixes #123750 --- src/vs/platform/actions/common/actions.ts | 1 + .../contrib/remote/browser/tunnelView.ts | 73 ++++++++++++++++++- .../remote/common/remoteExplorerService.ts | 32 +++++++- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index abe86723b1004..d073dea5950c5 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -129,6 +129,7 @@ export class MenuId { static readonly TouchBarContext = new MenuId('TouchBarContext'); static readonly TitleBarContext = new MenuId('TitleBarContext'); static readonly TunnelContext = new MenuId('TunnelContext'); + static readonly TunnelProtocol = new MenuId('TunnelProtocol'); static readonly TunnelPortInline = new MenuId('TunnelInline'); static readonly TunnelTitle = new MenuId('TunnelTitle'); static readonly TunnelLocalAddressInline = new MenuId('TunnelLocalAddressInline'); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index adb05e765875b..24ebccc190e0c 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -23,7 +23,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IMenuService, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, TUNNEL_VIEW_ID, parseAddress, CandidatePort, TunnelPrivacy, TunnelEditId, mapHasAddressLocalhostOrAllInterfaces } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, TUNNEL_VIEW_ID, parseAddress, CandidatePort, TunnelPrivacy, TunnelEditId, mapHasAddressLocalhostOrAllInterfaces, TunnelProtocol, Attributes } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -65,6 +65,13 @@ class TunnelTreeVirtualDelegate implements ITableVirtualDelegate { } } +function toTunnelProtocol(value: string | undefined): TunnelProtocol { + if (value === TunnelProtocol.Https) { + return TunnelProtocol.Https; + } + return TunnelProtocol.Http; +} + export interface ITunnelViewModel { readonly onForwardedPortsChanged: Event; readonly all: TunnelItem[]; @@ -422,7 +429,8 @@ class ActionBarRenderer extends Disposable implements ITableRenderer('tunnelType', TunnelType.Add, true); export const TunnelCloseableContextKey = new RawContextKey('tunnelCloseable', false, true); const TunnelPrivacyContextKey = new RawContextKey('tunnelPrivacy', undefined, true); +const TunnelProtocolContextKey = new RawContextKey('tunnelProtocol', TunnelProtocol.Http, true); const TunnelViewFocusContextKey = new RawContextKey('tunnelViewFocus', false, nls.localize('tunnel.focusContext', "Whether the Ports view has focus.")); const TunnelViewSelectionKeyName = 'tunnelViewSelection'; const TunnelViewSelectionContextKey = new RawContextKey(TunnelViewSelectionKeyName, undefined, true); @@ -681,6 +690,7 @@ export class TunnelPanel extends ViewPane { private tunnelTypeContext: IContextKey; private tunnelCloseableContext: IContextKey; private tunnelPrivacyContext: IContextKey; + private tunnelProtocolContext: IContextKey; private tunnelViewFocusContext: IContextKey; private tunnelViewSelectionContext: IContextKey; private tunnelViewMultiSelectionContext: IContextKey; @@ -714,6 +724,7 @@ export class TunnelPanel extends ViewPane { this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService); this.tunnelPrivacyContext = TunnelPrivacyContextKey.bindTo(contextKeyService); + this.tunnelProtocolContext = TunnelProtocolContextKey.bindTo(contextKeyService); this.tunnelViewFocusContext = TunnelViewFocusContextKey.bindTo(contextKeyService); this.tunnelViewSelectionContext = TunnelViewSelectionContextKey.bindTo(contextKeyService); this.tunnelViewMultiSelectionContext = TunnelViewMultiSelectionContextKey.bindTo(contextKeyService); @@ -876,12 +887,14 @@ export class TunnelPanel extends ViewPane { this.tunnelTypeContext.set(item.tunnelType); this.tunnelCloseableContext.set(!!item.closeable); this.tunnelPrivacyContext.set(item.privacy); + this.tunnelProtocolContext.set(toTunnelProtocol(item.localUri?.scheme)); this.portChangableContextKey.set(!!item.localPort); } else { this.tunnelTypeContext.reset(); this.tunnelViewSelectionContext.reset(); this.tunnelCloseableContext.reset(); this.tunnelPrivacyContext.reset(); + this.tunnelProtocolContext.reset(); this.portChangableContextKey.reset(); } } @@ -910,11 +923,13 @@ export class TunnelPanel extends ViewPane { this.tunnelTypeContext.set(node.tunnelType); this.tunnelCloseableContext.set(!!node.closeable); this.tunnelPrivacyContext.set(node.privacy); + this.tunnelProtocolContext.set(toTunnelProtocol(node.localUri?.scheme)); this.portChangableContextKey.set(!!node.localPort); } else { this.tunnelTypeContext.set(TunnelType.Add); this.tunnelCloseableContext.set(false); this.tunnelPrivacyContext.set(undefined); + this.tunnelProtocolContext.set(undefined); this.portChangableContextKey.set(false); } @@ -1365,6 +1380,34 @@ namespace MakePortPrivateAction { } } +namespace SetTunnelProtocolAction { + export const ID_HTTP = 'remote.tunnel.setProtocolHttp'; + export const ID_HTTPS = 'remote.tunnel.setProtocolHttps'; + export const LABEL_HTTP = nls.localize('remote.tunnel.protocolHttp', "HTTP"); + export const LABEL_HTTPS = nls.localize('remote.tunnel.protocolHttps', "HTTPS"); + + async function handler(arg: any, protocol: TunnelProtocol, remoteExplorerService: IRemoteExplorerService) { + if (arg instanceof TunnelItem) { + const attributes: Partial = { + protocol + }; + return remoteExplorerService.tunnelModel.configPortsAttributes.addAttributes(arg.remotePort, attributes); + } + } + + export function handlerHttp(): ICommandHandler { + return async (accessor, arg) => { + return handler(arg, TunnelProtocol.Http, accessor.get(IRemoteExplorerService)); + }; + } + + export function handlerHttps(): ICommandHandler { + return async (accessor, arg) => { + return handler(arg, TunnelProtocol.Https, accessor.get(IRemoteExplorerService)); + }; + } +} + const tunnelViewCommandsWeightBonus = 10; // give our commands a little bit more weight over other default list/tree commands const isForwardedExpr = TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded); @@ -1410,6 +1453,8 @@ CommandsRegistry.registerCommand(CopyAddressAction.COMMANDPALETTE_ID, CopyAddres CommandsRegistry.registerCommand(ChangeLocalPortAction.ID, ChangeLocalPortAction.handler()); CommandsRegistry.registerCommand(MakePortPublicAction.ID, MakePortPublicAction.handler()); CommandsRegistry.registerCommand(MakePortPrivateAction.ID, MakePortPrivateAction.handler()); +CommandsRegistry.registerCommand(SetTunnelProtocolAction.ID_HTTP, SetTunnelProtocolAction.handlerHttp()); +CommandsRegistry.registerCommand(SetTunnelProtocolAction.ID_HTTPS, SetTunnelProtocolAction.handlerHttps()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({ command: { @@ -1508,6 +1553,13 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ }, when: ContextKeyExpr.and(TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Public), isNotMultiSelectionExpr) })); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '2_localaddress', + order: 3, + submenu: MenuId.TunnelProtocol, + title: nls.localize('tunnelContext.protocolMenu', "Change Tunnel Protocol"), + when: ContextKeyExpr.and(isForwardedExpr, isNotMultiSelectionExpr) +})); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '3_forward', order: 0, @@ -1526,6 +1578,23 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ }, })); +MenuRegistry.appendMenuItem(MenuId.TunnelProtocol, ({ + order: 0, + command: { + id: SetTunnelProtocolAction.ID_HTTP, + title: SetTunnelProtocolAction.LABEL_HTTP, + toggled: TunnelProtocolContextKey.isEqualTo(TunnelProtocol.Http) + } +})); +MenuRegistry.appendMenuItem(MenuId.TunnelProtocol, ({ + order: 1, + command: { + id: SetTunnelProtocolAction.ID_HTTPS, + title: SetTunnelProtocolAction.LABEL_HTTPS, + toggled: TunnelProtocolContextKey.isEqualTo(TunnelProtocol.Https) + } +})); + MenuRegistry.appendMenuItem(MenuId.TunnelPortInline, ({ group: '0_manage', diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 3fb24b9fff0d6..ce6f4704cda80 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -11,7 +11,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TunnelInformation, TunnelDescription, IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; @@ -25,6 +25,7 @@ import { flatten } from 'vs/base/common/arrays'; import Severity from 'vs/base/common/severity'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { URI } from 'vs/base/common/uri'; +import { deepClone } from 'vs/base/common/objects'; export const IRemoteExplorerService = createDecorator('remoteExplorerService'); export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; @@ -147,12 +148,17 @@ export enum OnPortForward { Ignore = 'ignore' } +export enum TunnelProtocol { + Http = 'http', + Https = 'https' +} + export interface Attributes { label: string | undefined; onAutoForward: OnPortForward | undefined, elevateIfNeeded: boolean | undefined; requireLocalPort: boolean | undefined; - protocol: string | undefined; + protocol: TunnelProtocol | undefined; } interface PortRange { start: number, end: number } @@ -331,6 +337,26 @@ export class PortsAttributes extends Disposable { default: return undefined; } } + + public async addAttributes(port: number, attributes: Partial) { + let settingValue = this.configurationService.inspect(PortsAttributes.SETTING); + const userValue: any = settingValue.userLocalValue; + let newUserValue: any; + if (!userValue || !isObject(userValue)) { + newUserValue = {}; + } else { + newUserValue = deepClone(userValue); + } + + if (!newUserValue[`${port}`]) { + newUserValue[`${port}`] = {}; + } + for (const attribute in attributes) { + newUserValue[`${port}`][attribute] = (attributes)[attribute]; + } + + return this.configurationService.updateValue(PortsAttributes.SETTING, newUserValue, ConfigurationTarget.USER_LOCAL); + } } const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds @@ -354,7 +380,7 @@ export class TunnelModel extends Disposable { private _onEnvironmentTunnelsSet: Emitter = new Emitter(); public onEnvironmentTunnelsSet: Event = this._onEnvironmentTunnelsSet.event; private _environmentTunnelsSet: boolean = false; - private configPortsAttributes: PortsAttributes; + public readonly configPortsAttributes: PortsAttributes; private restoreListener: IDisposable | undefined; private portAttributesProviders: PortAttributesProvider[] = [];