mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Allow tunnel providers to define port privacy options (#133677)
This commit is contained in:
parent
fd6adaf30a
commit
a15378a9db
|
@ -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: () => {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
18
src/vs/vscode.proposed.d.ts
vendored
18
src/vs/vscode.proposed.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.'));
|
||||
|
|
|
@ -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)
|
||||
}));
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue