mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Add a modal confirmation before installing WaveShell (#212)
* init * integrate showShellPrompt flag * renive debugging code * remove debugging code * run gofmt. add migration files. * remove debugging code * remove migrations and adjust code. show prompt on import ssh configs as well. * fix show/hide logic * reset mmap.go * use resolveBool and utilfn.ContainsStr * make AlertModal take a generic 'confirmkey' instead of hard coding hideShellPrompt * rename confirmkey to confirmflag (to be consistent). move confirmflag checking into the alertmodal. short circuit with Promise.resolve(true) if noConfirm checked. * disable buttons while status is 'connecting' * minor refactor
This commit is contained in:
parent
00e709d515
commit
8f39f0fc5e
@ -13,4 +13,6 @@ export const LineContainer_Main = "main";
|
|||||||
export const LineContainer_History = "history";
|
export const LineContainer_History = "history";
|
||||||
export const LineContainer_Sidebar = "sidebar";
|
export const LineContainer_Sidebar = "sidebar";
|
||||||
|
|
||||||
|
export const ConfirmKey_HideShellPrompt = "hideshellprompt";
|
||||||
|
|
||||||
export const NoStrPos = -1;
|
export const NoStrPos = -1;
|
||||||
|
@ -188,14 +188,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #9e9e9e;
|
color: @term-bright-white;
|
||||||
transition: color 250ms cubic-bezier(0.4, 0, 0.23, 1);
|
transition: color 250ms cubic-bezier(0.4, 0, 0.23, 1);
|
||||||
}
|
}
|
||||||
input[type="checkbox"] + label > span {
|
input[type="checkbox"] + label > span {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 16px;
|
margin-right: 10px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -205,10 +205,6 @@
|
|||||||
transition: all 250ms cubic-bezier(0.4, 0, 0.23, 1);
|
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"] + label:hover > span,
|
||||||
input[type="checkbox"]:focus + label > span {
|
input[type="checkbox"]:focus + label > span {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
@ -99,23 +99,57 @@ class Toggle extends React.Component<{ checked: boolean; onChange: (value: boole
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Checkbox extends React.Component<
|
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() {
|
render() {
|
||||||
const { checked, onChange, label, id } = this.props;
|
const { label, className, id } = this.props;
|
||||||
|
const { checkedInternal } = this.state;
|
||||||
|
const checkboxId = id || this.generatedId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="checkbox">
|
<div className={cn("checkbox", className)}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={id}
|
id={checkboxId}
|
||||||
checked={checked}
|
checked={checkedInternal}
|
||||||
onChange={(e) => onChange(e.target.checked)}
|
onChange={this.handleChange}
|
||||||
aria-checked={checked}
|
aria-checked={checkedInternal}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
/>
|
/>
|
||||||
<label htmlFor={id}>
|
<label htmlFor={checkboxId}>
|
||||||
<span></span>
|
<span></span>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
|
@ -673,11 +673,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alert-modal {
|
.alert-modal {
|
||||||
width: 500px;
|
width: 510px;
|
||||||
|
|
||||||
.wave-modal-content {
|
.wave-modal-content {
|
||||||
.wave-modal-body {
|
.wave-modal-body {
|
||||||
padding: 40px 20px;
|
padding: 40px 20px;
|
||||||
|
|
||||||
|
.dontshowagain-text {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,11 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Button,
|
Button,
|
||||||
Status,
|
Status,
|
||||||
|
Checkbox,
|
||||||
} from "../common";
|
} from "../common";
|
||||||
import * as util from "../../../util/util";
|
import * as util from "../../../util/util";
|
||||||
import * as textmeasure from "../../../util/textmeasure";
|
import * as textmeasure from "../../../util/textmeasure";
|
||||||
|
import * as appconst from "../../appconst";
|
||||||
import { ClientDataType } from "../../../types/types";
|
import { ClientDataType } from "../../../types/types";
|
||||||
import { Screen } from "../../../model/model";
|
import { Screen } from "../../../model/model";
|
||||||
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
||||||
@ -216,6 +218,15 @@ class AlertModal extends React.Component<{}, {}> {
|
|||||||
GlobalModel.confirmAlert();
|
GlobalModel.confirmAlert();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleDontShowAgain(checked: boolean) {
|
||||||
|
let message = GlobalModel.alertMessage.get();
|
||||||
|
if (message.confirmflag == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GlobalCommandRunner.clientSetConfirmFlag(message.confirmflag, checked);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let message = GlobalModel.alertMessage.get();
|
let message = GlobalModel.alertMessage.get();
|
||||||
let title = message?.title ?? (message?.confirm ? "Confirm" : "Alert");
|
let title = message?.title ?? (message?.confirm ? "Confirm" : "Alert");
|
||||||
@ -229,16 +240,27 @@ class AlertModal extends React.Component<{}, {}> {
|
|||||||
<Markdown text={message?.message ?? ""} />
|
<Markdown text={message?.message ?? ""} />
|
||||||
</If>
|
</If>
|
||||||
<If condition={!message?.markdown}>{message?.message}</If>
|
<If condition={!message?.markdown}>{message?.message}</If>
|
||||||
|
<If condition={message.confirmflag}>
|
||||||
|
<Checkbox
|
||||||
|
onChange={this.handleDontShowAgain}
|
||||||
|
label={"Don't show me this again"}
|
||||||
|
className="dontshowagain-text"
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
</div>
|
</div>
|
||||||
<div className="wave-modal-footer">
|
<div className="wave-modal-footer">
|
||||||
<If condition={isConfirm}>
|
<If condition={isConfirm}>
|
||||||
<Button theme="secondary" onClick={this.closeModal}>
|
<Button theme="secondary" onClick={this.closeModal}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button autoFocus={true} onClick={this.handleOK}>Ok</Button>
|
<Button autoFocus={true} onClick={this.handleOK}>
|
||||||
|
Ok
|
||||||
|
</Button>
|
||||||
</If>
|
</If>
|
||||||
<If condition={!isConfirm}>
|
<If condition={!isConfirm}>
|
||||||
<Button autoFocus={true} onClick={this.handleOK}>Ok</Button>
|
<Button autoFocus={true} onClick={this.handleOK}>
|
||||||
|
Ok
|
||||||
|
</Button>
|
||||||
</If>
|
</If>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -503,6 +525,10 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
|
|||||||
this.errorStr = mobx.observable.box(this.remoteEdit?.errorstr ?? null, { name: "CreateRemote-errorStr" });
|
this.errorStr = mobx.observable.box(this.remoteEdit?.errorstr ?? null, { name: "CreateRemote-errorStr" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
GlobalModel.getClientData();
|
||||||
|
}
|
||||||
|
|
||||||
remoteCName(): string {
|
remoteCName(): string {
|
||||||
let hostName = this.tempHostName.get();
|
let hostName = this.tempHostName.get();
|
||||||
if (hostName == "") {
|
if (hostName == "") {
|
||||||
@ -521,6 +547,27 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
|
|||||||
return this.remoteEdit?.errorstr ?? null;
|
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
|
@boundMethod
|
||||||
submitRemote(): void {
|
submitRemote(): void {
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
@ -581,12 +628,6 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
|
||||||
handleClose(): void {
|
|
||||||
this.model.closeModal();
|
|
||||||
this.model.setRecentConnAdded(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
handleChangeKeyFile(value: string): void {
|
handleChangeKeyFile(value: string): void {
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
@ -802,7 +843,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
|
|||||||
<div className="settings-field settings-error">Error: {this.getErrorStr()}</div>
|
<div className="settings-field settings-error">Error: {this.getErrorStr()}</div>
|
||||||
</If>
|
</If>
|
||||||
</div>
|
</div>
|
||||||
<Modal.Footer onCancel={this.handleClose} onOk={this.submitRemote} okLabel="Connect" />
|
<Modal.Footer onCancel={this.model.closeModal} onOk={this.handleOk} okLabel="Connect" />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1097,6 +1138,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
|||||||
let termFontSize = GlobalModel.termFontSize.get();
|
let termFontSize = GlobalModel.termFontSize.get();
|
||||||
let termWidth = textmeasure.termWidthFromCols(RemotePtyCols, termFontSize);
|
let termWidth = textmeasure.termWidthFromCols(RemotePtyCols, termFontSize);
|
||||||
let remoteAliasText = util.isBlank(remote.remotealias) ? "(none)" : remote.remotealias;
|
let remoteAliasText = util.isBlank(remote.remotealias) ? "(none)" : remote.remotealias;
|
||||||
|
let selectedRemoteStatus = this.getSelectedRemote().status;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal className="rconndetail-modal">
|
<Modal className="rconndetail-modal">
|
||||||
@ -1175,7 +1217,18 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Modal.Footer onOk={this.handleClose} onCancel={this.handleClose} okLabel="Done" />
|
<div className="wave-modal-footer">
|
||||||
|
<Button
|
||||||
|
theme="secondary"
|
||||||
|
disabled={selectedRemoteStatus == "connecting"}
|
||||||
|
onClick={this.handleClose}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button disabled={selectedRemoteStatus == "connecting"} onClick={this.handleClose}>
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { GlobalModel, RemotesModel, GlobalCommandRunner } from "../../model/mode
|
|||||||
import { Button, IconButton, Status } from "../common/common";
|
import { Button, IconButton, Status } from "../common/common";
|
||||||
import * as T from "../../types/types";
|
import * as T from "../../types/types";
|
||||||
import * as util from "../../util/util";
|
import * as util from "../../util/util";
|
||||||
|
import * as appconst from "../appconst";
|
||||||
|
|
||||||
import "./connections.less";
|
import "./connections.less";
|
||||||
|
|
||||||
@ -74,10 +75,31 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
|
|||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
handleImportSshConfig(): void {
|
importSshConfig(): void {
|
||||||
GlobalCommandRunner.importSshConfig();
|
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
|
@boundMethod
|
||||||
handleRead(remoteId: string): void {
|
handleRead(remoteId: string): void {
|
||||||
GlobalModel.remotesModel.openReadModal(remoteId);
|
GlobalModel.remotesModel.openReadModal(remoteId);
|
||||||
@ -163,8 +185,8 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
|
|||||||
onClick={() => this.handleRead(item.remoteid)} // Moved onClick here
|
onClick={() => this.handleRead(item.remoteid)} // Moved onClick here
|
||||||
>
|
>
|
||||||
<td className="col-name">
|
<td className="col-name">
|
||||||
<Status status={this.getStatus(item.status)} text=""></Status>
|
<Status status={this.getStatus(item.status)} text=""></Status>
|
||||||
{this.getName(item)} {this.getImportSymbol(item)}
|
{this.getName(item)} {this.getImportSymbol(item)}
|
||||||
</td>
|
</td>
|
||||||
<td className="col-type">
|
<td className="col-type">
|
||||||
<div>{item.remotetype}</div>
|
<div>{item.remotetype}</div>
|
||||||
|
@ -3287,6 +3287,13 @@ class Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showAlert(alertMessage: AlertMessageType): Promise<boolean> {
|
showAlert(alertMessage: AlertMessageType): Promise<boolean> {
|
||||||
|
if (alertMessage.confirmflag != null) {
|
||||||
|
let cdata = GlobalModel.clientData.get();
|
||||||
|
let noConfirm = cdata.clientopts?.confirmflags?.[alertMessage.confirmflag];
|
||||||
|
if (noConfirm) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
this.alertMessage.set(alertMessage);
|
this.alertMessage.set(alertMessage);
|
||||||
GlobalModel.modalsModel.pushModal(appconst.ALERT);
|
GlobalModel.modalsModel.pushModal(appconst.ALERT);
|
||||||
@ -4652,6 +4659,12 @@ class CommandRunner {
|
|||||||
GlobalModel.submitCommand("client", "accepttos", null, { nohist: "1" }, true);
|
GlobalModel.submitCommand("client", "accepttos", null, { nohist: "1" }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientSetConfirmFlag(flag: string, value: boolean): Promise<CommandRtnType> {
|
||||||
|
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) {
|
editBookmark(bookmarkId: string, desc: string, cmdstr: string) {
|
||||||
let kwargs = {
|
let kwargs = {
|
||||||
nohist: "1",
|
nohist: "1",
|
||||||
|
@ -473,10 +473,15 @@ type FeOptsType = {
|
|||||||
termfontsize: number;
|
termfontsize: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ConfirmFlagsType = {
|
||||||
|
[k: string]: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type ClientOptsType = {
|
type ClientOptsType = {
|
||||||
notelemetry: boolean;
|
notelemetry: boolean;
|
||||||
noreleasecheck: boolean;
|
noreleasecheck: boolean;
|
||||||
acceptedtos: number;
|
acceptedtos: number;
|
||||||
|
confirmflags: ConfirmFlagsType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReleaseInfoType = {
|
type ReleaseInfoType = {
|
||||||
@ -525,6 +530,7 @@ type AlertMessageType = {
|
|||||||
message: string;
|
message: string;
|
||||||
confirm?: boolean;
|
confirm?: boolean;
|
||||||
markdown?: boolean;
|
markdown?: boolean;
|
||||||
|
confirmflag?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type HistorySearchParams = {
|
type HistorySearchParams = {
|
||||||
|
@ -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 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 RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
|
||||||
var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}
|
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 ScreenCmds = []string{"run", "comment", "cd", "cr", "clear", "sw", "reset", "signal", "chat"}
|
||||||
var NoHistCmds = []string{"_compgen", "line", "history", "_killserver"}
|
var NoHistCmds = []string{"_compgen", "line", "history", "_killserver"}
|
||||||
@ -213,6 +214,7 @@ func init() {
|
|||||||
registerCmdFn("client:set", ClientSetCommand)
|
registerCmdFn("client:set", ClientSetCommand)
|
||||||
registerCmdFn("client:notifyupdatewriter", ClientNotifyUpdateWriterCommand)
|
registerCmdFn("client:notifyupdatewriter", ClientNotifyUpdateWriterCommand)
|
||||||
registerCmdFn("client:accepttos", ClientAcceptTosCommand)
|
registerCmdFn("client:accepttos", ClientAcceptTosCommand)
|
||||||
|
registerCmdFn("client:setconfirmflag", ClientConfirmFlagCommand)
|
||||||
|
|
||||||
registerCmdFn("sidebar:open", SidebarOpenCommand)
|
registerCmdFn("sidebar:open", SidebarOpenCommand)
|
||||||
registerCmdFn("sidebar:close", SidebarCloseCommand)
|
registerCmdFn("sidebar:close", SidebarCloseCommand)
|
||||||
@ -4089,6 +4091,57 @@ func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp
|
|||||||
return update, nil
|
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 {
|
func validateOpenAIAPIToken(key string) error {
|
||||||
if len(key) > MaxOpenAIAPITokenLen {
|
if len(key) > MaxOpenAIAPITokenLen {
|
||||||
return fmt.Errorf("invalid openai token, too long")
|
return fmt.Errorf("invalid openai token, too long")
|
||||||
|
@ -11,10 +11,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
||||||
"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/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -240,7 +240,8 @@ func (c *parseContext) tokenizeDQ() ([]*WordType, bool) {
|
|||||||
|
|
||||||
// returns (words, eofexit)
|
// returns (words, eofexit)
|
||||||
// backticks (WordTypeBQ) handle backslash in a special way, but that seems to mainly effect execution (not completion)
|
// 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) {
|
func (c *parseContext) tokenizeRaw() ([]*WordType, bool) {
|
||||||
state := &tokenizeOutputState{}
|
state := &tokenizeOutputState{}
|
||||||
isExpSubShell := c.QC.cur() == WordTypeDP
|
isExpSubShell := c.QC.cur() == WordTypeDP
|
||||||
|
@ -267,9 +267,10 @@ func (tdata *TelemetryData) Scan(val interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptsType struct {
|
type ClientOptsType struct {
|
||||||
NoTelemetry bool `json:"notelemetry,omitempty"`
|
NoTelemetry bool `json:"notelemetry,omitempty"`
|
||||||
NoReleaseCheck bool `json:"noreleasecheck,omitempty"`
|
NoReleaseCheck bool `json:"noreleasecheck,omitempty"`
|
||||||
AcceptedTos int64 `json:"acceptedtos,omitempty"`
|
AcceptedTos int64 `json:"acceptedtos,omitempty"`
|
||||||
|
ConfirmFlags map[string]bool `json:"confirmflags,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeOptsType struct {
|
type FeOptsType struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user