clarify partial/full profile types

This commit is contained in:
Eugene
2026-01-24 17:30:11 +01:00
parent 0bb357c7af
commit 438ac9a6ff
32 changed files with 181 additions and 151 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ export { PromptModalComponent } from '../components/promptModal.component'
export * from './commands'
export { AppService } from '../services/app.service'
export { ConfigService, configMerge, ConfigProxy } from '../services/config.service'
export { ConfigService, configMerge, ConfigProxy, ProxifiedConfig, FullyDefined } from '../services/config.service'
export { DockingService, Screen } from '../services/docking.service'
export { Logger, ConsoleLogger, LogService } from '../services/log.service'
export { HomeBaseService } from '../services/homeBase.service'
+8 -7
View File
@@ -3,16 +3,17 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { BaseTabComponent } from '../components/baseTab.component'
import { NewTabParameters } from '../services/tabs.service'
import { FullyDefined } from '../services/config.service'
export interface Profile {
id: string
type: string
name: string
group?: string
group: string
options: any
icon?: string
color?: string
icon: string | null
color: string | null
disableDynamicTitle: boolean
behaviorOnSessionEnd: 'auto'|'keep'|'reconnect'|'close'
@@ -50,16 +51,16 @@ export type PartialProfileGroup<T extends ProfileGroup> = Omit<Omit<{
name: string
}
export interface ProfileSettingsComponent<P extends Profile> {
profile: P
export interface ProfileSettingsComponent<P extends Profile, PP extends ProfileProvider<P>> {
profile: FullyDefined<P>
save?: () => void
}
export abstract class ProfileProvider<P extends Profile> {
id: string
name: string
settingsComponent?: new (...args: any[]) => ProfileSettingsComponent<P>
configDefaults = {}
settingsComponent?: new (...args: any[]) => ProfileSettingsComponent<P, ProfileProvider<P>>
configDefaults: Pick<Profile, 'options'>
abstract getBuiltinProfiles (): Promise<PartialProfile<P>[]>
+38 -12
View File
@@ -21,7 +21,7 @@ export const configMergeByDefault = (a, b) => deepmerge(a, b) // eslint-disable-
const LATEST_VERSION = 1
function isStructuralMember (v) {
function isStructuralMember (v): v is AnyRec {
return v instanceof Object && !(v instanceof Array) &&
Object.keys(v).length > 0 && !v.__nonStructural
}
@@ -30,15 +30,38 @@ function isNonStructuralObjectMember (v): boolean {
return v instanceof Object && (v instanceof Array || v.__nonStructural)
}
// eslint-disable-next-line @typescript-eslint/no-type-alias
type AnyRec = Record<string, any>
// eslint-disable-next-line @typescript-eslint/no-type-alias
type IsRecord<T> = T extends object
// eslint-disable-next-line @typescript-eslint/ban-types
? (T extends Function ? false : true)
: false
// eslint-disable-next-line @typescript-eslint/no-type-alias
export type ProxifiedConfig<T extends AnyRec> = {
[K in keyof T]:
IsRecord<T[K]> extends true
? ProxifiedConfig<T[K]> // structural -> nested proxy
: T[K]; // leaf -> original type
}
// eslint-disable-next-line @typescript-eslint/no-type-alias
export type FullyDefined<T> = T extends object
? { [K in keyof T]-?: FullyDefined<T[K]> }
: T
/** @hidden */
export class ConfigProxy {
constructor (real: Record<string, any>, defaults: Record<string, any>) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class ConfigProxy<T extends AnyRec> {
constructor (real: Partial<T>, defaults: T) {
for (const key in defaults) {
if (isStructuralMember(defaults[key])) {
if (!real[key]) {
real[key] = {}
real[key] = {} as any
}
const proxy = new ConfigProxy(real[key], defaults[key])
const proxy = new ConfigProxy(real[key] as any, defaults[key])
Object.defineProperty(
this,
key,
@@ -64,7 +87,7 @@ export class ConfigProxy {
}
}
this.__getValue = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
this.__getValue = (key: keyof T) => { // eslint-disable-line @typescript-eslint/unbound-method
if (real[key] !== undefined) {
return real[key]
} else {
@@ -78,11 +101,11 @@ export class ConfigProxy {
}
}
this.__getDefault = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
this.__getDefault = (key: keyof T) => { // eslint-disable-line @typescript-eslint/unbound-method
return deepClone(defaults[key])
}
this.__setValue = (key: string, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method
this.__setValue = (key: keyof T, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method
if (deepEqual(value, this.__getDefault(key))) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete real[key]
@@ -95,7 +118,7 @@ export class ConfigProxy {
// Trigger removal of default values
for (const key in defaults) {
if (isStructuralMember(defaults[key])) {
this[key].__cleanup()
(this as any)[key].__cleanup()
} else {
const v = this.__getValue(key)
this.__setValue(key, v)
@@ -105,15 +128,18 @@ export class ConfigProxy {
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
__getValue (_key: string): any { }
__getValue (_key: keyof T): any { }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
__setValue (_key: string, _value: any) { }
__setValue (_key: keyof T, _value: any) { }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
__getDefault (_key: string): any { }
__getDefault (_key: keyof T): any { }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
__cleanup () { }
}
// eslint-disable-next-line @typescript-eslint/no-type-alias, @typescript-eslint/no-redeclare
// export type ConfigProxy<T extends AnyRec> = ProxifiedConfig<T>
@Injectable({ providedIn: 'root' })
export class ConfigService {
/**
+10 -8
View File
@@ -5,7 +5,7 @@ import { BaseTabComponent } from '../components/baseTab.component'
import { QuickConnectProfileProvider, PartialProfile, PartialProfileGroup, Profile, ProfileGroup, ProfileProvider } from '../api/profileProvider'
import { SelectorOption } from '../api/selector'
import { AppService } from './app.service'
import { configMerge, ConfigProxy, ConfigService } from './config.service'
import { configMerge, ConfigProxy, ConfigService, FullyDefined } from './config.service'
import { NotificationsService } from './notifications.service'
import { SelectorService } from './selector.service'
import deepClone from 'clone-deep'
@@ -14,7 +14,7 @@ import slugify from 'slugify'
@Injectable({ providedIn: 'root' })
export class ProfilesService {
private profileDefaults = {
private profileDefaults: Profile = {
id: '',
type: '',
name: '',
@@ -26,7 +26,6 @@ export class ProfilesService {
weight: 0,
isBuiltin: false,
isTemplate: false,
terminalColorScheme: null,
behaviorOnSessionEnd: 'auto',
}
@@ -53,7 +52,7 @@ export class ProfilesService {
}
getDescription <P extends Profile> (profile: PartialProfile<P>): string|null {
profile = this.getConfigProxyForProfile(profile)
profile = this.getConfigProxyForProfile(profile) as PartialProfile<P>
return this.providerForProfile(profile)?.getDescription(profile) ?? null
}
@@ -66,9 +65,9 @@ export class ProfilesService {
* arg: skipUserDefaults -> do not merge global provider defaults in ConfigProxy
* arg: skipGroupDefaults -> do not merge parent group provider defaults in ConfigProxy
*/
getConfigProxyForProfile <T extends Profile> (profile: PartialProfile<T>, options?: { skipGlobalDefaults?: boolean, skipGroupDefaults?: boolean }): T {
getConfigProxyForProfile <P extends Profile> (profile: PartialProfile<P>, options?: { skipGlobalDefaults?: boolean, skipGroupDefaults?: boolean }): FullyDefined<P> & ConfigProxy<FullyDefined<P>> {
const defaults = this.getProfileDefaults(profile, options).reduce(configMerge, {})
return new ConfigProxy(profile, defaults) as unknown as T
return new ConfigProxy(profile, defaults) as any
}
/**
@@ -214,7 +213,10 @@ export class ProfilesService {
const provider = this.providerForProfile(fullProfile)
const freeInputEquivalent = provider instanceof QuickConnectProfileProvider ? provider.intoQuickConnectString(fullProfile) ?? undefined : undefined
return {
...profile,
name: profile.name,
icon: profile.icon ?? undefined,
color: profile.color ?? undefined,
weight: profile.weight,
group: this.resolveProfileGroupName(profile.group ?? ''),
freeInputEquivalent,
description: provider?.getDescription(fullProfile),
@@ -234,7 +236,7 @@ export class ProfilesService {
...this.selectorOptionForProfile(p),
group: this.translate.instant('Recent'),
icon: 'fas fa-history',
color: p.color,
color: p.color ?? undefined,
weight: i - (recentProfiles.length + 1),
callback: async () => {
if (p.id) {
+8 -9
View File
@@ -32,16 +32,15 @@ export abstract class ShellProvider {
export interface SessionOptions {
restoreFromPTYID?: string
name?: string
restoreFromPTYID: string | null
command: string
args?: string[]
cwd?: string
env?: Record<string, string>
width?: number
height?: number
pauseAfterExit?: boolean
runAsAdministrator?: boolean
args: string[]
cwd: string | null
env: Record<string, string>
width: number | null
height: number | null
pauseAfterExit: boolean
runAsAdministrator: boolean
}
export interface LocalProfile extends BaseTerminalProfile {
@@ -40,7 +40,7 @@ export class CommandLineEditorComponent {
updateCommand () {
this.command = shellQuote.quote([
this.model.command,
...this.model.args ?? [],
...this.model.args,
])
}
@@ -1,15 +1,16 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component, Inject, Optional } from '@angular/core'
import { LocalProfile, UACService } from '../api'
import { PlatformService, ProfileSettingsComponent } from 'tabby-core'
import { FullyDefined, PlatformService, ProfileSettingsComponent } from 'tabby-core'
import { LocalProfilesService } from '../profiles'
/** @hidden */
@Component({
templateUrl: './localProfileSettings.component.pug',
})
export class LocalProfileSettingsComponent implements ProfileSettingsComponent<LocalProfile> {
profile: LocalProfile
export class LocalProfileSettingsComponent implements ProfileSettingsComponent<LocalProfile, LocalProfilesService> {
profile: FullyDefined<LocalProfile>
constructor (
@Optional() @Inject(UACService) public uac: UACService|undefined,
+4 -3
View File
@@ -51,13 +51,13 @@ export class LocalProfilesService extends ProfileProvider<LocalProfile> {
if (!profile.options.cwd) {
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
profile.options.cwd = await this.app.activeTab.session.getWorkingDirectory() ?? undefined
profile.options.cwd = await this.app.activeTab.session.getWorkingDirectory() ?? null
}
if (this.app.activeTab instanceof SplitTabComponent) {
const focusedTab = this.app.activeTab.getFocusedTab()
if (focusedTab instanceof TerminalTabComponent && focusedTab.session) {
profile.options.cwd = await focusedTab.session.getWorkingDirectory() ?? undefined
profile.options.cwd = await focusedTab.session.getWorkingDirectory() ?? null
}
}
}
@@ -77,10 +77,11 @@ export class LocalProfilesService extends ProfileProvider<LocalProfile> {
optionsFromShell (shell: Shell): SessionOptions {
return {
...this.configDefaults.options,
command: shell.command,
args: shell.args ?? [],
env: shell.env,
cwd: shell.cwd,
cwd: shell.cwd ?? null,
}
}
+4 -4
View File
@@ -63,7 +63,7 @@ export class Session extends BaseSession {
if (options.restoreFromPTYID) {
pty = await this.ptyInterface.restore(options.restoreFromPTYID)
options.restoreFromPTYID = undefined
options.restoreFromPTYID = null
}
if (!pty) {
@@ -74,7 +74,7 @@ export class Session extends BaseSession {
TERM: 'xterm-256color',
TERM_PROGRAM: 'Tabby',
},
substituteEnv(options.env ?? {}),
substituteEnv(options.env),
this.config.store.terminal.environment || {},
)
@@ -104,7 +104,7 @@ export class Session extends BaseSession {
cwd = undefined
}
pty = await this.ptyInterface.spawn(options.command, options.args ?? [], {
pty = await this.ptyInterface.spawn(options.command, options.args, {
name: 'xterm-256color',
cols: options.width ?? 80,
rows: options.height ?? 30,
@@ -152,7 +152,7 @@ export class Session extends BaseSession {
}
})
this.pauseAfterExit = options.pauseAfterExit ?? false
this.pauseAfterExit = options.pauseAfterExit
this.destroyed$.subscribe(() => this.pty!.unsubscribeAll())
}
+1 -1
View File
@@ -40,7 +40,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
click: async () => {
let workingDirectory = profile.options.cwd
if (!workingDirectory && tab instanceof TerminalTabComponent) {
workingDirectory = await tab.session?.getWorkingDirectory() ?? undefined
workingDirectory = await tab.session?.getWorkingDirectory() ?? null
}
await this.terminalService.openTab(profile, workingDirectory)
},
+16 -16
View File
@@ -12,15 +12,15 @@ export interface SerialProfile extends ConnectableTerminalProfile {
export interface SerialProfileOptions extends StreamProcessingOptions, LoginScriptsOptions {
port: string
baudrate?: number
databits?: number
stopbits?: number
parity?: string
rtscts?: boolean
xon?: boolean
xoff?: boolean
xany?: boolean
slowSend?: boolean
baudrate: number | null
databits: 5 | 6 | 7 | 8
stopbits: 1 | 1.5 | 2
parity: string
rtscts: boolean
xon: boolean
xoff: boolean
xany: boolean
slowSend: boolean
input: InputProcessingOptions,
}
@@ -81,13 +81,13 @@ export class SerialSession extends BaseSession {
path: this.profile.options.port,
autoOpen: false,
baudRate: parseInt(this.profile.options.baudrate as any),
dataBits: this.profile.options.databits ?? 8 as any,
stopBits: this.profile.options.stopbits ?? 1 as any,
parity: this.profile.options.parity ?? 'none',
rtscts: this.profile.options.rtscts ?? false,
xon: this.profile.options.xon ?? false,
xoff: this.profile.options.xoff ?? false,
xany: this.profile.options.xany ?? false,
dataBits: this.profile.options.databits,
stopBits: this.profile.options.stopbits,
parity: this.profile.options.parity,
rtscts: this.profile.options.rtscts,
xon: this.profile.options.xon,
xoff: this.profile.options.xoff,
xany: this.profile.options.xany,
})
let connected = false
await new Promise(async (resolve, reject) => {
@@ -1,16 +1,17 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core'
import { debounceTime, distinctUntilChanged, map } from 'rxjs'
import { HostAppService, Platform, ProfileSettingsComponent } from 'tabby-core'
import { FullyDefined, HostAppService, Platform, ProfileSettingsComponent } from 'tabby-core'
import { SerialPortInfo, BAUD_RATES, SerialProfile } from '../api'
import { SerialService } from '../services/serial.service'
import { SerialProfilesService } from '../profiles'
/** @hidden */
@Component({
templateUrl: './serialProfileSettings.component.pug',
})
export class SerialProfileSettingsComponent implements ProfileSettingsComponent<SerialProfile> {
profile: SerialProfile
export class SerialProfileSettingsComponent implements ProfileSettingsComponent<SerialProfile, SerialProfilesService> {
profile: FullyDefined<SerialProfile>
foundPorts: SerialPortInfo[]
Platform = Platform
@@ -8,7 +8,7 @@ import { ConfigProxy, ProfileGroup, Profile, ProfileProvider, PlatformService, T
templateUrl: './editProfileGroupModal.component.pug',
})
export class EditProfileGroupModalComponent<G extends ProfileGroup> {
@Input() group: G & ConfigProxy
@Input() group: G & ConfigProxy<G>
@Input() providers: ProfileProvider<Profile>[]
constructor (
@@ -2,7 +2,7 @@
import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } from 'rxjs'
import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigProxy, PartialProfileGroup, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService, TAB_COLORS, ProfileGroup, ConnectableProfileProvider } from 'tabby-core'
import { PartialProfileGroup, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService, TAB_COLORS, ProfileGroup, ConnectableProfileProvider, FullyDefined, ConfigProxy } from 'tabby-core'
const iconsData = require('../../../tabby-core/src/icons.json')
const iconsClassList = Object.keys(iconsData).map(
@@ -15,17 +15,17 @@ const iconsClassList = Object.keys(iconsData).map(
@Component({
templateUrl: './editProfileModal.component.pug',
})
export class EditProfileModalComponent<P extends Profile> {
@Input() profile: P & ConfigProxy
@Input() profileProvider: ProfileProvider<P>
@Input() settingsComponent: new () => ProfileSettingsComponent<P>
export class EditProfileModalComponent<P extends Profile, PP extends ProfileProvider<P>> {
@Input('profile') _profile: P
@Input() profileProvider: PP
@Input() settingsComponent: new () => ProfileSettingsComponent<P, PP>
@Input() defaultsMode: 'enabled'|'group'|'disabled' = 'disabled'
@Input() profileGroup: PartialProfileGroup<ProfileGroup> | undefined
groups: PartialProfileGroup<ProfileGroup>[]
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
private _profile: Profile
private settingsComponentInstance?: ProfileSettingsComponent<P>
protected profile: FullyDefined<P> & ConfigProxy<FullyDefined<P>>
private settingsComponentInstance?: ProfileSettingsComponent<P, PP>
constructor (
private injector: Injector,
@@ -56,8 +56,7 @@ export class EditProfileModalComponent<P extends Profile> {
}
ngOnInit () {
this._profile = this.profile
this.profile = this.profilesService.getConfigProxyForProfile(this.profile, { skipGlobalDefaults: this.defaultsMode === 'enabled', skipGroupDefaults: this.defaultsMode === 'group' })
this.profile = this.profilesService.getConfigProxyForProfile<P>(this._profile, { skipGlobalDefaults: this.defaultsMode === 'enabled', skipGroupDefaults: this.defaultsMode === 'group' })
}
ngAfterViewInit () {
@@ -90,7 +89,7 @@ export class EditProfileModalComponent<P extends Profile> {
save () {
if (!this.profileGroup) {
this.profile.group = undefined
this.profile.group = ''
} else {
this.profile.group = this.profileGroup.id
}
@@ -67,7 +67,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
base = await this.selector.show(
this.translate.instant('Select a base profile to use as a template'),
profiles.map(p => ({
icon: p.icon,
icon: p.icon ?? undefined,
description: this.profilesService.getDescription(p) ?? undefined,
name: p.group ? `${this.profilesService.resolveProfileGroupName(p.group)} / ${p.name}` : p.name,
result: p,
+19 -19
View File
@@ -17,25 +17,25 @@ export interface SSHProfileOptions extends LoginScriptsOptions {
host: string
port?: number
user: string
auth?: null|'password'|'publicKey'|'agent'|'keyboardInteractive'
password?: string
privateKeys?: string[]
keepaliveInterval?: number
keepaliveCountMax?: number
readyTimeout?: number
x11?: boolean
skipBanner?: boolean
jumpHost?: string
agentForward?: boolean
warnOnClose?: boolean
algorithms?: Record<string, string[]>
proxyCommand?: string
forwardedPorts?: ForwardedPortConfig[]
socksProxyHost?: string
socksProxyPort?: number
httpProxyHost?: string
httpProxyPort?: number
reuseSession?: boolean
auth: null|'password'|'publicKey'|'agent'|'keyboardInteractive'
password: string
privateKeys: string[]
keepaliveInterval: number
keepaliveCountMax: number
readyTimeout: number | null
x11: boolean
skipBanner: boolean
jumpHost: string | null
agentForward: boolean
warnOnClose: boolean
algorithms: Record<SSHAlgorithmType, string[]>
proxyCommand: string | null
forwardedPorts: ForwardedPortConfig[]
socksProxyHost: string | null
socksProxyPort: number | null
httpProxyHost: string | null
httpProxyPort: number | null
reuseSession: boolean
input: InputProcessingOptions,
}
@@ -3,19 +3,21 @@ import { Component, ViewChild } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { firstBy } from 'thenby'
import { FileProvidersService, Platform, HostAppService, PromptModalComponent, PartialProfile, ProfilesService } from 'tabby-core'
import { FileProvidersService, Platform, HostAppService, PromptModalComponent, PartialProfile, ProfilesService, ProfileSettingsComponent } from 'tabby-core'
import { LoginScriptsSettingsComponent } from 'tabby-terminal'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { ForwardedPortConfig, SSHAlgorithmType, SSHProfile } from '../api'
import { supportedAlgorithms } from '../algorithms'
import { FullyDefined, ProxifiedConfig } from 'tabby-core/src/services/config.service'
import { SSHProfilesService } from '../profiles'
/** @hidden */
@Component({
templateUrl: './sshProfileSettings.component.pug',
})
export class SSHProfileSettingsComponent {
export class SSHProfileSettingsComponent implements ProfileSettingsComponent<SSHProfile, SSHProfilesService> {
Platform = Platform
profile: SSHProfile
profile: ProxifiedConfig<FullyDefined<SSHProfile>>
hasSavedPassword: boolean
connectionMode: 'direct'|'proxyCommand'|'jumpHost'|'socksProxy'|'httpProxy' = 'direct'
@@ -39,14 +41,11 @@ export class SSHProfileSettingsComponent {
for (const k of Object.values(SSHAlgorithmType)) {
this.algorithms[k] = {}
for (const alg of this.profile.options.algorithms?.[k] ?? []) {
for (const alg of this.profile.options.algorithms[k]) {
this.algorithms[k][alg] = true
}
}
this.profile.options.auth = this.profile.options.auth ?? null
this.profile.options.privateKeys ??= []
if (this.profile.options.proxyCommand) {
this.connectionMode = 'proxyCommand'
} else if (this.profile.options.jumpHost) {
@@ -92,49 +91,48 @@ export class SSHProfileSettingsComponent {
const ref = await this.fileProviders.selectAndStoreFile(`private key for ${this.profile.name}`).catch(() => null)
if (ref) {
this.profile.options.privateKeys = [
...this.profile.options.privateKeys!,
...this.profile.options.privateKeys,
ref,
]
}
}
removePrivateKey (path: string) {
this.profile.options.privateKeys = this.profile.options.privateKeys?.filter(x => x !== path)
this.profile.options.privateKeys = this.profile.options.privateKeys.filter(x => x !== path)
}
save () {
for (const k of Object.values(SSHAlgorithmType)) {
this.profile.options.algorithms![k] = Object.entries(this.algorithms[k])
this.profile.options.algorithms[k] = Object.entries(this.algorithms[k])
.filter(([_, v]) => !!v)
.map(([key, _]) => key)
if(k !== SSHAlgorithmType.COMPRESSION) { this.profile.options.algorithms![k].sort() }
if(k !== SSHAlgorithmType.COMPRESSION) { this.profile.options.algorithms[k].sort() }
}
if (this.connectionMode !== 'jumpHost') {
this.profile.options.jumpHost = undefined
this.profile.options.jumpHost = null
}
if (this.connectionMode !== 'proxyCommand') {
this.profile.options.proxyCommand = undefined
this.profile.options.proxyCommand = null
}
if (this.connectionMode !== 'socksProxy') {
this.profile.options.socksProxyHost = undefined
this.profile.options.socksProxyPort = undefined
this.profile.options.socksProxyHost = null
this.profile.options.socksProxyPort = null
}
if (this.connectionMode !== 'httpProxy') {
this.profile.options.httpProxyHost = undefined
this.profile.options.httpProxyPort = undefined
this.profile.options.httpProxyHost = null
this.profile.options.httpProxyPort = null
}
this.loginScriptsSettings?.save()
}
onForwardAdded (fw: ForwardedPortConfig) {
this.profile.options.forwardedPorts = this.profile.options.forwardedPorts ?? []
this.profile.options.forwardedPorts.push(fw)
}
onForwardRemoved (fw: ForwardedPortConfig) {
this.profile.options.forwardedPorts = this.profile.options.forwardedPorts?.filter(x => x !== fw)
this.profile.options.forwardedPorts = this.profile.options.forwardedPorts.filter(x => x !== fw)
}
getConnectionDropdownTitle () {
+1 -1
View File
@@ -195,7 +195,7 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
if (!this.session?.open) {
return true
}
if (!(this.profile.options.warnOnClose ?? this.config.store.ssh.warnOnClose)) {
if (!this.profile.options.warnOnClose) {
return true
}
return (await this.platform.showMessageBox(
+1 -1
View File
@@ -14,7 +14,7 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
settingsComponent = SSHProfileSettingsComponent
configDefaults = {
options: {
host: null,
host: '',
port: 22,
user: 'root',
auth: null,
+3 -3
View File
@@ -44,7 +44,7 @@ export class SSHService {
uri += `;x-tunnelpasswordplain=${encodeURIComponent(jumpPassword)}`
}
}
if (jumpHostProfile.options.auth === 'publicKey' && jumpHostProfile.options.privateKeys && jumpHostProfile.options.privateKeys.length > 0) {
if (jumpHostProfile.options.auth === 'publicKey' && jumpHostProfile.options.privateKeys.length > 0) {
const privateKeyPairs = await this.convertPrivateKeyFileToPuTTYFormat(jumpHostProfile)
tmpFile = privateKeyPairs.privateKeyFile
if (tmpFile) {
@@ -80,7 +80,7 @@ export class SSHService {
}
async convertPrivateKeyFileToPuTTYFormat (profile: SSHProfile): Promise<{ passphrase: string|null, privateKeyFile: tmp.FileResult|null }> {
if (!profile.options.privateKeys || profile.options.privateKeys.length === 0) {
if (profile.options.privateKeys.length === 0) {
throw new Error('No private keys in profile')
}
const path = this.getWinSCPPath()
@@ -122,7 +122,7 @@ export class SSHService {
let tmpFile: tmp.FileResult|null = null
try {
if (session.activePrivateKey && session.profile.options.privateKeys && session.profile.options.privateKeys.length > 0) {
if (session.activePrivateKey && session.profile.options.privateKeys.length > 0) {
const profile = session.profile
const privateKeyPairs = await this.convertPrivateKeyFileToPuTTYFormat(profile)
tmpFile = privateKeyPairs.privateKeyFile
+1 -1
View File
@@ -40,7 +40,7 @@ export class SSHShellSession extends BaseSession {
this.logger.debug('Opening shell')
try {
this.shell = await this.ssh.openShellChannel({ x11: this.profile.options.x11 ?? false })
this.shell = await this.ssh.openShellChannel({ x11: this.profile.options.x11 })
} catch (err) {
if (err.toString().includes('Unable to request X11')) {
this.emitServiceMessage(' Make sure `xauth` is installed on the remote side')
+11 -11
View File
@@ -161,7 +161,7 @@ export class SSHSession {
async init (): Promise<void> {
this.allAuthMethods = [{ type: 'none' }]
if (!this.profile.options.auth || this.profile.options.auth === 'publicKey') {
if (this.profile.options.privateKeys?.length) {
if (this.profile.options.privateKeys.length) {
for (let pk of this.profile.options.privateKeys) {
// eslint-disable-next-line @typescript-eslint/init-declarations
let contents: Buffer
@@ -207,7 +207,7 @@ export class SSHSession {
} else {
// If user configured specific private keys, try to load their corresponding
// .pub files and use them first for agent-identity authentication
if (this.profile.options.privateKeys?.length) {
if (this.profile.options.privateKeys.length) {
for (let pk of this.profile.options.privateKeys) {
pk = pk.replace('%h', this.profile.options.host)
pk = pk.replace('%r', this.profile.options.user)
@@ -352,7 +352,7 @@ export class SSHSession {
const algorithms = {}
for (const key of Object.values(SSHAlgorithmType)) {
algorithms[key] = this.profile.options.algorithms![key].filter(x => supportedAlgorithms[key].includes(x))
algorithms[key] = this.profile.options.algorithms[key].filter(x => supportedAlgorithms[key].includes(x))
}
// eslint-disable-next-line @typescript-eslint/init-declarations
@@ -396,13 +396,13 @@ export class SSHSession {
},
{
preferred: {
ciphers: this.profile.options.algorithms?.[SSHAlgorithmType.CIPHER]?.filter(x => supportedAlgorithms[SSHAlgorithmType.CIPHER].includes(x)),
kex: this.profile.options.algorithms?.[SSHAlgorithmType.KEX]?.filter(x => supportedAlgorithms[SSHAlgorithmType.KEX].includes(x)),
mac: this.profile.options.algorithms?.[SSHAlgorithmType.HMAC]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HMAC].includes(x)),
key: this.profile.options.algorithms?.[SSHAlgorithmType.HOSTKEY]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HOSTKEY].includes(x)),
compression: this.profile.options.algorithms?.[SSHAlgorithmType.COMPRESSION]?.filter(x => supportedAlgorithms[SSHAlgorithmType.COMPRESSION].includes(x)),
ciphers: this.profile.options.algorithms[SSHAlgorithmType.CIPHER].filter(x => supportedAlgorithms[SSHAlgorithmType.CIPHER].includes(x)),
kex: this.profile.options.algorithms[SSHAlgorithmType.KEX].filter(x => supportedAlgorithms[SSHAlgorithmType.KEX].includes(x)),
mac: this.profile.options.algorithms[SSHAlgorithmType.HMAC].filter(x => supportedAlgorithms[SSHAlgorithmType.HMAC].includes(x)),
key: this.profile.options.algorithms[SSHAlgorithmType.HOSTKEY].filter(x => supportedAlgorithms[SSHAlgorithmType.HOSTKEY].includes(x)),
compression: this.profile.options.algorithms[SSHAlgorithmType.COMPRESSION].filter(x => supportedAlgorithms[SSHAlgorithmType.COMPRESSION].includes(x)),
},
keepaliveIntervalSeconds: Math.round((this.profile.options.keepaliveInterval ?? 15000) / 1000),
keepaliveIntervalSeconds: Math.round(this.profile.options.keepaliveInterval / 1000),
keepaliveCountMax: this.profile.options.keepaliveCountMax,
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : undefined,
},
@@ -466,7 +466,7 @@ export class SSHSession {
this.passwordStorage.savePassword(this.profile, this.savedPassword, this.authUsername ?? undefined)
}
for (const fw of this.profile.options.forwardedPorts ?? []) {
for (const fw of this.profile.options.forwardedPorts) {
this.addPortForward(Object.assign(new ForwardedPort(), fw))
}
@@ -566,7 +566,7 @@ export class SSHSession {
const keyDigest = crypto.createHash('sha256').update(key.bytes()).digest('base64')
const knownHost = this.knownHosts.getFor(selector)
const knownHost = this.profile.options.host ? this.knownHosts.getFor(selector) : null
if (!knownHost || knownHost.digest !== keyDigest) {
const modal = this.ngbModal.open(HostKeyPromptModalComponent)
modal.componentInstance.selector = selector
@@ -1,13 +1,14 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core'
import { ProfileSettingsComponent } from 'tabby-core'
import { FullyDefined, ProfileSettingsComponent } from 'tabby-core'
import { TelnetProfile } from '../session'
import { TelnetProfilesService } from '../profiles'
/** @hidden */
@Component({
templateUrl: './telnetProfileSettings.component.pug',
})
export class TelnetProfileSettingsComponent implements ProfileSettingsComponent<TelnetProfile> {
profile: TelnetProfile
export class TelnetProfileSettingsComponent implements ProfileSettingsComponent<TelnetProfile, TelnetProfilesService> {
profile: FullyDefined<TelnetProfile>
}
+1 -1
View File
@@ -14,7 +14,7 @@ export interface TelnetProfile extends ConnectableTerminalProfile {
export interface TelnetProfileOptions extends StreamProcessingOptions, LoginScriptsOptions {
host: string
port?: number
port: number | null
input: InputProcessingOptions,
}
@@ -3,7 +3,7 @@ import { Spinner } from 'cli-spinner'
import colors from 'ansi-colors'
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags, Component } from '@angular/core'
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
import { AppService, ConfigService, BaseTabComponent, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService, HostWindowService, ResettableTimeout, TranslateService, ThemesService } from 'tabby-core'
import { AppService, ConfigService, BaseTabComponent, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService, HostWindowService, ResettableTimeout, TranslateService, ThemesService, FullyDefined } from 'tabby-core'
import { BaseSession } from '../session'
@@ -97,7 +97,7 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
frontendReady = new Subject<void>()
size: ResizeEvent
profile: P
profile: FullyDefined<P>
/**
* Enables normal passthrough from session output to terminal input
+1 -1
View File
@@ -17,7 +17,7 @@ export interface TerminalColorScheme {
}
export interface BaseTerminalProfile extends Profile {
terminalColorScheme?: TerminalColorScheme
terminalColorScheme: TerminalColorScheme | null
}
export interface ConnectableTerminalProfile extends BaseTerminalProfile, ConnectableProfile {}
@@ -19,7 +19,7 @@ export class LoginScriptsSettingsComponent {
) { }
ngOnInit () {
this.scripts = this.options.scripts ?? []
this.scripts = this.options.scripts
}
async deleteScript (script: LoginScript) {
@@ -385,7 +385,7 @@ export class XTermFrontend extends Frontend {
this.xtermCore._scrollToBottom()
}
private configureColors (scheme: TerminalColorScheme|undefined): void {
private configureColors (scheme: TerminalColorScheme | null): void {
const appColorScheme = this.themes._getActiveColorScheme() as TerminalColorScheme
scheme = scheme ?? appColorScheme
+1 -1
View File
@@ -4,7 +4,7 @@ import { ConfigService, ThemesService } from 'tabby-core'
export function getTerminalBackgroundColor (
config: ConfigService,
themes: ThemesService,
scheme?: TerminalColorScheme,
scheme: TerminalColorScheme | null,
): string|null {
const appTheme = themes.findCurrentTheme()
const appColorScheme = themes._getActiveColorScheme() as TerminalColorScheme
@@ -10,7 +10,7 @@ export interface LoginScript {
}
export interface LoginScriptsOptions {
scripts?: LoginScript[]
scripts: LoginScript[]
}
export class LoginScriptProcessor extends SessionMiddleware {
@@ -32,7 +32,7 @@ export class LoginScriptProcessor extends SessionMiddleware {
options: LoginScriptsOptions,
) {
super()
this.remainingScripts = deepClone(options.scripts ?? [])
this.remainingScripts = deepClone(options.scripts)
for (const script of this.remainingScripts) {
if (!script.isRegex) {
script.expect = this.unescape(script.expect)
@@ -12,10 +12,10 @@ export type OutputMode = null | 'hex'
export type NewlineMode = null | 'cr' | 'lf' | 'crlf' | 'implicit_cr' | 'implicit_lf'
export interface StreamProcessingOptions {
inputMode?: InputMode
inputNewlines?: NewlineMode
outputMode?: OutputMode
outputNewlines?: NewlineMode
inputMode: InputMode
inputNewlines: NewlineMode
outputMode: OutputMode
outputNewlines: NewlineMode
}
export class TerminalStreamProcessor extends SessionMiddleware {
+1
View File
@@ -6,6 +6,7 @@ import { DemoTerminalTabComponent } from './components/terminalTab.component'
export class DemoProfilesService extends ProfileProvider<Profile> {
id = 'demo'
name = 'Demo'
configDefaults = { options: {} }
async getBuiltinProfiles (): Promise<PartialProfile<Profile>[]> {
return [