diff --git a/packages/teleterm/src/ui/appContext.ts b/packages/teleterm/src/ui/appContext.ts index 4e95accdf5..1560658ff0 100644 --- a/packages/teleterm/src/ui/appContext.ts +++ b/packages/teleterm/src/ui/appContext.ts @@ -55,7 +55,7 @@ export default class AppContext { this.modalsService, this.clustersService, this.notificationsService, - this.statePersistenceService, + this.statePersistenceService ); this.terminalsService = new TerminalsService(ptyServiceClient); @@ -81,9 +81,6 @@ export default class AppContext { async init(): Promise { await this.clustersService.syncRootClusters(); - const { rootClusterUri } = this.statePersistenceService.getWorkspaces(); - if (rootClusterUri) { - this.workspacesService.setActiveWorkspace(rootClusterUri); - } + this.workspacesService.restorePersistedState(); } } diff --git a/packages/teleterm/src/ui/services/clusters/clustersService.ts b/packages/teleterm/src/ui/services/clusters/clustersService.ts index 8b0c5c0f0a..50b3b5135b 100644 --- a/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -278,12 +278,28 @@ export class ClustersService extends ImmutableStore { } } + /** + * Removes cluster and its leaf clusters (if any) + */ async removeCluster(clusterUri: string) { await this.client.removeCluster(clusterUri); + const leafClustersUris = this.getClusters() + .filter( + item => + item.leaf && routing.ensureRootClusterUri(item.uri) === clusterUri + ) + .map(cluster => cluster.uri); this.setState(draft => { draft.clusters.delete(clusterUri); + leafClustersUris.forEach(leafClusterUri => { + draft.clusters.delete(leafClusterUri); + }); }); + this.removeResources(clusterUri); + leafClustersUris.forEach(leafClusterUri => { + this.removeResources(leafClusterUri); + }); } async getAuthSettings(clusterUri: string) { diff --git a/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts b/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts index 93e00c3782..c669f2bc81 100644 --- a/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts +++ b/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts @@ -102,6 +102,12 @@ export class ConnectionTrackerService extends ImmutableStore { this.setState(draft => { + // filter out connections from removed clusters + draft.connections = draft.connections.filter(i => { + const uri = i.kind === 'connection.gateway' ? i.targetUri : i.serverUri; + return !!this._clusterService.findClusterByResource(uri); + }); + // assign default "connected" values draft.connections.forEach(i => { if (i.kind === 'connection.gateway') { @@ -134,6 +140,9 @@ export class ConnectionTrackerService extends ImmutableStore('state'); - if (restored) { - this.state = restored; - } + saveConnectionTrackerState(connectionTracker: ConnectionTrackerState): void { + const newState: StatePersistenceState = { + ...this.getState(), + connectionTracker, + }; + this.putState(newState); } - saveConnectionTrackerState(navigatorState: ConnectionTrackerState): void { - this.state.connectionTracker = navigatorState; - this._fileStorage.put('state', this.state); + getConnectionTrackerState(): ConnectionTrackerState { + return this.getState().connectionTracker; } - getConnectionTrackerState(): ConnectionTrackerState { - return this.state.connectionTracker; + saveWorkspacesState(workspacesState: WorkspacesState): void { + const newState: StatePersistenceState = { + ...this.getState(), + workspacesState, + }; + this.putState(newState); + } + + getWorkspacesState(): WorkspacesState { + return this.getState().workspacesState; } - saveWorkspaces(workspacesState: WorkspacesState): void { - this.state.workspacesState.rootClusterUri = workspacesState.rootClusterUri; - for (let w in workspacesState.workspaces) { - if (workspacesState.workspaces[w]) { - this.state.workspacesState.workspaces[w] = { - location: workspacesState.workspaces[w].location, - localClusterUri: workspacesState.workspaces[w].localClusterUri, - documents: workspacesState.workspaces[w].documents, - }; - } - } - this._fileStorage.put('state', this.state); + private getState(): StatePersistenceState { + const defaultState: StatePersistenceState = { + connectionTracker: { + connections: [], + }, + workspacesState: { + workspaces: {}, + }, + }; + return this._fileStorage.get('state') || defaultState; } - getWorkspaces(): WorkspacesState { - return this.state.workspacesState; + private putState(state: StatePersistenceState): void { + this._fileStorage.put('state', state); } } diff --git a/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts b/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts index a956c2e771..fc712e575f 100644 --- a/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts +++ b/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts @@ -89,14 +89,16 @@ export class DocumentsService { } createGatewayDocument(opts: CreateGatewayDocumentOpts): DocumentGateway { - const { targetUri, title, targetUser } = opts; + const { targetUri, title, targetUser, port, gatewayUri } = opts; const uri = routing.getDocUri({ docId: unique() }); return { uri, kind: 'doc.gateway', targetUri, targetUser, + gatewayUri, title, + port }; } diff --git a/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts index 423cbabe34..166cd8824c 100644 --- a/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts +++ b/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts @@ -6,6 +6,7 @@ import { ClustersService } from 'teleterm/ui/services/clusters'; import { StatePersistenceService } from 'teleterm/ui/services/statePersistence'; import { isEqual } from 'lodash'; import { NotificationsService } from 'teleterm/ui/services/notifications'; +import { routing } from 'teleterm/ui/uri'; export interface WorkspacesState { rootClusterUri?: string; @@ -107,30 +108,16 @@ export class WorkspacesService extends ImmutableStore { setState(nextState: (draftState: WorkspacesState) => WorkspacesState | void) { super.setState(nextState); - this.statePersistenceService.saveWorkspaces(this.state); + this.persistState(); } setActiveWorkspace(clusterUri: string): Promise { const setWorkspace = () => { this.setState(draftState => { + // adding a new workspace if (!draftState.workspaces[clusterUri]) { - const persistedWorkspace = - this.statePersistenceService.getWorkspaces().workspaces[clusterUri]; - const defaultDocument = this.getWorkspaceDocumentService( - clusterUri - ).createClusterDocument({ clusterUri }); - - draftState.workspaces[clusterUri] = { - localClusterUri: persistedWorkspace?.localClusterUri || clusterUri, - location: defaultDocument.uri, - documents: [defaultDocument], - previous: persistedWorkspace?.documents - ? { - documents: persistedWorkspace.documents, - location: persistedWorkspace.location, - } - : undefined, - }; + draftState.workspaces[clusterUri] = + this.getWorkspaceDefaultState(clusterUri); } draftState.rootClusterUri = clusterUri; }); @@ -174,8 +161,7 @@ export class WorkspacesService extends ImmutableStore { }) .then(() => { return new Promise(resolve => { - if (!this.canReopenPreviousDocuments(this.getWorkspace(clusterUri))) { - this.discardPreviousDocuments(clusterUri); + if (!this.getWorkspace(clusterUri)?.previous) { return resolve(); } this.modalsService.openDocumentsReopenDialog({ @@ -205,6 +191,41 @@ export class WorkspacesService extends ImmutableStore { ); } + restorePersistedState(): void { + const persistedState = this.statePersistenceService.getWorkspacesState(); + const restoredWorkspaces = this.clustersService + .getClusters() + .reduce((workspaces, cluster) => { + const persistedWorkspace = persistedState.workspaces[cluster.uri]; + const workspaceDefaultState = this.getWorkspaceDefaultState( + persistedWorkspace?.localClusterUri || cluster.uri + ); + const persistedWorkspaceDocuments = persistedWorkspace.documents; + + workspaces[cluster.uri] = { + ...workspaceDefaultState, + previous: this.canReopenPreviousDocuments({ + previousDocuments: persistedWorkspaceDocuments, + currentDocuments: workspaceDefaultState.documents, + }) + ? { + location: persistedWorkspace.location, + documents: persistedWorkspaceDocuments, + } + : undefined, + }; + return workspaces; + }, {}); + + this.setState(draftState => { + draftState.workspaces = restoredWorkspaces; + }); + + if (persistedState.rootClusterUri) { + this.setActiveWorkspace(persistedState.rootClusterUri); + } + } + private reopenPreviousDocuments(clusterUri: string): void { this.setState(draftState => { const workspace = draftState.workspaces[clusterUri]; @@ -221,17 +242,47 @@ export class WorkspacesService extends ImmutableStore { }); } - private canReopenPreviousDocuments(workspace: Workspace): boolean { + private canReopenPreviousDocuments({ + previousDocuments, + currentDocuments, + }: { + previousDocuments: Document[]; + currentDocuments: Document[]; + }): boolean { const removeUri = (documents: Document[]) => documents.map(d => ({ ...d, uri: undefined })); return ( - workspace.previous && - workspace.previous.documents?.length && - !isEqual( - removeUri(workspace.previous.documents), - removeUri(workspace.documents) - ) + previousDocuments?.length && + !isEqual(removeUri(previousDocuments), removeUri(currentDocuments)) ); } + + private getWorkspaceDefaultState(localClusterUri: string): Workspace { + const rootClusterUri = routing.ensureRootClusterUri(localClusterUri); + const defaultDocument = this.getWorkspaceDocumentService( + rootClusterUri + ).createClusterDocument({ clusterUri: localClusterUri }); + return { + localClusterUri, + location: defaultDocument.uri, + documents: [defaultDocument], + }; + } + + private persistState(): void { + const stateToSave: WorkspacesState = { + rootClusterUri: this.state.rootClusterUri, + workspaces: {}, + }; + for (let w in this.state.workspaces) { + const workspace = this.state.workspaces[w]; + stateToSave.workspaces[w] = { + localClusterUri: workspace.localClusterUri, + location: workspace.previous?.location || workspace.location, + documents: workspace.previous?.documents || workspace.documents, + }; + } + this.statePersistenceService.saveWorkspacesState(stateToSave); + } }