From b731763746cf40eb7bdd44f1f32793147ee474d3 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Apr 2023 18:58:41 -0700 Subject: [PATCH] create a new remote, show connect timeout, move alias to edit remote, fix archiving flow --- src/model.ts | 9 + src/remotes.tsx | 490 +++++++++++++++++++++++++++++++++++++----------- src/sh2.less | 30 ++- src/types.ts | 1 + 4 files changed, 415 insertions(+), 115 deletions(-) diff --git a/src/model.ts b/src/model.ts index df602379..938e87d7 100644 --- a/src/model.ts +++ b/src/model.ts @@ -2311,6 +2311,12 @@ class RemotesModalModel { })(); } + deSelectRemote() : void { + mobx.action(() => { + this.selectedRemoteId.set(null); + })(); + } + openModalForEdit(redit : RemoteEditType) : void { mobx.action(() => { this.openState.set(true); @@ -2341,6 +2347,9 @@ class RemotesModalModel { cancelEditAuth() : void { mobx.action(() => { this.remoteEdit.set(null); + if (this.selectedRemoteId.get() == null) { + this.openModal(); + } })(); } diff --git a/src/remotes.tsx b/src/remotes.tsx index 0ce9bd03..b9e4c751 100644 --- a/src/remotes.tsx +++ b/src/remotes.tsx @@ -35,24 +35,165 @@ function getRemoteTitle(remote : RemoteType) { } @mobxReact.observer -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"}); +class AuthModeDropdown extends React.Component<{tempVal : OV}, {}> { + active : OV = mobx.observable.box(false, {name: "AuthModeDropdown-active"}); + + @boundMethod + toggleActive() : void { + mobx.action(() => { + this.active.set(!this.active.get()); + })(); + } + + @boundMethod + updateValue(val : string) : void { + mobx.action(() => { + this.props.tempVal.set(val); + this.active.set(false); + })(); + } + + render() { + return ( +
+
+ +
+
+
+
this.updateValue("none") } className="dropdown-item">none
+
this.updateValue("key") } className="dropdown-item">key
+
this.updateValue("password") } className="dropdown-item">password
+
this.updateValue("key+password") } className="dropdown-item">key+password
+
+
+
+ ); + } +} + +@mobxReact.observer +class ConnectModeDropdown extends React.Component<{tempVal : OV}, {}> { + active : OV = mobx.observable.box(false, {name: "ConnectModeDropdown-active"}); + + @boundMethod + toggleActive() : void { + mobx.action(() => { + this.active.set(!this.active.get()); + })(); + } + + @boundMethod + updateValue(val : string) : void { + mobx.action(() => { + this.props.tempVal.set(val); + this.active.set(false); + })(); + } + + render() { + return ( +
+
+ +
+
+
+
this.updateValue("startup") } className="dropdown-item">startup
+
this.updateValue("auto") } className="dropdown-item">auto
+
this.updateValue("manual") } className="dropdown-item">manual
+
+
+
+ ); + } +} + +@mobxReact.observer +class CreateRemote extends React.Component<{model : RemotesModalModel, remoteEdit : RemoteEditType}, {}> { + tempAlias : OV + tempHostName : OV; + tempPort : OV; tempAuthMode : OV; tempConnectMode : OV; tempManualMode : OV; tempPassword : OV; tempKeyFile : OV; + errorStr : 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)); + let {remoteEdit} = this.props; + this.tempAlias = mobx.observable.box("", {name: "CreateRemote-alias"}); + this.tempHostName = mobx.observable.box("", {name: "CreateRemote-hostName"}); + this.tempPort = mobx.observable.box("", {name: "CreateRemote-port"}); + this.tempAuthMode = mobx.observable.box("none", {name: "CreateRemote-authMode"}); + this.tempConnectMode = mobx.observable.box("auto", {name: "CreateRemote-connectMode"}); + this.tempKeyFile = mobx.observable.box("", {name: "CreateRemote-keystr"}); + this.tempPassword = mobx.observable.box("", {name: "CreateRemote-password"}); + this.errorStr = mobx.observable.box(remoteEdit.errorstr, {name: "CreateRemote-errorStr"}); + } + + getErrorStr() : string { + if (this.errorStr.get() != null) { + return this.errorStr.get(); + } + return this.props.remoteEdit.errorstr; + } + + @boundMethod + submitRemote() : void { + mobx.action(() => { + this.errorStr.set(null); + })(); + let authMode = this.tempAuthMode.get(); + let cname = this.tempHostName.get(); + if (cname == "") { + this.errorStr.set("You must specify a 'user@host' value to create a new connection"); + return; + } + let kwargs : Record = {}; + kwargs["alias"] = this.tempAlias.get(); + if (this.tempPort.get() != "" && this.tempPort.get() != "22") { + kwargs["port"] = this.tempPort.get(); + } + if (authMode == "key" || authMode == "key+password") { + if (this.tempKeyFile.get() == "") { + this.errorStr.set("When AuthMode is set to 'key', you must supply a valid key file name."); + return; + } + kwargs["key"] = this.tempKeyFile.get(); + } + else { + kwargs["key"] = ""; + } + if (authMode == "password" || authMode == "key+password") { + if (this.tempPassword.get() == "") { + this.errorStr.set("When AuthMode is set to 'password', you must supply a password."); + return; + } + kwargs["password"] = this.tempPassword.get(); + } + else { + kwargs["password"] = "" + } + kwargs["connectmode"] = this.tempConnectMode.get(); + kwargs["autoinstall"] = "1"; + kwargs["visual"] = "1"; + kwargs["submit"] = "1"; + GlobalCommandRunner.createRemote(cname, kwargs); } @boundMethod @@ -69,6 +210,178 @@ class RemoteAuthSettings extends React.Component<{model : RemotesModalModel, rem })(); } + @boundMethod + handleChangeAlias(e : any) : void { + mobx.action(() => { + this.tempAlias.set(e.target.value); + })(); + } + + @boundMethod + handleChangePort(e : any) : void { + mobx.action(() => { + this.tempPort.set(e.target.value); + })(); + } + + @boundMethod + handleChangeHostName(e : any) : void { + mobx.action(() => { + this.tempHostName.set(e.target.value); + })(); + } + + render() { + let {model, remote, remoteEdit} = this.props; + let authMode = this.tempAuthMode.get(); + return ( +
+
Create New Remote
+
+
+
user@host
+
+ + (Required) The user and host that you want to connect with. This is in the same format as you would pass to ssh, e.g. "ubuntu@test.mydomain.com". + +
+
+ +
+
+
+
+
Alias
+
+ + (Optional) A short alias to use when selecting or displaying this connection. + +
+
+ +
+
+
+
+
Port
+
+ + (Optional) Defaults to 22. Set if the server you are connecting to listens to a non-standard SSH port. + +
+
+ +
+
+
+
+
Auth Mode
+
+ +
    +
  • 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
+
+ +
    +
  • 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: {this.getErrorStr()} +
+
+
+
+
+
Cancel
+
Create Remote
+
+
+ ); + } +} + +@mobxReact.observer +class EditRemoteSettings extends React.Component<{model : RemotesModalModel, remote : RemoteType, remoteEdit : RemoteEditType}, {}> { + tempAlias : OV + tempAuthMode : OV; + tempConnectMode : OV; + tempManualMode : OV; + tempPassword : OV; + tempKeyFile : OV; + + constructor(props : any) { + super(props); + let {remote, remoteEdit} = this.props; + this.tempAlias = mobx.observable.box(remote.remotealias ?? "", {name: "EditRemoteSettings-alias"}); + this.tempAuthMode = mobx.observable.box(remote.authtype, {name: "EditRemoteSettings-authMode"}); + this.tempConnectMode = mobx.observable.box(remote.connectmode, {name: "EditRemoteSettings-connectMode"}); + this.tempKeyFile = mobx.observable.box(remoteEdit.keystr ?? "", {name: "EditRemoteSettings-keystr"}); + this.tempPassword = mobx.observable.box(remoteEdit.haspassword ? PasswordUnchangedSentinel : "", {name: "EditRemoteSettings-password"}); + } + + @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 + handleChangeAlias(e : any) : void { + mobx.action(() => { + this.tempAlias.set(e.target.value); + })(); + } + @boundMethod canResetPw() : boolean { let {remoteEdit} = this.props; @@ -92,106 +405,30 @@ class RemoteAuthSettings extends React.Component<{model : RemotesModalModel, rem } } - @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") { + if (authMode == "key" || authMode == "key+password") { kwargs["key"] = this.tempKeyFile.get(); } else { kwargs["key"] = ""; } - if (authMode == "pw" || authMode == "key+pw") { + if (authMode == "password" || authMode == "key+password") { kwargs["password"] = this.tempPassword.get(); } else { kwargs["password"] = "" } + kwargs["alias"] = this.tempAlias.get(); 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") { @@ -216,23 +453,36 @@ class RemoteAuthSettings extends React.Component<{model : RemotesModalModel, rem
{getRemoteTitle(remote)}
- Editing Authentication Settings + Editing Remote Settings +
+
+
+
Alias
+
+ + (Optional) A short alias to use when selecting or displaying this connection. + +
+
+ +
- Auth Mode -
-
-
{this.renderAuthModeDropdown()}
+
Auth Mode
+
    -
  • 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.
  • +
  • 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.
+
+
+
@@ -259,19 +509,19 @@ class RemoteAuthSettings extends React.Component<{model : RemotesModalModel, rem
- Connect Mode -
-
-
{this.renderConnectModeDropdown()}
+
Connect Mode
+
  • 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.
-
+
+
+
@@ -302,6 +552,13 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot this.props.model.createTermWrap(elem); } + componentDidUpdate() { + let {remote} = this.props; + if (remote == null || remote.archived) { + this.props.model.deSelectRemote(); + } + } + componentWillUnmount() { this.props.model.disposeTerm(); } @@ -349,6 +606,11 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot @boundMethod clickArchive(remoteId : string) : void { + let {remote} = this.props; + if (remote.status == "connected") { + GlobalModel.showAlert({message: "Cannot archived a connected remote. Disconnect and try again."}); + return; + } let prtn = GlobalModel.showAlert({message: "Are you sure you want to archive this connection?", confirm: true}); prtn.then((confirm) => { if (!confirm) { @@ -360,6 +622,7 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot @boundMethod editAlias(remoteId : string, alias : string) : void { + this.props.model.startEditAuth(); } renderInstallStatus(remote : RemoteType) : any { @@ -425,6 +688,8 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot } else if (remote.status == "connecting") { message = (remote.waitingforpassword ? "Connecting, waiting for user-input..." : "Connecting..."); + let connectTimeout = (remote.connecttimeout ?? 0); + message = message + " (" + connectTimeout + "s)"; buttons = [disconnectButton]; } else if (remote.status == "disconnected") { @@ -494,7 +759,13 @@ class RemoteDetailView extends React.Component<{model : RemotesModalModel, remot
Alias
- this.editAlias(remote.remoteid, val)} text={remoteAliasText ?? ""} value={remote.remotealias} placeholder="" maxLength={50}/> +
+ {remoteAliasText} +
this.editAlias()} className="button is-plain is-outlined is-small is-inline-height ml-2 update-auth-button"> + + Update Alias +
+
Auth Type
@@ -561,6 +832,7 @@ class RemotesModal extends React.Component<{model : RemotesModalModel}, {}> { @boundMethod clickAddRemote() : void { + GlobalCommandRunner.openCreateRemote(); } renderRemoteMenuItem(remote : RemoteType, selectedId : string) : any { @@ -609,6 +881,7 @@ class RemotesModal extends React.Component<{model : RemotesModalModel}, {}> { let remote : RemoteType = null; let isAuthEditMode = model.isAuthEditMode(); let selectedRemote = GlobalModel.getRemote(selectedRemoteId); + let remoteEdit = model.remoteEdit.get(); return (
@@ -627,14 +900,19 @@ class RemotesModal extends React.Component<{model : RemotesModalModel}, {}> {
- {this.renderEmptyDetail()} + + + + + {this.renderEmptyDetail()} + - +
diff --git a/src/sh2.less b/src/sh2.less index 881e306f..1beac9a3 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -2938,7 +2938,7 @@ input[type=checkbox] { } } - &.auth-editing { + &.auth-editing, &.create-remote { .settings-field.align-top { align-items: flex-start; @@ -2951,18 +2951,19 @@ input[type=checkbox] { } } + .settings-label { + display: flex; + flex-direction: row; + align-items: center; + } + .settings-field .settings-input .undo-icon { cursor: pointer; font-size: 18px; margin-left: 5px; } - .authmode-dropdown .dropdown-trigger button { - width: 120px; - justify-content: flex-start; - } - - .connectmode-dropdown .dropdown-trigger button { + .editremote-dropdown .dropdown-trigger button { width: 120px; justify-content: flex-start; } @@ -2984,8 +2985,16 @@ input[type=checkbox] { padding: 5px 5px 5px 12px; } - .info-message { - margin-left: 22px; + .settings-input { + .info-message { + margin-left: 22px; + } + } + + .settings-label { + .info-message { + margin-right: 15px; + } } .remoteedit-error { @@ -3005,6 +3014,9 @@ input[type=checkbox] { .info-message { position: relative; + font-weight: normal; + font-size: 12px; + color: @term-white; .message-content { position: absolute; diff --git a/src/types.ts b/src/types.ts index 34bcbc3c..3383e34f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -85,6 +85,7 @@ type RemoteType = { remotecanonicalname : string, remotevars : Record, status : RemoteStatusTypeStrs, + connecttimeout : number, errorstr : string, installstatus : string, installerrorstr : string,