merge main

This commit is contained in:
sawka 2024-03-07 22:47:44 -08:00
commit 371f3d87c1
42 changed files with 431 additions and 416 deletions

View File

@ -6,7 +6,7 @@
},
"productName": "Wave",
"description": "An open-source, cross-platform, AI-integrated, modern terminal for seamless workflows.",
"version": "0.6.1",
"version": "0.7.0",
"main": "dist/emain.js",
"license": "Apache-2.0",
"repository": {
@ -101,4 +101,4 @@
"scripts": {
"postinstall": "electron-builder install-app-deps"
}
}
}

View File

@ -1,6 +1,7 @@
/* Copyright 2024, Command Line Inc.
SPDX-License-Identifier: Apache-2.0 */
@import url("./term-default.css");
@import url("./term-dark.css");
:root {
@ -47,9 +48,9 @@
--app-bg-color: black;
--app-accent-color: rgb(88, 193, 66);
--app-accent-bg-color: rgba(88, 193, 66, 0.2);
--app-error-color: rgb(204, 0, 0);
--app-warning-color: rgb(255, 165, 0);
--app-success-color: rgb(38, 97, 26);
--app-error-color: rgb(229, 77, 46);
--app-warning-color: rgb(224, 185, 86);
--app-success-color: rgb(78, 154, 6);
--app-text-color: rgb(211, 215, 207);
--app-text-primary-color: rgb(255, 255, 255);
--app-text-secondary-color: rgb(195, 200, 194);
@ -70,9 +71,9 @@
/* scrollbar colors */
/* --scrollbar-background-color: rgba(21, 23, 21, 1); */
--scrollbar-background-color: #030303;
--scrollbar-thumb-color: #333;
--scrollbar-thumb-hover-color: rgb(211, 215, 207);
--scrollbar-background-color: var(--app-bg-color);
--scrollbar-thumb-color: rgba(255, 255, 255, 0.3);
--scrollbar-thumb-hover-color: rgba(255, 255, 255, 0.5);
/* code color */
--pre-bg-color: rgb(0, 0, 0);
@ -91,46 +92,20 @@
--form-element-border-color: rgba(241, 246, 243, 0.15);
--form-element-bg-color: var(--app-bg-color);
--form-element-text-color: var(--app-text-color);
--form-element-label-color: rgb(195, 200, 194);
--form-element-primary-color: rgb(78, 154, 6);
--form-element-secondary-color: rgba(255, 255, 255, 0.09);
--form-element-error-color: rgb(204, 0, 0);
--form-element-icon-color: #fff;
--form-element-disabled-text-color: #777;
--form-element-placeholder-color: #777;
/* button colors */
/* todo: use --form-element-* directly in elements */
--button-text-color: var(--form-element-text-color);
--button-primary-bg-color: var(--form-element-primary-color);
--button-primary-color: var(--form-element-text-color);
--button-secondary-bg-color: var(--form-element-secondary-color);
--button-warning-bg-color: var(--form-element-error-color);
/* input colors */
/* todo: use --form-element-* directly in elements */
--checkbox-text-color: var(--form-element-text-color);
--checkbox-bg-color: var(--form-element-primary-color);
--checkbox-check-color: var(--form-element-text-color);
/* dropdown colors */
/* todo: use --form-element-* directly in elements */
--dropdown-text-color: var(--form-element-text-color);
--dropdown-label-color: var(--form-element-label-color);
--dropdown-error-color: var(--form-element-error-color);
--dropdown-focus-color: var(--form-element-primary-color);
--dropdown-bg-color: var(--form-element-bg-color);
--dropdown-border-color: var(--form-element-border-color);
--form-element-text-color: var(--app-text-primary-color);
--form-element-label-color: var(--app-text-secondary-color);
--form-element-primary-color: var(--app-accent-color);
--form-element-secondary-color: rgba(255, 255, 255, 0.2);
--form-element-error-color: var(--app-error-color);
--form-element-icon-color: var(--app-icon-hover-color);
/* toggle colors */
--toggle-bg-color: rgb(51, 51, 51);
--toggle-thumb-color: rgb(211, 215, 207);
--toggle-checked-bg-color: var(--form-element-primary-color);
--toggle-bg-color: var(--app-border-color);
--toggle-thumb-color: var(--app-text-color);
--toggle-checked-bg-color: var(--app-accent-color);
/* cmdstrcode colors */
--cmdstrcode-bg-color: rgb(0, 0, 0);
--cmdstrcode-text-color: rgb(211, 215, 207);
--cmdstrcode-text-color: var(--app-text-color);
/* markdown colors */
--markdown-bg-color: rgb(35, 35, 35);
@ -138,29 +113,29 @@
/* status(remote) colors */
/* todo: all status colors must be unified */
--status-connected-color: rgb(70, 167, 88);
--status-connecting-color: rgb(245, 217, 10);
--status-error-color: rgb(229, 77, 46);
--status-disconnected-color: rgb(195, 200, 194);
--status-connected-color: var(--app-success-color);
--status-connecting-color: var(--app-warning-color);
--status-error-color: var(--app-error-color);
--status-disconnected-color: var(--app-text-secondary-color);
/* status indicator colors */
/* todo: all status colors must be unified */
--status-indicator-color: rgb(211, 215, 207);
--status-indicator-error: rgb(204, 0, 0);
--status-indicator-success: rgb(78, 154, 6);
--status-indicator-color: var(--app-text-color);
--status-indicator-error: var(--status-error-color);
--status-indicator-success: var(--status-connected-color);
/* status(version) colors */
/* todo: all status colors must be unified */
--status-outdated-color: rgb(196, 160, 0);
--status-updated-color: rgb(78, 154, 6);
--status-outdated-color: var(--status-connecting-color);
--status-updated-color: var(--status-connected-color);
/* term status colors */
/* todo: all status colors must be unified */
--term-error-color: rgb(204, 0, 0);
--term-warning-color: rgb(196, 160, 0);
--term-error-color: var(--status-error-color);
--term-warning-color: var(--status-connecting-color);
/* hotkey colors */
--hotkey-text-color: rgb(195, 200, 194);
--hotkey-text-color: var(--app-text-secondary-color);
/* sidebar colors */
--sidebar-highlight-color: rgba(241, 246, 243, 0.08);
@ -178,10 +153,10 @@
--line-svg-hover-fill-color: #eceeec;
--line-selected-border-color: rgb(193, 195, 193);
--line-separator-color: rgb(126, 126, 126);
--line-error-color: #cc0000;
--line-warning-color: #ffa500;
--line-error-color: var(--app-error-color);
--line-warning-color: var(--app-warning-color);
--line-base-soft-blue-color: #729fcf;
--line-active-border-color: rgb(97, 158, 72);
--line-active-border-color: var(--app-accent-color);
--line-selected-bg-color: rgba(255, 255, 255, 0.05);
--line-selected-border-left-color: #777777;
--line-selected-error-border-color: rgba(204, 0, 0, 0.8);
@ -192,12 +167,12 @@
--line-meta-user-color: rgba(140, 184, 232);
--line-svg-color: rgba(236, 238, 236, 0.6);
--line-svg-hover-color: rgba(236, 238, 236, 1);
--line-status-success-fill: rgb(88, 193, 66);
--line-status-error-fill: #cc0000;
--line-status-warning-fill: #ffa500;
--line-status-success-fill: var(--app-success-color);
--line-status-error-fill: var(--app-error-color);
--line-status-warning-fill: var(--app-warning-color);
--line-actions-inactive-color: rgba(255, 255, 255, 0.5);
--line-actions-active-color: rgba(255, 255, 255, 1);
--line-actions-bg-color: rgba(21, 23, 21, 1);
--line-actions-bg-color: rgba(255, 255, 255, 0.15);
/* view colors */
/* todo: bookmarks is a view, colors must be unified with --view* colors */
@ -207,8 +182,8 @@
--bookmarks-control-hover-color: rgb(255, 255, 255);
/* view colors */
--view-error-color: rgb(204, 0, 0);
--view-text-color: rgb(195, 200, 194);
--view-error-color: var(--app-error-color);
--view-text-color: var(--app-text-color);
/* table colors */
--table-border-color: rgba(241, 246, 243, 0.15);

View File

@ -2,10 +2,13 @@
SPDX-License-Identifier: Apache-2.0 */
@import "./default.css";
@import "./term-default.css";
@import "./term-light.css";
:root {
--app-bg-color: #fefefe;
--app-accent-color: rgb(75, 166, 57);
--app-accent-bg-color: rgba(75, 166, 57, 0.2);
--app-text-color: #000;
--app-text-primary-color: rgb(0, 0, 0, 0.9);
--app-text-secondary-color: rgb(0, 0, 0, 0.7);
@ -42,7 +45,7 @@
--form-element-bg-color: var(--app-bg-color);
--form-element-text-color: var(--app-text-color);
--form-element-label-color: rgba(0, 0, 0, 0.6);
--form-element-secondary-color: rgba(255, 255, 255, 0.09);
--form-element-secondary-color: rgba(0, 0, 0, 0.09);
--form-element-icon-color: rgb(0, 0, 0, 0.6);
--form-element-disabled-text-color: #b7b7b7;
--form-element-placeholder-color: #b7b7b7;
@ -55,16 +58,17 @@
--cmdinput-textarea-border-color: var(--form-element-border-color);
/* scroll colors */
--scrollbar-background-color: rgba(0, 0, 0, 0.3);
--scrollbar-thumb-color: rgba(0, 0, 0, 0.4);
--scrollbar-thumb-hover-color: rgba(0, 0, 0, 0.5);
/* cmdstrcode colors */
--cmdstrcode-bg-color: var(--term-black);
--cmdstrcode-text-color: var(--term-white);
--scrollbar-background-color: var(--app-bg-color);
--scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
--scrollbar-thumb-hover-color: rgba(0, 0, 0, 0.4);
/* line color */
--line-actions-bg-color: rgba(0, 0, 0, 0.4);
--line-actions-bg-color: rgba(0, 0, 0, 0.1);
--line-actions-inactive-color: rgba(0, 0, 0, 0.3);
--line-actions-active-color: rgba(0, 0, 0, 1);
/* toggle colors */
--toggle-thumb-color: var(--app-bg-color);
--logo-button-hover-bg-color: #f0f0f0;

View File

@ -11,9 +11,9 @@
--term-green: #4e9a06;
--term-yellow: #c4a000;
--term-blue: #3465a4;
--term-magenta: #75507b;
--term-magenta: #bc3fbc;
--term-cyan: #06989a;
--term-white: #d3d7cf;
--term-white: #d0d0d0;
--term-bright-black: #555753;
--term-bright-red: #ef2929;
--term-bright-green: #58c142;
@ -21,7 +21,7 @@
--term-bright-blue: #32afff;
--term-bright-magenta: #ad7fa8;
--term-bright-cyan: #34e2e2;
--term-bright-white: #ffffff;
--term-bright-white: #e7e7e7;
--term-gray: #8b918a; /* not an official terminal color */
--term-cmdtext: #ffffff;

View File

@ -5,7 +5,5 @@
/* term colors */
--term-foreground: #000000;
--term-background: #fefefe;
--term-black: #fefefe;
--term-white: #000000;
--term-cmdtext: #000000;
}

View File

@ -12,7 +12,7 @@ body {
body {
&.is-dev .sidebar {
background-color: var(--app-panel-bg-color-dev);
--app-panel-bg-color: var(--app-panel-bg-color-dev);
}
}
@ -210,10 +210,10 @@ svg.icon {
.dropdown,
.button {
display: inline-block;
border: 1px solid var(--button-secondary-bg-color) !important;
border: 1px solid var(--form-element-secondary-color) !important;
border-radius: 5px;
cursor: pointer;
background-color: var(--button-secondary-bg-color) !important;
background-color: var(--form-element-secondary-color) !important;
color: var(--app-accent-color);
.hoverEffect;
&:hover {
@ -221,7 +221,7 @@ svg.icon {
}
&.disabled {
color: var(--app-text-color);
background: var(--button-secondary-bg-color);
background: var(--form-element-secondary-color);
opacity: 0.5;
cursor: initial;
}
@ -382,9 +382,9 @@ a.a-block {
}
&.connect-button {
color: var(--button-primary-bg-color);
color: var(--form-element-primary-color);
&:hover {
color: var(--button-primary-bg-color);
color: var(--form-element-primary-color);
}
}
@ -396,9 +396,9 @@ a.a-block {
}
&.success-button {
color: var(--button-primary-bg-color);
color: var(--form-element-primary-color);
&:hover {
color: var(--button-primary-bg-color);
color: var(--form-element-primary-color);
}
}

View File

@ -111,11 +111,12 @@ class App extends React.Component<{}, {}> {
// used to force a full reload of the application
const renderVersion = GlobalModel.renderVersion.get();
const sidebarCollapsed = GlobalModel.mainSidebarModel.getCollapsed();
const lightDarkClass = GlobalModel.isThemeDark() ? "is-dark" : "is-light";
return (
<div
key={"version-" + renderVersion}
id="main"
className={cn("platform-" + platform, { "sidebar-collapsed": sidebarCollapsed })}
className={cn("platform-" + platform, { "sidebar-collapsed": sidebarCollapsed }, lightDarkClass)}
onContextMenu={this.handleContextMenu}
>
<If condition={sidebarCollapsed}>

View File

@ -1,5 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="globe">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M5.51561 7.24988C5.58926 5.49464 5.91878 3.94637 6.3934 2.81642C6.66453 2.17096 6.96822 1.69729 7.26714 1.39629C7.56327 1.0981 7.81035 1.00977 8.00003 1.00977C8.1897 1.00977 8.43678 1.0981 8.73291 1.39629C9.03183 1.69729 9.33553 2.17096 9.60665 2.81642C10.0813 3.94637 10.4108 5.49464 10.4844 7.24988H5.51561ZM4.01441 7.24988H1.03973C1.31693 4.64752 3.02019 2.47128 5.35297 1.51768C5.22994 1.74443 5.1158 1.98471 5.01045 2.23552C4.44737 3.57606 4.0887 5.32893 4.01441 7.24988ZM10.6471 1.51768C10.7701 1.74443 10.8842 1.98471 10.9896 2.23552C11.5527 3.57606 11.9114 5.32893 11.9856 7.24988H14.9603C14.6831 4.64752 12.9799 2.47128 10.6471 1.51768ZM10.4842 8.74988C10.4101 10.5028 10.0808 12.049 9.60665 13.1777C9.33553 13.8232 9.03183 14.2968 8.73291 14.5978C8.43678 14.896 8.1897 14.9844 8.00003 14.9844C7.81035 14.9844 7.56327 14.896 7.26714 14.5978C6.96822 14.2968 6.66453 13.8232 6.3934 13.1777C5.91928 12.049 5.58997 10.5028 5.51585 8.74988H10.4842ZM11.9854 8.74988H14.9603C14.683 11.3537 12.978 13.5309 10.6432 14.4837C10.7677 14.2548 10.8831 14.0121 10.9896 13.7586C11.5521 12.4194 11.9107 10.6686 11.9854 8.74988ZM5.35689 14.4837C3.0221 13.5309 1.31708 11.3537 1.03973 8.74988H4.01463C4.0894 10.6686 4.44792 12.4194 5.01045 13.7586C5.11692 14.0121 5.23237 14.2548 5.35689 14.4837Z" fill="#C3C8C2"/>
</g>
</svg>
<g id="globe">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd"
d="M5.51561 7.24988C5.58926 5.49464 5.91878 3.94637 6.3934 2.81642C6.66453 2.17096 6.96822 1.69729 7.26714 1.39629C7.56327 1.0981 7.81035 1.00977 8.00003 1.00977C8.1897 1.00977 8.43678 1.0981 8.73291 1.39629C9.03183 1.69729 9.33553 2.17096 9.60665 2.81642C10.0813 3.94637 10.4108 5.49464 10.4844 7.24988H5.51561ZM4.01441 7.24988H1.03973C1.31693 4.64752 3.02019 2.47128 5.35297 1.51768C5.22994 1.74443 5.1158 1.98471 5.01045 2.23552C4.44737 3.57606 4.0887 5.32893 4.01441 7.24988ZM10.6471 1.51768C10.7701 1.74443 10.8842 1.98471 10.9896 2.23552C11.5527 3.57606 11.9114 5.32893 11.9856 7.24988H14.9603C14.6831 4.64752 12.9799 2.47128 10.6471 1.51768ZM10.4842 8.74988C10.4101 10.5028 10.0808 12.049 9.60665 13.1777C9.33553 13.8232 9.03183 14.2968 8.73291 14.5978C8.43678 14.896 8.1897 14.9844 8.00003 14.9844C7.81035 14.9844 7.56327 14.896 7.26714 14.5978C6.96822 14.2968 6.66453 13.8232 6.3934 13.1777C5.91928 12.049 5.58997 10.5028 5.51585 8.74988H10.4842ZM11.9854 8.74988H14.9603C14.683 11.3537 12.978 13.5309 10.6432 14.4837C10.7677 14.2548 10.8831 14.0121 10.9896 13.7586C11.5521 12.4194 11.9107 10.6686 11.9854 8.74988ZM5.35689 14.4837C3.0221 13.5309 1.31708 11.3537 1.03973 8.74988H4.01463C4.0894 10.6686 4.44792 12.4194 5.01045 13.7586C5.11692 14.0121 5.23237 14.2548 5.35689 14.4837Z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M142.9 142.9c-17.5 17.5-30.1 38-37.8 59.8L44.8 181.4C55.6 150.7 73.2 122 97.6 97.6c87.5-87.5 229.3-87.5 316.8 0l0 0L472 40l24 24V224H336l-24-24 57.1-57.1 0 0c-62.5-62.5-163.8-62.5-226.3 0zm0 226.3c62.5 62.5 163.8 62.5 226.3 0c17.5-17.5 30.1-38 37.8-59.8l60.4 21.3c-10.8 30.6-28.4 59.3-52.9 83.7c-87.5 87.5-229.3 87.5-316.7 0l0 0L40 472 16 448V288H176l24 24-57.1 57.1z"/></svg>

After

Width:  |  Height:  |  Size: 613 B

View File

@ -5,8 +5,6 @@ import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import { If } from "tsx-control-statements/components";
import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, RemotesModel, getApi } from "@/models";
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "@/common/elements";
import { commandRtnHandler, isBlank } from "@/util/util";
@ -192,19 +190,17 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
/>
</div>
</div>
<If condition={GlobalModel.isDev}>
<div className="settings-field">
<div className="settings-label">Theme</div>
<div className="settings-input">
<Dropdown
className="theme-dropdown"
options={this.getThemes()}
defaultValue={curTheme}
onChange={this.handleChangeTheme}
/>
</div>
<div className="settings-field">
<div className="settings-label">Theme</div>
<div className="settings-input">
<Dropdown
className="theme-dropdown"
options={this.getThemes()}
defaultValue={curTheme}
onChange={this.handleChangeTheme}
/>
</div>
</If>
</div>
<div className="settings-field">
<div className="settings-label">Client ID</div>
<div className="settings-input">{cdata.clientid}</div>

View File

@ -18,20 +18,20 @@
background: none;
i {
fill: var(--button-primary-bg-color);
fill: var(--form-element-primary-color);
}
&.solid {
color: var(--button-primary-color);
background: var(--button-primary-bg-color);
color: var(--form-element-text-color);
background: var(--form-element-primary-color);
i {
fill: var(--button-primary-color);
fill: var(--form-element-text-color);
}
}
&.outlined {
border: 1px solid var(--button-primary-bg-color);
border: 1px solid var(--form-element-primary-color);
}
&.ghost {
@ -39,29 +39,29 @@
}
&:hover {
color: var(--button-primary-color);
color: var(--form-element-text-color);
}
}
&.secondary {
color: var(--button-text-color);
color: var(--form-element-text-color);
background: none;
&.solid {
color: var(--button-text-color);
background: var(--button-secondary-bg-color);
color: var(--form-element-text-color);
background: var(--form-element-secondary-color);
box-shadow: none;
}
&.outlined {
border: 1px solid var(--button-secondary-bg-color);
border: 1px solid var(--form-element-secondary-color);
}
&.ghost {
padding: 6px 10px;
i {
fill: var(--button-primary-bg-color);
fill: var(--form-element-primary-color);
}
}
}

View File

@ -10,7 +10,7 @@
position: relative;
display: flex;
align-items: center;
color: var(--checkbox-bg-color);
color: var(--form-element-primary-color);
transition: color 250ms cubic-bezier(0.4, 0, 0.23, 1);
}
input[type="checkbox"] + label > span {
@ -36,7 +36,7 @@
}
input[type="checkbox"]:checked + label > span {
border: 10px solid var(--button-primary-bg-color);
border: 10px solid var(--form-element-primary-color);
}
input[type="checkbox"]:checked + label > span:before {
content: "";
@ -45,8 +45,8 @@
left: 3px;
width: 7px;
height: 12px;
border-right: 2px solid var(--checkbox-check-color);
border-bottom: 2px solid var(--checkbox-check-color);
border-right: 2px solid var(--form-element-text-color);
border-bottom: 2px solid var(--form-element-text-color);
transform: rotate(45deg);
transform-origin: 0% 100%;
animation: checkbox-check 500ms cubic-bezier(0.4, 0, 0.23, 1);

View File

@ -36,7 +36,6 @@
}
.code-div {
background-color: var(--cmdstrcode-bg-color);
display: flex;
flex-direction: row;
min-width: 100px;
@ -49,7 +48,6 @@
color: var(--cmdstrcode-text-color);
white-space: pre;
padding: 2px 8px 2px 8px;
background-color: var(--cmdstrcode-bg-color);
font-size: var(--termfontsize);
font-family: var(--termfontfamily);
line-height: var(--termlineheight);

View File

@ -3,10 +3,10 @@
height: 44px;
min-width: 150px;
width: 100%;
border: 1px solid var(--dropdown-border-color);
border: 2px solid var(--form-element-border-color);
border-radius: 6px;
background: var(--dropdown-bg-color);
line-height: 22px;
background: var(--form-element-bg-color);
&.no-label {
height: 34px;
@ -18,7 +18,7 @@
top: 16px;
font-size: 12.5px;
transition: all 0.3s;
color: var(--dropdown-label-color);
color: var(--form-element-label-color);
line-height: 10px;
&.float {
@ -89,11 +89,15 @@
}
&-error {
border-color: var(--dropdown-error-color);
border-color: var(--form-element-error-color);
}
&:focus {
border-color: var(--dropdown-focus-color);
border-color: var(--form-element-primary-color);
}
.lefticon {
color: var(--app-text-color);
}
}
@ -110,13 +114,13 @@
align-items: flex-start;
gap: 4px;
border-radius: 6px;
border: 1px solid var(--dropdown-border-color);
background: var(--dropdown-bg-color);
border: 2px solid var(--form-element-border-color);
background: var(--form-element-bg-color);
// box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5), 0px 3px 8px 0px rgba(0, 0, 0, 0.35), 0px 0px 0.5px 0px #fff inset,
// 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
animation-fill-mode: forwards;
z-index: 1000;
color: var(--dropdown-text-color);
color: var(--form-element-text-color);
}
.wave-dropdown-menu-close {

View File

@ -13,7 +13,6 @@ export { NumberField } from "./numberfield";
export { PasswordField } from "./passwordfield";
export { ResizableSidebar } from "./resizablesidebar";
export { SettingsError } from "./settingserror";
export { ShowWaveShellInstallPrompt } from "./showwaveshellinstallprompt";
export { Status } from "./status";
export { TextField } from "./textfield";
export { Toggle } from "./toggle";

View File

@ -25,7 +25,7 @@
padding: 0 10px;
vertical-align: middle;
line-height: var(--screentabs-height);
color: var(--term-white);
color: var(--app-text-color);
margin: 0;
}

View File

@ -1,28 +0,0 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { GlobalModel } from "@/models";
import * as appconst from "@/app/appconst";
function ShowWaveShellInstallPrompt(callbackFn: () => void) {
let message: string = `
In order to use Wave's advanced features like unified history and persistent sessions, Wave installs a small, open-source helper program called WaveShell on your remote machine. WaveShell does not open any external ports and only communicates with your *local* Wave terminal instance over ssh. For more information please see [the docs](https://docs.waveterm.dev/reference/waveshell).
`;
message = message.trim();
let prtn = GlobalModel.showAlert({
message: message,
confirm: true,
markdown: true,
confirmflag: appconst.ConfirmKey_HideShellPrompt,
});
prtn.then((confirm) => {
if (!confirm) {
return;
}
if (callbackFn) {
callbackFn();
}
});
}
export { ShowWaveShellInstallPrompt };

View File

@ -3,10 +3,10 @@
align-items: center;
border-radius: 6px;
position: relative;
height: 44px;
min-height: 44px;
min-width: 412px;
gap: 6px;
border: 1px solid var(--form-element-border-color);
border: 2px solid var(--form-element-border-color);
background: var(--form-element-bg-color);
&:hover {
@ -27,15 +27,14 @@
&-inner {
display: flex;
align-items: flex-end;
height: 100%;
flex-direction: column;
position: relative;
flex-grow: 1;
--inner-padding: 5px 0 5px 16px;
&-label {
position: absolute;
left: 16px;
top: 16px;
padding: var(--inner-padding);
margin-bottom: -10px;
font-size: 12.5px;
transition: all 0.3s;
color: var(--form-element-label-color);
@ -55,7 +54,7 @@
width: 100%;
height: 30px;
border: none;
padding: 5px 0 5px 16px;
padding: var(--inner-padding);
font-size: 16px;
outline: none;
background-color: transparent;

View File

@ -15,7 +15,7 @@
i {
display: inline;
font-size: 13px;
fill: var(--checkbox-text-color);
fill: var(--form-element-text-color);
padding-top: 0.2em;
}
}

View File

@ -7,16 +7,7 @@ import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import { If } from "tsx-control-statements/components";
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "@/models";
import {
Modal,
TextField,
NumberField,
InputDecoration,
Dropdown,
PasswordField,
Tooltip,
ShowWaveShellInstallPrompt,
} from "@/elements";
import { Modal, TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip } from "@/elements";
import * as util from "@/util/util";
import "./createremoteconn.less";
@ -73,12 +64,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
}
@boundMethod
handleOk(): void {
ShowWaveShellInstallPrompt(this.submitRemote);
}
@boundMethod
submitRemote(): void {
handleSubmitRemote(): void {
mobx.action(() => {
this.errorStr.set(null);
})();
@ -275,7 +261,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
{ value: "none", label: "none" },
{ value: "key", label: "key" },
{ value: "password", label: "password" },
{ value: "key+password", label: "key+password" },
{ value: "key+password", label: "key+passphrase" },
]}
value={this.tempAuthMode.get()}
onChange={(val: string) => {
@ -288,17 +274,18 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
message={
<ul>
<li>
<b>none</b> - no authentication, or authentication is already
configured in your ssh config.
<b>none</b> - no authentication details are stored.
</li>
<li>
<b>key</b> - use a private key.
<b>key</b> - provide a custom private key for authentication.
</li>
<li>
<b>password</b> - use a password.
<b>password</b> - provide a password (to save) for
authentication.
</li>
<li>
<b>key+password</b> - use a key with a passphrase.
<b>key+passphrase</b> - provide a custom private key with a
passphrase (to save) for authentication.
</li>
</ul>
}
@ -374,7 +361,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
<div className="settings-field settings-error">Error: {this.getErrorStr()}</div>
</If>
</div>
<Modal.Footer onCancel={this.model.closeModal} onOk={this.handleOk} okLabel="Connect" />
<Modal.Footer onCancel={this.model.closeModal} onOk={this.handleSubmitRemote} okLabel="Connect" />
</Modal>
);
}

View File

@ -26,12 +26,16 @@
width: 16px;
height: 16px;
flex-shrink: 0;
fill: var(--app-text-secondary-color);
}
.status-icon {
position: absolute;
left: 7px;
top: 8px;
circle {
stroke: var(--app-bg-color);
}
}
}

View File

@ -5,7 +5,7 @@ import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import { For } from "tsx-control-statements/components";
import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
import { Toggle, InlineSettingsTextEdit, SettingsError, Modal, Dropdown, Tooltip } from "@/elements";
@ -38,15 +38,13 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
screen: Screen;
sessionId: string;
screenId: string;
remotes: RemoteType[];
constructor(props) {
super(props);
let screenSettingsModal = GlobalModel.screenSettingsModal.get();
let { sessionId, screenId } = screenSettingsModal;
this.sessionId = sessionId;
const screenSettingsModal = GlobalModel.screenSettingsModal.get();
const { sessionId, screenId } = screenSettingsModal;
this.screenId = screenId;
this.screen = GlobalModel.getScreenById(sessionId, screenId);
if (this.screen == null || sessionId == null || screenId == null) {
@ -67,8 +65,8 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
value: remote.remotecanonicalname,
}))
.sort((a, b) => {
let connValA = util.getRemoteConnVal(a);
let connValB = util.getRemoteConnVal(b);
const connValA = util.getRemoteConnVal(a);
const connValB = util.getRemoteConnVal(b);
if (connValA !== connValB) {
return connValA - connValB;
}
@ -92,7 +90,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
if (this.screen.getTabColor() == color) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { tabcolor: color }, false);
const prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { tabcolor: color }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@ -101,7 +99,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
if (this.screen.getTabIcon() == icon) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.screen.screenId, { tabicon: icon }, false);
const prtn = GlobalCommandRunner.screenSetSettings(this.screen.screenId, { tabicon: icon }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@ -113,7 +111,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
if (this.screen.archived.get() == val) {
return;
}
let prtn = GlobalCommandRunner.screenArchive(this.screenId, val);
const prtn = GlobalCommandRunner.screenArchive(this.screenId, val);
util.commandRtnHandler(prtn, this.errorMessage);
}
@ -125,13 +123,13 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
if (this.screen.isWebShared() == val) {
return;
}
let message = val ? WebShareConfirmMarkdown : WebStopShareConfirmMarkdown;
let alertRtn = GlobalModel.showAlert({ message: message, confirm: true, markdown: true });
const message = val ? WebShareConfirmMarkdown : WebStopShareConfirmMarkdown;
const alertRtn = GlobalModel.showAlert({ message: message, confirm: true, markdown: true });
alertRtn.then((result) => {
if (!result) {
return;
}
let prtn = GlobalCommandRunner.screenWebShare(this.screen.screenId, val);
const prtn = GlobalCommandRunner.screenWebShare(this.screen.screenId, val);
util.commandRtnHandler(prtn, this.errorMessage);
});
}
@ -141,7 +139,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
if (this.screen == null) {
return null;
}
let shareLink = this.screen.getWebShareUrl();
const shareLink = this.screen.getWebShareUrl();
if (shareLink == null) {
return;
}
@ -164,7 +162,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
if (util.isStrEq(val, this.screen.name.get())) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { name: val }, false);
const prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { name: val }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@ -176,7 +174,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
if (util.isStrEq(val, this.screen.getShareName())) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { sharename: val }, false);
const prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { sharename: val }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@ -216,14 +214,14 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
}
render() {
let screen = this.screen;
const screen = this.screen;
if (screen == null) {
return null;
}
let color: string = null;
let icon: string = null;
let index: number = 0;
let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
const curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
return (
<Modal className="screen-settings-modal">

View File

@ -3,10 +3,14 @@
.wave-modal-content {
.wave-modal-body {
padding: 20px 20px;
padding: 0px 20px 0px 20px;
.userinput-query {
margin-bottom: 10px;
.wave-modal-dialog {
padding: 20px 0px 20px 0px;
.userinput-query {
margin-bottom: 10px;
}
}
}
}

View File

@ -1,7 +1,7 @@
import * as React from "react";
import { GlobalModel } from "@/models";
import { Choose, When, If } from "tsx-control-statements/components";
import { Modal, PasswordField, Markdown } from "@/elements";
import { Modal, PasswordField, Markdown, Checkbox } from "@/elements";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import "./userinput.less";
@ -9,6 +9,7 @@ import "./userinput.less";
export const UserInputModal = (userInputRequest: UserInputRequest) => {
const [responseText, setResponseText] = React.useState("");
const [countdown, setCountdown] = React.useState(Math.floor(userInputRequest.timeoutms / 1000));
const checkboxStatus = React.useRef(false);
const handleSendCancel = React.useCallback(() => {
GlobalModel.sendUserInput({
@ -24,16 +25,19 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
type: "userinputresp",
requestid: userInputRequest.requestid,
text: responseText,
checkboxstat: checkboxStatus.current,
});
GlobalModel.remotesModel.closeModal();
}, [responseText, userInputRequest]);
const handleSendConfirm = React.useCallback(
(response: boolean) => {
console.log(`checkbox ${checkboxStatus}\n\n`);
GlobalModel.sendUserInput({
type: "userinputresp",
requestid: userInputRequest.requestid,
confirm: response,
checkboxstat: checkboxStatus.current,
});
GlobalModel.remotesModel.closeModal();
},
@ -71,23 +75,32 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
<Modal className="userinput-modal">
<Modal.Header onClose={handleSendCancel} title={userInputRequest.title + ` (${countdown}s)`} />
<div className="wave-modal-body">
<div className="userinput-query">
<If condition={userInputRequest.markdown}>
<Markdown text={userInputRequest.querytext} extraClassName="bottom-margin" />
</If>
<If condition={!userInputRequest.markdown}>{userInputRequest.querytext}</If>
<div className="wave-modal-dialog">
<div className="userinput-query">
<If condition={userInputRequest.markdown}>
<Markdown text={userInputRequest.querytext} extraClassName="bottom-margin" />
</If>
<If condition={!userInputRequest.markdown}>{userInputRequest.querytext}</If>
</div>
<Choose>
<When condition={userInputRequest.responsetype == "text"}>
<PasswordField
onChange={setResponseText}
value={responseText}
maxLength={400}
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</When>
</Choose>
</div>
<Choose>
<When condition={userInputRequest.responsetype == "text"}>
<PasswordField
onChange={setResponseText}
value={responseText}
maxLength={400}
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</When>
</Choose>
<If condition={userInputRequest.checkboxmsg != ""}>
<Checkbox
onChange={() => (checkboxStatus.current = !checkboxStatus.current)}
label={userInputRequest.checkboxmsg}
className="checkbox-text"
/>
</If>
</div>
<Choose>
<When condition={userInputRequest.responsetype == "text"}>

View File

@ -1,5 +1,6 @@
.rconndetail-modal {
width: 631px;
width: auto;
max-width: 80vw;
min-height: 565px;
.wave-modal-content {

View File

@ -12,56 +12,42 @@
}
}
.term-prompt.term-prompt-color {
.is-dark .term-prompt.term-prompt-color {
.term-prompt-branch {
color: var(--term-white);
}
.term-prompt-python {
color: var(--term-magenta);
}
.term-prompt-remote {
color: var(--term-bright-green);
&.color-green {
color: var(--term-green);
}
&.color-red {
color: var(--term-red);
}
&.color-blue {
color: var(--term-bright-blue);
}
&.color-yellow {
color: var(--term-bright-yellow);
}
&.color-magenta {
color: var(--term-bright-magenta);
}
&.color-cyan {
color: var(--termt-bright-cyan);
}
&.color-white {
color: var(--term-bright-white);
}
&.color-orange {
color: var(--term-orange);
}
}
.term-prompt-cwd {
color: var(--term-bright-green);
}
&.term-prompt-isroot .term-prompt-cwd {
color: var(--term-red);
}
}
.is-light .term-prompt.term-prompt-color {
.term-prompt-branch {
color: var(--term-black);
}
.term-prompt-remote {
color: var(--term-green);
}
.term-prompt-cwd {
color: var(--term-green);
}
&.term-prompt-isroot .term-prompt-cwd {
color: var(--term-red);
}
}
.term-prompt.term-prompt-color {
.term-prompt-python {
color: var(--term-magenta);
}
}

View File

@ -30,9 +30,9 @@ function getRemoteStr(rptr: RemotePtrType): string {
if (rptr == null || isBlank(rptr.remoteid)) {
return "(invalid remote)";
}
let username = isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid);
let remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid);
let fullRef = makeFullRemoteRef(username, remoteRef, rptr.name);
const username = isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid);
const remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid);
const fullRef = makeFullRemoteRef(username, remoteRef, rptr.name);
return fullRef;
}
@ -40,11 +40,11 @@ function getShortVEnv(venvDir: string): string {
if (isBlank(venvDir)) {
return "";
}
let lastSlash = venvDir.lastIndexOf("/");
const lastSlash = venvDir.lastIndexOf("/");
if (lastSlash == -1) {
return venvDir;
}
return venvDir.substr(lastSlash + 1);
return venvDir.substring(lastSlash + 1);
}
function replaceHomePath(path: string, homeDir: string): string {
@ -52,7 +52,7 @@ function replaceHomePath(path: string, homeDir: string): string {
return "~";
}
if (path.startsWith(homeDir + "/")) {
return "~" + path.substr(homeDir.length);
return "~" + path.substring(homeDir.length);
}
return path;
}
@ -62,7 +62,7 @@ function getCwdStr(remote: RemoteType, state: Record<string, string>): string {
return "~";
}
let cwd = state.cwd;
if (remote && remote.remotevars.home) {
if (remote?.remotevars.home) {
cwd = replaceHomePath(cwd, remote.remotevars.home);
}
return cwd;
@ -71,30 +71,29 @@ function getCwdStr(remote: RemoteType, state: Record<string, string>): string {
@mobxReact.observer
class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<string, string>; color: boolean }, {}> {
render() {
let rptr = this.props.rptr;
const rptr = this.props.rptr;
if (rptr == null || isBlank(rptr.remoteid)) {
return <span className={cn("term-prompt", "color-green")}>&nbsp;</span>;
}
let termFontSize = GlobalModel.getTermFontSize();
let remote = GlobalModel.getRemote(this.props.rptr.remoteid);
let remoteStr = getRemoteStr(rptr);
let festate = this.props.festate ?? {};
let cwd = getCwdStr(remote, festate);
const remote = GlobalModel.getRemote(this.props.rptr.remoteid);
const remoteStr = getRemoteStr(rptr);
const festate = this.props.festate ?? {};
const cwd = getCwdStr(remote, festate);
let isRoot = false;
if (remote && remote.remotevars) {
if (remote?.remotevars) {
if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") {
isRoot = true;
}
}
let remoteColorClass = isRoot ? "color-red" : "color-green";
if (remote && remote.remoteopts && remote.remoteopts.color) {
if (remote?.remoteopts?.color) {
remoteColorClass = "color-" + remote.remoteopts.color;
}
let remoteTitle: string = null;
if (remote && remote.remotecanonicalname) {
if (remote?.remotecanonicalname) {
remoteTitle = "connected to " + remote.remotecanonicalname;
}
let cwdElem = <span className="term-prompt-cwd">{cwd}</span>;
const cwdElem = <span className="term-prompt-cwd">{cwd}</span>;
let remoteElem = null;
if (remoteStr != "local") {
remoteElem = (
@ -107,7 +106,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
let pythonElem = null;
let condaElem = null;
if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) {
let branchName = festate["PROMPTVAR_GITBRANCH"];
const branchName = festate["PROMPTVAR_GITBRANCH"];
branchElem = (
<span title="current git branch" className="term-prompt-branch">
git:({branchName}){" "}
@ -115,8 +114,8 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
);
}
if (!isBlank(festate["VIRTUAL_ENV"])) {
let venvDir = festate["VIRTUAL_ENV"];
let venv = getShortVEnv(venvDir);
const venvDir = festate["VIRTUAL_ENV"];
const venv = getShortVEnv(venvDir);
pythonElem = (
<span title="python venv" className="term-prompt-python">
venv:({venv}){" "}
@ -124,7 +123,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
);
}
if (!isBlank(festate["CONDA_DEFAULT_ENV"])) {
let condaEnv = festate["CONDA_DEFAULT_ENV"];
const condaEnv = festate["CONDA_DEFAULT_ENV"];
condaElem = (
<span title="conda env" className="term-prompt-python">
conda:({condaEnv}){" "}

View File

@ -8,7 +8,7 @@ import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import cn from "classnames";
import { GlobalModel, RemotesModel, GlobalCommandRunner } from "@/models";
import { Button, Status, ShowWaveShellInstallPrompt } from "@/common/elements";
import { Button, Status } from "@/common/elements";
import * as util from "@/util/util";
import "./connections.less";
@ -71,14 +71,9 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
}
@boundMethod
importSshConfig(): void {
GlobalCommandRunner.importSshConfig();
}
@boundMethod
handleImportSshConfig(): void {
ShowWaveShellInstallPrompt(this.importSshConfig);
GlobalCommandRunner.importSshConfig();
}
@boundMethod

View File

@ -77,7 +77,7 @@
.search-checkbox {
margin: 0 15px;
padding: 5px 10px 5px 10px;
background: var(--button-secondary-bg-color);
background: var(--form-element-secondary-color);
border-radius: 4px;
display: flex;
flex-direction: row;
@ -100,9 +100,6 @@
flex-direction: row;
align-items: center;
margin-left: 15px;
.fromts-text {
}
}
.reset-button {

View File

@ -1,3 +1,5 @@
@import "@/common/icons/icons.less";
.line {
margin: 0;
padding: calc(var(--termpad) * 2) var(--termpad) calc(var(--termpad) * 2 + 1px) calc(var(--termpad) * 3);
@ -6,6 +8,7 @@
overflow: hidden;
flex-shrink: 0;
position: relative;
line-height: 11px;
font-weight: normal;
font-family: var(--termfontfamily);
scroll-margin-bottom: 20px;
@ -27,9 +30,21 @@
}
}
.centered-icon {
.positional-icon-visible;
height: 15px;
width: 15px;
.positional-icon-inner {
height: 15px;
width: 15px;
&:first-child {
font-size: var(--termfontsize-sm);
}
}
}
&.active.selected {
.line-mask {
background-color: var(--line-selected-bg-color);
border: 2px solid var(--line-active-border-color);
border-left: 4px solid var(--line-active-border-color);
}
@ -92,35 +107,6 @@
}
}
.simple-line-status {
.linenum {
cursor: pointer;
}
.avatar {
display: inline-block;
height: 1em;
margin-left: 0.5em;
vertical-align: text-top;
fill: var(--primary-background-color);
}
.success {
color: var(--primary-accent-color);
fill: var(--primary-accent-color);
}
.fail {
color: var(--line-error-color);
fill: var(--line-error-color);
}
.warning {
color: var(--line-warning-color);
fill: var(--line-warning-color);
}
}
&.top-border {
border-top: 1px solid #777;
padding: 10px;
@ -171,7 +157,7 @@
}
&:hover {
color: var(--app-icon-hover-color);
color: var(--line-actions-active-color);
}
padding: 4px 4px;
@ -203,7 +189,6 @@
.user {
color: var(--line-meta-user-color);
font-weight: bold;
margin-top: 1px;
margin-right: 10px;
}
@ -213,6 +198,31 @@
font-size: var(--termfontsize-sm);
line-height: var(--termlineheight-sm);
color: var(--term-gray);
align-items: center;
.status-icon {
display: inline-block;
margin-left: 0.5em;
height: var(--termfontsize-sm);
line-height: 1;
svg {
height: var(--termfontsize-sm);
width: var(--termfontsize-sm);
vertical-align: baseline;
}
}
i.fail {
color: var(--line-error-color);
fill: var(--line-error-color);
}
i.warning,
svg.warning {
color: var(--line-warning-color);
fill: var(--line-warning-color);
}
}
.meta-divider {
@ -310,7 +320,6 @@
margin: 6px 0 2px 10px;
padding: 2px 5px 0px 5px;
display: inline-block;
font-size: 10px;
color: var(--term-text-white);
background-color: #151715;
}

View File

@ -28,6 +28,7 @@ import * as util from "@/util/util";
import * as textmeasure from "@/util/textmeasure";
import "./line.less";
import { CenteredIcon, RotateIcon } from "../common/icons/icons";
const DebugHeightProblems = false;
const MinLine = 0;
@ -317,21 +318,19 @@ class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRigh
icon = <i className="fail fa-sharp fa-solid fa-xmark" />;
iconTitle = "error";
} else if (status == "running" || status == "detached") {
icon = <i className="warning fa-sharp fa-solid fa-rotate fa-spin" />;
icon = <RotateIcon className="warning spin rotate" />;
iconTitle = "running";
} else {
icon = <i className="fail fa-sharp fa-solid fa-question" />;
iconTitle = "unknown";
}
return (
<div
onContextMenu={this.props.onRightClick}
title={iconTitle}
className={cn("simple-line-status", "status-" + status)}
>
<span className="linenum">{lineNumStr}</span>
<div className="avatar">{icon}</div>
</div>
<>
<div className="linenum">{lineNumStr}</div>
<div title={iconTitle} className={cn("status-icon", "status-" + status)}>
{icon}
</div>
</>
);
}
}

View File

@ -1,3 +1,5 @@
@import "@/common/icons/icons.less";
.cmd-input {
max-height: max(300px, 40%);
display: flex;
@ -10,9 +12,6 @@
border-top: 2px solid var(--app-border-color);
background-color: var(--app-bg-color);
&.active {
}
&.has-info,
&.has-aichat,
&.has-history {
@ -43,11 +42,18 @@
align-items: center;
.cmdinput-icon {
display: inline-flex;
color: var(--app-icon-hover-color);
opacity: 0.5;
&.running-cmds i {
color: var(--app-warning-color);
.centered-icon {
.positional-icon-visible;
}
&.running-cmds {
.rotate {
fill: var(--app-warning-color);
}
}
&.active {

View File

@ -15,7 +15,7 @@ import { TextAreaInput } from "./textareainput";
import { InfoMsg } from "./infomsg";
import { HistoryInfo } from "./historyinfo";
import { Prompt } from "@/common/prompt/prompt";
import { RotateIcon } from "@/common/icons/icons";
import { CenteredIcon, RotateIcon } from "@/common/icons/icons";
import { AIChat } from "./aichat";
import "./cmdinput.less";
@ -155,7 +155,10 @@ class CmdInput extends React.Component<{}, {}> {
title="Filter for Running Commands"
onClick={() => this.toggleFilter(screen)}
>
{numRunningLines} <i className="fa-sharp fa-regular fa-rotate fa-spin fa-fw" />
<CenteredIcon>{numRunningLines}</CenteredIcon>{" "}
<CenteredIcon>
<RotateIcon className="rotate warning spin" />
</CenteredIcon>
</div>
</If>
<div key="ai" title="Wave AI (Cmd-Space)" className="cmdinput-icon" onClick={this.clickAIAction}>

View File

@ -249,12 +249,16 @@
width: 16px;
height: 16px;
flex-shrink: 0;
fill: var(--app-text-secondary-color);
}
.status-icon {
position: absolute;
left: 7px;
top: 8px;
circle {
stroke: var(--app-bg-color);
}
}
}

View File

@ -22,6 +22,7 @@ const WaveDevVarName = "WAVETERM_DEV";
const AuthKeyFile = "waveterm.authkey";
const DevServerEndpoint = "http://127.0.0.1:8090";
const ProdServerEndpoint = "http://127.0.0.1:1619";
const startTs = Date.now();
const isDev = process.env[WaveDevVarName] != null;
const waveHome = getWaveHomeDir();
@ -333,6 +334,10 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
webPreferences: {
preload: path.join(getElectronAppBasePath(), DistDir, "preload.js"),
},
show: false,
});
win.once("ready-to-show", () => {
win.show();
});
const indexHtml = isDev ? "index-dev.html" : "index.html";
win.loadFile(path.join(getElectronAppBasePath(), "public", indexHtml));
@ -617,12 +622,12 @@ function getFetchHeaders() {
}
async function getClientDataPoll(loopNum: number): Promise<ClientDataType | null> {
const lastTime = loopNum >= 6;
const lastTime = loopNum >= 30;
const cdata = await getClientData(!lastTime, loopNum);
if (lastTime || cdata != null) {
return cdata;
}
await sleep(1000);
await sleep(200);
return getClientDataPoll(loopNum + 1);
}
@ -897,6 +902,10 @@ electron.ipcMain.on("change-auto-update", (_, enable: boolean) => {
* @param clientData The client data to use to configure the auto-updater. If the clientData has noreleasecheck set to true, the auto-updater will be disabled.
*/
function configureAutoUpdaterStartup(clientData: ClientDataType) {
if (clientData == null) {
configureAutoUpdater(false);
return;
}
configureAutoUpdater(!clientData.clientopts.noreleasecheck);
}

View File

@ -61,7 +61,7 @@
top: 0.2em;
right: 12rem;
border-radius: 5px;
background-color: var(--button-secondary-bg-color);
background-color: var(--form-element-secondary-color);
color: var(--app-error-color);
z-index: 1;
padding: 0 0.8em;

View File

@ -666,6 +666,7 @@ declare global {
title: string;
markdown: boolean;
timeoutms: number;
checkboxmsg: string;
};
type UserInputResponsePacket = {
@ -674,6 +675,7 @@ declare global {
text?: string;
confirm?: boolean;
errormsg?: string;
checkboxstat?: boolean;
};
type RenderModeType = "normal" | "collapsed" | "expanded";

View File

@ -2202,7 +2202,7 @@ func NewHostInfo(hostName string) (*HostInfoType, error) {
func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
home := base.GetHomeDir()
localConfig := filepath.Join(home, ".ssh", "config")
systemConfig := filepath.Join("/", "ssh", "config")
systemConfig := filepath.Join("/etc", "ssh", "config")
sshConfigFiles := []string{localConfig, systemConfig}
ssh_config.ReloadConfigs()
hostPatterns, hostPatternsErr := resolveSshConfigPatterns(sshConfigFiles)

View File

@ -258,7 +258,7 @@ func isRtnStateCmd(cmd syntax.Command) bool {
}
arg1 := getCallExprLitArg(callExpr, 1)
if arg0 == "git" {
if arg1 == "checkout" || arg1 == "switch" {
if arg1 == "checkout" || arg1 == "co" || arg1 == "switch" {
return true
}
}

View File

@ -1202,14 +1202,54 @@ func (msh *MShellProc) RunInstall(autoInstall bool) {
msh.WriteToPtyBuffer("*error: cannot install on archived remote\n")
return
}
if autoInstall {
var makeClientCtx context.Context
var makeClientCancelFn context.CancelFunc
msh.WithLock(func() {
makeClientCtx, makeClientCancelFn = context.WithCancel(context.Background())
msh.MakeClientCancelFn = makeClientCancelFn
msh.MakeClientDeadline = nil
go msh.NotifyRemoteUpdate()
})
defer makeClientCancelFn()
clientData, err := sstore.EnsureClientData(makeClientCtx)
if err != nil {
msh.WriteToPtyBuffer("*error: cannot obtain client data: %v", err)
return
}
hideShellPrompt := clientData.ClientOpts.ConfirmFlags["hideshellprompt"]
baseStatus := msh.GetStatus()
if baseStatus == StatusConnected {
ctx, cancelFn := context.WithTimeout(makeClientCtx, 60*time.Second)
defer cancelFn()
request := &userinput.UserInputRequestType{
ResponseType: "confirm",
QueryText: "Waveshell is running on your connection and must be restarted to re-install. Would you like to continue?",
Title: "Restart Waveshell",
}
response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request)
if err != nil {
if err == context.Canceled {
msh.WriteToPtyBuffer("installation canceled by user\n")
} else {
msh.WriteToPtyBuffer("timed out waiting for user input\n")
}
return
}
if !response.Confirm {
msh.WriteToPtyBuffer("installation canceled by user\n")
return
}
} else if !hideShellPrompt {
ctx, cancelFn := context.WithTimeout(makeClientCtx, 60*time.Second)
defer cancelFn()
request := &userinput.UserInputRequestType{
ResponseType: "confirm",
QueryText: "Waveshell must be reinstalled on the connection to continue. Would you like to install it?",
Title: "Install Waveshell",
CheckBoxMsg: "Don't show me this again",
}
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
defer cancelFn()
response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request)
if err != nil {
var errMsg error
@ -1234,28 +1274,24 @@ func (msh *MShellProc) RunInstall(autoInstall bool) {
})
return
}
}
baseStatus := msh.GetStatus()
if baseStatus == StatusConnected {
request := &userinput.UserInputRequestType{
ResponseType: "confirm",
QueryText: "Waveshell is running on your connection and must be restarted to re-install. Would you like to continue?",
Title: "Restart Waveshell",
}
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
defer cancelFn()
response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request)
if err != nil {
if err == context.Canceled {
msh.WriteToPtyBuffer("installation canceled by user\n")
} else {
msh.WriteToPtyBuffer("timed out waiting for user input\n")
if response.CheckboxStat {
clientData.ClientOpts.ConfirmFlags["hideshellprompt"] = true
err = sstore.SetClientOpts(makeClientCtx, clientData.ClientOpts)
if err != nil {
msh.WriteToPtyBuffer("*error, %s\n", err)
msh.setErrorStatus(err)
return
}
return
}
if !response.Confirm {
msh.WriteToPtyBuffer("installation canceled by user\n")
return
//reload updated clientdata before sending
clientData, err = sstore.EnsureClientData(makeClientCtx)
if err != nil {
msh.WriteToPtyBuffer("*error, %s\n", err)
msh.setErrorStatus(err)
return
}
update := scbus.MakeUpdatePacket()
update.AddUpdate(*clientData)
}
}
curStatus := msh.GetInstallStatus()
@ -1267,14 +1303,14 @@ func (msh *MShellProc) RunInstall(autoInstall bool) {
msh.WriteToPtyBuffer("*error: cannot install on a local remote\n")
return
}
_, err := shellapi.MakeShellApi(packet.ShellType_bash)
_, err = shellapi.MakeShellApi(packet.ShellType_bash)
if err != nil {
msh.WriteToPtyBuffer("*error: %v\n", err)
return
}
if msh.Client == nil {
remoteDisplayName := fmt.Sprintf("%s [%s]", remoteCopy.RemoteAlias, remoteCopy.RemoteCanonicalName)
client, err := ConnectToClient(remoteCopy.SSHOpts, remoteDisplayName)
client, err := ConnectToClient(makeClientCtx, remoteCopy.SSHOpts, remoteDisplayName)
if err != nil {
statusErr := fmt.Errorf("ssh cannot connect to client: %w", err)
msh.setInstallErrorStatus(statusErr)
@ -1481,7 +1517,7 @@ func (msh *MShellProc) getActiveShellTypes(ctx context.Context) ([]string, error
return utilfn.CombineStrArrays(rtn, activeShells), nil
}
func (msh *MShellProc) createWaveshellSession(remoteCopy sstore.RemoteType) (shexec.ConnInterface, error) {
func (msh *MShellProc) createWaveshellSession(clientCtx context.Context, remoteCopy sstore.RemoteType) (shexec.ConnInterface, error) {
msh.WithLock(func() {
msh.Err = nil
msh.ErrNoInitPk = false
@ -1510,7 +1546,7 @@ func (msh *MShellProc) createWaveshellSession(remoteCopy sstore.RemoteType) (she
wsSession = shexec.CmdWrap{Cmd: ecmd}
} else if msh.Client == nil {
remoteDisplayName := fmt.Sprintf("%s [%s]", remoteCopy.RemoteAlias, remoteCopy.RemoteCanonicalName)
client, err := ConnectToClient(remoteCopy.SSHOpts, remoteDisplayName)
client, err := ConnectToClient(clientCtx, remoteCopy.SSHOpts, remoteDisplayName)
if err != nil {
return nil, fmt.Errorf("ssh cannot connect to client: %w", err)
}
@ -1559,16 +1595,6 @@ func (NewLauncher) Launch(msh *MShellProc, interactive bool) {
msh.WriteToPtyBuffer("remote is trying to install, cancel install before trying to connect again\n")
return
}
msh.WriteToPtyBuffer("connecting to %s...\n", remoteCopy.RemoteCanonicalName)
wsSession, err := msh.createWaveshellSession(remoteCopy)
if err != nil {
msh.WriteToPtyBuffer("*error, %s\n", err.Error())
msh.setErrorStatus(err)
msh.WithLock(func() {
msh.Client = nil
})
return
}
var makeClientCtx context.Context
var makeClientCancelFn context.CancelFunc
msh.WithLock(func() {
@ -1578,6 +1604,16 @@ func (NewLauncher) Launch(msh *MShellProc, interactive bool) {
go msh.NotifyRemoteUpdate()
})
defer makeClientCancelFn()
msh.WriteToPtyBuffer("connecting to %s...\n", remoteCopy.RemoteCanonicalName)
wsSession, err := msh.createWaveshellSession(makeClientCtx, remoteCopy)
if err != nil {
msh.WriteToPtyBuffer("*error, %s\n", err.Error())
msh.setErrorStatus(err)
msh.WithLock(func() {
msh.Client = nil
})
return
}
cproc, err := shexec.MakeClientProc(makeClientCtx, wsSession)
msh.WithLock(func() {
msh.MakeClientCancelFn = nil

View File

@ -65,7 +65,7 @@ func createDummySigner() ([]ssh.Signer, error) {
// they were successes. An error in this function prevents any other
// keys from being attempted. But if there's an error because of a dummy
// file, the library can still try again with a new key.
func createPublicKeyCallback(sshKeywords *SshKeywords, passphrase string) func() ([]ssh.Signer, error) {
func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, passphrase string) func() ([]ssh.Signer, error) {
var identityFiles []string
existingKeys := make(map[string][]byte)
@ -124,7 +124,7 @@ func createPublicKeyCallback(sshKeywords *SshKeywords, passphrase string) func()
QueryText: fmt.Sprintf("Enter passphrase for the SSH key: %s", identityFile),
Title: "Publickey Auth + Passphrase",
}
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
ctx, cancelFn := context.WithTimeout(connCtx, 60*time.Second)
defer cancelFn()
response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request)
if err != nil {
@ -150,11 +150,11 @@ func createDefaultPasswordCallbackPrompt(password string) func() (secret string,
}
}
func createInteractivePasswordCallbackPrompt(remoteDisplayName string) func() (secret string, err error) {
func createInteractivePasswordCallbackPrompt(connCtx context.Context, remoteDisplayName string) func() (secret string, err error) {
return func() (secret string, err error) {
// limited to 15 seconds for some reason. this should be investigated more
// in the future
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
ctx, cancelFn := context.WithTimeout(connCtx, 60*time.Second)
defer cancelFn()
queryText := fmt.Sprintf(
"Password Authentication requested from connection \n"+
@ -174,13 +174,13 @@ func createInteractivePasswordCallbackPrompt(remoteDisplayName string) func() (s
}
}
func createCombinedPasswordCallbackPrompt(password string, remoteDisplayName string) func() (secret string, err error) {
func createCombinedPasswordCallbackPrompt(connCtx context.Context, password string, remoteDisplayName string) func() (secret string, err error) {
var once sync.Once
return func() (secret string, err error) {
var prompt func() (secret string, err error)
once.Do(func() { prompt = createDefaultPasswordCallbackPrompt(password) })
if prompt == nil {
prompt = createInteractivePasswordCallbackPrompt(remoteDisplayName)
prompt = createInteractivePasswordCallbackPrompt(connCtx, remoteDisplayName)
}
return prompt()
}
@ -199,14 +199,14 @@ func createNaiveKbdInteractiveChallenge(password string) func(name, instruction
}
}
func createInteractiveKbdInteractiveChallenge(remoteName string) func(name, instruction string, questions []string, echos []bool) (answers []string, err error) {
func createInteractiveKbdInteractiveChallenge(connCtx context.Context, remoteName string) func(name, instruction string, questions []string, echos []bool) (answers []string, err error) {
return func(name, instruction string, questions []string, echos []bool) (answers []string, err error) {
if len(questions) != len(echos) {
return nil, fmt.Errorf("bad response from server: questions has len %d, echos has len %d", len(questions), len(echos))
}
for i, question := range questions {
echo := echos[i]
answer, err := promptChallengeQuestion(question, echo, remoteName)
answer, err := promptChallengeQuestion(connCtx, question, echo, remoteName)
if err != nil {
return nil, err
}
@ -216,10 +216,10 @@ func createInteractiveKbdInteractiveChallenge(remoteName string) func(name, inst
}
}
func promptChallengeQuestion(question string, echo bool, remoteName string) (answer string, err error) {
func promptChallengeQuestion(connCtx context.Context, question string, echo bool, remoteName string) (answer string, err error) {
// limited to 15 seconds for some reason. this should be investigated more
// in the future
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
ctx, cancelFn := context.WithTimeout(connCtx, 60*time.Second)
defer cancelFn()
queryText := fmt.Sprintf(
"Keyboard Interactive Authentication requested from connection \n"+
@ -238,13 +238,13 @@ func promptChallengeQuestion(question string, echo bool, remoteName string) (ans
return response.Text, nil
}
func createCombinedKbdInteractiveChallenge(password string, remoteName string) ssh.KeyboardInteractiveChallenge {
func createCombinedKbdInteractiveChallenge(connCtx context.Context, password string, remoteName string) ssh.KeyboardInteractiveChallenge {
var once sync.Once
return func(name, instruction string, questions []string, echos []bool) (answers []string, err error) {
var challenge ssh.KeyboardInteractiveChallenge
once.Do(func() { challenge = createNaiveKbdInteractiveChallenge(password) })
if challenge == nil {
challenge = createInteractiveKbdInteractiveChallenge(remoteName)
challenge = createInteractiveKbdInteractiveChallenge(connCtx, remoteName)
}
return challenge(name, instruction, questions, echos)
}
@ -505,7 +505,20 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) {
return waveHostKeyCallback, nil
}
func ConnectToClient(opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Client, error) {
func DialContext(ctx context.Context, network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
d := net.Dialer{Timeout: config.Timeout}
conn, err := d.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
if err != nil {
return nil, err
}
return ssh.NewClient(c, chans, reqs), nil
}
func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Client, error) {
sshConfigKeywords, err := findSshConfigKeywords(opts.SSHHost)
if err != nil {
return nil, err
@ -516,9 +529,9 @@ func ConnectToClient(opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Clien
return nil, err
}
publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(sshKeywords, opts.SSHPassword))
keyboardInteractive := ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(opts.SSHPassword, remoteDisplayName))
passwordCallback := ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(opts.SSHPassword, remoteDisplayName))
publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(connCtx, sshKeywords, opts.SSHPassword))
keyboardInteractive := ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(connCtx, opts.SSHPassword, remoteDisplayName))
passwordCallback := ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(connCtx, opts.SSHPassword, remoteDisplayName))
// batch mode turns off interactive input. this means the number of
// attemtps must drop to 1 with this setup
@ -566,7 +579,7 @@ func ConnectToClient(opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Clien
HostKeyCallback: hostKeyCallback,
}
networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port
return ssh.Dial("tcp", networkAddr, clientConfig)
return DialContext(connCtx, "tcp", networkAddr, clientConfig)
}
type SshKeywords struct {

View File

@ -21,6 +21,7 @@ type UserInputRequestType struct {
Title string `json:"title"`
Markdown bool `json:"markdown"`
TimeoutMs int `json:"timeoutms"`
CheckBoxMsg string `json:"checkboxmsg"`
}
func (*UserInputRequestType) GetType() string {
@ -39,11 +40,12 @@ const UserInputResponsePacketStr = "userinputresp"
// An RpcResponse for user input requests
type UserInputResponsePacketType struct {
Type string `json:"type"`
RequestId string `json:"requestid"`
Text string `json:"text,omitempty"`
Confirm bool `json:"confirm,omitempty"`
ErrorMsg string `json:"errormsg,omitempty"`
Type string `json:"type"`
RequestId string `json:"requestid"`
Text string `json:"text,omitempty"`
Confirm bool `json:"confirm,omitempty"`
ErrorMsg string `json:"errormsg,omitempty"`
CheckboxStat bool `json:"checkboxstat,omitempty"`
}
func (*UserInputResponsePacketType) GetType() string {