mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
SSH Configuration Import Alert Modal (#218)
* add an alert modal for the sshconfig import button When the sshconfig import button is pressed, there currently is no visual indicator of what changed. This adds an alert modal to pop up only in the case where the gui button is used. * improve alert modal for sshconfig imports The previous message for SSH configuration imports was vague and did not provide detailed information as what happened during the import. This clarifies that by specifying which remotes were deleted, created, and updated. Updates are only ran and recorded if they would actually change something. * fix port value limiting The SSH config import port limiting was correct but set off a warning in linters. It has been updated to do the same behavior in a different way. Also, port limiting was never added to manually adding a new remote. This change adds it there as well. * change user-facing term to connection Previously, the ssh configuration alert modal used to use the word "remote" to describe connections. "Remote" is the internal name but it isn't consistent with what is being displayed to users. So it has been replaced with "Connection" instead to match. * change remote to connection for ssh import buttons Like the previous change, the word "remote" was used instead of "connection." This was for the tooltips added to connections that had been imported in the connections menu. * update one more remote -> connection
This commit is contained in:
parent
fad48b0d09
commit
c2a894b280
@ -1004,7 +1004,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
<Button theme="secondary" disabled={true}>
|
||||
Edit
|
||||
<Tooltip
|
||||
message={`Remotes imported from an ssh config file cannot be edited inside waveterm. To edit these, you must edit the config file and import it again.`}
|
||||
message={`Connections imported from an ssh config file cannot be edited inside waveterm. To edit these, you must edit the config file and import it again.`}
|
||||
icon={<i className="fa-sharp fa-regular fa-fw fa-ban" />}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-fw fa-ban" />
|
||||
@ -1017,7 +1017,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
<Tooltip
|
||||
message={
|
||||
<span>
|
||||
Remotes imported from an ssh config file can be deleted, but will come back upon
|
||||
Connections imported from an ssh config file can be deleted, but will come back upon
|
||||
importing again. They will stay removed if you follow{" "}
|
||||
<a href="https://docs.waveterm.dev/features/sshconfig-imports">this procedure</a>.
|
||||
</span>
|
||||
|
@ -3811,6 +3811,10 @@ class Model {
|
||||
this.remotesModel.openEditModal({ ...rview.remoteedit });
|
||||
}
|
||||
}
|
||||
if (interactive && "alertmessage" in update) {
|
||||
let alertMessage: AlertMessageType = update.alertmessage;
|
||||
this.showAlert(alertMessage);
|
||||
}
|
||||
if ("cmdline" in update) {
|
||||
this.inputModel.updateCmdLine(update.cmdline);
|
||||
}
|
||||
@ -4482,7 +4486,7 @@ class CommandRunner {
|
||||
}
|
||||
|
||||
importSshConfig() {
|
||||
GlobalModel.submitCommand("remote", "parse", null, null, false);
|
||||
GlobalModel.submitCommand("remote", "parse", null, { nohist: "1", visual: "1" }, true);
|
||||
}
|
||||
|
||||
screenSelectLine(lineArg: string, focusVal?: string) {
|
||||
|
@ -285,6 +285,7 @@ type ModelUpdateType = {
|
||||
clientdata?: ClientDataType;
|
||||
historyviewdata?: HistoryViewDataType;
|
||||
remoteview?: RemoteViewType;
|
||||
alertmessage?: AlertMessageType;
|
||||
};
|
||||
|
||||
type HistoryViewDataType = {
|
||||
|
@ -1255,6 +1255,10 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b
|
||||
if portVal == 0 && uhPort != 0 {
|
||||
portVal = uhPort
|
||||
}
|
||||
if portVal < 0 || portVal > 65535 {
|
||||
// 0 is used as a sentinel value for the default in this case
|
||||
return nil, fmt.Errorf("invalid port argument, \"%d\" is not in the range of 1 to 65535", portVal)
|
||||
}
|
||||
sshOpts.SSHPort = portVal
|
||||
canonicalName = remoteUser + "@" + remoteHost
|
||||
if portVal != 0 && portVal != 22 {
|
||||
@ -1514,6 +1518,47 @@ type HostInfoType struct {
|
||||
Ignore bool
|
||||
}
|
||||
|
||||
func createSshImportSummary(changeList map[string][]string) string {
|
||||
totalNumChanges := len(changeList["create"]) + len(changeList["delete"]) + len(changeList["update"]) + len(changeList["createErr"]) + len(changeList["deleteErr"]) + len(changeList["updateErr"])
|
||||
if totalNumChanges == 0 {
|
||||
return "No changes made from ssh config import"
|
||||
}
|
||||
remoteStatusMsgs := map[string]string{
|
||||
"delete": "Deleted %d connection%s: %s",
|
||||
"create": "Created %d connection%s: %s",
|
||||
"update": "Edited %d connection%s: %s",
|
||||
"deleteErr": "Error deleting %d connection%s: %s",
|
||||
"createErr": "Error creating %d connection%s: %s",
|
||||
"updateErr": "Error editing %d connection%s: %s",
|
||||
}
|
||||
|
||||
changeTypeKeys := []string{"delete", "create", "update", "deleteErr", "createErr", "updateErr"}
|
||||
|
||||
var outMsgs []string
|
||||
for _, changeTypeKey := range changeTypeKeys {
|
||||
changes := changeList[changeTypeKey]
|
||||
if len(changes) > 0 {
|
||||
rawStatusMsg := remoteStatusMsgs[changeTypeKey]
|
||||
var pluralize string
|
||||
if len(changes) == 1 {
|
||||
pluralize = ""
|
||||
} else {
|
||||
pluralize = "s"
|
||||
}
|
||||
newMsg := fmt.Sprintf(rawStatusMsg, len(changes), pluralize, strings.Join(changes, ", "))
|
||||
outMsgs = append(outMsgs, newMsg)
|
||||
}
|
||||
}
|
||||
|
||||
var pluralize string
|
||||
if totalNumChanges == 1 {
|
||||
pluralize = ""
|
||||
} else {
|
||||
pluralize = "s"
|
||||
}
|
||||
return fmt.Sprintf("%d connection%s changed:\n\n%s", totalNumChanges, pluralize, strings.Join(outMsgs, "\n\n"))
|
||||
}
|
||||
|
||||
func NewHostInfo(hostName string) (*HostInfoType, error) {
|
||||
userName, _ := ssh_config.GetStrict(hostName, "User")
|
||||
if userName == "" {
|
||||
@ -1539,7 +1584,7 @@ func NewHostInfo(hostName string) (*HostInfoType, error) {
|
||||
// do not make assumptions about port if incorrectly configured
|
||||
return nil, fmt.Errorf("could not parse \"%s\" (%s) - %s could not be converted to a valid port\n", hostName, canonicalName, portStr)
|
||||
}
|
||||
if int(int16(portVal)) != portVal {
|
||||
if portVal <= 0 || portVal > 65535 {
|
||||
return nil, fmt.Errorf("could not parse port \"%d\": number is not valid for a port\n", portVal)
|
||||
}
|
||||
}
|
||||
@ -1603,6 +1648,8 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
|
||||
hostInfoInConfig[hostInfo.CanonicalName] = hostInfo
|
||||
}
|
||||
|
||||
remoteChangeList := make(map[string][]string)
|
||||
|
||||
// remove all previously imported remotes that
|
||||
// no longer have a canonical pattern in the config files
|
||||
for importedRemoteCanonicalName, importedRemote := range previouslyImportedRemotes {
|
||||
@ -1611,17 +1658,17 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
|
||||
if !importedRemote.Archived && (hostInfo == nil || hostInfo.Ignore) {
|
||||
err = remote.ArchiveRemote(ctx, importedRemote.RemoteId)
|
||||
if err != nil {
|
||||
remoteChangeList["deleteErr"] = append(remoteChangeList["deleteErr"], importedRemote.RemoteCanonicalName)
|
||||
log.Printf("sshconfig-import: failed to remove remote \"%s\" (%s)\n", importedRemote.RemoteAlias, importedRemote.RemoteCanonicalName)
|
||||
} else {
|
||||
remoteChangeList["delete"] = append(remoteChangeList["delete"], importedRemote.RemoteCanonicalName)
|
||||
log.Printf("sshconfig-import: archived remote \"%s\" (%s)\n", importedRemote.RemoteAlias, importedRemote.RemoteCanonicalName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatedRemotes []string
|
||||
for _, hostInfo := range parsedHostData {
|
||||
previouslyImportedRemote := previouslyImportedRemotes[hostInfo.CanonicalName]
|
||||
updatedRemotes = append(updatedRemotes, hostInfo.CanonicalName)
|
||||
if hostInfo.Ignore {
|
||||
log.Printf("sshconfig-import: ignore remote[%s] as specified in config file\n", hostInfo.CanonicalName)
|
||||
continue
|
||||
@ -1637,15 +1684,23 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
|
||||
}
|
||||
msh := remote.GetRemoteById(previouslyImportedRemote.RemoteId)
|
||||
if msh == nil {
|
||||
remoteChangeList["updateErr"] = append(remoteChangeList["updateErr"], hostInfo.CanonicalName)
|
||||
log.Printf("strange, msh for remote %s [%s] not found\n", hostInfo.CanonicalName, previouslyImportedRemote.RemoteId)
|
||||
continue
|
||||
} else {
|
||||
err := msh.UpdateRemote(ctx, editMap)
|
||||
if err != nil {
|
||||
log.Printf("error updating remote[%s]: %v\n", hostInfo.CanonicalName, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if msh.Remote.ConnectMode == hostInfo.ConnectMode && msh.Remote.SSHOpts.SSHIdentity == hostInfo.SshKeyFile && msh.Remote.RemoteAlias == hostInfo.Host {
|
||||
// silently skip this one. it didn't fail, but no changes were needed
|
||||
continue
|
||||
}
|
||||
|
||||
err := msh.UpdateRemote(ctx, editMap)
|
||||
if err != nil {
|
||||
remoteChangeList["updateErr"] = append(remoteChangeList["updateErr"], hostInfo.CanonicalName)
|
||||
log.Printf("error updating remote[%s]: %v\n", hostInfo.CanonicalName, err)
|
||||
continue
|
||||
}
|
||||
remoteChangeList["update"] = append(remoteChangeList["update"], hostInfo.CanonicalName)
|
||||
log.Printf("sshconfig-import: found previously imported remote with canonical name \"%s\": it has been updated\n", hostInfo.CanonicalName)
|
||||
} else {
|
||||
sshOpts := &sstore.SSHOpts{
|
||||
@ -1674,21 +1729,31 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
|
||||
}
|
||||
err := remote.AddRemote(ctx, r, false)
|
||||
if err != nil {
|
||||
remoteChangeList["createErr"] = append(remoteChangeList["createErr"], hostInfo.CanonicalName)
|
||||
log.Printf("sshconfig-import: failed to add remote \"%s\" (%s): it is being skipped\n", hostInfo.Host, hostInfo.CanonicalName)
|
||||
continue
|
||||
}
|
||||
remoteChangeList["create"] = append(remoteChangeList["create"], hostInfo.CanonicalName)
|
||||
log.Printf("sshconfig-import: created remote \"%s\" (%s)\n", hostInfo.Host, hostInfo.CanonicalName)
|
||||
}
|
||||
}
|
||||
|
||||
update := &sstore.ModelUpdate{Remotes: remote.GetAllRemoteRuntimeState()}
|
||||
update.Info = &sstore.InfoMsgType{}
|
||||
if len(updatedRemotes) == 0 {
|
||||
update.Info.InfoMsg = "no connections imported from ssh config."
|
||||
outMsg := createSshImportSummary(remoteChangeList)
|
||||
visualEdit := resolveBool(pk.Kwargs["visual"], false)
|
||||
if visualEdit {
|
||||
update := &sstore.ModelUpdate{}
|
||||
update.AlertMessage = &sstore.AlertMessageType{
|
||||
Title: "SSH Config Import",
|
||||
Message: outMsg,
|
||||
Markdown: true,
|
||||
}
|
||||
return update, nil
|
||||
} else {
|
||||
update.Info.InfoMsg = fmt.Sprintf("imported %d connection(s) from ssh config file: %s\n", len(updatedRemotes), strings.Join(updatedRemotes, ", "))
|
||||
update := &sstore.ModelUpdate{}
|
||||
update.Info = &sstore.InfoMsgType{}
|
||||
update.Info.InfoMsg = outMsg
|
||||
return update, nil
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
|
@ -60,6 +60,7 @@ type ModelUpdate struct {
|
||||
RemoteView *RemoteViewType `json:"remoteview,omitempty"`
|
||||
ScreenTombstones []*ScreenTombstoneType `json:"screentombstones,omitempty"`
|
||||
SessionTombstones []*SessionTombstoneType `json:"sessiontombstones,omitempty"`
|
||||
AlertMessage *AlertMessageType `json:"alertmessage,omitempty"`
|
||||
}
|
||||
|
||||
func (*ModelUpdate) UpdateType() string {
|
||||
@ -128,6 +129,13 @@ type RemoteEditType struct {
|
||||
HasPassword bool `json:"haspassword,omitempty"`
|
||||
}
|
||||
|
||||
type AlertMessageType struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Confirm bool `json:"confirm,omitempty"`
|
||||
Markdown bool `json:"markdown,omitempty"`
|
||||
}
|
||||
|
||||
type InfoMsgType struct {
|
||||
InfoTitle string `json:"infotitle"`
|
||||
InfoError string `json:"infoerror,omitempty"`
|
||||
|
Loading…
Reference in New Issue
Block a user