waveterm/wavesrv/pkg/sstore/updatebus.go
Sylvie Crowe c2a894b280
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
2024-01-09 16:13:23 -08:00

262 lines
7.2 KiB
Go

// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package sstore
import (
"fmt"
"log"
"sync"
"github.com/wavetermdev/waveterm/wavesrv/pkg/utilfn"
)
var MainBus *UpdateBus = MakeUpdateBus()
const PtyDataUpdateStr = "pty"
const ModelUpdateStr = "model"
const UpdateChSize = 100
type UpdatePacket interface {
UpdateType() string
Clean()
}
type PtyDataUpdate struct {
ScreenId string `json:"screenid,omitempty"`
LineId string `json:"lineid,omitempty"`
RemoteId string `json:"remoteid,omitempty"`
PtyPos int64 `json:"ptypos"`
PtyData64 string `json:"ptydata64"`
PtyDataLen int64 `json:"ptydatalen"`
}
func (*PtyDataUpdate) UpdateType() string {
return PtyDataUpdateStr
}
func (pdu *PtyDataUpdate) Clean() {}
type ModelUpdate struct {
Sessions []*SessionType `json:"sessions,omitempty"`
ActiveSessionId string `json:"activesessionid,omitempty"`
Screens []*ScreenType `json:"screens,omitempty"`
ScreenLines *ScreenLinesType `json:"screenlines,omitempty"`
Line *LineType `json:"line,omitempty"`
Lines []*LineType `json:"lines,omitempty"`
Cmd *CmdType `json:"cmd,omitempty"`
CmdLine *utilfn.StrWithPos `json:"cmdline,omitempty"`
Info *InfoMsgType `json:"info,omitempty"`
ClearInfo bool `json:"clearinfo,omitempty"`
Remotes []RemoteRuntimeState `json:"remotes,omitempty"`
History *HistoryInfoType `json:"history,omitempty"`
Interactive bool `json:"interactive"`
Connect bool `json:"connect,omitempty"`
MainView string `json:"mainview,omitempty"`
Bookmarks []*BookmarkType `json:"bookmarks,omitempty"`
SelectedBookmark string `json:"selectedbookmark,omitempty"`
HistoryViewData *HistoryViewData `json:"historyviewdata,omitempty"`
ClientData *ClientData `json:"clientdata,omitempty"`
RemoteView *RemoteViewType `json:"remoteview,omitempty"`
ScreenTombstones []*ScreenTombstoneType `json:"screentombstones,omitempty"`
SessionTombstones []*SessionTombstoneType `json:"sessiontombstones,omitempty"`
AlertMessage *AlertMessageType `json:"alertmessage,omitempty"`
}
func (*ModelUpdate) UpdateType() string {
return ModelUpdateStr
}
func (update *ModelUpdate) Clean() {
if update == nil {
return
}
update.ClientData = update.ClientData.Clean()
}
func (update *ModelUpdate) UpdateScreen(newScreen *ScreenType) {
if newScreen == nil {
return
}
for idx, screen := range update.Screens {
if screen.ScreenId == newScreen.ScreenId {
update.Screens[idx] = newScreen
return
}
}
update.Screens = append(update.Screens, newScreen)
}
// only sets InfoError if InfoError is not already set
func (update *ModelUpdate) AddInfoError(errStr string) {
if update.Info == nil {
update.Info = &InfoMsgType{}
}
if update.Info.InfoError == "" {
update.Info.InfoError = errStr
}
}
type RemoteViewType struct {
RemoteShowAll bool `json:"remoteshowall,omitempty"`
PtyRemoteId string `json:"ptyremoteid,omitempty"`
RemoteEdit *RemoteEditType `json:"remoteedit,omitempty"`
}
func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *ModelUpdate {
msg := fmt.Sprintf(infoMsgFmt, args...)
return &ModelUpdate{
Info: &InfoMsgType{InfoMsg: msg},
}
}
type HistoryViewData struct {
Items []*HistoryItemType `json:"items"`
Offset int `json:"offset"`
RawOffset int `json:"rawoffset"`
NextRawOffset int `json:"nextrawoffset"`
HasMore bool `json:"hasmore"`
Lines []*LineType `json:"lines"`
Cmds []*CmdType `json:"cmds"`
}
type RemoteEditType struct {
RemoteEdit bool `json:"remoteedit"`
RemoteId string `json:"remoteid,omitempty"`
ErrorStr string `json:"errorstr,omitempty"`
InfoStr string `json:"infostr,omitempty"`
KeyStr string `json:"keystr,omitempty"`
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"`
InfoMsg string `json:"infomsg,omitempty"`
InfoMsgHtml bool `json:"infomsghtml,omitempty"`
WebShareLink bool `json:"websharelink,omitempty"`
InfoComps []string `json:"infocomps,omitempty"`
InfoCompsMore bool `json:"infocompssmore,omitempty"`
InfoLines []string `json:"infolines,omitempty"`
TimeoutMs int64 `json:"timeoutms,omitempty"`
}
type HistoryInfoType struct {
HistoryType string `json:"historytype"`
SessionId string `json:"sessionid,omitempty"`
ScreenId string `json:"screenid,omitempty"`
Items []*HistoryItemType `json:"items"`
Show bool `json:"show"`
}
type UpdateChannel struct {
ScreenId string
ClientId string
Ch chan interface{}
}
func (uch UpdateChannel) Match(screenId string) bool {
if screenId == "" {
return true
}
return screenId == uch.ScreenId
}
type UpdateBus struct {
Lock *sync.Mutex
Channels map[string]UpdateChannel
}
func MakeUpdateBus() *UpdateBus {
return &UpdateBus{
Lock: &sync.Mutex{},
Channels: make(map[string]UpdateChannel),
}
}
// always returns a new channel
func (bus *UpdateBus) RegisterChannel(clientId string, screenId string) chan interface{} {
bus.Lock.Lock()
defer bus.Lock.Unlock()
uch, found := bus.Channels[clientId]
if found {
close(uch.Ch)
uch.ScreenId = screenId
uch.Ch = make(chan interface{}, UpdateChSize)
} else {
uch = UpdateChannel{
ClientId: clientId,
ScreenId: screenId,
Ch: make(chan interface{}, UpdateChSize),
}
}
bus.Channels[clientId] = uch
return uch.Ch
}
func (bus *UpdateBus) UnregisterChannel(clientId string) {
bus.Lock.Lock()
defer bus.Lock.Unlock()
uch, found := bus.Channels[clientId]
if found {
close(uch.Ch)
delete(bus.Channels, clientId)
}
}
func (bus *UpdateBus) SendUpdate(update UpdatePacket) {
if update == nil {
return
}
update.Clean()
bus.Lock.Lock()
defer bus.Lock.Unlock()
for _, uch := range bus.Channels {
select {
case uch.Ch <- update:
default:
log.Printf("[error] dropped update on updatebus uch clientid=%s\n", uch.ClientId)
}
}
}
func (bus *UpdateBus) SendScreenUpdate(screenId string, update UpdatePacket) {
if update == nil {
return
}
update.Clean()
bus.Lock.Lock()
defer bus.Lock.Unlock()
for _, uch := range bus.Channels {
if uch.Match(screenId) {
select {
case uch.Ch <- update:
default:
log.Printf("[error] dropped update on updatebus uch clientid=%s\n", uch.ClientId)
}
}
}
}
func MakeSessionsUpdateForRemote(sessionId string, ri *RemoteInstance) []*SessionType {
return []*SessionType{
{
SessionId: sessionId,
Remotes: []*RemoteInstance{ri},
},
}
}
type BookmarksViewType struct {
Bookmarks []*BookmarkType `json:"bookmarks"`
}