diff --git a/src/app/appconst.ts b/src/app/appconst.ts
index aa4c29a0..69bcf604 100644
--- a/src/app/appconst.ts
+++ b/src/app/appconst.ts
@@ -13,4 +13,6 @@ export const LineContainer_Main = "main";
export const LineContainer_History = "history";
export const LineContainer_Sidebar = "sidebar";
+export const ConfirmKey_HideShellPrompt = "hideshellprompt";
+
export const NoStrPos = -1;
diff --git a/src/app/common/common.less b/src/app/common/common.less
index 8b4b832a..8552e15f 100644
--- a/src/app/common/common.less
+++ b/src/app/common/common.less
@@ -188,14 +188,14 @@
position: relative;
display: flex;
align-items: center;
- color: #9e9e9e;
+ color: @term-bright-white;
transition: color 250ms cubic-bezier(0.4, 0, 0.23, 1);
}
input[type="checkbox"] + label > span {
display: flex;
justify-content: center;
align-items: center;
- margin-right: 16px;
+ margin-right: 10px;
width: 20px;
height: 20px;
background: transparent;
@@ -205,10 +205,6 @@
transition: all 250ms cubic-bezier(0.4, 0, 0.23, 1);
}
- input[type="checkbox"] + label:hover,
- input[type="checkbox"]:focus + label {
- color: #fff;
- }
input[type="checkbox"] + label:hover > span,
input[type="checkbox"]:focus + label > span {
background: rgba(255, 255, 255, 0.1);
diff --git a/src/app/common/common.tsx b/src/app/common/common.tsx
index 3e271b57..69b8b52b 100644
--- a/src/app/common/common.tsx
+++ b/src/app/common/common.tsx
@@ -99,23 +99,57 @@ class Toggle extends React.Component<{ checked: boolean; onChange: (value: boole
}
class Checkbox extends React.Component<
- { checked: boolean; onChange: (value: boolean) => void; label: React.ReactNode; id: string },
- {}
+ {
+ checked?: boolean;
+ defaultChecked?: boolean;
+ onChange: (value: boolean) => void;
+ label: React.ReactNode;
+ className?: string;
+ id?: string;
+ },
+ { checkedInternal: boolean }
> {
+ generatedId;
+ static idCounter = 0;
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ checkedInternal: this.props.checked !== undefined ? this.props.checked : Boolean(this.props.defaultChecked),
+ };
+ this.generatedId = `checkbox-${Checkbox.idCounter++}`;
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.checked !== undefined && this.props.checked !== prevProps.checked) {
+ this.setState({ checkedInternal: this.props.checked });
+ }
+ }
+
+ handleChange = (e) => {
+ const newChecked = e.target.checked;
+ if (this.props.checked === undefined) {
+ this.setState({ checkedInternal: newChecked });
+ }
+ this.props.onChange(newChecked);
+ };
+
render() {
- const { checked, onChange, label, id } = this.props;
+ const { label, className, id } = this.props;
+ const { checkedInternal } = this.state;
+ const checkboxId = id || this.generatedId;
return (
-
+
onChange(e.target.checked)}
- aria-checked={checked}
+ id={checkboxId}
+ checked={checkedInternal}
+ onChange={this.handleChange}
+ aria-checked={checkedInternal}
role="checkbox"
/>
-
-
+
-
+
@@ -503,6 +525,10 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
this.errorStr = mobx.observable.box(this.remoteEdit?.errorstr ?? null, { name: "CreateRemote-errorStr" });
}
+ componentDidMount(): void {
+ GlobalModel.getClientData();
+ }
+
remoteCName(): string {
let hostName = this.tempHostName.get();
if (hostName == "") {
@@ -521,6 +547,27 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
return this.remoteEdit?.errorstr ?? null;
}
+ @boundMethod
+ handleOk(): void {
+ this.showShellPrompt(this.submitRemote);
+ }
+
+ @boundMethod
+ showShellPrompt(cb: () => void): void {
+ let prtn = GlobalModel.showAlert({
+ message:
+ "You are about to install WaveShell on a remote machine. Please be aware that WaveShell will be executed on the remote system.",
+ confirm: true,
+ confirmflag: appconst.ConfirmKey_HideShellPrompt,
+ });
+ prtn.then((confirm) => {
+ if (!confirm) {
+ return;
+ }
+ cb();
+ });
+ }
+
@boundMethod
submitRemote(): void {
mobx.action(() => {
@@ -581,12 +628,6 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
});
}
- @boundMethod
- handleClose(): void {
- this.model.closeModal();
- this.model.setRecentConnAdded(false);
- }
-
@boundMethod
handleChangeKeyFile(value: string): void {
mobx.action(() => {
@@ -802,7 +843,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
Error: {this.getErrorStr()}
-
+
);
}
@@ -1097,6 +1138,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
let termFontSize = GlobalModel.termFontSize.get();
let termWidth = textmeasure.termWidthFromCols(RemotePtyCols, termFontSize);
let remoteAliasText = util.isBlank(remote.remotealias) ? "(none)" : remote.remotealias;
+ let selectedRemoteStatus = this.getSelectedRemote().status;
return (
@@ -1175,7 +1217,18 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
-
+
+
+
+
);
}
diff --git a/src/app/connections/connections.tsx b/src/app/connections/connections.tsx
index e9b1e03e..56017b75 100644
--- a/src/app/connections/connections.tsx
+++ b/src/app/connections/connections.tsx
@@ -11,6 +11,7 @@ import { GlobalModel, RemotesModel, GlobalCommandRunner } from "../../model/mode
import { Button, IconButton, Status } from "../common/common";
import * as T from "../../types/types";
import * as util from "../../util/util";
+import * as appconst from "../appconst";
import "./connections.less";
@@ -74,10 +75,31 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
}
@boundMethod
- handleImportSshConfig(): void {
+ importSshConfig(): void {
GlobalCommandRunner.importSshConfig();
}
+ @boundMethod
+ handleImportSshConfig(): void {
+ this.showShellPrompt(this.importSshConfig);
+ }
+
+ @boundMethod
+ showShellPrompt(cb: () => void): void {
+ let prtn = GlobalModel.showAlert({
+ message:
+ "You are about to install WaveShell on a remote machine. Please be aware that WaveShell will be executed on the remote system.",
+ confirm: true,
+ confirmflag: appconst.ConfirmKey_HideShellPrompt,
+ });
+ prtn.then((confirm) => {
+ if (!confirm) {
+ return;
+ }
+ cb();
+ });
+ }
+
@boundMethod
handleRead(remoteId: string): void {
GlobalModel.remotesModel.openReadModal(remoteId);
@@ -163,8 +185,8 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
onClick={() => this.handleRead(item.remoteid)} // Moved onClick here
>
-
- {this.getName(item)} {this.getImportSymbol(item)}
+
+ {this.getName(item)} {this.getImportSymbol(item)}
|
{item.remotetype}
diff --git a/src/model/model.ts b/src/model/model.ts
index 9ba1051d..7934fbe7 100644
--- a/src/model/model.ts
+++ b/src/model/model.ts
@@ -3287,6 +3287,13 @@ class Model {
}
showAlert(alertMessage: AlertMessageType): Promise {
+ if (alertMessage.confirmflag != null) {
+ let cdata = GlobalModel.clientData.get();
+ let noConfirm = cdata.clientopts?.confirmflags?.[alertMessage.confirmflag];
+ if (noConfirm) {
+ return Promise.resolve(true);
+ }
+ }
mobx.action(() => {
this.alertMessage.set(alertMessage);
GlobalModel.modalsModel.pushModal(appconst.ALERT);
@@ -4652,6 +4659,12 @@ class CommandRunner {
GlobalModel.submitCommand("client", "accepttos", null, { nohist: "1" }, true);
}
+ clientSetConfirmFlag(flag: string, value: boolean): Promise {
+ let kwargs = { nohist: "1" };
+ let valueStr = value ? "1" : "0";
+ return GlobalModel.submitCommand("client", "setconfirmflag", [flag, valueStr], kwargs, false);
+ }
+
editBookmark(bookmarkId: string, desc: string, cmdstr: string) {
let kwargs = {
nohist: "1",
diff --git a/src/types/types.ts b/src/types/types.ts
index 758392ff..29b6fb07 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -473,10 +473,15 @@ type FeOptsType = {
termfontsize: number;
};
+type ConfirmFlagsType = {
+ [k: string]: boolean;
+};
+
type ClientOptsType = {
notelemetry: boolean;
noreleasecheck: boolean;
acceptedtos: number;
+ confirmflags: ConfirmFlagsType;
};
type ReleaseInfoType = {
@@ -525,6 +530,7 @@ type AlertMessageType = {
message: string;
confirm?: boolean;
markdown?: boolean;
+ confirmflag?: string;
};
type HistorySearchParams = {
diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go
index 96472a81..810771a5 100644
--- a/wavesrv/pkg/cmdrunner/cmdrunner.go
+++ b/wavesrv/pkg/cmdrunner/cmdrunner.go
@@ -89,6 +89,7 @@ var ColorNames = []string{"yellow", "blue", "pink", "mint", "cyan", "violet", "o
var TabIcons = []string{"square", "sparkle", "fire", "ghost", "cloud", "compass", "crown", "droplet", "graduation-cap", "heart", "file"}
var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}
+var ConfirmFlags = []string{"hideshellprompt"}
var ScreenCmds = []string{"run", "comment", "cd", "cr", "clear", "sw", "reset", "signal", "chat"}
var NoHistCmds = []string{"_compgen", "line", "history", "_killserver"}
@@ -213,6 +214,7 @@ func init() {
registerCmdFn("client:set", ClientSetCommand)
registerCmdFn("client:notifyupdatewriter", ClientNotifyUpdateWriterCommand)
registerCmdFn("client:accepttos", ClientAcceptTosCommand)
+ registerCmdFn("client:setconfirmflag", ClientConfirmFlagCommand)
registerCmdFn("sidebar:open", SidebarOpenCommand)
registerCmdFn("sidebar:close", SidebarCloseCommand)
@@ -4089,6 +4091,57 @@ func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp
return update, nil
}
+var confirmKeyRe = regexp.MustCompile(`^[a-z][a-z0-9_]*$`)
+
+// confirm flags must be all lowercase and only contain letters, numbers, and underscores (and start with letter)
+func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
+ // Check for valid arguments length
+ if len(pk.Args) < 2 {
+ return nil, fmt.Errorf("invalid arguments: expected at least 2, got %d", len(pk.Args))
+ }
+
+ // Extract confirmKey and value from pk.Args
+ confirmKey := pk.Args[0]
+ if !confirmKeyRe.MatchString(confirmKey) {
+ return nil, fmt.Errorf("invalid confirm flag key: %s", confirmKey)
+ }
+ value := resolveBool(pk.Args[1], true)
+ validKey := utilfn.ContainsStr(ConfirmFlags, confirmKey)
+ if !validKey {
+ return nil, fmt.Errorf("invalid confirm flag key: %s", confirmKey)
+ }
+
+ clientData, err := sstore.EnsureClientData(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("cannot retrieve client data: %v", err)
+ }
+
+ // Initialize ConfirmFlags if it's nil
+ if clientData.ClientOpts.ConfirmFlags == nil {
+ clientData.ClientOpts.ConfirmFlags = make(map[string]bool)
+ }
+
+ // Set the confirm flag
+ clientData.ClientOpts.ConfirmFlags[confirmKey] = value
+
+ err = sstore.SetClientOpts(ctx, clientData.ClientOpts)
+ if err != nil {
+ return nil, fmt.Errorf("error updating client data: %v", err)
+ }
+
+ // Retrieve updated client data
+ clientData, err = sstore.EnsureClientData(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
+ }
+
+ update := &sstore.ModelUpdate{
+ ClientData: clientData,
+ }
+
+ return update, nil
+}
+
func validateOpenAIAPIToken(key string) error {
if len(key) > MaxOpenAIAPITokenLen {
return fmt.Errorf("invalid openai token, too long")
diff --git a/wavesrv/pkg/cmdrunner/resolver.go b/wavesrv/pkg/cmdrunner/resolver.go
index 43ed6c43..d429cc9c 100644
--- a/wavesrv/pkg/cmdrunner/resolver.go
+++ b/wavesrv/pkg/cmdrunner/resolver.go
@@ -11,10 +11,10 @@ import (
"strconv"
"strings"
+ "github.com/google/uuid"
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
- "github.com/google/uuid"
)
const (
diff --git a/wavesrv/pkg/shparse/tokenize.go b/wavesrv/pkg/shparse/tokenize.go
index 32ae75b0..7d0cc1b0 100644
--- a/wavesrv/pkg/shparse/tokenize.go
+++ b/wavesrv/pkg/shparse/tokenize.go
@@ -240,7 +240,8 @@ func (c *parseContext) tokenizeDQ() ([]*WordType, bool) {
// returns (words, eofexit)
// backticks (WordTypeBQ) handle backslash in a special way, but that seems to mainly effect execution (not completion)
-// de_backslash => removes initial backslash in \`, \\, and \$ before execution
+//
+// de_backslash => removes initial backslash in \`, \\, and \$ before execution
func (c *parseContext) tokenizeRaw() ([]*WordType, bool) {
state := &tokenizeOutputState{}
isExpSubShell := c.QC.cur() == WordTypeDP
diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go
index 4f25dbdc..9e4f646d 100644
--- a/wavesrv/pkg/sstore/sstore.go
+++ b/wavesrv/pkg/sstore/sstore.go
@@ -267,9 +267,10 @@ func (tdata *TelemetryData) Scan(val interface{}) error {
}
type ClientOptsType struct {
- NoTelemetry bool `json:"notelemetry,omitempty"`
- NoReleaseCheck bool `json:"noreleasecheck,omitempty"`
- AcceptedTos int64 `json:"acceptedtos,omitempty"`
+ NoTelemetry bool `json:"notelemetry,omitempty"`
+ NoReleaseCheck bool `json:"noreleasecheck,omitempty"`
+ AcceptedTos int64 `json:"acceptedtos,omitempty"`
+ ConfirmFlags map[string]bool `json:"confirmflags,omitempty"`
}
type FeOptsType struct {
|