mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
Move bookmarks, history, playbook, and telemetry code out of sstore (#493)
* break out telemetry and playbook * break out bookmarks * add license disclaimers
This commit is contained in:
parent
dcc7b2943e
commit
a121bd4bb5
@ -45,6 +45,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scws"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scws"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/telemetry"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/wsshell"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/wsshell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -211,7 +212,7 @@ func HandleLogActiveState(w http.ResponseWriter, r *http.Request) {
|
|||||||
WriteJsonError(w, fmt.Errorf(ErrorDecodingJson, err))
|
WriteJsonError(w, fmt.Errorf(ErrorDecodingJson, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
activity := sstore.ActivityUpdate{}
|
activity := telemetry.ActivityUpdate{}
|
||||||
if activeState.Fg {
|
if activeState.Fg {
|
||||||
activity.FgMinutes = 1
|
activity.FgMinutes = 1
|
||||||
}
|
}
|
||||||
@ -222,7 +223,7 @@ func HandleLogActiveState(w http.ResponseWriter, r *http.Request) {
|
|||||||
activity.OpenMinutes = 1
|
activity.OpenMinutes = 1
|
||||||
}
|
}
|
||||||
activity.NumConns = remote.NumRemotes()
|
activity.NumConns = remote.NumRemotes()
|
||||||
err = sstore.UpdateCurrentActivity(r.Context(), activity)
|
err = telemetry.UpdateCurrentActivity(r.Context(), activity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJsonError(w, fmt.Errorf("error updating activity: %w", err))
|
WriteJsonError(w, fmt.Errorf("error updating activity: %w", err))
|
||||||
return
|
return
|
||||||
@ -998,7 +999,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("PCLOUD_ENDPOINT=%s\n", pcloud.GetEndpoint())
|
log.Printf("PCLOUD_ENDPOINT=%s\n", pcloud.GetEndpoint())
|
||||||
sstore.UpdateActivityWrap(context.Background(), sstore.ActivityUpdate{NumConns: remote.NumRemotes()}, "numconns") // set at least one record into activity
|
telemetry.UpdateActivityWrap(context.Background(), telemetry.ActivityUpdate{NumConns: remote.NumRemotes()}, "numconns") // set at least one record into activity
|
||||||
installSignalHandlers()
|
installSignalHandlers()
|
||||||
go telemetryLoop()
|
go telemetryLoop()
|
||||||
go stdinReadWatch()
|
go stdinReadWatch()
|
||||||
|
204
wavesrv/pkg/bookmarks/bookmarks.go
Normal file
204
wavesrv/pkg/bookmarks/bookmarks.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bookmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BookmarkType struct {
|
||||||
|
BookmarkId string `json:"bookmarkid"`
|
||||||
|
CreatedTs int64 `json:"createdts"`
|
||||||
|
CmdStr string `json:"cmdstr"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
OrderIdx int64 `json:"orderidx"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *BookmarkType) GetSimpleKey() string {
|
||||||
|
return bm.BookmarkId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *BookmarkType) ToMap() map[string]interface{} {
|
||||||
|
rtn := make(map[string]interface{})
|
||||||
|
rtn["bookmarkid"] = bm.BookmarkId
|
||||||
|
rtn["createdts"] = bm.CreatedTs
|
||||||
|
rtn["cmdstr"] = bm.CmdStr
|
||||||
|
rtn["alias"] = bm.Alias
|
||||||
|
rtn["description"] = bm.Description
|
||||||
|
rtn["tags"] = dbutil.QuickJsonArr(bm.Tags)
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *BookmarkType) FromMap(m map[string]interface{}) bool {
|
||||||
|
dbutil.QuickSetStr(&bm.BookmarkId, m, "bookmarkid")
|
||||||
|
dbutil.QuickSetInt64(&bm.CreatedTs, m, "createdts")
|
||||||
|
dbutil.QuickSetStr(&bm.Alias, m, "alias")
|
||||||
|
dbutil.QuickSetStr(&bm.CmdStr, m, "cmdstr")
|
||||||
|
dbutil.QuickSetStr(&bm.Description, m, "description")
|
||||||
|
dbutil.QuickSetJsonArr(&bm.Tags, m, "tags")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type bookmarkOrderType struct {
|
||||||
|
BookmarkId string
|
||||||
|
OrderIdx int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBookmarks(ctx context.Context, tag string) ([]*BookmarkType, error) {
|
||||||
|
var bms []*BookmarkType
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
var query string
|
||||||
|
if tag == "" {
|
||||||
|
query = `SELECT * FROM bookmark`
|
||||||
|
bms = dbutil.SelectMapsGen[*BookmarkType](tx, query)
|
||||||
|
} else {
|
||||||
|
query = `SELECT * FROM bookmark WHERE EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)`
|
||||||
|
bms = dbutil.SelectMapsGen[*BookmarkType](tx, query, tag)
|
||||||
|
}
|
||||||
|
bmMap := dbutil.MakeGenMap(bms)
|
||||||
|
var orders []bookmarkOrderType
|
||||||
|
query = `SELECT bookmarkid, orderidx FROM bookmark_order WHERE tag = ?`
|
||||||
|
tx.Select(&orders, query, tag)
|
||||||
|
for _, bmOrder := range orders {
|
||||||
|
bm := bmMap[bmOrder.BookmarkId]
|
||||||
|
if bm != nil {
|
||||||
|
bm.OrderIdx = bmOrder.OrderIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
return bms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBookmarkById(ctx context.Context, bookmarkId string, tag string) (*BookmarkType, error) {
|
||||||
|
var rtn *BookmarkType
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT * FROM bookmark WHERE bookmarkid = ?`
|
||||||
|
rtn = dbutil.GetMapGen[*BookmarkType](tx, query, bookmarkId)
|
||||||
|
if rtn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
query = `SELECT orderidx FROM bookmark_order WHERE bookmarkid = ? AND tag = ?`
|
||||||
|
orderIdx := tx.GetInt(query, bookmarkId, tag)
|
||||||
|
rtn.OrderIdx = int64(orderIdx)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBookmarkIdByArg(ctx context.Context, bookmarkArg string) (string, error) {
|
||||||
|
var rtnId string
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
if len(bookmarkArg) == 8 {
|
||||||
|
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid LIKE (? || '%')`
|
||||||
|
rtnId = tx.GetString(query, bookmarkArg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
||||||
|
rtnId = tx.GetString(query, bookmarkArg)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return "", txErr
|
||||||
|
}
|
||||||
|
return rtnId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBookmarkIdsByCmdStr(ctx context.Context, cmdStr string) ([]string, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) ([]string, error) {
|
||||||
|
query := `SELECT bookmarkid FROM bookmark WHERE cmdstr = ?`
|
||||||
|
bmIds := tx.SelectStrings(query, cmdStr)
|
||||||
|
return bmIds, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignores OrderIdx field
|
||||||
|
func InsertBookmark(ctx context.Context, bm *BookmarkType) error {
|
||||||
|
if bm == nil || bm.BookmarkId == "" {
|
||||||
|
return fmt.Errorf("invalid empty bookmark id")
|
||||||
|
}
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
||||||
|
if tx.Exists(query, bm.BookmarkId) {
|
||||||
|
return fmt.Errorf("bookmarkid already exists")
|
||||||
|
}
|
||||||
|
query = `INSERT INTO bookmark ( bookmarkid, createdts, cmdstr, alias, tags, description)
|
||||||
|
VALUES (:bookmarkid,:createdts,:cmdstr,:alias,:tags,:description)`
|
||||||
|
tx.NamedExec(query, bm.ToMap())
|
||||||
|
for _, tag := range append(bm.Tags, "") {
|
||||||
|
query = `SELECT COALESCE(max(orderidx), 0) FROM bookmark_order WHERE tag = ?`
|
||||||
|
maxOrder := tx.GetInt(query, tag)
|
||||||
|
query = `INSERT INTO bookmark_order (tag, bookmarkid, orderidx) VALUES (?, ?, ?)`
|
||||||
|
tx.Exec(query, tag, bm.BookmarkId, maxOrder+1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
BookmarkField_Desc = "desc"
|
||||||
|
BookmarkField_CmdStr = "cmdstr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EditBookmark(ctx context.Context, bookmarkId string, editMap map[string]interface{}) error {
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
||||||
|
if !tx.Exists(query, bookmarkId) {
|
||||||
|
return fmt.Errorf("bookmark not found")
|
||||||
|
}
|
||||||
|
if desc, found := editMap[BookmarkField_Desc]; found {
|
||||||
|
query = `UPDATE bookmark SET description = ? WHERE bookmarkid = ?`
|
||||||
|
tx.Exec(query, desc, bookmarkId)
|
||||||
|
}
|
||||||
|
if cmdStr, found := editMap[BookmarkField_CmdStr]; found {
|
||||||
|
query = `UPDATE bookmark SET cmdstr = ? WHERE bookmarkid = ?`
|
||||||
|
tx.Exec(query, cmdStr, bookmarkId)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixupBookmarkOrder(tx *sstore.TxWrap) {
|
||||||
|
query := `
|
||||||
|
WITH new_order AS (
|
||||||
|
SELECT tag, bookmarkid, row_number() OVER (PARTITION BY tag ORDER BY orderidx) AS newidx FROM bookmark_order
|
||||||
|
)
|
||||||
|
UPDATE bookmark_order
|
||||||
|
SET orderidx = new_order.newidx
|
||||||
|
FROM new_order
|
||||||
|
WHERE bookmark_order.tag = new_order.tag AND bookmark_order.bookmarkid = new_order.bookmarkid
|
||||||
|
`
|
||||||
|
tx.Exec(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteBookmark(ctx context.Context, bookmarkId string) error {
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
||||||
|
if !tx.Exists(query, bookmarkId) {
|
||||||
|
return fmt.Errorf("bookmark not found")
|
||||||
|
}
|
||||||
|
query = `DELETE FROM bookmark WHERE bookmarkid = ?`
|
||||||
|
tx.Exec(query, bookmarkId)
|
||||||
|
query = `DELETE FROM bookmark_order WHERE bookmarkid = ?`
|
||||||
|
tx.Exec(query, bookmarkId)
|
||||||
|
fixupBookmarkOrder(tx)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
23
wavesrv/pkg/bookmarks/updatetypes.go
Normal file
23
wavesrv/pkg/bookmarks/updatetypes.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bookmarks
|
||||||
|
|
||||||
|
import "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
|
|
||||||
|
type BookmarksUpdate struct {
|
||||||
|
Bookmarks []*BookmarkType `json:"bookmarks"`
|
||||||
|
SelectedBookmark string `json:"selectedbookmark,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BookmarksUpdate) GetType() string {
|
||||||
|
return "bookmarks"
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddBookmarksUpdate(update *scbus.ModelUpdatePacketType, bookmarks []*BookmarkType, selectedBookmark *string) {
|
||||||
|
if selectedBookmark == nil {
|
||||||
|
update.AddUpdate(BookmarksUpdate{Bookmarks: bookmarks})
|
||||||
|
} else {
|
||||||
|
update.AddUpdate(BookmarksUpdate{Bookmarks: bookmarks, SelectedBookmark: *selectedBookmark})
|
||||||
|
}
|
||||||
|
}
|
@ -35,8 +35,10 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/shellutil"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/shellutil"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/utilfn"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/utilfn"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/bookmarks"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/comp"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/comp"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/history"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/promptenc"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/promptenc"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker"
|
||||||
@ -47,6 +49,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/telemetry"
|
||||||
"golang.org/x/mod/semver"
|
"golang.org/x/mod/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -473,7 +476,7 @@ func doHistoryExpansion(ctx context.Context, ids resolvedIds, hnum int) (string,
|
|||||||
foundHistoryNum := hnum
|
foundHistoryNum := hnum
|
||||||
if hnum == -1 {
|
if hnum == -1 {
|
||||||
var err error
|
var err error
|
||||||
foundHistoryNum, err = sstore.GetLastHistoryLineNum(ctx, ids.ScreenId)
|
foundHistoryNum, err = history.GetLastHistoryLineNum(ctx, ids.ScreenId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot expand history, error finding last history item: %v", err)
|
return "", fmt.Errorf("cannot expand history, error finding last history item: %v", err)
|
||||||
}
|
}
|
||||||
@ -481,7 +484,7 @@ func doHistoryExpansion(ctx context.Context, ids resolvedIds, hnum int) (string,
|
|||||||
return "", fmt.Errorf("cannot expand history, no last history item")
|
return "", fmt.Errorf("cannot expand history, no last history item")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hitem, err := sstore.GetHistoryItemByLineNum(ctx, ids.ScreenId, foundHistoryNum)
|
hitem, err := history.GetHistoryItemByLineNum(ctx, ids.ScreenId, foundHistoryNum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot get history item '%d': %v", foundHistoryNum, err)
|
return "", fmt.Errorf("cannot get history item '%d': %v", foundHistoryNum, err)
|
||||||
}
|
}
|
||||||
@ -666,7 +669,7 @@ func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, history
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
hitem := &sstore.HistoryItemType{
|
hitem := &history.HistoryItemType{
|
||||||
HistoryId: scbase.GenWaveUUID(),
|
HistoryId: scbase.GenWaveUUID(),
|
||||||
Ts: time.Now().UnixMilli(),
|
Ts: time.Now().UnixMilli(),
|
||||||
UserId: DefaultUserId,
|
UserId: DefaultUserId,
|
||||||
@ -690,7 +693,7 @@ func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, history
|
|||||||
if !isMetaCmd && historyContext.RemotePtr != nil {
|
if !isMetaCmd && historyContext.RemotePtr != nil {
|
||||||
hitem.Remote = *historyContext.RemotePtr
|
hitem.Remote = *historyContext.RemotePtr
|
||||||
}
|
}
|
||||||
err = sstore.InsertHistoryItem(ctx, hitem)
|
err = history.InsertHistoryItem(ctx, hitem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -706,7 +709,7 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.U
|
|||||||
}
|
}
|
||||||
evalDepth := getEvalDepth(ctx)
|
evalDepth := getEvalDepth(ctx)
|
||||||
if pk.Interactive && evalDepth == 0 {
|
if pk.Interactive && evalDepth == 0 {
|
||||||
sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{NumCommands: 1}, "numcommands")
|
telemetry.UpdateActivityWrap(ctx, telemetry.ActivityUpdate{NumCommands: 1}, "numcommands")
|
||||||
}
|
}
|
||||||
if evalDepth > MaxEvalDepth {
|
if evalDepth > MaxEvalDepth {
|
||||||
return nil, fmt.Errorf("alias/history expansion max-depth exceeded")
|
return nil, fmt.Errorf("alias/history expansion max-depth exceeded")
|
||||||
@ -3352,8 +3355,8 @@ func validateRemoteColor(color string, typeStr string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SessionOpenSharedCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
func SessionOpenSharedCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||||
activity := sstore.ActivityUpdate{ClickShared: 1}
|
activity := telemetry.ActivityUpdate{ClickShared: 1}
|
||||||
sstore.UpdateActivityWrap(ctx, activity, "click-shared")
|
telemetry.UpdateActivityWrap(ctx, activity, "click-shared")
|
||||||
return nil, fmt.Errorf("shared sessions are not available in this version of prompt (stay tuned)")
|
return nil, fmt.Errorf("shared sessions are not available in this version of prompt (stay tuned)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3615,11 +3618,11 @@ func MainViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
|||||||
update := scbus.MakeUpdatePacket()
|
update := scbus.MakeUpdatePacket()
|
||||||
mainViewArg := pk.Args[0]
|
mainViewArg := pk.Args[0]
|
||||||
if mainViewArg == sstore.MainViewSession {
|
if mainViewArg == sstore.MainViewSession {
|
||||||
update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewSession})
|
update.AddUpdate(&MainViewUpdate{MainView: sstore.MainViewSession})
|
||||||
} else if mainViewArg == sstore.MainViewConnections {
|
} else if mainViewArg == sstore.MainViewConnections {
|
||||||
update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewConnections})
|
update.AddUpdate(&MainViewUpdate{MainView: sstore.MainViewConnections})
|
||||||
} else if mainViewArg == sstore.MainViewSettings {
|
} else if mainViewArg == sstore.MainViewSettings {
|
||||||
update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewSettings})
|
update.AddUpdate(&MainViewUpdate{MainView: sstore.MainViewSettings})
|
||||||
} else if mainViewArg == sstore.MainViewHistory {
|
} else if mainViewArg == sstore.MainViewHistory {
|
||||||
return nil, fmt.Errorf("use /history instead")
|
return nil, fmt.Errorf("use /history instead")
|
||||||
} else if mainViewArg == sstore.MainViewBookmarks {
|
} else if mainViewArg == sstore.MainViewBookmarks {
|
||||||
@ -3825,7 +3828,7 @@ func HistoryPurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
|||||||
}
|
}
|
||||||
historyIds = append(historyIds, historyArg)
|
historyIds = append(historyIds, historyArg)
|
||||||
}
|
}
|
||||||
err := sstore.PurgeHistoryByIds(ctx, historyIds)
|
err := history.PurgeHistoryByIds(ctx, historyIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("/history:purge error purging items: %v", err)
|
return nil, fmt.Errorf("/history:purge error purging items: %v", err)
|
||||||
}
|
}
|
||||||
@ -3837,7 +3840,7 @@ const HistoryViewPageSize = 50
|
|||||||
var cmdFilterLs = regexp.MustCompile(`^ls(\s|$)`)
|
var cmdFilterLs = regexp.MustCompile(`^ls(\s|$)`)
|
||||||
var cmdFilterCd = regexp.MustCompile(`^cd(\s|$)`)
|
var cmdFilterCd = regexp.MustCompile(`^cd(\s|$)`)
|
||||||
|
|
||||||
func historyCmdFilter(hitem *sstore.HistoryItemType) bool {
|
func historyCmdFilter(hitem *history.HistoryItemType) bool {
|
||||||
cmdStr := hitem.CmdStr
|
cmdStr := hitem.CmdStr
|
||||||
if cmdStr == "" || strings.Index(cmdStr, ";") != -1 || strings.Index(cmdStr, "\n") != -1 {
|
if cmdStr == "" || strings.Index(cmdStr, ";") != -1 || strings.Index(cmdStr, "\n") != -1 {
|
||||||
return true
|
return true
|
||||||
@ -3864,7 +3867,7 @@ func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
opts := sstore.HistoryQueryOpts{MaxItems: HistoryViewPageSize, Offset: offset, RawOffset: rawOffset}
|
opts := history.HistoryQueryOpts{MaxItems: HistoryViewPageSize, Offset: offset, RawOffset: rawOffset}
|
||||||
if pk.Kwargs["text"] != "" {
|
if pk.Kwargs["text"] != "" {
|
||||||
opts.SearchText = pk.Kwargs["text"]
|
opts.SearchText = pk.Kwargs["text"]
|
||||||
}
|
}
|
||||||
@ -3903,25 +3906,25 @@ func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid meta arg (must be boolean): %v", err)
|
return nil, fmt.Errorf("invalid meta arg (must be boolean): %v", err)
|
||||||
}
|
}
|
||||||
hresult, err := sstore.GetHistoryItems(ctx, opts)
|
hresult, err := history.GetHistoryItems(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hvdata := &sstore.HistoryViewData{
|
hvdata := &history.HistoryViewData{
|
||||||
Items: hresult.Items,
|
Items: hresult.Items,
|
||||||
Offset: hresult.Offset,
|
Offset: hresult.Offset,
|
||||||
RawOffset: hresult.RawOffset,
|
RawOffset: hresult.RawOffset,
|
||||||
NextRawOffset: hresult.NextRawOffset,
|
NextRawOffset: hresult.NextRawOffset,
|
||||||
HasMore: hresult.HasMore,
|
HasMore: hresult.HasMore,
|
||||||
}
|
}
|
||||||
lines, cmds, err := sstore.GetLineCmdsFromHistoryItems(ctx, hvdata.Items)
|
lines, cmds, err := history.GetLineCmdsFromHistoryItems(ctx, hvdata.Items)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hvdata.Lines = lines
|
hvdata.Lines = lines
|
||||||
hvdata.Cmds = cmds
|
hvdata.Cmds = cmds
|
||||||
update := scbus.MakeUpdatePacket()
|
update := scbus.MakeUpdatePacket()
|
||||||
update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewHistory, HistoryView: hvdata})
|
update.AddUpdate(&MainViewUpdate{MainView: sstore.MainViewHistory, HistoryView: hvdata})
|
||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3957,17 +3960,17 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbu
|
|||||||
} else if htype == HistoryTypeSession {
|
} else if htype == HistoryTypeSession {
|
||||||
hScreenId = ""
|
hScreenId = ""
|
||||||
}
|
}
|
||||||
hopts := sstore.HistoryQueryOpts{MaxItems: maxItems, SessionId: hSessionId, ScreenId: hScreenId}
|
hopts := history.HistoryQueryOpts{MaxItems: maxItems, SessionId: hSessionId, ScreenId: hScreenId}
|
||||||
hresult, err := sstore.GetHistoryItems(ctx, hopts)
|
hresult, err := history.GetHistoryItems(ctx, hopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
show := !resolveBool(pk.Kwargs["noshow"], false)
|
show := !resolveBool(pk.Kwargs["noshow"], false)
|
||||||
if show {
|
if show {
|
||||||
sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{HistoryView: 1}, "history")
|
telemetry.UpdateActivityWrap(ctx, telemetry.ActivityUpdate{HistoryView: 1}, "history")
|
||||||
}
|
}
|
||||||
update := scbus.MakeUpdatePacket()
|
update := scbus.MakeUpdatePacket()
|
||||||
update.AddUpdate(sstore.HistoryInfoType{
|
update.AddUpdate(history.HistoryInfoType{
|
||||||
HistoryType: htype,
|
HistoryType: htype,
|
||||||
SessionId: ids.SessionId,
|
SessionId: ids.SessionId,
|
||||||
ScreenId: ids.ScreenId,
|
ScreenId: ids.ScreenId,
|
||||||
@ -4310,16 +4313,16 @@ func BookmarksShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
|||||||
if len(pk.Args) > 0 {
|
if len(pk.Args) > 0 {
|
||||||
tagName = pk.Args[0]
|
tagName = pk.Args[0]
|
||||||
}
|
}
|
||||||
bms, err := sstore.GetBookmarks(ctx, tagName)
|
bms, err := bookmarks.GetBookmarks(ctx, tagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot retrieve bookmarks: %v", err)
|
return nil, fmt.Errorf("cannot retrieve bookmarks: %v", err)
|
||||||
}
|
}
|
||||||
sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{BookmarksView: 1}, "bookmarks")
|
telemetry.UpdateActivityWrap(ctx, telemetry.ActivityUpdate{BookmarksView: 1}, "bookmarks")
|
||||||
update := scbus.MakeUpdatePacket()
|
update := scbus.MakeUpdatePacket()
|
||||||
|
|
||||||
update.AddUpdate(&sstore.MainViewUpdate{
|
update.AddUpdate(&MainViewUpdate{
|
||||||
MainView: sstore.MainViewBookmarks,
|
MainView: sstore.MainViewBookmarks,
|
||||||
BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms},
|
BookmarksView: &bookmarks.BookmarksUpdate{Bookmarks: bms},
|
||||||
})
|
})
|
||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
@ -4329,7 +4332,7 @@ func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
|
|||||||
return nil, fmt.Errorf("/bookmark:set requires one argument (bookmark id)")
|
return nil, fmt.Errorf("/bookmark:set requires one argument (bookmark id)")
|
||||||
}
|
}
|
||||||
bookmarkArg := pk.Args[0]
|
bookmarkArg := pk.Args[0]
|
||||||
bookmarkId, err := sstore.GetBookmarkIdByArg(ctx, bookmarkArg)
|
bookmarkId, err := bookmarks.GetBookmarkIdByArg(ctx, bookmarkArg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error trying to resolve bookmark: %v", err)
|
return nil, fmt.Errorf("error trying to resolve bookmark: %v", err)
|
||||||
}
|
}
|
||||||
@ -4338,25 +4341,25 @@ func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
|
|||||||
}
|
}
|
||||||
editMap := make(map[string]interface{})
|
editMap := make(map[string]interface{})
|
||||||
if descStr, found := pk.Kwargs["desc"]; found {
|
if descStr, found := pk.Kwargs["desc"]; found {
|
||||||
editMap[sstore.BookmarkField_Desc] = descStr
|
editMap[bookmarks.BookmarkField_Desc] = descStr
|
||||||
}
|
}
|
||||||
if cmdStr, found := pk.Kwargs["cmdstr"]; found {
|
if cmdStr, found := pk.Kwargs["cmdstr"]; found {
|
||||||
editMap[sstore.BookmarkField_CmdStr] = cmdStr
|
editMap[bookmarks.BookmarkField_CmdStr] = cmdStr
|
||||||
}
|
}
|
||||||
if len(editMap) == 0 {
|
if len(editMap) == 0 {
|
||||||
return nil, fmt.Errorf("no fields set, can set %s", formatStrs([]string{"desc", "cmdstr"}, "or", false))
|
return nil, fmt.Errorf("no fields set, can set %s", formatStrs([]string{"desc", "cmdstr"}, "or", false))
|
||||||
}
|
}
|
||||||
err = sstore.EditBookmark(ctx, bookmarkId, editMap)
|
err = bookmarks.EditBookmark(ctx, bookmarkId, editMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error trying to edit bookmark: %v", err)
|
return nil, fmt.Errorf("error trying to edit bookmark: %v", err)
|
||||||
}
|
}
|
||||||
bm, err := sstore.GetBookmarkById(ctx, bookmarkId, "")
|
bm, err := bookmarks.GetBookmarkById(ctx, bookmarkId, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error retrieving edited bookmark: %v", err)
|
return nil, fmt.Errorf("error retrieving edited bookmark: %v", err)
|
||||||
}
|
}
|
||||||
bms := []*sstore.BookmarkType{bm}
|
bms := []*bookmarks.BookmarkType{bm}
|
||||||
update := scbus.MakeUpdatePacket()
|
update := scbus.MakeUpdatePacket()
|
||||||
sstore.AddBookmarksUpdate(update, bms, nil)
|
bookmarks.AddBookmarksUpdate(update, bms, nil)
|
||||||
update.AddUpdate(sstore.InfoMsgUpdate("bookmark edited"))
|
update.AddUpdate(sstore.InfoMsgUpdate("bookmark edited"))
|
||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
@ -4366,20 +4369,20 @@ func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
|
|||||||
return nil, fmt.Errorf("/bookmark:delete requires one argument (bookmark id)")
|
return nil, fmt.Errorf("/bookmark:delete requires one argument (bookmark id)")
|
||||||
}
|
}
|
||||||
bookmarkArg := pk.Args[0]
|
bookmarkArg := pk.Args[0]
|
||||||
bookmarkId, err := sstore.GetBookmarkIdByArg(ctx, bookmarkArg)
|
bookmarkId, err := bookmarks.GetBookmarkIdByArg(ctx, bookmarkArg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error trying to resolve bookmark: %v", err)
|
return nil, fmt.Errorf("error trying to resolve bookmark: %v", err)
|
||||||
}
|
}
|
||||||
if bookmarkId == "" {
|
if bookmarkId == "" {
|
||||||
return nil, fmt.Errorf("bookmark not found")
|
return nil, fmt.Errorf("bookmark not found")
|
||||||
}
|
}
|
||||||
err = sstore.DeleteBookmark(ctx, bookmarkId)
|
err = bookmarks.DeleteBookmark(ctx, bookmarkId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error deleting bookmark: %v", err)
|
return nil, fmt.Errorf("error deleting bookmark: %v", err)
|
||||||
}
|
}
|
||||||
update := scbus.MakeUpdatePacket()
|
update := scbus.MakeUpdatePacket()
|
||||||
bms := []*sstore.BookmarkType{{BookmarkId: bookmarkId, Remove: true}}
|
bms := []*bookmarks.BookmarkType{{BookmarkId: bookmarkId, Remove: true}}
|
||||||
sstore.AddBookmarksUpdate(update, bms, nil)
|
bookmarks.AddBookmarksUpdate(update, bms, nil)
|
||||||
update.AddUpdate(sstore.InfoMsgUpdate("bookmark deleted"))
|
update.AddUpdate(sstore.InfoMsgUpdate("bookmark deleted"))
|
||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
@ -4407,7 +4410,7 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
|||||||
if cmdObj == nil {
|
if cmdObj == nil {
|
||||||
return nil, fmt.Errorf("cannot bookmark non-cmd line")
|
return nil, fmt.Errorf("cannot bookmark non-cmd line")
|
||||||
}
|
}
|
||||||
existingBmIds, err := sstore.GetBookmarkIdsByCmdStr(ctx, cmdObj.CmdStr)
|
existingBmIds, err := bookmarks.GetBookmarkIdsByCmdStr(ctx, cmdObj.CmdStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error trying to retrieve current boookmarks: %v", err)
|
return nil, fmt.Errorf("error trying to retrieve current boookmarks: %v", err)
|
||||||
}
|
}
|
||||||
@ -4415,7 +4418,7 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
|||||||
if len(existingBmIds) > 0 {
|
if len(existingBmIds) > 0 {
|
||||||
newBmId = existingBmIds[0]
|
newBmId = existingBmIds[0]
|
||||||
} else {
|
} else {
|
||||||
newBm := &sstore.BookmarkType{
|
newBm := &bookmarks.BookmarkType{
|
||||||
BookmarkId: uuid.New().String(),
|
BookmarkId: uuid.New().String(),
|
||||||
CreatedTs: time.Now().UnixMilli(),
|
CreatedTs: time.Now().UnixMilli(),
|
||||||
CmdStr: cmdObj.CmdStr,
|
CmdStr: cmdObj.CmdStr,
|
||||||
@ -4423,17 +4426,17 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
|||||||
Tags: nil,
|
Tags: nil,
|
||||||
Description: "",
|
Description: "",
|
||||||
}
|
}
|
||||||
err = sstore.InsertBookmark(ctx, newBm)
|
err = bookmarks.InsertBookmark(ctx, newBm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot insert bookmark: %v", err)
|
return nil, fmt.Errorf("cannot insert bookmark: %v", err)
|
||||||
}
|
}
|
||||||
newBmId = newBm.BookmarkId
|
newBmId = newBm.BookmarkId
|
||||||
}
|
}
|
||||||
bms, err := sstore.GetBookmarks(ctx, "")
|
bms, err := bookmarks.GetBookmarks(ctx, "")
|
||||||
update := scbus.MakeUpdatePacket()
|
update := scbus.MakeUpdatePacket()
|
||||||
update.AddUpdate(&sstore.MainViewUpdate{
|
update.AddUpdate(&MainViewUpdate{
|
||||||
MainView: sstore.MainViewBookmarks,
|
MainView: sstore.MainViewBookmarks,
|
||||||
BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms, SelectedBookmark: newBmId},
|
BookmarksView: &bookmarks.BookmarksUpdate{Bookmarks: bms, SelectedBookmark: newBmId},
|
||||||
})
|
})
|
||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
|
19
wavesrv/pkg/cmdrunner/updatetypes.go
Normal file
19
wavesrv/pkg/cmdrunner/updatetypes.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package cmdrunner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/bookmarks"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/history"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MainViewUpdate struct {
|
||||||
|
MainView string `json:"mainview"`
|
||||||
|
HistoryView *history.HistoryViewData `json:"historyview,omitempty"`
|
||||||
|
BookmarksView *bookmarks.BookmarksUpdate `json:"bookmarksview,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MainViewUpdate) GetType() string {
|
||||||
|
return "mainview"
|
||||||
|
}
|
326
wavesrv/pkg/history/history.go
Normal file
326
wavesrv/pkg/history/history.go
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HistoryItemType struct {
|
||||||
|
HistoryId string `json:"historyid"`
|
||||||
|
Ts int64 `json:"ts"`
|
||||||
|
UserId string `json:"userid"`
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
ScreenId string `json:"screenid"`
|
||||||
|
LineId string `json:"lineid"`
|
||||||
|
HadError bool `json:"haderror"`
|
||||||
|
CmdStr string `json:"cmdstr"`
|
||||||
|
Remote sstore.RemotePtrType `json:"remote"`
|
||||||
|
IsMetaCmd bool `json:"ismetacmd"`
|
||||||
|
ExitCode *int64 `json:"exitcode,omitempty"`
|
||||||
|
DurationMs *int64 `json:"durationms,omitempty"`
|
||||||
|
FeState sstore.FeStateType `json:"festate,omitempty"`
|
||||||
|
Tags map[string]bool `json:"tags,omitempty"`
|
||||||
|
LineNum int64 `json:"linenum" dbmap:"-"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
|
||||||
|
// only for updates
|
||||||
|
Remove bool `json:"remove" dbmap:"-"`
|
||||||
|
|
||||||
|
// transient (string because of different history orderings)
|
||||||
|
HistoryNum string `json:"historynum" dbmap:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HistoryItemType) ToMap() map[string]interface{} {
|
||||||
|
rtn := make(map[string]interface{})
|
||||||
|
rtn["historyid"] = h.HistoryId
|
||||||
|
rtn["ts"] = h.Ts
|
||||||
|
rtn["userid"] = h.UserId
|
||||||
|
rtn["sessionid"] = h.SessionId
|
||||||
|
rtn["screenid"] = h.ScreenId
|
||||||
|
rtn["lineid"] = h.LineId
|
||||||
|
rtn["linenum"] = h.LineNum
|
||||||
|
rtn["haderror"] = h.HadError
|
||||||
|
rtn["cmdstr"] = h.CmdStr
|
||||||
|
rtn["remoteownerid"] = h.Remote.OwnerId
|
||||||
|
rtn["remoteid"] = h.Remote.RemoteId
|
||||||
|
rtn["remotename"] = h.Remote.Name
|
||||||
|
rtn["ismetacmd"] = h.IsMetaCmd
|
||||||
|
rtn["exitcode"] = h.ExitCode
|
||||||
|
rtn["durationms"] = h.DurationMs
|
||||||
|
rtn["festate"] = dbutil.QuickJson(h.FeState)
|
||||||
|
rtn["tags"] = dbutil.QuickJson(h.Tags)
|
||||||
|
rtn["status"] = h.Status
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HistoryItemType) FromMap(m map[string]interface{}) bool {
|
||||||
|
dbutil.QuickSetStr(&h.HistoryId, m, "historyid")
|
||||||
|
dbutil.QuickSetInt64(&h.Ts, m, "ts")
|
||||||
|
dbutil.QuickSetStr(&h.UserId, m, "userid")
|
||||||
|
dbutil.QuickSetStr(&h.SessionId, m, "sessionid")
|
||||||
|
dbutil.QuickSetStr(&h.ScreenId, m, "screenid")
|
||||||
|
dbutil.QuickSetStr(&h.LineId, m, "lineid")
|
||||||
|
dbutil.QuickSetBool(&h.HadError, m, "haderror")
|
||||||
|
dbutil.QuickSetStr(&h.CmdStr, m, "cmdstr")
|
||||||
|
dbutil.QuickSetStr(&h.Remote.OwnerId, m, "remoteownerid")
|
||||||
|
dbutil.QuickSetStr(&h.Remote.RemoteId, m, "remoteid")
|
||||||
|
dbutil.QuickSetStr(&h.Remote.Name, m, "remotename")
|
||||||
|
dbutil.QuickSetBool(&h.IsMetaCmd, m, "ismetacmd")
|
||||||
|
dbutil.QuickSetStr(&h.HistoryNum, m, "historynum")
|
||||||
|
dbutil.QuickSetInt64(&h.LineNum, m, "linenum")
|
||||||
|
dbutil.QuickSetNullableInt64(&h.ExitCode, m, "exitcode")
|
||||||
|
dbutil.QuickSetNullableInt64(&h.DurationMs, m, "durationms")
|
||||||
|
dbutil.QuickSetJson(&h.FeState, m, "festate")
|
||||||
|
dbutil.QuickSetJson(&h.Tags, m, "tags")
|
||||||
|
dbutil.QuickSetStr(&h.Status, m, "status")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryQueryOpts struct {
|
||||||
|
Offset int
|
||||||
|
MaxItems int
|
||||||
|
FromTs int64
|
||||||
|
SearchText string
|
||||||
|
SessionId string
|
||||||
|
RemoteId string
|
||||||
|
ScreenId string
|
||||||
|
NoMeta bool
|
||||||
|
RawOffset int
|
||||||
|
FilterFn func(*HistoryItemType) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryQueryResult struct {
|
||||||
|
MaxItems int
|
||||||
|
Items []*HistoryItemType
|
||||||
|
Offset int // the offset shown to user
|
||||||
|
RawOffset int // internal offset
|
||||||
|
HasMore bool
|
||||||
|
NextRawOffset int // internal offset used by pager for next query
|
||||||
|
|
||||||
|
prevItems int // holds number of items skipped by RawOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryViewData struct {
|
||||||
|
Items []*HistoryItemType `json:"items"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
RawOffset int `json:"rawoffset"`
|
||||||
|
NextRawOffset int `json:"nextrawoffset"`
|
||||||
|
HasMore bool `json:"hasmore"`
|
||||||
|
Lines []*sstore.LineType `json:"lines"`
|
||||||
|
Cmds []*sstore.CmdType `json:"cmds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const HistoryCols = "h.historyid, h.ts, h.userid, h.sessionid, h.screenid, h.lineid, h.haderror, h.cmdstr, h.remoteownerid, h.remoteid, h.remotename, h.ismetacmd, h.linenum, h.exitcode, h.durationms, h.festate, h.tags, h.status"
|
||||||
|
const DefaultMaxHistoryItems = 1000
|
||||||
|
|
||||||
|
func InsertHistoryItem(ctx context.Context, hitem *HistoryItemType) error {
|
||||||
|
if hitem == nil {
|
||||||
|
return fmt.Errorf("cannot insert nil history item")
|
||||||
|
}
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `INSERT INTO history
|
||||||
|
( historyid, ts, userid, sessionid, screenid, lineid, haderror, cmdstr, remoteownerid, remoteid, remotename, ismetacmd, linenum, exitcode, durationms, festate, tags, status) VALUES
|
||||||
|
(:historyid,:ts,:userid,:sessionid,:screenid,:lineid,:haderror,:cmdstr,:remoteownerid,:remoteid,:remotename,:ismetacmd,:linenum,:exitcode,:durationms,:festate,:tags,:status)`
|
||||||
|
tx.NamedExec(query, hitem.ToMap())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
|
||||||
|
const HistoryQueryChunkSize = 1000
|
||||||
|
|
||||||
|
func _getNextHistoryItem(items []*HistoryItemType, index int, filterFn func(*HistoryItemType) bool) (*HistoryItemType, int) {
|
||||||
|
for ; index < len(items); index++ {
|
||||||
|
item := items[index]
|
||||||
|
if filterFn(item) {
|
||||||
|
return item, index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, index
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if done, false if we still need to process more items
|
||||||
|
func (result *HistoryQueryResult) processItem(item *HistoryItemType, rawOffset int) bool {
|
||||||
|
if result.prevItems < result.Offset {
|
||||||
|
result.prevItems++
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(result.Items) == result.MaxItems {
|
||||||
|
result.HasMore = true
|
||||||
|
result.NextRawOffset = rawOffset
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(result.Items) == 0 {
|
||||||
|
result.RawOffset = rawOffset
|
||||||
|
}
|
||||||
|
result.Items = append(result.Items, item)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHistoryQueryWithFilter(tx *sstore.TxWrap, opts HistoryQueryOpts) (*HistoryQueryResult, error) {
|
||||||
|
if opts.MaxItems == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid query, maxitems is 0")
|
||||||
|
}
|
||||||
|
rtn := &HistoryQueryResult{Offset: opts.Offset, MaxItems: opts.MaxItems}
|
||||||
|
var rawOffset int
|
||||||
|
if opts.RawOffset >= opts.Offset {
|
||||||
|
rtn.prevItems = opts.Offset
|
||||||
|
rawOffset = opts.RawOffset
|
||||||
|
} else {
|
||||||
|
rawOffset = 0
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
resultItems, err := runHistoryQuery(tx, opts, rawOffset, HistoryQueryChunkSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
isDone := false
|
||||||
|
for resultIdx := 0; resultIdx < len(resultItems); resultIdx++ {
|
||||||
|
if opts.FilterFn != nil && !opts.FilterFn(resultItems[resultIdx]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isDone = rtn.processItem(resultItems[resultIdx], rawOffset+resultIdx)
|
||||||
|
if isDone {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isDone {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(resultItems) < HistoryQueryChunkSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rawOffset += HistoryQueryChunkSize
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHistoryQuery(tx *sstore.TxWrap, opts HistoryQueryOpts, realOffset int, itemLimit int) ([]*HistoryItemType, error) {
|
||||||
|
// check sessionid/screenid format because we are directly inserting them into the SQL
|
||||||
|
if opts.SessionId != "" {
|
||||||
|
_, err := uuid.Parse(opts.SessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed sessionid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opts.ScreenId != "" {
|
||||||
|
_, err := uuid.Parse(opts.ScreenId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed screenid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opts.RemoteId != "" {
|
||||||
|
_, err := uuid.Parse(opts.RemoteId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed remoteid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
whereClause := "WHERE 1"
|
||||||
|
var queryArgs []interface{}
|
||||||
|
hNumStr := ""
|
||||||
|
if opts.SessionId != "" && opts.ScreenId != "" {
|
||||||
|
whereClause += fmt.Sprintf(" AND h.sessionid = '%s' AND h.screenid = '%s'", opts.SessionId, opts.ScreenId)
|
||||||
|
hNumStr = ""
|
||||||
|
} else if opts.SessionId != "" {
|
||||||
|
whereClause += fmt.Sprintf(" AND h.sessionid = '%s'", opts.SessionId)
|
||||||
|
hNumStr = "s"
|
||||||
|
} else {
|
||||||
|
hNumStr = "g"
|
||||||
|
}
|
||||||
|
if opts.SearchText != "" {
|
||||||
|
whereClause += " AND h.cmdstr LIKE ? ESCAPE '\\'"
|
||||||
|
likeArg := opts.SearchText
|
||||||
|
likeArg = strings.ReplaceAll(likeArg, "%", "\\%")
|
||||||
|
likeArg = strings.ReplaceAll(likeArg, "_", "\\_")
|
||||||
|
queryArgs = append(queryArgs, "%"+likeArg+"%")
|
||||||
|
}
|
||||||
|
if opts.FromTs > 0 {
|
||||||
|
whereClause += fmt.Sprintf(" AND h.ts <= %d", opts.FromTs)
|
||||||
|
}
|
||||||
|
if opts.RemoteId != "" {
|
||||||
|
whereClause += fmt.Sprintf(" AND h.remoteid = '%s'", opts.RemoteId)
|
||||||
|
}
|
||||||
|
if opts.NoMeta {
|
||||||
|
whereClause += " AND NOT h.ismetacmd"
|
||||||
|
}
|
||||||
|
query := fmt.Sprintf("SELECT %s, ('%s' || CAST((row_number() OVER win) as text)) historynum FROM history h %s WINDOW win AS (ORDER BY h.ts, h.historyid) ORDER BY h.ts DESC, h.historyid DESC LIMIT %d OFFSET %d", HistoryCols, hNumStr, whereClause, itemLimit, realOffset)
|
||||||
|
marr := tx.SelectMaps(query, queryArgs...)
|
||||||
|
rtn := make([]*HistoryItemType, len(marr))
|
||||||
|
for idx, m := range marr {
|
||||||
|
hitem := dbutil.FromMap[*HistoryItemType](m)
|
||||||
|
rtn[idx] = hitem
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHistoryItems(ctx context.Context, opts HistoryQueryOpts) (*HistoryQueryResult, error) {
|
||||||
|
var rtn *HistoryQueryResult
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
var err error
|
||||||
|
rtn, err = runHistoryQueryWithFilter(tx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHistoryItemByLineNum(ctx context.Context, screenId string, lineNum int) (*HistoryItemType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (*HistoryItemType, error) {
|
||||||
|
query := `SELECT * FROM history WHERE screenid = ? AND linenum = ?`
|
||||||
|
hitem := dbutil.GetMapGen[*HistoryItemType](tx, query, screenId, lineNum)
|
||||||
|
return hitem, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLastHistoryLineNum(ctx context.Context, screenId string) (int, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (int, error) {
|
||||||
|
query := `SELECT COALESCE(max(linenum), 0) FROM history WHERE screenid = ?`
|
||||||
|
maxLineNum := tx.GetInt(query, screenId)
|
||||||
|
return maxLineNum, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLineIdsFromHistoryItems(historyItems []*HistoryItemType) []string {
|
||||||
|
var rtn []string
|
||||||
|
for _, hitem := range historyItems {
|
||||||
|
if hitem.LineId != "" {
|
||||||
|
rtn = append(rtn, hitem.LineId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryItemType) ([]*sstore.LineType, []*sstore.CmdType, error) {
|
||||||
|
if len(historyItems) == 0 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
return sstore.WithTxRtn3(ctx, func(tx *sstore.TxWrap) ([]*sstore.LineType, []*sstore.CmdType, error) {
|
||||||
|
lineIdsJsonArr := dbutil.QuickJsonArr(getLineIdsFromHistoryItems(historyItems))
|
||||||
|
query := `SELECT * FROM line WHERE lineid IN (SELECT value FROM json_each(?))`
|
||||||
|
lineArr := dbutil.SelectMappable[*sstore.LineType](tx, query, lineIdsJsonArr)
|
||||||
|
query = `SELECT * FROM cmd WHERE lineid IN (SELECT value FROM json_each(?))`
|
||||||
|
cmdArr := dbutil.SelectMapsGen[*sstore.CmdType](tx, query, lineIdsJsonArr)
|
||||||
|
return lineArr, cmdArr, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurgeHistoryByIds(ctx context.Context, historyIds []string) error {
|
||||||
|
return sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `DELETE FROM history WHERE historyid IN (SELECT value FROM json_each(?))`
|
||||||
|
tx.Exec(query, dbutil.QuickJsonArr(historyIds))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
16
wavesrv/pkg/history/updatetypes.go
Normal file
16
wavesrv/pkg/history/updatetypes.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package history
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (HistoryInfoType) GetType() string {
|
||||||
|
return "history"
|
||||||
|
}
|
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/rtnstate"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/rtnstate"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PCloudEndpoint = "https://api.waveterm.dev/central"
|
const PCloudEndpoint = "https://api.waveterm.dev/central"
|
||||||
@ -156,7 +157,7 @@ func SendTelemetry(ctx context.Context, force bool) error {
|
|||||||
if !force && clientData.ClientOpts.NoTelemetry {
|
if !force && clientData.ClientOpts.NoTelemetry {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
activity, err := sstore.GetNonUploadedActivity(ctx)
|
activity, err := telemetry.GetNonUploadedActivity(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot get activity: %v", err)
|
return fmt.Errorf("cannot get activity: %v", err)
|
||||||
}
|
}
|
||||||
@ -164,7 +165,7 @@ func SendTelemetry(ctx context.Context, force bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("[pcloud] sending telemetry data\n")
|
log.Printf("[pcloud] sending telemetry data\n")
|
||||||
dayStr := sstore.GetCurDayStr()
|
dayStr := telemetry.GetCurDayStr()
|
||||||
defaultShellType := shellapi.DetectLocalShellType()
|
defaultShellType := shellapi.DetectLocalShellType()
|
||||||
input := TelemetryInputType{UserId: clientData.UserId, ClientId: clientData.ClientId, CurDay: dayStr, DefaultShell: defaultShellType, Activity: activity}
|
input := TelemetryInputType{UserId: clientData.UserId, ClientId: clientData.ClientId, CurDay: dayStr, DefaultShell: defaultShellType, Activity: activity}
|
||||||
req, err := makeAnonPostReq(ctx, TelemetryUrl, input)
|
req, err := makeAnonPostReq(ctx, TelemetryUrl, input)
|
||||||
@ -175,7 +176,7 @@ func SendTelemetry(ctx context.Context, force bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = sstore.MarkActivityAsUploaded(ctx, activity)
|
err = telemetry.MarkActivityAsUploaded(ctx, activity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error marking activity as uploaded: %v", err)
|
return fmt.Errorf("error marking activity as uploaded: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/rtnstate"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/rtnstate"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NoTelemetryInputType struct {
|
type NoTelemetryInputType struct {
|
||||||
@ -19,11 +20,11 @@ type NoTelemetryInputType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TelemetryInputType struct {
|
type TelemetryInputType struct {
|
||||||
UserId string `json:"userid"`
|
UserId string `json:"userid"`
|
||||||
ClientId string `json:"clientid"`
|
ClientId string `json:"clientid"`
|
||||||
CurDay string `json:"curday"`
|
CurDay string `json:"curday"`
|
||||||
DefaultShell string `json:"defaultshell"`
|
DefaultShell string `json:"defaultshell"`
|
||||||
Activity []*sstore.ActivityType `json:"activity"`
|
Activity []*telemetry.ActivityType `json:"activity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebShareUpdateType struct {
|
type WebShareUpdateType struct {
|
||||||
|
162
wavesrv/pkg/playbook/playbook.go
Normal file
162
wavesrv/pkg/playbook/playbook.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package playbook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlaybookType struct {
|
||||||
|
PlaybookId string `json:"playbookid"`
|
||||||
|
PlaybookName string `json:"playbookname"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
EntryIds []string `json:"entryids"`
|
||||||
|
|
||||||
|
// this is not persisted to DB, just for transport to FE
|
||||||
|
Entries []*PlaybookEntry `json:"entries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PlaybookType) ToMap() map[string]interface{} {
|
||||||
|
rtn := make(map[string]interface{})
|
||||||
|
rtn["playbookid"] = p.PlaybookId
|
||||||
|
rtn["playbookname"] = p.PlaybookName
|
||||||
|
rtn["description"] = p.Description
|
||||||
|
rtn["entryids"] = dbutil.QuickJsonArr(p.EntryIds)
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PlaybookType) FromMap(m map[string]interface{}) bool {
|
||||||
|
dbutil.QuickSetStr(&p.PlaybookId, m, "playbookid")
|
||||||
|
dbutil.QuickSetStr(&p.PlaybookName, m, "playbookname")
|
||||||
|
dbutil.QuickSetStr(&p.Description, m, "description")
|
||||||
|
dbutil.QuickSetJsonArr(&p.Entries, m, "entries")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// reorders p.Entries to match p.EntryIds
|
||||||
|
func (p *PlaybookType) OrderEntries() {
|
||||||
|
if len(p.Entries) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m := make(map[string]*PlaybookEntry)
|
||||||
|
for _, entry := range p.Entries {
|
||||||
|
m[entry.EntryId] = entry
|
||||||
|
}
|
||||||
|
newList := make([]*PlaybookEntry, 0, len(p.EntryIds))
|
||||||
|
for _, entryId := range p.EntryIds {
|
||||||
|
entry := m[entryId]
|
||||||
|
if entry != nil {
|
||||||
|
newList = append(newList, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Entries = newList
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes from p.EntryIds (not from p.Entries)
|
||||||
|
func (p *PlaybookType) RemoveEntry(entryIdToRemove string) {
|
||||||
|
if len(p.EntryIds) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newList := make([]string, 0, len(p.EntryIds)-1)
|
||||||
|
for _, entryId := range p.EntryIds {
|
||||||
|
if entryId == entryIdToRemove {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newList = append(newList, entryId)
|
||||||
|
}
|
||||||
|
p.EntryIds = newList
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaybookEntry struct {
|
||||||
|
PlaybookId string `json:"playbookid"`
|
||||||
|
EntryId string `json:"entryid"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
CmdStr string `json:"cmdstr"`
|
||||||
|
UpdatedTs int64 `json:"updatedts"`
|
||||||
|
CreatedTs int64 `json:"createdts"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePlaybook(ctx context.Context, name string) (*PlaybookType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (*PlaybookType, error) {
|
||||||
|
query := `SELECT playbookid FROM playbook WHERE name = ?`
|
||||||
|
if tx.Exists(query, name) {
|
||||||
|
return nil, fmt.Errorf("playbook %q already exists", name)
|
||||||
|
}
|
||||||
|
rtn := &PlaybookType{}
|
||||||
|
rtn.PlaybookId = uuid.New().String()
|
||||||
|
rtn.PlaybookName = name
|
||||||
|
query = `INSERT INTO playbook ( playbookid, playbookname, description, entryids)
|
||||||
|
VALUES (:playbookid,:playbookname,:description,:entryids)`
|
||||||
|
tx.Exec(query, rtn.ToMap())
|
||||||
|
return rtn, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectPlaybook(tx *sstore.TxWrap, playbookId string) *PlaybookType {
|
||||||
|
query := `SELECT * FROM playbook where playbookid = ?`
|
||||||
|
playbook := dbutil.GetMapGen[*PlaybookType](tx, query, playbookId)
|
||||||
|
return playbook
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPlaybookEntry(ctx context.Context, entry *PlaybookEntry) error {
|
||||||
|
if entry.EntryId == "" {
|
||||||
|
return fmt.Errorf("invalid entryid")
|
||||||
|
}
|
||||||
|
return sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
playbook := selectPlaybook(tx, entry.PlaybookId)
|
||||||
|
if playbook == nil {
|
||||||
|
return fmt.Errorf("cannot add entry, playbook does not exist")
|
||||||
|
}
|
||||||
|
query := `SELECT entryid FROM playbook_entry WHERE entryid = ?`
|
||||||
|
if tx.Exists(query, entry.EntryId) {
|
||||||
|
return fmt.Errorf("cannot add entry, entryid already exists")
|
||||||
|
}
|
||||||
|
query = `INSERT INTO playbook_entry ( entryid, playbookid, description, alias, cmdstr, createdts, updatedts)
|
||||||
|
VALUES (:entryid,:playbookid,:description,:alias,:cmdstr,:createdts,:updatedts)`
|
||||||
|
tx.Exec(query, entry)
|
||||||
|
playbook.EntryIds = append(playbook.EntryIds, entry.EntryId)
|
||||||
|
query = `UPDATE playbook SET entryids = ? WHERE playbookid = ?`
|
||||||
|
tx.Exec(query, dbutil.QuickJsonArr(playbook.EntryIds), entry.PlaybookId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemovePlaybookEntry(ctx context.Context, playbookId string, entryId string) error {
|
||||||
|
return sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
playbook := selectPlaybook(tx, playbookId)
|
||||||
|
if playbook == nil {
|
||||||
|
return fmt.Errorf("cannot remove playbook entry, playbook does not exist")
|
||||||
|
}
|
||||||
|
query := `SELECT entryid FROM playbook_entry WHERE entryid = ?`
|
||||||
|
if !tx.Exists(query, entryId) {
|
||||||
|
return fmt.Errorf("cannot remove playbook entry, entry does not exist")
|
||||||
|
}
|
||||||
|
query = `DELETE FROM playbook_entry WHERE entryid = ?`
|
||||||
|
tx.Exec(query, entryId)
|
||||||
|
playbook.RemoveEntry(entryId)
|
||||||
|
query = `UPDATE playbook SET entryids = ? WHERE playbookid = ?`
|
||||||
|
tx.Exec(query, dbutil.QuickJsonArr(playbook.EntryIds), playbookId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlaybookById(ctx context.Context, playbookId string) (*PlaybookType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (*PlaybookType, error) {
|
||||||
|
rtn := selectPlaybook(tx, playbookId)
|
||||||
|
if rtn == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
query := `SELECT * FROM playbook_entry WHERE playbookid = ?`
|
||||||
|
tx.Select(&rtn.Entries, query, playbookId)
|
||||||
|
rtn.OrderEntries()
|
||||||
|
return rtn, nil
|
||||||
|
})
|
||||||
|
}
|
@ -39,6 +39,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/telemetry"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/userinput"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/userinput"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@ -1419,8 +1420,8 @@ func getStateVarsFromInitPk(initPk *packet.InitPacketType) map[string]string {
|
|||||||
return rtn
|
return rtn
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeReinitErrorUpdate(shellType string) sstore.ActivityUpdate {
|
func makeReinitErrorUpdate(shellType string) telemetry.ActivityUpdate {
|
||||||
rtn := sstore.ActivityUpdate{}
|
rtn := telemetry.ActivityUpdate{}
|
||||||
if shellType == packet.ShellType_bash {
|
if shellType == packet.ShellType_bash {
|
||||||
rtn.ReinitBashErrors = 1
|
rtn.ReinitBashErrors = 1
|
||||||
} else if shellType == packet.ShellType_zsh {
|
} else if shellType == packet.ShellType_zsh {
|
||||||
@ -1441,7 +1442,7 @@ func (msh *MShellProc) ReInit(ctx context.Context, ck base.CommandKey, shellType
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if rtnErr != nil {
|
if rtnErr != nil {
|
||||||
sstore.UpdateActivityWrap(ctx, makeReinitErrorUpdate(shellType), "reiniterror")
|
telemetry.UpdateActivityWrap(ctx, makeReinitErrorUpdate(shellType), "reiniterror")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
startTs := time.Now()
|
startTs := time.Now()
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/sawka/txwrap"
|
"github.com/sawka/txwrap"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
|
||||||
@ -25,9 +24,6 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const HistoryCols = "h.historyid, h.ts, h.userid, h.sessionid, h.screenid, h.lineid, h.haderror, h.cmdstr, h.remoteownerid, h.remoteid, h.remotename, h.ismetacmd, h.linenum, h.exitcode, h.durationms, h.festate, h.tags, h.status"
|
|
||||||
const DefaultMaxHistoryItems = 1000
|
|
||||||
|
|
||||||
var updateWriterCVar = sync.NewCond(&sync.Mutex{})
|
var updateWriterCVar = sync.NewCond(&sync.Mutex{})
|
||||||
var WebScreenPtyPosLock = &sync.Mutex{}
|
var WebScreenPtyPosLock = &sync.Mutex{}
|
||||||
var WebScreenPtyPosDelIntent = make(map[string]bool) // map[screenid + ":" + lineid] -> bool
|
var WebScreenPtyPosDelIntent = make(map[string]bool) // map[screenid + ":" + lineid] -> bool
|
||||||
@ -235,178 +231,6 @@ func UpdateRemoteStateVars(ctx context.Context, remoteId string, stateVars map[s
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func InsertHistoryItem(ctx context.Context, hitem *HistoryItemType) error {
|
|
||||||
if hitem == nil {
|
|
||||||
return fmt.Errorf("cannot insert nil history item")
|
|
||||||
}
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `INSERT INTO history
|
|
||||||
( historyid, ts, userid, sessionid, screenid, lineid, haderror, cmdstr, remoteownerid, remoteid, remotename, ismetacmd, linenum, exitcode, durationms, festate, tags, status) VALUES
|
|
||||||
(:historyid,:ts,:userid,:sessionid,:screenid,:lineid,:haderror,:cmdstr,:remoteownerid,:remoteid,:remotename,:ismetacmd,:linenum,:exitcode,:durationms,:festate,:tags,:status)`
|
|
||||||
tx.NamedExec(query, hitem.ToMap())
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
|
|
||||||
const HistoryQueryChunkSize = 1000
|
|
||||||
|
|
||||||
func _getNextHistoryItem(items []*HistoryItemType, index int, filterFn func(*HistoryItemType) bool) (*HistoryItemType, int) {
|
|
||||||
for ; index < len(items); index++ {
|
|
||||||
item := items[index]
|
|
||||||
if filterFn(item) {
|
|
||||||
return item, index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, index
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns true if done, false if we still need to process more items
|
|
||||||
func (result *HistoryQueryResult) processItem(item *HistoryItemType, rawOffset int) bool {
|
|
||||||
if result.prevItems < result.Offset {
|
|
||||||
result.prevItems++
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(result.Items) == result.MaxItems {
|
|
||||||
result.HasMore = true
|
|
||||||
result.NextRawOffset = rawOffset
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if len(result.Items) == 0 {
|
|
||||||
result.RawOffset = rawOffset
|
|
||||||
}
|
|
||||||
result.Items = append(result.Items, item)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHistoryQueryWithFilter(tx *TxWrap, opts HistoryQueryOpts) (*HistoryQueryResult, error) {
|
|
||||||
if opts.MaxItems == 0 {
|
|
||||||
return nil, fmt.Errorf("invalid query, maxitems is 0")
|
|
||||||
}
|
|
||||||
rtn := &HistoryQueryResult{Offset: opts.Offset, MaxItems: opts.MaxItems}
|
|
||||||
var rawOffset int
|
|
||||||
if opts.RawOffset >= opts.Offset {
|
|
||||||
rtn.prevItems = opts.Offset
|
|
||||||
rawOffset = opts.RawOffset
|
|
||||||
} else {
|
|
||||||
rawOffset = 0
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
resultItems, err := runHistoryQuery(tx, opts, rawOffset, HistoryQueryChunkSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
isDone := false
|
|
||||||
for resultIdx := 0; resultIdx < len(resultItems); resultIdx++ {
|
|
||||||
if opts.FilterFn != nil && !opts.FilterFn(resultItems[resultIdx]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
isDone = rtn.processItem(resultItems[resultIdx], rawOffset+resultIdx)
|
|
||||||
if isDone {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isDone {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(resultItems) < HistoryQueryChunkSize {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
rawOffset += HistoryQueryChunkSize
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHistoryQuery(tx *TxWrap, opts HistoryQueryOpts, realOffset int, itemLimit int) ([]*HistoryItemType, error) {
|
|
||||||
// check sessionid/screenid format because we are directly inserting them into the SQL
|
|
||||||
if opts.SessionId != "" {
|
|
||||||
_, err := uuid.Parse(opts.SessionId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed sessionid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.ScreenId != "" {
|
|
||||||
_, err := uuid.Parse(opts.ScreenId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed screenid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.RemoteId != "" {
|
|
||||||
_, err := uuid.Parse(opts.RemoteId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed remoteid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
whereClause := "WHERE 1"
|
|
||||||
var queryArgs []interface{}
|
|
||||||
hNumStr := ""
|
|
||||||
if opts.SessionId != "" && opts.ScreenId != "" {
|
|
||||||
whereClause += fmt.Sprintf(" AND h.sessionid = '%s' AND h.screenid = '%s'", opts.SessionId, opts.ScreenId)
|
|
||||||
hNumStr = ""
|
|
||||||
} else if opts.SessionId != "" {
|
|
||||||
whereClause += fmt.Sprintf(" AND h.sessionid = '%s'", opts.SessionId)
|
|
||||||
hNumStr = "s"
|
|
||||||
} else {
|
|
||||||
hNumStr = "g"
|
|
||||||
}
|
|
||||||
if opts.SearchText != "" {
|
|
||||||
whereClause += " AND h.cmdstr LIKE ? ESCAPE '\\'"
|
|
||||||
likeArg := opts.SearchText
|
|
||||||
likeArg = strings.ReplaceAll(likeArg, "%", "\\%")
|
|
||||||
likeArg = strings.ReplaceAll(likeArg, "_", "\\_")
|
|
||||||
queryArgs = append(queryArgs, "%"+likeArg+"%")
|
|
||||||
}
|
|
||||||
if opts.FromTs > 0 {
|
|
||||||
whereClause += fmt.Sprintf(" AND h.ts <= %d", opts.FromTs)
|
|
||||||
}
|
|
||||||
if opts.RemoteId != "" {
|
|
||||||
whereClause += fmt.Sprintf(" AND h.remoteid = '%s'", opts.RemoteId)
|
|
||||||
}
|
|
||||||
if opts.NoMeta {
|
|
||||||
whereClause += " AND NOT h.ismetacmd"
|
|
||||||
}
|
|
||||||
query := fmt.Sprintf("SELECT %s, ('%s' || CAST((row_number() OVER win) as text)) historynum FROM history h %s WINDOW win AS (ORDER BY h.ts, h.historyid) ORDER BY h.ts DESC, h.historyid DESC LIMIT %d OFFSET %d", HistoryCols, hNumStr, whereClause, itemLimit, realOffset)
|
|
||||||
marr := tx.SelectMaps(query, queryArgs...)
|
|
||||||
rtn := make([]*HistoryItemType, len(marr))
|
|
||||||
for idx, m := range marr {
|
|
||||||
hitem := dbutil.FromMap[*HistoryItemType](m)
|
|
||||||
rtn[idx] = hitem
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHistoryItems(ctx context.Context, opts HistoryQueryOpts) (*HistoryQueryResult, error) {
|
|
||||||
var rtn *HistoryQueryResult
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
var err error
|
|
||||||
rtn, err = runHistoryQueryWithFilter(tx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHistoryItemByLineNum(ctx context.Context, screenId string, lineNum int) (*HistoryItemType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (*HistoryItemType, error) {
|
|
||||||
query := `SELECT * FROM history WHERE screenid = ? AND linenum = ?`
|
|
||||||
hitem := dbutil.GetMapGen[*HistoryItemType](tx, query, screenId, lineNum)
|
|
||||||
return hitem, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLastHistoryLineNum(ctx context.Context, screenId string) (int, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (int, error) {
|
|
||||||
query := `SELECT COALESCE(max(linenum), 0) FROM history WHERE screenid = ?`
|
|
||||||
maxLineNum := tx.GetInt(query, screenId)
|
|
||||||
return maxLineNum, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// includes archived sessions
|
// includes archived sessions
|
||||||
func GetBareSessions(ctx context.Context) ([]*SessionType, error) {
|
func GetBareSessions(ctx context.Context) ([]*SessionType, error) {
|
||||||
var rtn []*SessionType
|
var rtn []*SessionType
|
||||||
@ -2273,92 +2097,6 @@ func GetRIsForScreen(ctx context.Context, sessionId string, screenId string) ([]
|
|||||||
return rtn, nil
|
return rtn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCurDayStr() string {
|
|
||||||
now := time.Now()
|
|
||||||
dayStr := now.Format("2006-01-02")
|
|
||||||
return dayStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wraps UpdateCurrentActivity, but ignores errors
|
|
||||||
func UpdateActivityWrap(ctx context.Context, update ActivityUpdate, debugStr string) {
|
|
||||||
err := UpdateCurrentActivity(ctx, update)
|
|
||||||
if err != nil {
|
|
||||||
// ignore error, just log, since this is not critical
|
|
||||||
log.Printf("error updating current activity (%s): %v\n", debugStr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateCurrentActivity(ctx context.Context, update ActivityUpdate) error {
|
|
||||||
now := time.Now()
|
|
||||||
dayStr := GetCurDayStr()
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
var tdata TelemetryData
|
|
||||||
query := `SELECT tdata FROM activity WHERE day = ?`
|
|
||||||
found := tx.Get(&tdata, query, dayStr)
|
|
||||||
if !found {
|
|
||||||
query = `INSERT INTO activity (day, uploaded, tdata, tzname, tzoffset, clientversion, clientarch, buildtime, osrelease)
|
|
||||||
VALUES (?, 0, ?, ?, ?, ?, ? , ? , ?)`
|
|
||||||
tzName, tzOffset := now.Zone()
|
|
||||||
if len(tzName) > MaxTzNameLen {
|
|
||||||
tzName = tzName[0:MaxTzNameLen]
|
|
||||||
}
|
|
||||||
tx.Exec(query, dayStr, tdata, tzName, tzOffset, scbase.WaveVersion, scbase.ClientArch(), scbase.BuildTime, scbase.UnameKernelRelease())
|
|
||||||
}
|
|
||||||
tdata.NumCommands += update.NumCommands
|
|
||||||
tdata.FgMinutes += update.FgMinutes
|
|
||||||
tdata.ActiveMinutes += update.ActiveMinutes
|
|
||||||
tdata.OpenMinutes += update.OpenMinutes
|
|
||||||
tdata.ClickShared += update.ClickShared
|
|
||||||
tdata.HistoryView += update.HistoryView
|
|
||||||
tdata.BookmarksView += update.BookmarksView
|
|
||||||
tdata.ReinitBashErrors += update.ReinitBashErrors
|
|
||||||
tdata.ReinitZshErrors += update.ReinitZshErrors
|
|
||||||
if update.NumConns > 0 {
|
|
||||||
tdata.NumConns = update.NumConns
|
|
||||||
}
|
|
||||||
query = `UPDATE activity
|
|
||||||
SET tdata = ?,
|
|
||||||
clientversion = ?,
|
|
||||||
buildtime = ?
|
|
||||||
WHERE day = ?`
|
|
||||||
tx.Exec(query, tdata, scbase.WaveVersion, scbase.BuildTime, dayStr)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetNonUploadedActivity(ctx context.Context) ([]*ActivityType, error) {
|
|
||||||
var rtn []*ActivityType
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT * FROM activity WHERE uploaded = 0 ORDER BY day DESC LIMIT 30`
|
|
||||||
tx.Select(&rtn, query)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// note, will not mark the current day as uploaded
|
|
||||||
func MarkActivityAsUploaded(ctx context.Context, activityArr []*ActivityType) error {
|
|
||||||
dayStr := GetCurDayStr()
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `UPDATE activity SET uploaded = 1 WHERE day = ?`
|
|
||||||
for _, activity := range activityArr {
|
|
||||||
if activity.Day == dayStr {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tx.Exec(query, activity.Day)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func foundInStrArr(strs []string, s string) bool {
|
func foundInStrArr(strs []string, s string) bool {
|
||||||
for _, sval := range strs {
|
for _, sval := range strs {
|
||||||
if s == sval {
|
if s == sval {
|
||||||
@ -2423,271 +2161,6 @@ func GetDBVersion(ctx context.Context) (int, error) {
|
|||||||
return version, txErr
|
return version, txErr
|
||||||
}
|
}
|
||||||
|
|
||||||
type bookmarkOrderType struct {
|
|
||||||
BookmarkId string
|
|
||||||
OrderIdx int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBookmarks(ctx context.Context, tag string) ([]*BookmarkType, error) {
|
|
||||||
var bms []*BookmarkType
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
var query string
|
|
||||||
if tag == "" {
|
|
||||||
query = `SELECT * FROM bookmark`
|
|
||||||
bms = dbutil.SelectMapsGen[*BookmarkType](tx, query)
|
|
||||||
} else {
|
|
||||||
query = `SELECT * FROM bookmark WHERE EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)`
|
|
||||||
bms = dbutil.SelectMapsGen[*BookmarkType](tx, query, tag)
|
|
||||||
}
|
|
||||||
bmMap := dbutil.MakeGenMap(bms)
|
|
||||||
var orders []bookmarkOrderType
|
|
||||||
query = `SELECT bookmarkid, orderidx FROM bookmark_order WHERE tag = ?`
|
|
||||||
tx.Select(&orders, query, tag)
|
|
||||||
for _, bmOrder := range orders {
|
|
||||||
bm := bmMap[bmOrder.BookmarkId]
|
|
||||||
if bm != nil {
|
|
||||||
bm.OrderIdx = bmOrder.OrderIdx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
return bms, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBookmarkById(ctx context.Context, bookmarkId string, tag string) (*BookmarkType, error) {
|
|
||||||
var rtn *BookmarkType
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT * FROM bookmark WHERE bookmarkid = ?`
|
|
||||||
rtn = dbutil.GetMapGen[*BookmarkType](tx, query, bookmarkId)
|
|
||||||
if rtn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
query = `SELECT orderidx FROM bookmark_order WHERE bookmarkid = ? AND tag = ?`
|
|
||||||
orderIdx := tx.GetInt(query, bookmarkId, tag)
|
|
||||||
rtn.OrderIdx = int64(orderIdx)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBookmarkIdByArg(ctx context.Context, bookmarkArg string) (string, error) {
|
|
||||||
var rtnId string
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
if len(bookmarkArg) == 8 {
|
|
||||||
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid LIKE (? || '%')`
|
|
||||||
rtnId = tx.GetString(query, bookmarkArg)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
|
||||||
rtnId = tx.GetString(query, bookmarkArg)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return "", txErr
|
|
||||||
}
|
|
||||||
return rtnId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBookmarkIdsByCmdStr(ctx context.Context, cmdStr string) ([]string, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) ([]string, error) {
|
|
||||||
query := `SELECT bookmarkid FROM bookmark WHERE cmdstr = ?`
|
|
||||||
bmIds := tx.SelectStrings(query, cmdStr)
|
|
||||||
return bmIds, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignores OrderIdx field
|
|
||||||
func InsertBookmark(ctx context.Context, bm *BookmarkType) error {
|
|
||||||
if bm == nil || bm.BookmarkId == "" {
|
|
||||||
return fmt.Errorf("invalid empty bookmark id")
|
|
||||||
}
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
|
||||||
if tx.Exists(query, bm.BookmarkId) {
|
|
||||||
return fmt.Errorf("bookmarkid already exists")
|
|
||||||
}
|
|
||||||
query = `INSERT INTO bookmark ( bookmarkid, createdts, cmdstr, alias, tags, description)
|
|
||||||
VALUES (:bookmarkid,:createdts,:cmdstr,:alias,:tags,:description)`
|
|
||||||
tx.NamedExec(query, bm.ToMap())
|
|
||||||
for _, tag := range append(bm.Tags, "") {
|
|
||||||
query = `SELECT COALESCE(max(orderidx), 0) FROM bookmark_order WHERE tag = ?`
|
|
||||||
maxOrder := tx.GetInt(query, tag)
|
|
||||||
query = `INSERT INTO bookmark_order (tag, bookmarkid, orderidx) VALUES (?, ?, ?)`
|
|
||||||
tx.Exec(query, tag, bm.BookmarkId, maxOrder+1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
BookmarkField_Desc = "desc"
|
|
||||||
BookmarkField_CmdStr = "cmdstr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func EditBookmark(ctx context.Context, bookmarkId string, editMap map[string]interface{}) error {
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
|
||||||
if !tx.Exists(query, bookmarkId) {
|
|
||||||
return fmt.Errorf("bookmark not found")
|
|
||||||
}
|
|
||||||
if desc, found := editMap[BookmarkField_Desc]; found {
|
|
||||||
query = `UPDATE bookmark SET description = ? WHERE bookmarkid = ?`
|
|
||||||
tx.Exec(query, desc, bookmarkId)
|
|
||||||
}
|
|
||||||
if cmdStr, found := editMap[BookmarkField_CmdStr]; found {
|
|
||||||
query = `UPDATE bookmark SET cmdstr = ? WHERE bookmarkid = ?`
|
|
||||||
tx.Exec(query, cmdStr, bookmarkId)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixupBookmarkOrder(tx *TxWrap) {
|
|
||||||
query := `
|
|
||||||
WITH new_order AS (
|
|
||||||
SELECT tag, bookmarkid, row_number() OVER (PARTITION BY tag ORDER BY orderidx) AS newidx FROM bookmark_order
|
|
||||||
)
|
|
||||||
UPDATE bookmark_order
|
|
||||||
SET orderidx = new_order.newidx
|
|
||||||
FROM new_order
|
|
||||||
WHERE bookmark_order.tag = new_order.tag AND bookmark_order.bookmarkid = new_order.bookmarkid
|
|
||||||
`
|
|
||||||
tx.Exec(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteBookmark(ctx context.Context, bookmarkId string) error {
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
|
|
||||||
if !tx.Exists(query, bookmarkId) {
|
|
||||||
return fmt.Errorf("bookmark not found")
|
|
||||||
}
|
|
||||||
query = `DELETE FROM bookmark WHERE bookmarkid = ?`
|
|
||||||
tx.Exec(query, bookmarkId)
|
|
||||||
query = `DELETE FROM bookmark_order WHERE bookmarkid = ?`
|
|
||||||
tx.Exec(query, bookmarkId)
|
|
||||||
fixupBookmarkOrder(tx)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreatePlaybook(ctx context.Context, name string) (*PlaybookType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (*PlaybookType, error) {
|
|
||||||
query := `SELECT playbookid FROM playbook WHERE name = ?`
|
|
||||||
if tx.Exists(query, name) {
|
|
||||||
return nil, fmt.Errorf("playbook %q already exists", name)
|
|
||||||
}
|
|
||||||
rtn := &PlaybookType{}
|
|
||||||
rtn.PlaybookId = uuid.New().String()
|
|
||||||
rtn.PlaybookName = name
|
|
||||||
query = `INSERT INTO playbook ( playbookid, playbookname, description, entryids)
|
|
||||||
VALUES (:playbookid,:playbookname,:description,:entryids)`
|
|
||||||
tx.Exec(query, rtn.ToMap())
|
|
||||||
return rtn, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectPlaybook(tx *TxWrap, playbookId string) *PlaybookType {
|
|
||||||
query := `SELECT * FROM playbook where playbookid = ?`
|
|
||||||
playbook := dbutil.GetMapGen[*PlaybookType](tx, query, playbookId)
|
|
||||||
return playbook
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddPlaybookEntry(ctx context.Context, entry *PlaybookEntry) error {
|
|
||||||
if entry.EntryId == "" {
|
|
||||||
return fmt.Errorf("invalid entryid")
|
|
||||||
}
|
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
playbook := selectPlaybook(tx, entry.PlaybookId)
|
|
||||||
if playbook == nil {
|
|
||||||
return fmt.Errorf("cannot add entry, playbook does not exist")
|
|
||||||
}
|
|
||||||
query := `SELECT entryid FROM playbook_entry WHERE entryid = ?`
|
|
||||||
if tx.Exists(query, entry.EntryId) {
|
|
||||||
return fmt.Errorf("cannot add entry, entryid already exists")
|
|
||||||
}
|
|
||||||
query = `INSERT INTO playbook_entry ( entryid, playbookid, description, alias, cmdstr, createdts, updatedts)
|
|
||||||
VALUES (:entryid,:playbookid,:description,:alias,:cmdstr,:createdts,:updatedts)`
|
|
||||||
tx.Exec(query, entry)
|
|
||||||
playbook.EntryIds = append(playbook.EntryIds, entry.EntryId)
|
|
||||||
query = `UPDATE playbook SET entryids = ? WHERE playbookid = ?`
|
|
||||||
tx.Exec(query, quickJsonArr(playbook.EntryIds), entry.PlaybookId)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemovePlaybookEntry(ctx context.Context, playbookId string, entryId string) error {
|
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
playbook := selectPlaybook(tx, playbookId)
|
|
||||||
if playbook == nil {
|
|
||||||
return fmt.Errorf("cannot remove playbook entry, playbook does not exist")
|
|
||||||
}
|
|
||||||
query := `SELECT entryid FROM playbook_entry WHERE entryid = ?`
|
|
||||||
if !tx.Exists(query, entryId) {
|
|
||||||
return fmt.Errorf("cannot remove playbook entry, entry does not exist")
|
|
||||||
}
|
|
||||||
query = `DELETE FROM playbook_entry WHERE entryid = ?`
|
|
||||||
tx.Exec(query, entryId)
|
|
||||||
playbook.RemoveEntry(entryId)
|
|
||||||
query = `UPDATE playbook SET entryids = ? WHERE playbookid = ?`
|
|
||||||
tx.Exec(query, quickJsonArr(playbook.EntryIds), playbookId)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPlaybookById(ctx context.Context, playbookId string) (*PlaybookType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (*PlaybookType, error) {
|
|
||||||
rtn := selectPlaybook(tx, playbookId)
|
|
||||||
if rtn == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
query := `SELECT * FROM playbook_entry WHERE playbookid = ?`
|
|
||||||
tx.Select(&rtn.Entries, query, playbookId)
|
|
||||||
rtn.OrderEntries()
|
|
||||||
return rtn, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLineIdsFromHistoryItems(historyItems []*HistoryItemType) []string {
|
|
||||||
var rtn []string
|
|
||||||
for _, hitem := range historyItems {
|
|
||||||
if hitem.LineId != "" {
|
|
||||||
rtn = append(rtn, hitem.LineId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rtn
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryItemType) ([]*LineType, []*CmdType, error) {
|
|
||||||
if len(historyItems) == 0 {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
return WithTxRtn3(ctx, func(tx *TxWrap) ([]*LineType, []*CmdType, error) {
|
|
||||||
lineIdsJsonArr := quickJsonArr(getLineIdsFromHistoryItems(historyItems))
|
|
||||||
query := `SELECT * FROM line WHERE lineid IN (SELECT value FROM json_each(?))`
|
|
||||||
lineArr := dbutil.SelectMappable[*LineType](tx, query, lineIdsJsonArr)
|
|
||||||
query = `SELECT * FROM cmd WHERE lineid IN (SELECT value FROM json_each(?))`
|
|
||||||
cmdArr := dbutil.SelectMapsGen[*CmdType](tx, query, lineIdsJsonArr)
|
|
||||||
return lineArr, cmdArr, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func PurgeHistoryByIds(ctx context.Context, historyIds []string) error {
|
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `DELETE FROM history WHERE historyid IN (SELECT value FROM json_each(?))`
|
|
||||||
tx.Exec(query, quickJsonArr(historyIds))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func CountScreenWebShares(ctx context.Context) (int, error) {
|
func CountScreenWebShares(ctx context.Context) (int, error) {
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (int, error) {
|
return WithTxRtn(ctx, func(tx *TxWrap) (int, error) {
|
||||||
query := `SELECT count(*) FROM screen WHERE sharemode = ?`
|
query := `SELECT count(*) FROM screen WHERE sharemode = ?`
|
||||||
@ -2704,37 +2177,38 @@ func CountScreenLines(ctx context.Context, screenId string) (int, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func CanScreenWebShare(ctx context.Context, screen *ScreenType) error {
|
// Below is currently not used and is causing circular dependency due to moving telemetry code to a new package. It will likely be rewritten whenever we add back webshare and should be moved to a different package then.
|
||||||
if screen == nil {
|
// func CanScreenWebShare(ctx context.Context, screen *ScreenType) error {
|
||||||
return fmt.Errorf("cannot share screen, not found")
|
// if screen == nil {
|
||||||
}
|
// return fmt.Errorf("cannot share screen, not found")
|
||||||
if screen.ShareMode == ShareModeWeb {
|
// }
|
||||||
return fmt.Errorf("screen is already shared to web")
|
// if screen.ShareMode == ShareModeWeb {
|
||||||
}
|
// return fmt.Errorf("screen is already shared to web")
|
||||||
if screen.ShareMode != ShareModeLocal {
|
// }
|
||||||
return fmt.Errorf("screen cannot be shared, invalid current share mode %q (must be local)", screen.ShareMode)
|
// if screen.ShareMode != ShareModeLocal {
|
||||||
}
|
// return fmt.Errorf("screen cannot be shared, invalid current share mode %q (must be local)", screen.ShareMode)
|
||||||
if screen.Archived {
|
// }
|
||||||
return fmt.Errorf("screen cannot be shared, must un-archive before sharing")
|
// if screen.Archived {
|
||||||
}
|
// return fmt.Errorf("screen cannot be shared, must un-archive before sharing")
|
||||||
webShareCount, err := CountScreenWebShares(ctx)
|
// }
|
||||||
if err != nil {
|
// webShareCount, err := CountScreenWebShares(ctx)
|
||||||
return fmt.Errorf("screen cannot be share: error getting webshare count: %v", err)
|
// if err != nil {
|
||||||
}
|
// return fmt.Errorf("screen cannot be share: error getting webshare count: %v", err)
|
||||||
if webShareCount >= MaxWebShareScreenCount {
|
// }
|
||||||
go UpdateCurrentActivity(context.Background(), ActivityUpdate{WebShareLimit: 1})
|
// if webShareCount >= MaxWebShareScreenCount {
|
||||||
return fmt.Errorf("screen cannot be shared, limited to a maximum of %d shared screen(s)", MaxWebShareScreenCount)
|
// go UpdateCurrentActivity(context.Background(), ActivityUpdate{WebShareLimit: 1})
|
||||||
}
|
// return fmt.Errorf("screen cannot be shared, limited to a maximum of %d shared screen(s)", MaxWebShareScreenCount)
|
||||||
lineCount, err := CountScreenLines(ctx, screen.ScreenId)
|
// }
|
||||||
if err != nil {
|
// lineCount, err := CountScreenLines(ctx, screen.ScreenId)
|
||||||
return fmt.Errorf("screen cannot be share: error getting screen line count: %v", err)
|
// if err != nil {
|
||||||
}
|
// return fmt.Errorf("screen cannot be share: error getting screen line count: %v", err)
|
||||||
if lineCount > MaxWebShareLineCount {
|
// }
|
||||||
go UpdateCurrentActivity(context.Background(), ActivityUpdate{WebShareLimit: 1})
|
// if lineCount > MaxWebShareLineCount {
|
||||||
return fmt.Errorf("screen cannot be shared, limited to a maximum of %d lines", MaxWebShareLineCount)
|
// go UpdateCurrentActivity(context.Background(), ActivityUpdate{WebShareLimit: 1})
|
||||||
}
|
// return fmt.Errorf("screen cannot be shared, limited to a maximum of %d lines", MaxWebShareLineCount)
|
||||||
return nil
|
// }
|
||||||
}
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
func ScreenWebShareStart(ctx context.Context, screenId string, shareOpts ScreenWebShareOpts) error {
|
func ScreenWebShareStart(ctx context.Context, screenId string, shareOpts ScreenWebShareOpts) error {
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
return WithTx(ctx, func(tx *TxWrap) error {
|
||||||
|
@ -110,6 +110,7 @@ const (
|
|||||||
SSHConfigSrcTypeImport = "sshconfig-import"
|
SSHConfigSrcTypeImport = "sshconfig-import"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: move to webshare package once sstore code is more modular
|
||||||
const (
|
const (
|
||||||
ShareModeLocal = "local"
|
ShareModeLocal = "local"
|
||||||
ShareModeWeb = "web"
|
ShareModeWeb = "web"
|
||||||
@ -154,8 +155,6 @@ const (
|
|||||||
UpdateType_PtyPos = "pty:pos"
|
UpdateType_PtyPos = "pty:pos"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxTzNameLen = 50
|
|
||||||
|
|
||||||
var globalDBLock = &sync.Mutex{}
|
var globalDBLock = &sync.Mutex{}
|
||||||
var globalDB *sqlx.DB
|
var globalDB *sqlx.DB
|
||||||
var globalDBErr error
|
var globalDBErr error
|
||||||
@ -233,56 +232,6 @@ type ClientWinSizeType struct {
|
|||||||
FullScreen bool `json:"fullscreen,omitempty"`
|
FullScreen bool `json:"fullscreen,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActivityUpdate struct {
|
|
||||||
FgMinutes int
|
|
||||||
ActiveMinutes int
|
|
||||||
OpenMinutes int
|
|
||||||
NumCommands int
|
|
||||||
ClickShared int
|
|
||||||
HistoryView int
|
|
||||||
BookmarksView int
|
|
||||||
NumConns int
|
|
||||||
WebShareLimit int
|
|
||||||
ReinitBashErrors int
|
|
||||||
ReinitZshErrors int
|
|
||||||
BuildTime string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActivityType struct {
|
|
||||||
Day string `json:"day"`
|
|
||||||
Uploaded bool `json:"-"`
|
|
||||||
TData TelemetryData `json:"tdata"`
|
|
||||||
TzName string `json:"tzname"`
|
|
||||||
TzOffset int `json:"tzoffset"`
|
|
||||||
ClientVersion string `json:"clientversion"`
|
|
||||||
ClientArch string `json:"clientarch"`
|
|
||||||
BuildTime string `json:"buildtime"`
|
|
||||||
DefaultShell string `json:"defaultshell"`
|
|
||||||
OSRelease string `json:"osrelease"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TelemetryData struct {
|
|
||||||
NumCommands int `json:"numcommands"`
|
|
||||||
ActiveMinutes int `json:"activeminutes"`
|
|
||||||
FgMinutes int `json:"fgminutes"`
|
|
||||||
OpenMinutes int `json:"openminutes"`
|
|
||||||
ClickShared int `json:"clickshared,omitempty"`
|
|
||||||
HistoryView int `json:"historyview,omitempty"`
|
|
||||||
BookmarksView int `json:"bookmarksview,omitempty"`
|
|
||||||
NumConns int `json:"numconns"`
|
|
||||||
WebShareLimit int `json:"websharelimit,omitempty"`
|
|
||||||
ReinitBashErrors int `json:"reinitbasherrors,omitempty"`
|
|
||||||
ReinitZshErrors int `json:"reinitzsherrors,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tdata TelemetryData) Value() (driver.Value, error) {
|
|
||||||
return quickValueJson(tdata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tdata *TelemetryData) Scan(val interface{}) error {
|
|
||||||
return quickScanJson(tdata, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SidebarValueType struct {
|
type SidebarValueType struct {
|
||||||
Collapsed bool `json:"collapsed"`
|
Collapsed bool `json:"collapsed"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
@ -398,52 +347,6 @@ type SessionStatsType struct {
|
|||||||
DiskStats SessionDiskSizeType `json:"diskstats"`
|
DiskStats SessionDiskSizeType `json:"diskstats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HistoryItemType) ToMap() map[string]interface{} {
|
|
||||||
rtn := make(map[string]interface{})
|
|
||||||
rtn["historyid"] = h.HistoryId
|
|
||||||
rtn["ts"] = h.Ts
|
|
||||||
rtn["userid"] = h.UserId
|
|
||||||
rtn["sessionid"] = h.SessionId
|
|
||||||
rtn["screenid"] = h.ScreenId
|
|
||||||
rtn["lineid"] = h.LineId
|
|
||||||
rtn["linenum"] = h.LineNum
|
|
||||||
rtn["haderror"] = h.HadError
|
|
||||||
rtn["cmdstr"] = h.CmdStr
|
|
||||||
rtn["remoteownerid"] = h.Remote.OwnerId
|
|
||||||
rtn["remoteid"] = h.Remote.RemoteId
|
|
||||||
rtn["remotename"] = h.Remote.Name
|
|
||||||
rtn["ismetacmd"] = h.IsMetaCmd
|
|
||||||
rtn["exitcode"] = h.ExitCode
|
|
||||||
rtn["durationms"] = h.DurationMs
|
|
||||||
rtn["festate"] = quickJson(h.FeState)
|
|
||||||
rtn["tags"] = quickJson(h.Tags)
|
|
||||||
rtn["status"] = h.Status
|
|
||||||
return rtn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *HistoryItemType) FromMap(m map[string]interface{}) bool {
|
|
||||||
quickSetStr(&h.HistoryId, m, "historyid")
|
|
||||||
quickSetInt64(&h.Ts, m, "ts")
|
|
||||||
quickSetStr(&h.UserId, m, "userid")
|
|
||||||
quickSetStr(&h.SessionId, m, "sessionid")
|
|
||||||
quickSetStr(&h.ScreenId, m, "screenid")
|
|
||||||
quickSetStr(&h.LineId, m, "lineid")
|
|
||||||
quickSetBool(&h.HadError, m, "haderror")
|
|
||||||
quickSetStr(&h.CmdStr, m, "cmdstr")
|
|
||||||
quickSetStr(&h.Remote.OwnerId, m, "remoteownerid")
|
|
||||||
quickSetStr(&h.Remote.RemoteId, m, "remoteid")
|
|
||||||
quickSetStr(&h.Remote.Name, m, "remotename")
|
|
||||||
quickSetBool(&h.IsMetaCmd, m, "ismetacmd")
|
|
||||||
quickSetStr(&h.HistoryNum, m, "historynum")
|
|
||||||
quickSetInt64(&h.LineNum, m, "linenum")
|
|
||||||
dbutil.QuickSetNullableInt64(&h.ExitCode, m, "exitcode")
|
|
||||||
dbutil.QuickSetNullableInt64(&h.DurationMs, m, "durationms")
|
|
||||||
quickSetJson(&h.FeState, m, "festate")
|
|
||||||
quickSetJson(&h.Tags, m, "tags")
|
|
||||||
quickSetStr(&h.Status, m, "status")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenOptsType struct {
|
type ScreenOptsType struct {
|
||||||
TabColor string `json:"tabcolor,omitempty"`
|
TabColor string `json:"tabcolor,omitempty"`
|
||||||
TabIcon string `json:"tabicon,omitempty"`
|
TabIcon string `json:"tabicon,omitempty"`
|
||||||
@ -619,55 +522,6 @@ type ScreenAnchorType struct {
|
|||||||
AnchorOffset int `json:"anchoroffset,omitempty"`
|
AnchorOffset int `json:"anchoroffset,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HistoryItemType struct {
|
|
||||||
HistoryId string `json:"historyid"`
|
|
||||||
Ts int64 `json:"ts"`
|
|
||||||
UserId string `json:"userid"`
|
|
||||||
SessionId string `json:"sessionid"`
|
|
||||||
ScreenId string `json:"screenid"`
|
|
||||||
LineId string `json:"lineid"`
|
|
||||||
HadError bool `json:"haderror"`
|
|
||||||
CmdStr string `json:"cmdstr"`
|
|
||||||
Remote RemotePtrType `json:"remote"`
|
|
||||||
IsMetaCmd bool `json:"ismetacmd"`
|
|
||||||
ExitCode *int64 `json:"exitcode,omitempty"`
|
|
||||||
DurationMs *int64 `json:"durationms,omitempty"`
|
|
||||||
FeState FeStateType `json:"festate,omitempty"`
|
|
||||||
Tags map[string]bool `json:"tags,omitempty"`
|
|
||||||
LineNum int64 `json:"linenum" dbmap:"-"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
|
|
||||||
// only for updates
|
|
||||||
Remove bool `json:"remove" dbmap:"-"`
|
|
||||||
|
|
||||||
// transient (string because of different history orderings)
|
|
||||||
HistoryNum string `json:"historynum" dbmap:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryQueryOpts struct {
|
|
||||||
Offset int
|
|
||||||
MaxItems int
|
|
||||||
FromTs int64
|
|
||||||
SearchText string
|
|
||||||
SessionId string
|
|
||||||
RemoteId string
|
|
||||||
ScreenId string
|
|
||||||
NoMeta bool
|
|
||||||
RawOffset int
|
|
||||||
FilterFn func(*HistoryItemType) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryQueryResult struct {
|
|
||||||
MaxItems int
|
|
||||||
Items []*HistoryItemType
|
|
||||||
Offset int // the offset shown to user
|
|
||||||
RawOffset int // internal offset
|
|
||||||
HasMore bool
|
|
||||||
NextRawOffset int // internal offset used by pager for next query
|
|
||||||
|
|
||||||
prevItems int // holds number of items skipped by RawOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
type TermOpts struct {
|
type TermOpts struct {
|
||||||
Rows int64 `json:"rows"`
|
Rows int64 `json:"rows"`
|
||||||
Cols int64 `json:"cols"`
|
Cols int64 `json:"cols"`
|
||||||
@ -850,114 +704,6 @@ type OpenAIResponse struct {
|
|||||||
Choices []OpenAIChoiceType `json:"choices,omitempty"`
|
Choices []OpenAIChoiceType `json:"choices,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlaybookType struct {
|
|
||||||
PlaybookId string `json:"playbookid"`
|
|
||||||
PlaybookName string `json:"playbookname"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
EntryIds []string `json:"entryids"`
|
|
||||||
|
|
||||||
// this is not persisted to DB, just for transport to FE
|
|
||||||
Entries []*PlaybookEntry `json:"entries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PlaybookType) ToMap() map[string]interface{} {
|
|
||||||
rtn := make(map[string]interface{})
|
|
||||||
rtn["playbookid"] = p.PlaybookId
|
|
||||||
rtn["playbookname"] = p.PlaybookName
|
|
||||||
rtn["description"] = p.Description
|
|
||||||
rtn["entryids"] = quickJsonArr(p.EntryIds)
|
|
||||||
return rtn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PlaybookType) FromMap(m map[string]interface{}) bool {
|
|
||||||
quickSetStr(&p.PlaybookId, m, "playbookid")
|
|
||||||
quickSetStr(&p.PlaybookName, m, "playbookname")
|
|
||||||
quickSetStr(&p.Description, m, "description")
|
|
||||||
quickSetJsonArr(&p.Entries, m, "entries")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// reorders p.Entries to match p.EntryIds
|
|
||||||
func (p *PlaybookType) OrderEntries() {
|
|
||||||
if len(p.Entries) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m := make(map[string]*PlaybookEntry)
|
|
||||||
for _, entry := range p.Entries {
|
|
||||||
m[entry.EntryId] = entry
|
|
||||||
}
|
|
||||||
newList := make([]*PlaybookEntry, 0, len(p.EntryIds))
|
|
||||||
for _, entryId := range p.EntryIds {
|
|
||||||
entry := m[entryId]
|
|
||||||
if entry != nil {
|
|
||||||
newList = append(newList, entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.Entries = newList
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes from p.EntryIds (not from p.Entries)
|
|
||||||
func (p *PlaybookType) RemoveEntry(entryIdToRemove string) {
|
|
||||||
if len(p.EntryIds) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newList := make([]string, 0, len(p.EntryIds)-1)
|
|
||||||
for _, entryId := range p.EntryIds {
|
|
||||||
if entryId == entryIdToRemove {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newList = append(newList, entryId)
|
|
||||||
}
|
|
||||||
p.EntryIds = newList
|
|
||||||
}
|
|
||||||
|
|
||||||
type PlaybookEntry struct {
|
|
||||||
PlaybookId string `json:"playbookid"`
|
|
||||||
EntryId string `json:"entryid"`
|
|
||||||
Alias string `json:"alias"`
|
|
||||||
CmdStr string `json:"cmdstr"`
|
|
||||||
UpdatedTs int64 `json:"updatedts"`
|
|
||||||
CreatedTs int64 `json:"createdts"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Remove bool `json:"remove,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BookmarkType struct {
|
|
||||||
BookmarkId string `json:"bookmarkid"`
|
|
||||||
CreatedTs int64 `json:"createdts"`
|
|
||||||
CmdStr string `json:"cmdstr"`
|
|
||||||
Alias string `json:"alias,omitempty"`
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
OrderIdx int64 `json:"orderidx"`
|
|
||||||
Remove bool `json:"remove,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BookmarkType) GetSimpleKey() string {
|
|
||||||
return bm.BookmarkId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BookmarkType) ToMap() map[string]interface{} {
|
|
||||||
rtn := make(map[string]interface{})
|
|
||||||
rtn["bookmarkid"] = bm.BookmarkId
|
|
||||||
rtn["createdts"] = bm.CreatedTs
|
|
||||||
rtn["cmdstr"] = bm.CmdStr
|
|
||||||
rtn["alias"] = bm.Alias
|
|
||||||
rtn["description"] = bm.Description
|
|
||||||
rtn["tags"] = quickJsonArr(bm.Tags)
|
|
||||||
return rtn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BookmarkType) FromMap(m map[string]interface{}) bool {
|
|
||||||
quickSetStr(&bm.BookmarkId, m, "bookmarkid")
|
|
||||||
quickSetInt64(&bm.CreatedTs, m, "createdts")
|
|
||||||
quickSetStr(&bm.Alias, m, "alias")
|
|
||||||
quickSetStr(&bm.CmdStr, m, "cmdstr")
|
|
||||||
quickSetStr(&bm.Description, m, "description")
|
|
||||||
quickSetJsonArr(&bm.Tags, m, "tags")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResolveItem struct {
|
type ResolveItem struct {
|
||||||
Name string
|
Name string
|
||||||
Num int
|
Num int
|
||||||
|
@ -91,18 +91,6 @@ func (ClearInfoUpdate) GetType() string {
|
|||||||
return "clearinfo"
|
return "clearinfo"
|
||||||
}
|
}
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (HistoryInfoType) GetType() string {
|
|
||||||
return "history"
|
|
||||||
}
|
|
||||||
|
|
||||||
type InteractiveUpdate bool
|
type InteractiveUpdate bool
|
||||||
|
|
||||||
func (InteractiveUpdate) GetType() string {
|
func (InteractiveUpdate) GetType() string {
|
||||||
@ -122,43 +110,6 @@ func (ConnectUpdate) GetType() string {
|
|||||||
return "connect"
|
return "connect"
|
||||||
}
|
}
|
||||||
|
|
||||||
type MainViewUpdate struct {
|
|
||||||
MainView string `json:"mainview"`
|
|
||||||
HistoryView *HistoryViewData `json:"historyview,omitempty"`
|
|
||||||
BookmarksView *BookmarksUpdate `json:"bookmarksview,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (MainViewUpdate) GetType() string {
|
|
||||||
return "mainview"
|
|
||||||
}
|
|
||||||
|
|
||||||
type BookmarksUpdate struct {
|
|
||||||
Bookmarks []*BookmarkType `json:"bookmarks"`
|
|
||||||
SelectedBookmark string `json:"selectedbookmark,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (BookmarksUpdate) GetType() string {
|
|
||||||
return "bookmarks"
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddBookmarksUpdate(update *scbus.ModelUpdatePacketType, bookmarks []*BookmarkType, selectedBookmark *string) {
|
|
||||||
if selectedBookmark == nil {
|
|
||||||
update.AddUpdate(BookmarksUpdate{Bookmarks: bookmarks})
|
|
||||||
} else {
|
|
||||||
update.AddUpdate(BookmarksUpdate{Bookmarks: bookmarks, SelectedBookmark: *selectedBookmark})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
type RemoteEditType struct {
|
||||||
RemoteEdit bool `json:"remoteedit"`
|
RemoteEdit bool `json:"remoteedit"`
|
||||||
RemoteId string `json:"remoteid,omitempty"`
|
RemoteId string `json:"remoteid,omitempty"`
|
||||||
|
153
wavesrv/pkg/telemetry/telemetry.go
Normal file
153
wavesrv/pkg/telemetry/telemetry.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package telemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MaxTzNameLen = 50
|
||||||
|
|
||||||
|
type ActivityUpdate struct {
|
||||||
|
FgMinutes int
|
||||||
|
ActiveMinutes int
|
||||||
|
OpenMinutes int
|
||||||
|
NumCommands int
|
||||||
|
ClickShared int
|
||||||
|
HistoryView int
|
||||||
|
BookmarksView int
|
||||||
|
NumConns int
|
||||||
|
WebShareLimit int
|
||||||
|
ReinitBashErrors int
|
||||||
|
ReinitZshErrors int
|
||||||
|
BuildTime string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityType struct {
|
||||||
|
Day string `json:"day"`
|
||||||
|
Uploaded bool `json:"-"`
|
||||||
|
TData TelemetryData `json:"tdata"`
|
||||||
|
TzName string `json:"tzname"`
|
||||||
|
TzOffset int `json:"tzoffset"`
|
||||||
|
ClientVersion string `json:"clientversion"`
|
||||||
|
ClientArch string `json:"clientarch"`
|
||||||
|
BuildTime string `json:"buildtime"`
|
||||||
|
DefaultShell string `json:"defaultshell"`
|
||||||
|
OSRelease string `json:"osrelease"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelemetryData struct {
|
||||||
|
NumCommands int `json:"numcommands"`
|
||||||
|
ActiveMinutes int `json:"activeminutes"`
|
||||||
|
FgMinutes int `json:"fgminutes"`
|
||||||
|
OpenMinutes int `json:"openminutes"`
|
||||||
|
ClickShared int `json:"clickshared,omitempty"`
|
||||||
|
HistoryView int `json:"historyview,omitempty"`
|
||||||
|
BookmarksView int `json:"bookmarksview,omitempty"`
|
||||||
|
NumConns int `json:"numconns"`
|
||||||
|
WebShareLimit int `json:"websharelimit,omitempty"`
|
||||||
|
ReinitBashErrors int `json:"reinitbasherrors,omitempty"`
|
||||||
|
ReinitZshErrors int `json:"reinitzsherrors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tdata TelemetryData) Value() (driver.Value, error) {
|
||||||
|
return dbutil.QuickValueJson(tdata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tdata *TelemetryData) Scan(val interface{}) error {
|
||||||
|
return dbutil.QuickScanJson(tdata, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wraps UpdateCurrentActivity, but ignores errors
|
||||||
|
func UpdateActivityWrap(ctx context.Context, update ActivityUpdate, debugStr string) {
|
||||||
|
err := UpdateCurrentActivity(ctx, update)
|
||||||
|
if err != nil {
|
||||||
|
// ignore error, just log, since this is not critical
|
||||||
|
log.Printf("error updating current activity (%s): %v\n", debugStr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCurDayStr() string {
|
||||||
|
now := time.Now()
|
||||||
|
dayStr := now.Format("2006-01-02")
|
||||||
|
return dayStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCurrentActivity(ctx context.Context, update ActivityUpdate) error {
|
||||||
|
now := time.Now()
|
||||||
|
dayStr := GetCurDayStr()
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
var tdata TelemetryData
|
||||||
|
query := `SELECT tdata FROM activity WHERE day = ?`
|
||||||
|
found := tx.Get(&tdata, query, dayStr)
|
||||||
|
if !found {
|
||||||
|
query = `INSERT INTO activity (day, uploaded, tdata, tzname, tzoffset, clientversion, clientarch, buildtime, osrelease)
|
||||||
|
VALUES (?, 0, ?, ?, ?, ?, ? , ? , ?)`
|
||||||
|
tzName, tzOffset := now.Zone()
|
||||||
|
if len(tzName) > MaxTzNameLen {
|
||||||
|
tzName = tzName[0:MaxTzNameLen]
|
||||||
|
}
|
||||||
|
tx.Exec(query, dayStr, tdata, tzName, tzOffset, scbase.WaveVersion, scbase.ClientArch(), scbase.BuildTime, scbase.UnameKernelRelease())
|
||||||
|
}
|
||||||
|
tdata.NumCommands += update.NumCommands
|
||||||
|
tdata.FgMinutes += update.FgMinutes
|
||||||
|
tdata.ActiveMinutes += update.ActiveMinutes
|
||||||
|
tdata.OpenMinutes += update.OpenMinutes
|
||||||
|
tdata.ClickShared += update.ClickShared
|
||||||
|
tdata.HistoryView += update.HistoryView
|
||||||
|
tdata.BookmarksView += update.BookmarksView
|
||||||
|
tdata.ReinitBashErrors += update.ReinitBashErrors
|
||||||
|
tdata.ReinitZshErrors += update.ReinitZshErrors
|
||||||
|
if update.NumConns > 0 {
|
||||||
|
tdata.NumConns = update.NumConns
|
||||||
|
}
|
||||||
|
query = `UPDATE activity
|
||||||
|
SET tdata = ?,
|
||||||
|
clientversion = ?,
|
||||||
|
buildtime = ?
|
||||||
|
WHERE day = ?`
|
||||||
|
tx.Exec(query, tdata, scbase.WaveVersion, scbase.BuildTime, dayStr)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNonUploadedActivity(ctx context.Context) ([]*ActivityType, error) {
|
||||||
|
var rtn []*ActivityType
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT * FROM activity WHERE uploaded = 0 ORDER BY day DESC LIMIT 30`
|
||||||
|
tx.Select(&rtn, query)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// note, will not mark the current day as uploaded
|
||||||
|
func MarkActivityAsUploaded(ctx context.Context, activityArr []*ActivityType) error {
|
||||||
|
dayStr := GetCurDayStr()
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `UPDATE activity SET uploaded = 1 WHERE day = ?`
|
||||||
|
for _, activity := range activityArr {
|
||||||
|
if activity.Day == dayStr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tx.Exec(query, activity.Day)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user