diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 74b350277..0fd1a2234 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -175,7 +175,6 @@ func main() { log.Printf("error setting auth key: %v\n", err) return } - err = service.ValidateServiceMap() if err != nil { log.Printf("error validating service map: %v\n", err) @@ -186,6 +185,11 @@ func main() { log.Printf("error ensuring wave home dir: %v\n", err) return } + err = wavebase.EnsureWaveDBDir() + if err != nil { + log.Printf("error ensuring wave db dir: %v\n", err) + return + } waveLock, err := wavebase.AcquireWaveLock() if err != nil { log.Printf("error acquiring wave lock (another instance of Wave is likely running): %v\n", err) @@ -197,7 +201,6 @@ func main() { log.Printf("error releasing wave lock: %v\n", err) } }() - log.Printf("wave version: %s (%s)\n", WaveVersion, BuildTime) log.Printf("wave home dir: %s\n", wavebase.GetWaveHomeDir()) err = filestore.InitFilestore() @@ -210,6 +213,10 @@ func main() { log.Printf("error initializing wstore: %v\n", err) return } + migrateErr := wstore.TryMigrateOldHistory() + if migrateErr != nil { + log.Printf("error migrating old history: %v\n", migrateErr) + } go func() { err := shellutil.InitCustomShellStartupFiles() if err != nil { diff --git a/db/migrations-wstore/000004_history.down.sql b/db/migrations-wstore/000004_history.down.sql new file mode 100644 index 000000000..556e9b40a --- /dev/null +++ b/db/migrations-wstore/000004_history.down.sql @@ -0,0 +1 @@ +DROP TABLE history_migrated; \ No newline at end of file diff --git a/db/migrations-wstore/000004_history.up.sql b/db/migrations-wstore/000004_history.up.sql new file mode 100644 index 000000000..6b0f2a684 --- /dev/null +++ b/db/migrations-wstore/000004_history.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE history_migrated ( + historyid varchar(36) PRIMARY KEY, + ts bigint NOT NULL, + remotename varchar(200) NOT NULL, + haderror boolean NOT NULL, + cmdstr text NOT NULL, + exitcode int NULL DEFAULT NULL, + durationms int NULL DEFAULT NULL +); diff --git a/emain/emain.ts b/emain/emain.ts index b20f3d065..2e62d0ae1 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -65,7 +65,7 @@ const waveHome = getWaveHomeDir(); const oldConsoleLog = console.log; const loggerTransports: winston.transport[] = [ - new winston.transports.File({ filename: path.join(getWaveHomeDir(), "waveterm-app.log"), level: "info" }), + new winston.transports.File({ filename: path.join(getWaveHomeDir(), "waveapp.log"), level: "info" }), ]; if (isDev) { loggerTransports.push(new winston.transports.Console()); diff --git a/frontend/app/asset/dots-anim-4.svg b/frontend/app/asset/dots-anim-4.svg new file mode 100644 index 000000000..028aaf349 --- /dev/null +++ b/frontend/app/asset/dots-anim-4.svg @@ -0,0 +1,18 @@ + +dots anim 4 + + + + + + + + + \ No newline at end of file diff --git a/frontend/app/block/block.less b/frontend/app/block/block.less index b4718cde4..bc13f890a 100644 --- a/frontend/app/block/block.less +++ b/frontend/app/block/block.less @@ -174,6 +174,15 @@ overflow: hidden; padding-right: 4px; } + + .connecting-svg { + position: relative; + top: 5px; + left: 9px; + svg { + fill: var(--warning-color); + } + } } .block-frame-textelems-wrapper { diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx index 33ba28736..a5f22b9ea 100644 --- a/frontend/app/block/blockutil.tsx +++ b/frontend/app/block/blockutil.tsx @@ -9,6 +9,7 @@ import * as util from "@/util/util"; import clsx from "clsx"; import * as jotai from "jotai"; import * as React from "react"; +import DotsSvg from "../asset/dots-anim-4.svg"; export const colorRegex = /^((#[0-9a-f]{6,8})|([a-z]+))$/; @@ -225,11 +226,16 @@ export const ConnectionButton = React.memo( } else { titleText = "Connected to " + connection; let iconName = "arrow-right-arrow-left"; + let iconSvg = null; if (connStatus?.status == "connecting") { color = "var(--warning-color)"; titleText = "Connecting to " + connection; - iconName = "rotate"; - shouldSpin = true; + shouldSpin = false; + iconSvg = ( +
+ +
+ ); } else if (connStatus?.status == "error") { color = "var(--error-color)"; titleText = "Error connecting to " + connection; @@ -242,12 +248,16 @@ export const ConnectionButton = React.memo( titleText = "Disconnected from " + connection; showDisconnectedSlash = true; } - connIconElem = ( - - ); + if (iconSvg != null) { + connIconElem = iconSvg; + } else { + connIconElem = ( + + ); + } } return ( diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 3013f3de0..0993c2cdd 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -36,6 +36,7 @@ declare global { type Client = WaveObj & { windowids: string[]; tosagreed?: number; + historymigrated?: boolean; }; // wshrpc.CommandAppendIJsonData diff --git a/pkg/filestore/blockstore_dbsetup.go b/pkg/filestore/blockstore_dbsetup.go index 3580ebd65..11bf1fd11 100644 --- a/pkg/filestore/blockstore_dbsetup.go +++ b/pkg/filestore/blockstore_dbsetup.go @@ -51,7 +51,7 @@ func InitFilestore() error { func GetDBName() string { waveHome := wavebase.GetWaveHomeDir() - return filepath.Join(waveHome, FilestoreDBName) + return filepath.Join(waveHome, wavebase.WaveDBDir, FilestoreDBName) } func MakeDB(ctx context.Context) (*sqlx.DB, error) { diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go index f755dd8fa..ac3cfdf22 100644 --- a/pkg/util/shellutil/shellutil.go +++ b/pkg/util/shellutil/shellutil.go @@ -36,9 +36,9 @@ const WaveAppPathVarName = "WAVETERM_APP_PATH" const AppPathBinDir = "bin" const ( - ZshIntegrationDir = "zsh-integration" - BashIntegrationDir = "bash-integration" - PwshIntegrationDir = "pwsh-integration" + ZshIntegrationDir = "shell/zsh" + BashIntegrationDir = "shell/bash" + PwshIntegrationDir = "shell/pwsh" WaveHomeBinDir = "bin" ZshStartup_Zprofile = ` diff --git a/pkg/wavebase/wavebase.go b/pkg/wavebase/wavebase.go index 76545da5e..5cb50aaa9 100644 --- a/pkg/wavebase/wavebase.go +++ b/pkg/wavebase/wavebase.go @@ -25,12 +25,13 @@ import ( var WaveVersion = "0.0.0" var BuildTime = "0" -const DefaultWaveHome = "~/.w2" -const DevWaveHome = "~/.w2-dev" +const DefaultWaveHome = "~/.waveterm" +const DevWaveHome = "~/.waveterm-dev" const WaveHomeVarName = "WAVETERM_HOME" const WaveDevVarName = "WAVETERM_DEV" -const WaveLockFile = "waveterm.lock" +const WaveLockFile = "wave.lock" const DomainSocketBaseName = "wave.sock" +const WaveDBDir = "db" const JwtSecret = "waveterm" // TODO generate and store this var baseLock = &sync.Mutex{} @@ -90,6 +91,10 @@ func EnsureWaveHomeDir() error { return CacheEnsureDir(GetWaveHomeDir(), "wavehome", 0700, "wave home directory") } +func EnsureWaveDBDir() error { + return CacheEnsureDir(filepath.Join(GetWaveHomeDir(), WaveDBDir), "wavedb", 0700, "wave db directory") +} + func CacheEnsureDir(dirName string, cacheKey string, perm os.FileMode, dirDesc string) error { baseLock.Lock() ok := ensureDirCache[cacheKey] diff --git a/pkg/waveobj/wtype.go b/pkg/waveobj/wtype.go index 6e53cf1cf..9494c995a 100644 --- a/pkg/waveobj/wtype.go +++ b/pkg/waveobj/wtype.go @@ -112,11 +112,12 @@ func (update *WaveObjUpdate) UnmarshalJSON(data []byte) error { } type Client struct { - OID string `json:"oid"` - Version int `json:"version"` - WindowIds []string `json:"windowids"` - Meta MetaMapType `json:"meta"` - TosAgreed int64 `json:"tosagreed,omitempty"` + OID string `json:"oid"` + Version int `json:"version"` + WindowIds []string `json:"windowids"` + Meta MetaMapType `json:"meta"` + TosAgreed int64 `json:"tosagreed,omitempty"` + HistoryMigrated bool `json:"historymigrated,omitempty"` } func (*Client) GetOType() string { diff --git a/pkg/wstore/wstore_dboldmigration.go b/pkg/wstore/wstore_dboldmigration.go new file mode 100644 index 000000000..0fcbff5c1 --- /dev/null +++ b/pkg/wstore/wstore_dboldmigration.go @@ -0,0 +1,112 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package wstore + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/jmoiron/sqlx" + "github.com/wavetermdev/thenextwave/pkg/wavebase" + "github.com/wavetermdev/thenextwave/pkg/waveobj" +) + +const OldDBName = "~/.waveterm/waveterm.db" + +func GetOldDBName() string { + return wavebase.ExpandHomeDir(OldDBName) +} + +func MakeOldDB(ctx context.Context) (*sqlx.DB, error) { + dbName := GetOldDBName() + rtn, err := sqlx.Open("sqlite3", fmt.Sprintf("file:%s?mode=ro&_busy_timeout=5000", dbName)) + if err != nil { + return nil, err + } + rtn.DB.SetMaxOpenConns(1) + return rtn, nil +} + +type OldHistoryType struct { + HistoryId string + Ts int64 + RemoteName string + HadError bool + CmdStr string + ExitCode int + DurationMs int64 +} + +func GetAllOldHistory() ([]*OldHistoryType, error) { + query := ` + SELECT + h.historyid, + h.ts, + COALESCE(r.remotecanonicalname, '') as remotename, + h.haderror, + h.cmdstr, + COALESCE(h.exitcode, 0) as exitcode, + COALESCE(h.durationms, 0) as durationms + FROM history h, remote r + WHERE h.remoteid = r.remoteid + AND NOT h.ismetacmd + ` + db, err := MakeOldDB(context.Background()) + if err != nil { + return nil, err + } + defer db.Close() + var rtn []*OldHistoryType + err = db.Select(&rtn, query) + if err != nil { + return nil, err + } + return rtn, nil +} + +func ReplaceOldHistory(ctx context.Context, hist []*OldHistoryType) error { + return WithTx(ctx, func(tx *TxWrap) error { + query := `DELETE FROM history_migrated` + tx.Exec(query) + query = `INSERT INTO history_migrated (historyid, ts, remotename, haderror, cmdstr, exitcode, durationms) + VALUES (?, ?, ?, ?, ?, ?, ?)` + for _, hobj := range hist { + tx.Exec(query, hobj.HistoryId, hobj.Ts, hobj.RemoteName, hobj.HadError, hobj.CmdStr, hobj.ExitCode, hobj.DurationMs) + } + return nil + }) +} + +func TryMigrateOldHistory() error { + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + client, err := DBGetSingleton[*waveobj.Client](ctx) + if err != nil { + return err + } + if client.HistoryMigrated { + return nil + } + log.Printf("trying to migrate old wave history\n") + client.HistoryMigrated = true + err = DBUpdate(ctx, client) + if err != nil { + return err + } + hist, err := GetAllOldHistory() + if err != nil { + return err + } + if len(hist) == 0 { + return nil + } + err = ReplaceOldHistory(ctx, hist) + if err != nil { + return err + } + log.Printf("migrated %d old wave history records\n", len(hist)) + return nil +} diff --git a/pkg/wstore/wstore_dbsetup.go b/pkg/wstore/wstore_dbsetup.go index b6258da8d..fa255ed87 100644 --- a/pkg/wstore/wstore_dbsetup.go +++ b/pkg/wstore/wstore_dbsetup.go @@ -43,7 +43,7 @@ func InitWStore() error { func GetDBName() string { waveHome := wavebase.GetWaveHomeDir() - return filepath.Join(waveHome, WStoreDBName) + return filepath.Join(waveHome, wavebase.WaveDBDir, WStoreDBName) } func MakeDB(ctx context.Context) (*sqlx.DB, error) {