diff --git a/src/model.ts b/src/model.ts index 0a07177d..df602379 100644 --- a/src/model.ts +++ b/src/model.ts @@ -5,7 +5,7 @@ import {debounce} from "throttle-debounce"; import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, boundInt, isModKeyPress} from "./util"; import {TermWrap} from "./term"; import {v4 as uuidv4} from "uuid"; -import type {SessionDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel, PtyDataType, BookmarkType, ClientDataType, HistoryViewDataType, AlertMessageType, HistorySearchParams, FocusTypeStrs, ScreenLinesType, HistoryTypeStrs, RendererPluginType, WindowSize, ClientMigrationInfo, WebShareOpts, TermContextUnion} from "./types"; +import type {SessionDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel, PtyDataType, BookmarkType, ClientDataType, HistoryViewDataType, AlertMessageType, HistorySearchParams, FocusTypeStrs, ScreenLinesType, HistoryTypeStrs, RendererPluginType, WindowSize, ClientMigrationInfo, WebShareOpts, TermContextUnion, RemoteEditType, RemoteViewType} from "./types"; import {WSControl} from "./ws"; import {measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows} from "./textmeasure"; import dayjs from "dayjs"; @@ -2283,12 +2283,13 @@ class BookmarksModel { } class RemotesModalModel { + openState : OV = mobx.observable.box(false, {name: "RemotesModalModel-isOpen"}); selectedRemoteId : OV = mobx.observable.box(null, {name: "RemotesModalModel-selectedRemoteId"}); remoteTermWrap : TermWrap; remoteTermWrapFocus : OV = mobx.observable.box(false, {name: "RemotesModalModel-remoteTermWrapFocus"}); showNoInputMsg : OV = mobx.observable.box(false, {name: "RemotesModel-showNoInputMg"}); showNoInputTimeoutId : any = null; - authEditMode : OV = mobx.observable.box(false, {name: "RemotesModal-authEditMode"}); + remoteEdit : OV = mobx.observable.box(null, {name: "RemoteModal-remoteEdit"}); openModal(remoteId? : string) : void { if (remoteId == null) { @@ -2304,7 +2305,17 @@ class RemotesModalModel { } } mobx.action(() => { + this.openState.set(true); this.selectedRemoteId.set(remoteId); + this.remoteEdit.set(null); + })(); + } + + openModalForEdit(redit : RemoteEditType) : void { + mobx.action(() => { + this.openState.set(true); + this.selectedRemoteId.set(redit.remoteid); + this.remoteEdit.set(redit); })(); } @@ -2314,31 +2325,38 @@ class RemotesModalModel { } mobx.action(() => { this.selectedRemoteId.set(remoteId); - this.authEditMode.set(false); + this.remoteEdit.set(null); })(); } @boundMethod startEditAuth() : void { - mobx.action(() => { - this.authEditMode.set(true); - })(); + let remoteId = this.selectedRemoteId.get(); + if (remoteId != null) { + GlobalCommandRunner.openEditRemote(remoteId); + } } @boundMethod cancelEditAuth() : void { mobx.action(() => { - this.authEditMode.set(false); + this.remoteEdit.set(null); })(); } isOpen() : boolean { - return this.selectedRemoteId.get() != null; + return this.openState.get(); + } + + isAuthEditMode() : boolean { + return this.remoteEdit.get() != null; } closeModal() : void { mobx.action(() => { + this.openState.set(false); this.selectedRemoteId.set(null); + this.remoteEdit.set(null); })(); } @@ -2618,6 +2636,9 @@ class Model { } if (e.code == "Escape") { e.preventDefault(); + if (this.clearModals()) { + return; + } let inputModel = this.inputModel; inputModel.toggleInfoMsg(); if (inputModel.inputMode.get() != null) { @@ -2631,6 +2652,41 @@ class Model { } } + clearModals() : boolean { + let didSomething = false; + mobx.action(() => { + if (GlobalModel.screenSettingsModal.get()) { + GlobalModel.screenSettingsModal.set(null); + didSomething = true; + } + if (GlobalModel.sessionSettingsModal.get()) { + GlobalModel.sessionSettingsModal.set(null); + didSomething = true; + } + if (GlobalModel.screenSettingsModal.get()) { + GlobalModel.screenSettingsModal.set(null); + didSomething = true; + } + if (GlobalModel.remotesModalModel.isOpen()) { + GlobalModel.remotesModalModel.closeModal(); + didSomething = true; + } + if (GlobalModel.clientSettingsModal.get()) { + GlobalModel.clientSettingsModal.set(false); + didSomething = true; + } + if (GlobalModel.lineSettingsModal.get()) { + GlobalModel.lineSettingsModal.set(null); + didSomething = true; + } + if (GlobalModel.welcomeModalOpen.get()) { + GlobalModel.welcomeModalOpen.set(false); + didSomething = true; + } + })(); + return didSomething; + } + restartLocalServer() : void { getApi().restartLocalServer(); } @@ -2913,6 +2969,18 @@ class Model { let info : InfoType = update.info; this.inputModel.flashInfoMsg(info, info.timeoutms); } + if (interactive && "remoteview" in update) { + let rview : RemoteViewType = update.remoteview; + if (rview.remoteshowall) { + this.remotesModalModel.openModal(); + } + else if (rview.remoteedit != null) { + this.remotesModalModel.openModalForEdit(rview.remoteedit); + } + else if (rview.ptyremoteid) { + this.remotesModalModel.openModal(rview.ptyremoteid); + } + } if ("cmdline" in update) { this.inputModel.updateCmdLine(update.cmdline); } diff --git a/src/remotes.tsx b/src/remotes.tsx index bab85720..0ce9bd03 100644 --- a/src/remotes.tsx +++ b/src/remotes.tsx @@ -6,8 +6,8 @@ import {boundMethod} from "autobind-decorator"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames"; import {GlobalModel, GlobalCommandRunner, getTermPtyData, RemotesModalModel} from "./model"; -import {Toggle, RemoteStatusLight, InlineSettingsTextEdit} from "./elements"; -import {RemoteType, RemoteInputPacketType} from "./types"; +import {Toggle, RemoteStatusLight, InlineSettingsTextEdit, InfoMessage} from "./elements"; +import {RemoteType, RemoteInputPacketType, RemoteEditType} from "./types"; import * as util from "./util"; import * as textmeasure from "./textmeasure"; import {TermWrap} from "./term"; @@ -18,6 +18,7 @@ type OMap = mobx.ObservableMap; const RemotePtyRows = 8; const RemotePtyCols = 80; +const PasswordUnchangedSentinel = "--unchanged--"; function getRemoteCNWithPort(remote : RemoteType) { if (util.isBlank(remote.remotevars.port) || remote.remotevars.port == "22") { @@ -34,18 +35,254 @@ function getRemoteTitle(remote : RemoteType) { } @mobxReact.observer -class RemoteAuthSettings extends React.Component<{model : RemotesModalModel, remote : RemoteType}, {}> { - render() { - let {model, remote} = this.props; +class RemoteAuthSettings extends React.Component<{model : RemotesModalModel, remote : RemoteType, remoteEdit : RemoteEditType}, {}> { + authModeDropdownActive : OV = mobx.observable.box(false, {name: "RemoteAuthSettings-authModeDropdownActive"}); + connectModeDropdownActive : OV = mobx.observable.box(false, {name: "RemoteAuthSettings-connectModeDropdownActive"}); + tempAuthMode : OV; + tempConnectMode : OV; + tempManualMode : OV; + tempPassword : OV; + tempKeyFile : OV; + + constructor(props : any) { + super(props); + let {remote, remoteEdit} = this.props; + this.tempAuthMode = mobx.observable.box(remote.authtype, {name: "RemoteAuthSettings-authMode"}); + this.tempConnectMode = mobx.observable.box(remote.connectmode, {name: "RemoteAuthSettings-connectMode"}); + this.tempKeyFile = mobx.observable.box(remoteEdit.keystr ?? "", {name: "RemoteAuthSettings-keystr"}); + this.tempPassword = mobx.observable.box(remoteEdit.haspassword ? PasswordUnchangedSentinel : "", {name: "RemoteAuthSettings-password"}); + console.log("tempkeyfile", this.tempKeyFile.get()); + console.log("keystr", mobx.toJS(remoteEdit)); + } + + @boundMethod + handleChangeKeyFile(e : any) : void { + mobx.action(() => { + this.tempKeyFile.set(e.target.value); + })(); + } + + @boundMethod + handleChangePassword(e : any) : void { + mobx.action(() => { + this.tempPassword.set(e.target.value); + })(); + } + + @boundMethod + canResetPw() : boolean { + let {remoteEdit} = this.props; + if (remoteEdit == null) { + return false; + } + return remoteEdit.haspassword && this.tempPassword.get() != PasswordUnchangedSentinel; + } + + @boundMethod + resetPw() : void { + mobx.action(() => { + this.tempPassword.set(PasswordUnchangedSentinel); + })(); + } + + @boundMethod + onFocusPassword(e : any) { + if (this.tempPassword.get() == PasswordUnchangedSentinel) { + e.target.select(); + } + } + + @boundMethod + clickSetAuthMode(authMode : string) : void { + mobx.action(() => { + this.tempAuthMode.set(authMode); + this.authModeDropdownActive.set(false); + })(); + } + + @boundMethod + toggleAuthModeDropdown() : void { + mobx.action(() => { + this.authModeDropdownActive.set(!this.authModeDropdownActive.get()); + })(); + } + + @boundMethod + submitRemote() : void { + let {remote} = this.props; + let authMode = this.tempAuthMode.get(); + let kwargs : Record = {}; + if (authMode == "key" || authMode == "key+pw") { + kwargs["key"] = this.tempKeyFile.get(); + } + else { + kwargs["key"] = ""; + } + if (authMode == "pw" || authMode == "key+pw") { + kwargs["password"] = this.tempPassword.get(); + } + else { + kwargs["password"] = "" + } + kwargs["connectmode"] = this.tempConnectMode.get(); + kwargs["visual"] = "1"; + kwargs["submit"] = "1"; + GlobalCommandRunner.editRemote(remote.remoteid, kwargs); + } + + renderAuthModeDropdown() : any { return ( -
+
+
+ +
+
+
+
this.clickSetAuthMode("none") } className="dropdown-item">none
+
this.clickSetAuthMode("key") } className="dropdown-item">key
+
this.clickSetAuthMode("password") } className="dropdown-item">password
+
this.clickSetAuthMode("key+password") } className="dropdown-item">key+password
+
+
+
+ ); + } + + @boundMethod + clickSetConnectMode(connectMode : string) : void { + mobx.action(() => { + this.tempConnectMode.set(connectMode); + this.connectModeDropdownActive.set(false); + })(); + } + + @boundMethod + toggleConnectModeDropdown() : void { + mobx.action(() => { + this.connectModeDropdownActive.set(!this.connectModeDropdownActive.get()); + })(); + } + + renderConnectModeDropdown() : any { + return ( +
+
+ +
+
+
+
this.clickSetConnectMode("startup") } className="dropdown-item">startup
+
this.clickSetConnectMode("auto") } className="dropdown-item">auto
+
this.clickSetConnectMode("manual") } className="dropdown-item">manual
+
+
+
+ ); + } + + renderAuthModeMessage() : any { + let authMode = this.tempAuthMode.get(); + if (authMode == "none") { + return (This connection requires no authentication.
Or authentication is already configured in ssh_config.
); + } + if (authMode == "key") { + return (Use a public/private keypair.); + } + if (authMode == "password") { + return (Use a password.); + } + if (authMode == "key+password") { + return (Use a public/private keypair with a passphrase.); + } + return null; + } + + render() { + let {model, remote, remoteEdit} = this.props; + let authMode = this.tempAuthMode.get(); + return ( +
{getRemoteTitle(remote)}
-
+
Editing Authentication Settings
-
+
+
+ Auth Mode +
+
+
{this.renderAuthModeDropdown()}
+ +
    +
  • none - No authentication, or authentication is already configured in your ssh config.
  • +
  • key - Use a private key.
  • +
  • password - Use a password.
  • +
  • key+password - Use a key with a passphrase.
  • +
+
+
+
+ +
+
+ SSH Keyfile +
+
+ +
+
+
+ +
+
+ {authMode == "password" ? "SSH Password" : "Key Passphrase"} +
+
+ + +
+
+
+
+
+
+
+ Connect Mode +
+
+
{this.renderConnectModeDropdown()}
+ +
    +
  • startup - connect when [prompt] starts.
  • +
  • auto - connect when you first run a command using this connection.
  • +
  • manual - connect manually. Note, if your connection requires manual input, like an OPT code, you must use this setting.
  • +
+ +
+
+
+ +
+ Error: {remoteEdit.errorstr ?? "An error occured"} +
+
+
+
+
Cancel
-
Submit
+
Submit
); @@ -117,7 +354,7 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot if (!confirm) { return; } - console.log("archive remote", remoteId); + GlobalCommandRunner.archiveRemote(remoteId); }); } @@ -166,18 +403,22 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot
this.connectRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Connect Now
); let tryReconnectButton = ( -
this.connectRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Try Reconnect
+
this.connectRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Try Reconnect
); let updateAuthButton = ( -
this.editAuthSettings()} className="button is-plain is-outlined is-small">Update Auth Settings
+
this.editAuthSettings()} className="button is-plain is-outlined is-small">Update Auth Settings
); let cancelInstallButton = ( -
this.cancelInstall(remote.remoteid)} className="button is-prompt-danger is-outlined is-small">Cancel Install
+
this.cancelInstall(remote.remoteid)} className="button is-prompt-danger is-outlined is-small">Cancel Install
); let installNowButton = ( -
this.installRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Install Now
+
this.installRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Install Now
); - + if (remote.local) { + installNowButton = null; + updateAuthButton = null; + cancelInstallButton = null; + } if (remote.status == "connected") { message = "Connected and ready to run commands."; buttons = [disconnectButton]; @@ -258,11 +499,16 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot
Auth Type
- {remote.authtype} -
this.editAuthSettings()} className="button is-plain is-outlined is-small is-inline-height ml-2 update-auth-button"> - - Update Auth Settings -
+ + {remote.authtype} +
this.editAuthSettings()} className="button is-plain is-outlined is-small is-inline-height ml-2 update-auth-button"> + + Update Auth Settings +
+
+ + local +
@@ -272,14 +518,16 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot
{this.renderInstallStatus(remote)} -
-
Archive
-
-
this.clickArchive(remote.remoteid)} className="button is-prompt-danger is-outlined is-small is-inline-height"> - Archive This Connection + +
+
Archive
+
+
this.clickArchive(remote.remoteid)} className="button is-prompt-danger is-outlined is-small is-inline-height"> + Archive This Connection +
-
+
{remoteMessage} @@ -359,7 +607,7 @@ class RemotesModal extends React.Component<{model : RemotesModalModel}, {}> { let selectedRemoteId = model.selectedRemoteId.get(); let allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice()); let remote : RemoteType = null; - let isAuthEditMode = model.authEditMode.get(); + let isAuthEditMode = model.isAuthEditMode(); let selectedRemote = GlobalModel.getRemote(selectedRemoteId); return (
@@ -386,13 +634,10 @@ class RemotesModal extends React.Component<{model : RemotesModalModel}, {}> { - +
-
); diff --git a/src/types.ts b/src/types.ts index baedf21e..34bcbc3c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -274,6 +274,7 @@ type ModelUpdateType = { selectedbookmark? : string, clientdata? : ClientDataType, historyviewdata? : HistoryViewDataType, + remoteview? : RemoteViewType, }; type HistoryViewDataType = { @@ -330,6 +331,9 @@ type InfoType = { infocomps? : string[], infocompsmore? : boolean, timeoutms? : number, +}; + +type RemoteViewType = { ptyremoteid? : string, remoteedit? : RemoteEditType, remoteshowall? : boolean, @@ -583,4 +587,4 @@ type RendererContainerType = { type LineHeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight : number) => void; -export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs, WebFullScreen, WebScreen, WebLine, WebCmd, RemoteTermContext, TermContextUnion, WebRemote, PtyDataUpdate, WebShareWSMessage, LineHeightChangeCallbackType, LineFactoryProps, LineInterface, RendererContainerType}; +export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs, WebFullScreen, WebScreen, WebLine, WebCmd, RemoteTermContext, TermContextUnion, WebRemote, PtyDataUpdate, WebShareWSMessage, LineHeightChangeCallbackType, LineFactoryProps, LineInterface, RendererContainerType, RemoteViewType};