Skip to content

Commit

Permalink
Add context menu for tunnel protocol
Browse files Browse the repository at this point in the history
Fixes #123750
  • Loading branch information
alexr00 committed May 18, 2021
1 parent 414e5db commit 0f5ceb2
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
73 changes: 71 additions & 2 deletions src/vs/workbench/contrib/remote/browser/tunnelView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -65,6 +65,13 @@ class TunnelTreeVirtualDelegate implements ITableVirtualDelegate<ITunnelItem> {
}
}

function toTunnelProtocol(value: string | undefined): TunnelProtocol {
if (value === TunnelProtocol.Https) {
return TunnelProtocol.Https;
}
return TunnelProtocol.Http;
}

export interface ITunnelViewModel {
readonly onForwardedPortsChanged: Event<void>;
readonly all: TunnelItem[];
Expand Down Expand Up @@ -422,7 +429,8 @@ class ActionBarRenderer extends Disposable implements ITableRenderer<ActionBarCe
['view', TUNNEL_VIEW_ID],
[TunnelTypeContextKey.key, element.tunnel.tunnelType],
[TunnelCloseableContextKey.key, element.tunnel.closeable],
[TunnelPrivacyContextKey.key, element.tunnel.privacy]
[TunnelPrivacyContextKey.key, element.tunnel.privacy],
[TunnelProtocolContextKey.key, toTunnelProtocol(element.tunnel.localUri?.scheme)]
];
const contextKeyService = this.contextKeyService.createOverlay(context);
const disposableStore = new DisposableStore();
Expand Down Expand Up @@ -664,6 +672,7 @@ class TunnelItem implements ITunnelItem {
export const TunnelTypeContextKey = new RawContextKey<TunnelType>('tunnelType', TunnelType.Add, true);
export const TunnelCloseableContextKey = new RawContextKey<boolean>('tunnelCloseable', false, true);
const TunnelPrivacyContextKey = new RawContextKey<TunnelPrivacy | undefined>('tunnelPrivacy', undefined, true);
const TunnelProtocolContextKey = new RawContextKey<TunnelProtocol | undefined>('tunnelProtocol', TunnelProtocol.Http, true);
const TunnelViewFocusContextKey = new RawContextKey<boolean>('tunnelViewFocus', false, nls.localize('tunnel.focusContext', "Whether the Ports view has focus."));
const TunnelViewSelectionKeyName = 'tunnelViewSelection';
const TunnelViewSelectionContextKey = new RawContextKey<ITunnelItem | undefined>(TunnelViewSelectionKeyName, undefined, true);
Expand All @@ -681,6 +690,7 @@ export class TunnelPanel extends ViewPane {
private tunnelTypeContext: IContextKey<TunnelType>;
private tunnelCloseableContext: IContextKey<boolean>;
private tunnelPrivacyContext: IContextKey<TunnelPrivacy | undefined>;
private tunnelProtocolContext: IContextKey<TunnelProtocol | undefined>;
private tunnelViewFocusContext: IContextKey<boolean>;
private tunnelViewSelectionContext: IContextKey<ITunnelItem | undefined>;
private tunnelViewMultiSelectionContext: IContextKey<ITunnelItem[] | undefined>;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<Attributes> = {
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);
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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,
Expand All @@ -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',
Expand Down
32 changes: 29 additions & 3 deletions src/vs/workbench/services/remote/common/remoteExplorerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<IRemoteExplorerService>('remoteExplorerService');
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -331,6 +337,26 @@ export class PortsAttributes extends Disposable {
default: return undefined;
}
}

public async addAttributes(port: number, attributes: Partial<Attributes>) {
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] = (<any>attributes)[attribute];
}

return this.configurationService.updateValue(PortsAttributes.SETTING, newUserValue, ConfigurationTarget.USER_LOCAL);
}
}

const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds
Expand All @@ -354,7 +380,7 @@ export class TunnelModel extends Disposable {
private _onEnvironmentTunnelsSet: Emitter<void> = new Emitter();
public onEnvironmentTunnelsSet: Event<void> = this._onEnvironmentTunnelsSet.event;
private _environmentTunnelsSet: boolean = false;
private configPortsAttributes: PortsAttributes;
public readonly configPortsAttributes: PortsAttributes;
private restoreListener: IDisposable | undefined;

private portAttributesProviders: PortAttributesProvider[] = [];
Expand Down

0 comments on commit 0f5ceb2

Please sign in to comment.