Allow tunnel providers to define port privacy options (#133677)

This commit is contained in:
Alex Ross 2021-09-23 16:24:51 +02:00 committed by GitHub
parent fd6adaf30a
commit a15378a9db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 254 additions and 143 deletions

View file

@ -230,7 +230,27 @@ export function activate(context: vscode.ExtensionContext) {
}, (progress) => doResolve(_authority, progress));
},
tunnelFactory,
tunnelFeatures: { elevation: true, public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') },
tunnelFeatures: {
elevation: true,
public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts'),
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
{
id: 'public',
label: 'Public',
themeIcon: 'eye'
},
{
id: 'other',
label: 'Other',
themeIcon: 'circuit-board'
},
{
id: 'private',
label: 'Private',
themeIcon: 'eye-closed'
}
] : []
},
showCandidatePort
});
context.subscriptions.push(authorityResolverDisposable);
@ -389,6 +409,7 @@ async function tunnelFactory(tunnelOptions: vscode.TunnelOptions, tunnelCreation
localAddress,
remoteAddress: tunnelOptions.remoteAddress,
public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') && tunnelOptions.public,
privacy: tunnelOptions.privacy,
protocol: tunnelOptions.protocol,
onDidDispose: onDidDispose.event,
dispose: () => {

View file

@ -137,6 +137,7 @@ export class MenuId {
static readonly TouchBarContext = new MenuId('TouchBarContext');
static readonly TitleBarContext = new MenuId('TitleBarContext');
static readonly TunnelContext = new MenuId('TunnelContext');
static readonly TunnelPrivacy = new MenuId('TunnelPrivacy');
static readonly TunnelProtocol = new MenuId('TunnelProtocol');
static readonly TunnelPortInline = new MenuId('TunnelInline');
static readonly TunnelTitle = new MenuId('TunnelTitle');

View file

@ -19,7 +19,7 @@ export interface RemoteTunnel {
readonly tunnelRemoteHost: string;
readonly tunnelLocalPort?: number;
readonly localAddress: string;
readonly public: boolean;
readonly privacy: string;
readonly protocol?: string;
dispose(silent?: boolean): Promise<void>;
}
@ -29,6 +29,7 @@ export interface TunnelOptions {
localAddressPort?: number;
label?: string;
public?: boolean;
privacy?: string;
protocol?: string;
}
@ -37,13 +38,29 @@ export enum TunnelProtocol {
Https = 'https'
}
export enum TunnelPrivacyId {
ConstantPrivate = 'constantPrivate', // private, and changing is unsupported
Private = 'private',
Public = 'public'
}
export interface TunnelCreationOptions {
elevationRequired?: boolean;
}
export interface TunnelPrivacy {
themeIcon: string;
id: string;
label: string;
}
export interface TunnelProviderFeatures {
elevation: boolean;
/**
* @deprecated
*/
public: boolean;
privacyOptions: TunnelPrivacy[];
}
export interface ITunnelProvider {
@ -76,8 +93,13 @@ export interface ITunnel {
*/
localAddress: string;
/**
* @deprecated Use privacy instead
*/
public?: boolean;
privacy?: string;
protocol?: string;
/**
@ -92,7 +114,8 @@ export interface ITunnelService {
readonly _serviceBrand: undefined;
readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly canMakePublic: boolean;
readonly canChangePrivacy: boolean;
readonly privacyOptions: TunnelPrivacy[];
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<{ host: string, port: number; }>;
readonly canElevate: boolean;
@ -100,7 +123,7 @@ export interface ITunnelService {
readonly onAddedTunnelProvider: Event<void>;
canTunnel(uri: URI): boolean;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, isPublic?: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable;
}
@ -149,7 +172,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
protected readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel | undefined>; }>>();
protected _tunnelProvider: ITunnelProvider | undefined;
protected _canElevate: boolean = false;
private _canMakePublic: boolean = false;
private _privacyOptions: TunnelPrivacy[] = [];
public constructor(
@ILogService protected readonly logService: ILogService
@ -164,20 +187,20 @@ export abstract class AbstractTunnelService implements ITunnelService {
if (!provider) {
// clear features
this._canElevate = false;
this._canMakePublic = false;
this._privacyOptions = [];
this._onAddedTunnelProvider.fire();
return {
dispose: () => { }
};
}
this._canElevate = features.elevation;
this._canMakePublic = features.public;
this._privacyOptions = features.privacyOptions;
this._onAddedTunnelProvider.fire();
return {
dispose: () => {
this._tunnelProvider = undefined;
this._canElevate = false;
this._canMakePublic = false;
this._privacyOptions = [];
}
};
}
@ -186,8 +209,12 @@ export abstract class AbstractTunnelService implements ITunnelService {
return this._canElevate;
}
public get canMakePublic() {
return this._canMakePublic;
public get canChangePrivacy() {
return this._privacyOptions.length > 0;
}
public get privacyOptions() {
return this._privacyOptions;
}
public get tunnels(): Promise<readonly RemoteTunnel[]> {
@ -217,7 +244,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
this._tunnels.clear();
}
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, isPublic: boolean = false, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, privacy: string = TunnelPrivacyId.Private, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
this.logService.trace(`ForwardedPorts: (TunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
if (!addressProvider) {
return undefined;
@ -227,7 +254,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic, protocol);
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
if (!resolvedTunnel) {
this.logService.trace(`ForwardedPorts: (TunnelService) Tunnel was not created.`);
return resolvedTunnel;
@ -255,7 +282,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
tunnelRemoteHost: tunnel.tunnelRemoteHost,
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
public: tunnel.public,
privacy: tunnel.privacy,
protocol: tunnel.protocol,
dispose: async () => {
this.logService.trace(`ForwardedPorts: (TunnelService) dispose request for ${tunnel.tunnelRemoteHost}:${tunnel.tunnelRemotePort} `);
@ -344,14 +371,14 @@ export abstract class AbstractTunnelService implements ITunnelService {
return !!extractLocalHostUriMetaDataForPortMapping(uri);
}
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, public: isPublic, protocol };
const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, privacy, public: privacy !== TunnelPrivacyId.Private, protocol };
const tunnel = tunnelProvider.forwardPort(tunnelOptions, creationInfo);
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created by provider.');
if (tunnel) {

View file

@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { AbstractTunnelService, isAllInterfaces, isLocalhost, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { AbstractTunnelService, isAllInterfaces, isLocalhost, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/remote/common/tunnel';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';
@ -27,7 +27,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public tunnelLocalPort!: number;
public tunnelRemoteHost: string;
public localAddress!: string;
public readonly public = false;
public readonly privacy = TunnelPrivacyId.Private;
private readonly _options: IConnectionOptions;
private readonly _server: net.Server;
@ -149,7 +149,7 @@ export class BaseTunnelService extends AbstractTunnelService {
return (this.configurationService.getValue('remote.localPortHost') === 'localhost') ? '127.0.0.1' : '0.0.0.0';
}
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
@ -157,7 +157,7 @@ export class BaseTunnelService extends AbstractTunnelService {
}
if (this._tunnelProvider) {
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic, protocol);
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
} else {
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
const options: IConnectionOptions = {

View file

@ -44,12 +44,22 @@ declare module 'vscode' {
isTrusted?: boolean;
}
export interface TunnelPrivacy {
themeIcon: string;
id: string;
label: string;
}
export interface TunnelOptions {
remoteAddress: { port: number, host: string; };
// The desired local port. If this port can't be used, then another will be chosen.
localAddressPort?: number;
label?: string;
/**
* @deprecated Use privacy instead
*/
public?: boolean;
privacy?: string;
protocol?: string;
}
@ -57,7 +67,11 @@ declare module 'vscode' {
remoteAddress: { port: number, host: string; };
//The complete local address(ex. localhost:1234)
localAddress: { port: number, host: string; } | string;
/**
* @deprecated Use privacy instead
*/
public?: boolean;
privacy?: string;
// If protocol is not provided it is assumed to be http, regardless of the localAddress.
protocol?: string;
}
@ -144,7 +158,11 @@ declare module 'vscode' {
*/
tunnelFeatures?: {
elevation: boolean;
/**
* @deprecated Use privacy instead
*/
public: boolean;
privacyOptions: TunnelPrivacy[];
};
candidatePortSource?: CandidatePortSource;

View file

@ -177,6 +177,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
public: tunnel.public,
privacy: tunnel.privacy,
protocol: tunnel.protocol ?? TunnelProtocol.Http,
dispose: async (silent?: boolean) => {
this.logService.trace(`ForwardedPorts: (MainThreadTunnelService) Closing tunnel from tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);

View file

@ -6,7 +6,7 @@
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as vscode from 'vscode';
import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId } from 'vs/platform/remote/common/tunnel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
@ -17,12 +17,19 @@ export interface TunnelDto {
remoteAddress: { port: number, host: string };
localAddress: { port: number, host: string } | string;
public: boolean;
privacy: TunnelPrivacyId | string;
protocol: string | undefined;
}
export namespace TunnelDto {
export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto {
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress, public: !!tunnel.public, protocol: tunnel.protocol };
return {
remoteAddress: tunnel.remoteAddress,
localAddress: tunnel.localAddress,
public: !!tunnel.public,
privacy: tunnel.privacy ?? (tunnel.public ? TunnelPrivacyId.Public : TunnelPrivacyId.Private),
protocol: tunnel.protocol
};
}
export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto {
return {
@ -31,7 +38,8 @@ export namespace TunnelDto {
port: tunnel.tunnelRemotePort
},
localAddress: tunnel.localAddress,
public: tunnel.public,
public: tunnel.privacy !== TunnelPrivacyId.ConstantPrivate && tunnel.privacy !== TunnelPrivacyId.ConstantPrivate,
privacy: tunnel.privacy,
protocol: tunnel.protocol
};
}

View file

@ -6,6 +6,7 @@
import { MainThreadTunnelServiceShape, MainContext, PortAttributesProviderSelector } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import type * as vscode from 'vscode';
import * as nls from 'vs/nls';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { URI } from 'vs/base/common/uri';
@ -295,9 +296,26 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
if (provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
elevation: false,
public: false
let privacyOptions = provider.tunnelFeatures?.privacyOptions ?? [];
if (provider.tunnelFeatures?.public && (privacyOptions.length === 0)) {
privacyOptions = [
{
id: 'private',
label: nls.localize('tunnelPrivacy.private', "Private"),
themeIcon: 'lock'
},
{
id: 'public',
label: nls.localize('tunnelPrivacy.public', "Public"),
themeIcon: 'eye'
}
];
}
this._proxy.$setTunnelProvider({
elevation: !!provider.tunnelFeatures?.elevation,
public: !!provider.tunnelFeatures?.public,
privacyOptions
});
}
} else {

View file

@ -43,6 +43,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
@ITunnelService private readonly tunnelService: ITunnelService,
@IActivityService private readonly activityService: IActivityService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
) {
@ -76,7 +77,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu
if (this.environmentService.remoteAuthority && viewEnabled) {
const viewContainer = await this.getViewContainer();
const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService);
const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService, this.tunnelService), this.environmentService);
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
if (viewContainer) {
this.remoteExplorerService.enablePortsFeatures();

View file

@ -18,7 +18,6 @@ export const remoteExplorerViewIcon = registerIcon('remote-explorer-view-icon',
export const portsViewIcon = registerIcon('ports-view-icon', Codicon.plug, nls.localize('portsViewIcon', 'View icon of the remote ports view.'));
export const portIcon = registerIcon('ports-view-icon', Codicon.plug, nls.localize('portIcon', 'Icon representing a remote port.'));
export const privatePortIcon = registerIcon('private-ports-view-icon', Codicon.lock, nls.localize('privatePortIcon', 'Icon representing a private remote port.'));
export const publicPortIcon = registerIcon('public-ports-view-icon', Codicon.eye, nls.localize('publicPortIcon', 'Icon representing a public remote port.'));
export const forwardPortIcon = registerIcon('ports-forward-icon', Codicon.plus, nls.localize('forwardPortIcon', 'Icon for the forward action.'));
export const stopForwardIcon = registerIcon('ports-stop-forward-icon', Codicon.x, nls.localize('stopForwardIcon', 'Icon for the stop forwarding action.'));

View file

@ -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, Attributes, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, TUNNEL_VIEW_ID, parseAddress, CandidatePort, TunnelEditId, mapHasAddressLocalhostOrAllInterfaces, Attributes, TunnelSource } 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';
@ -34,12 +34,12 @@ import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platfor
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { URI } from 'vs/base/common/uri';
import { isAllInterfaces, isLocalhost, isPortPrivileged, ITunnelService, RemoteTunnel, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { isAllInterfaces, isLocalhost, isPortPrivileged, ITunnelService, RemoteTunnel, TunnelPrivacy, TunnelPrivacyId, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { copyAddressIcon, forwardedPortWithoutProcessIcon, forwardedPortWithProcessIcon, forwardPortIcon, labelPortIcon, openBrowserIcon, openPreviewIcon, portsViewIcon, privatePortIcon, publicPortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons';
import { copyAddressIcon, forwardedPortWithoutProcessIcon, forwardedPortWithProcessIcon, forwardPortIcon, labelPortIcon, openBrowserIcon, openPreviewIcon, portsViewIcon, privatePortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons';
import { IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { isMacintosh, isWeb } from 'vs/base/common/platform';
@ -51,6 +51,7 @@ import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { STATUS_BAR_HOST_NAME_BACKGROUND } from 'vs/workbench/common/theme';
import { Codicon } from 'vs/base/common/codicons';
export const forwardedPortsViewEnabled = new RawContextKey<boolean>('forwardedPortsViewEnabled', false, nls.localize('tunnel.forwardedPortsViewEnabled', "Whether the Ports view is enabled."));
@ -93,11 +94,17 @@ export class TunnelViewModel implements ITunnelViewModel {
originTooltip: '',
privacyTooltip: '',
source: { source: TunnelSource.User, description: '' },
protocol: TunnelProtocol.Http
protocol: TunnelProtocol.Http,
privacy: {
id: TunnelPrivacyId.Private,
themeIcon: privatePortIcon.id,
label: nls.localize('tunnelPrivacy.private', "Private")
}
};
constructor(
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
@ITunnelService private readonly tunnelService: ITunnelService
) {
this.model = remoteExplorerService.tunnelModel;
this.onForwardedPortsChanged = Event.any(this.model.onForwardPort, this.model.onClosePort, this.model.onPortName, this.model.onCandidatesChanged);
@ -129,7 +136,7 @@ export class TunnelViewModel implements ITunnelViewModel {
private get forwarded(): TunnelItem[] {
const forwarded = Array.from(this.model.forwarded.values()).map(tunnel => {
const tunnelItem = TunnelItem.createFromTunnel(this.remoteExplorerService, tunnel);
const tunnelItem = TunnelItem.createFromTunnel(this.remoteExplorerService, this.tunnelService, tunnel);
this.addProcessInfoFromCandidate(tunnelItem);
return tunnelItem;
}).sort((a: TunnelItem, b: TunnelItem) => {
@ -144,7 +151,7 @@ export class TunnelViewModel implements ITunnelViewModel {
private get detected(): TunnelItem[] {
return Array.from(this.model.detected.values()).map(tunnel => {
const tunnelItem = TunnelItem.createFromTunnel(this.remoteExplorerService, tunnel, TunnelType.Detected, false);
const tunnelItem = TunnelItem.createFromTunnel(this.remoteExplorerService, this.tunnelService, tunnel, TunnelType.Detected, false);
this.addProcessInfoFromCandidate(tunnelItem);
return tunnelItem;
});
@ -297,12 +304,12 @@ class PrivacyColumn implements ITableColumn<ITunnelItem, ActionBarCell> {
return emptyCell(row);
}
const label = row.privacy === TunnelPrivacy.Public ? nls.localize('tunnel.privacyPublic', "Public") : nls.localize('tunnel.privacyPrivate', "Private");
const label = row.privacy?.label;
let tooltip: string = '';
if (row instanceof TunnelItem) {
tooltip = `${row.privacyTooltip} ${row.tooltipPostfix}`;
tooltip = `${row.privacy.label} ${row.tooltipPostfix}`;
}
return { label, tunnel: row, icon: row.icon, editId: TunnelEditId.None, tooltip };
return { label, tunnel: row, icon: { id: row.privacy.themeIcon }, editId: TunnelEditId.None, tooltip };
}
}
@ -536,7 +543,8 @@ class ActionBarRenderer extends Disposable implements ITableRenderer<ActionBarCe
}
class TunnelItem implements ITunnelItem {
static createFromTunnel(remoteExplorerService: IRemoteExplorerService, tunnel: Tunnel, type: TunnelType = TunnelType.Forwarded, closeable?: boolean) {
static createFromTunnel(remoteExplorerService: IRemoteExplorerService, tunnelService: ITunnelService,
tunnel: Tunnel, type: TunnelType = TunnelType.Forwarded, closeable?: boolean) {
return new TunnelItem(type,
tunnel.remoteHost,
tunnel.remotePort,
@ -551,7 +559,8 @@ class TunnelItem implements ITunnelItem {
tunnel.runningProcess,
tunnel.pid,
tunnel.privacy,
remoteExplorerService);
remoteExplorerService,
tunnelService);
}
constructor(
@ -568,8 +577,9 @@ class TunnelItem implements ITunnelItem {
public name?: string,
private runningProcess?: string,
private pid?: number,
public privacy?: TunnelPrivacy,
private remoteExplorerService?: IRemoteExplorerService
private _privacy?: TunnelPrivacyId | string,
private remoteExplorerService?: IRemoteExplorerService,
private tunnelService?: ITunnelService
) { }
get label(): string {
@ -609,19 +619,6 @@ class TunnelItem implements ITunnelItem {
return description;
}
get icon(): ThemeIcon | undefined {
switch (this.privacy) {
case TunnelPrivacy.Public: return publicPortIcon;
default: {
if (this.tunnelType !== TunnelType.Add) {
return privatePortIcon;
} else {
return undefined;
}
}
}
}
get tooltipPostfix(): string {
let information: string;
if (this.localAddress) {
@ -660,15 +657,28 @@ class TunnelItem implements ITunnelItem {
return this.source.description;
}
get privacyTooltip(): string {
return `${this.privacy === TunnelPrivacy.Public ? nls.localize('remote.tunnel.tooltipPublic', "Accessible publicly. ") :
nls.localize('remote.tunnel.tooltipPrivate', "Only accessible from this machine. ")}`;
get privacy(): TunnelPrivacy {
if (this.tunnelService?.privacyOptions) {
return this.tunnelService?.privacyOptions.find(element => element.id === this._privacy) ??
{
id: '',
themeIcon: Codicon.question.id,
label: nls.localize('tunnelPrivacy.unknown', "Unknown")
};
} else {
return {
id: TunnelPrivacyId.Private,
themeIcon: privatePortIcon.id,
label: nls.localize('tunnelPrivacy.private', "Private")
};
}
}
}
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 TunnelPrivacyContextKey = new RawContextKey<TunnelPrivacyId | string | undefined>('tunnelPrivacy', undefined, true);
const TunnelPrivacyEnabledContextKey = new RawContextKey<boolean>('tunnelPrivacyEnabled', false, 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';
@ -686,7 +696,8 @@ export class TunnelPanel extends ViewPane {
private table!: WorkbenchTable<ITunnelItem>;
private tunnelTypeContext: IContextKey<TunnelType>;
private tunnelCloseableContext: IContextKey<boolean>;
private tunnelPrivacyContext: IContextKey<TunnelPrivacy | undefined>;
private tunnelPrivacyContext: IContextKey<TunnelPrivacyId | string | undefined>;
private tunnelPrivacyEnabledContext: IContextKey<boolean>;
private tunnelProtocolContext: IContextKey<TunnelProtocol | undefined>;
private tunnelViewFocusContext: IContextKey<boolean>;
private tunnelViewSelectionContext: IContextKey<ITunnelItem | undefined>;
@ -721,6 +732,8 @@ export class TunnelPanel extends ViewPane {
this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService);
this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService);
this.tunnelPrivacyContext = TunnelPrivacyContextKey.bindTo(contextKeyService);
this.tunnelPrivacyEnabledContext = TunnelPrivacyEnabledContextKey.bindTo(contextKeyService);
this.tunnelPrivacyEnabledContext.set(tunnelService.privacyOptions.length !== 0);
this.tunnelProtocolContext = TunnelProtocolContextKey.bindTo(contextKeyService);
this.tunnelViewFocusContext = TunnelViewFocusContextKey.bindTo(contextKeyService);
this.tunnelViewSelectionContext = TunnelViewSelectionContextKey.bindTo(contextKeyService);
@ -741,6 +754,23 @@ export class TunnelPanel extends ViewPane {
this._register(toDisposable(() => {
this.titleActions = [];
}));
this.registerPrivacyActions();
}
private registerPrivacyActions() {
for (const privacyOption of this.tunnelService.privacyOptions) {
const optionId = `remote.tunnel.privacy${privacyOption.id}`;
CommandsRegistry.registerCommand(optionId, ChangeTunnelPrivacyAction.handler(privacyOption.id));
MenuRegistry.appendMenuItem(MenuId.TunnelPrivacy, ({
order: 0,
command: {
id: optionId,
title: privacyOption.label,
toggled: TunnelPrivacyContextKey.isEqualTo(privacyOption.id)
}
}));
}
}
get portCount(): number {
@ -759,7 +789,7 @@ export class TunnelPanel extends ViewPane {
this.menuService, this.contextViewService, this.themeService, this.remoteExplorerService, this.commandService,
this.configurationService, this.hoverService);
const columns = [new IconColumn(), new PortColumn(), new LocalAddressColumn(), new RunningProcessColumn()];
if (this.tunnelService.canMakePublic) {
if (this.tunnelService.canChangePrivacy) {
columns.push(new PrivacyColumn());
}
columns.push(new OriginColumn());
@ -780,7 +810,7 @@ export class TunnelPanel extends ViewPane {
accessibilityProvider: {
getAriaLabel: (item: ITunnelItem) => {
if (item instanceof TunnelItem) {
return `${item.tooltipPostfix} ${item.portTooltip} ${item.iconTooltip} ${item.processTooltip} ${item.originTooltip} ${this.tunnelService.canMakePublic ? item.privacyTooltip : ''}`;
return `${item.tooltipPostfix} ${item.portTooltip} ${item.iconTooltip} ${item.processTooltip} ${item.originTooltip} ${this.tunnelService.canChangePrivacy ? item.privacy.label : ''}`;
} else {
return item.label;
}
@ -877,7 +907,7 @@ export class TunnelPanel extends ViewPane {
this.tunnelViewSelectionContext.set(item);
this.tunnelTypeContext.set(item.tunnelType);
this.tunnelCloseableContext.set(!!item.closeable);
this.tunnelPrivacyContext.set(item.privacy);
this.tunnelPrivacyContext.set(item.privacy.id);
this.tunnelProtocolContext.set(item.protocol === TunnelProtocol.Https ? TunnelProtocol.Https : TunnelProtocol.Https);
this.portChangableContextKey.set(!!item.localPort);
} else {
@ -929,7 +959,7 @@ export class TunnelPanel extends ViewPane {
this.table.setFocus([this.table.indexOf(node)]);
this.tunnelTypeContext.set(node.tunnelType);
this.tunnelCloseableContext.set(!!node.closeable);
this.tunnelPrivacyContext.set(node.privacy);
this.tunnelPrivacyContext.set(node.privacy.id);
this.tunnelProtocolContext.set(node.protocol);
this.portChangableContextKey.set(!!node.localPort);
} else {
@ -1117,9 +1147,9 @@ interface QuickPickTunnel extends IQuickPickItem {
tunnel?: ITunnelItem
}
function makeTunnelPicks(tunnels: Tunnel[], remoteExplorerService: IRemoteExplorerService): QuickPickInput<QuickPickTunnel>[] {
function makeTunnelPicks(tunnels: Tunnel[], remoteExplorerService: IRemoteExplorerService, tunnelService: ITunnelService): QuickPickInput<QuickPickTunnel>[] {
const picks: QuickPickInput<QuickPickTunnel>[] = tunnels.map(forwarded => {
const item = TunnelItem.createFromTunnel(remoteExplorerService, forwarded);
const item = TunnelItem.createFromTunnel(remoteExplorerService, tunnelService, forwarded);
return {
label: item.label,
description: item.processDescription,
@ -1162,9 +1192,10 @@ namespace ClosePortAction {
return async (accessor) => {
const quickInputService = accessor.get(IQuickInputService);
const remoteExplorerService = accessor.get(IRemoteExplorerService);
const tunnelService = accessor.get(ITunnelService);
const commandService = accessor.get(ICommandService);
const picks: QuickPickInput<QuickPickTunnel>[] = makeTunnelPicks(Array.from(remoteExplorerService.tunnelModel.forwarded.values()).filter(tunnel => tunnel.closeable), remoteExplorerService);
const picks: QuickPickInput<QuickPickTunnel>[] = makeTunnelPicks(Array.from(remoteExplorerService.tunnelModel.forwarded.values()).filter(tunnel => tunnel.closeable), remoteExplorerService, tunnelService);
const result = await quickInputService.pick(picks, { placeHolder: nls.localize('remote.tunnel.closePlaceholder', "Choose a port to stop forwarding") });
if (result && result.tunnel) {
await remoteExplorerService.close({ host: result.tunnel.remoteHost, port: result.tunnel.remotePort });
@ -1250,12 +1281,13 @@ namespace OpenPortInBrowserCommandPaletteAction {
export function handler(): ICommandHandler {
return async (accessor, arg) => {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
const tunnelService = accessor.get(ITunnelService);
const model = remoteExplorerService.tunnelModel;
const quickPickService = accessor.get(IQuickInputService);
const openerService = accessor.get(IOpenerService);
const commandService = accessor.get(ICommandService);
const options: QuickPickTunnel[] = [...model.forwarded, ...model.detected].map(value => {
const tunnelItem = TunnelItem.createFromTunnel(remoteExplorerService, value[1]);
const tunnelItem = TunnelItem.createFromTunnel(remoteExplorerService, tunnelService, value[1]);
return {
label: tunnelItem.label,
description: tunnelItem.processDescription,
@ -1307,11 +1339,12 @@ namespace CopyAddressAction {
return async (accessor, arg) => {
const quickInputService = accessor.get(IQuickInputService);
const remoteExplorerService = accessor.get(IRemoteExplorerService);
const tunnelService = accessor.get(ITunnelService);
const commandService = accessor.get(ICommandService);
const clipboardService = accessor.get(IClipboardService);
const tunnels = Array.from(remoteExplorerService.tunnelModel.forwarded.values()).concat(Array.from(remoteExplorerService.tunnelModel.detected.values()));
const result = await quickInputService.pick(makeTunnelPicks(tunnels, remoteExplorerService), { placeHolder: nls.localize('remote.tunnel.copyAddressPlaceholdter', "Choose a forwarded port") });
const result = await quickInputService.pick(makeTunnelPicks(tunnels, remoteExplorerService, tunnelService), { placeHolder: nls.localize('remote.tunnel.copyAddressPlaceholdter', "Choose a forwarded port") });
if (result && result.tunnel) {
await copyAddress(remoteExplorerService, clipboardService, result.tunnel);
} else if (result) {
@ -1369,11 +1402,8 @@ namespace ChangeLocalPortAction {
}
}
namespace MakePortPublicAction {
export const ID = 'remote.tunnel.makePublic';
export const LABEL = nls.localize('remote.tunnel.makePublic', "Make Public");
export function handler(): ICommandHandler {
namespace ChangeTunnelPrivacyAction {
export function handler(privacyId: string): ICommandHandler {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
@ -1383,29 +1413,7 @@ namespace MakePortPublicAction {
local: arg.localPort,
name: arg.name,
elevateIfNeeded: true,
isPublic: true,
source: arg.source
});
}
};
}
}
namespace MakePortPrivateAction {
export const ID = 'remote.tunnel.makePrivate';
export const LABEL = nls.localize('remote.tunnel.makePrivate', "Make Private");
export function handler(): ICommandHandler {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort });
return remoteExplorerService.forward({
remote: { host: arg.remoteHost, port: arg.remotePort },
local: arg.localPort,
name: arg.name,
elevateIfNeeded: true,
isPublic: false,
privacy: privacyId,
source: arg.source
});
}
@ -1446,6 +1454,7 @@ const tunnelViewCommandsWeightBonus = 10; // give our commands a little bit more
const isForwardedExpr = TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded);
const isForwardedOrDetectedExpr = ContextKeyExpr.or(isForwardedExpr, TunnelTypeContextKey.isEqualTo(TunnelType.Detected));
const isNotMultiSelectionExpr = TunnelViewMultiSelectionContextKey.isEqualTo(undefined);
const isNotPrivateExpr = ContextKeyExpr.and(TunnelPrivacyContextKey.notEqualsTo(TunnelPrivacyId.Private), TunnelPrivacyContextKey.notEqualsTo(TunnelPrivacyId.ConstantPrivate));
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: LabelTunnelAction.ID,
@ -1484,8 +1493,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
CommandsRegistry.registerCommand(CopyAddressAction.COMMANDPALETTE_ID, CopyAddressAction.commandPaletteHandler());
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());
@ -1535,7 +1542,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
title: OpenPortInPreviewAction.LABEL,
},
when: ContextKeyExpr.and(
ContextKeyExpr.or(WebContextKey.negate(), TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Public)),
ContextKeyExpr.or(WebContextKey.negate(), isNotPrivateExpr),
isForwardedOrDetectedExpr,
isNotMultiSelectionExpr)
}));
@ -1571,20 +1578,9 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
group: '2_localaddress',
order: 2,
command: {
id: MakePortPublicAction.ID,
title: MakePortPublicAction.LABEL,
},
when: ContextKeyExpr.and(TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Private), isNotMultiSelectionExpr)
}));
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
group: '2_localaddress',
order: 2,
command: {
id: MakePortPrivateAction.ID,
title: MakePortPrivateAction.LABEL,
},
when: ContextKeyExpr.and(TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Public), isNotMultiSelectionExpr)
submenu: MenuId.TunnelPrivacy,
title: nls.localize('tunnelContext.privacyMenu', "Port Privacy"),
when: TunnelPrivacyEnabledContextKey
}));
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
group: '2_localaddress',
@ -1686,7 +1682,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelLocalAddressInline, ({
icon: openPreviewIcon
},
when: ContextKeyExpr.and(
ContextKeyExpr.or(WebContextKey.negate(), TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Public)),
ContextKeyExpr.or(WebContextKey.negate(), isNotPrivateExpr),
isForwardedOrDetectedExpr)
}));

View file

@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions, ITunnel, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import * as nls from 'vs/nls';
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions, ITunnel, TunnelProtocol, TunnelPrivacyId } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@ -24,6 +25,23 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC
super();
const tunnelFactory = environmentService.options?.tunnelProvider?.tunnelFactory;
if (tunnelFactory) {
let privacyOptions = environmentService.options?.tunnelProvider?.features?.privacyOptions ?? [];
if (environmentService.options?.tunnelProvider?.features?.public
&& (privacyOptions.length === 0)) {
privacyOptions = [
{
id: 'private',
label: nls.localize('tunnelPrivacy.private', "Private"),
themeIcon: 'lock'
},
{
id: 'public',
label: nls.localize('tunnelPrivacy.public', "Public"),
themeIcon: 'eye'
}
];
}
this._register(tunnelService.setTunnelProvider({
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined => {
let tunnelPromise: Promise<ITunnel> | undefined;
@ -53,14 +71,18 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC
// The tunnel factory may give us an inaccessible local address.
// To make sure this doesn't happen, resolve the uri immediately.
localAddress: await this.resolveExternalUri(localAddress),
public: !!tunnel.public,
privacy: tunnel.privacy ?? (tunnel.public ? TunnelPrivacyId.Public : TunnelPrivacyId.Private),
protocol: tunnel.protocol ?? TunnelProtocol.Http,
dispose: async () => { await tunnel.dispose(); }
};
resolve(remoteTunnel);
});
}
}, environmentService.options?.tunnelProvider?.features ?? { elevation: false, public: false }));
}, {
elevation: !!environmentService.options?.tunnelProvider?.features?.elevation,
public: !!environmentService.options?.tunnelProvider?.features?.public,
privacyOptions
}));
remoteExplorerService.setTunnelInformation(undefined);
}
}

View file

@ -271,7 +271,8 @@ class SimpleTunnelService implements ITunnelService {
tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
canElevate: boolean = false;
canMakePublic = false;
canChangePrivacy = false;
privacyOptions = [];
onTunnelOpened = Event.None;
onTunnelClosed = Event.None;
onAddedTunnelProvider = Event.None;

View file

@ -18,7 +18,7 @@ export class TunnelService extends AbstractTunnelService {
super(logService);
}
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
@ -26,7 +26,7 @@ export class TunnelService extends AbstractTunnelService {
}
if (this._tunnelProvider) {
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic, protocol);
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
}
return undefined;
}

View file

@ -8,14 +8,13 @@ import { Event, Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel, TunnelPrivacy, TunnelPrivacyId, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IEditableData } from 'vs/workbench/common/views';
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';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { isNumber, isObject, isString } from 'vs/base/common/types';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { hash } from 'vs/base/common/hash';
@ -44,12 +43,6 @@ export enum TunnelType {
Add = 'Add'
}
export enum TunnelPrivacy {
ConstantPrivate = 'ConstantPrivate', // private, and changing is unsupported
Private = 'Private',
Public = 'Public'
}
export interface ITunnelItem {
tunnelType: TunnelType;
remoteHost: string;
@ -64,9 +57,8 @@ export interface ITunnelItem {
source: TunnelSource,
description: string
};
privacy?: TunnelPrivacy;
privacy: TunnelPrivacy;
processDescription?: string;
readonly icon?: ThemeIcon;
readonly label: string;
}
@ -86,7 +78,7 @@ interface TunnelProperties {
description: string
},
elevateIfNeeded?: boolean,
isPublic?: boolean
privacy?: string
}
export enum TunnelSource {
@ -113,7 +105,7 @@ export interface Tunnel {
localPort?: number;
name?: string;
closeable?: boolean;
privacy: TunnelPrivacy;
privacy: TunnelPrivacyId | string;
runningProcess: string | undefined;
hasRunningProcess?: boolean;
pid: number | undefined;
@ -469,7 +461,7 @@ export class TunnelModel extends Disposable {
runningProcess: matchingCandidate?.detail,
hasRunningProcess: !!matchingCandidate,
pid: matchingCandidate?.pid,
privacy: this.makeTunnelPrivacy(tunnel.public),
privacy: tunnel.privacy,
source: UserTunnelSource,
});
this.remoteTunnels.set(key, tunnel);
@ -496,7 +488,7 @@ export class TunnelModel extends Disposable {
runningProcess: matchingCandidate?.detail,
hasRunningProcess: !!matchingCandidate,
pid: matchingCandidate?.pid,
privacy: this.makeTunnelPrivacy(tunnel.public),
privacy: tunnel.privacy,
source: UserTunnelSource,
});
}
@ -526,10 +518,6 @@ export class TunnelModel extends Disposable {
return URI.parse(`${protocol}://${localAddress}`);
}
private makeTunnelPrivacy(isPublic: boolean) {
return isPublic ? TunnelPrivacy.Public : this.tunnelService.canMakePublic ? TunnelPrivacy.Private : TunnelPrivacy.ConstantPrivate;
}
private async getStorageKey(): Promise<string> {
const workspace = this.workspaceContextService.getWorkspace();
const workspaceHash = workspace.configuration ? hash(workspace.configuration.path) : (workspace.folders.length > 0 ? hash(workspace.folders[0].uri.path) : undefined);
@ -559,7 +547,7 @@ export class TunnelModel extends Disposable {
remote: { host: tunnel.remoteHost, port: tunnel.remotePort },
local: tunnel.localPort,
name: tunnel.name,
isPublic: tunnel.privacy === TunnelPrivacy.Public
privacy: tunnel.privacy
});
}
}
@ -623,7 +611,7 @@ export class TunnelModel extends Disposable {
const key = makeAddress(tunnelProperties.remote.host, tunnelProperties.remote.port);
this.inProgress.set(key, true);
const tunnel = await this.tunnelService.openTunnel(addressProvider, tunnelProperties.remote.host, tunnelProperties.remote.port, localPort, (!tunnelProperties.elevateIfNeeded) ? attributes?.elevateIfNeeded : tunnelProperties.elevateIfNeeded, tunnelProperties.isPublic, attributes?.protocol);
const tunnel = await this.tunnelService.openTunnel(addressProvider, tunnelProperties.remote.host, tunnelProperties.remote.port, localPort, (!tunnelProperties.elevateIfNeeded) ? attributes?.elevateIfNeeded : tunnelProperties.elevateIfNeeded, tunnelProperties.privacy, attributes?.protocol);
if (tunnel && tunnel.localAddress) {
const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces<CandidatePort>(this._candidates ?? new Map(), tunnelProperties.remote.host, tunnelProperties.remote.port);
const protocol = (tunnel.protocol ?
@ -642,7 +630,7 @@ export class TunnelModel extends Disposable {
hasRunningProcess: !!matchingCandidate,
pid: matchingCandidate?.pid,
source: tunnelProperties.source ?? UserTunnelSource,
privacy: this.makeTunnelPrivacy(tunnel.public),
privacy: tunnel.privacy,
};
this.forwarded.set(key, newForward);
this.remoteTunnels.set(key, tunnel);
@ -710,7 +698,7 @@ export class TunnelModel extends Disposable {
runningProcess: matchingCandidate?.detail,
hasRunningProcess: !!matchingCandidate,
pid: matchingCandidate?.pid,
privacy: TunnelPrivacy.ConstantPrivate,
privacy: TunnelPrivacyId.ConstantPrivate,
source: {
source: TunnelSource.Extension,
description: nls.localize('tunnel.staticallyForwarded', "Statically Forwarded")

View file

@ -70,8 +70,13 @@ interface ITunnelOptions {
label?: string;
/**
* @deprecated Use privacy instead
*/
public?: boolean;
privacy?: string;
protocol?: string;
}
@ -92,8 +97,13 @@ interface ITunnel {
*/
localAddress: string;
/**
* @deprecated Use privacy instead
*/
public?: boolean;
privacy?: string;
/**
* If protocol is not provided, it is assumed to be http, regardless of the localAddress
*/