From 662172db31b2b384e166d4cea0a6d83f9caab18a Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 7 Mar 2024 14:54:03 -0800 Subject: [PATCH 1/5] Bump version to 0.7.0 (#406) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e0feb2717..5c221a70f 100644 --- a/package.json +++ b/package.json @@ -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" } -} +} \ No newline at end of file From d6124c27b6dfe63c34ef70195d0bcf27d139d73c Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 7 Mar 2024 21:32:48 -0800 Subject: [PATCH 2/5] Unify color definitions and clean up light mode (#405) * Unify color definitions and clean up light mode * consolidate form colors * increase border thickness on dropdown and text * remove dev conditional for theme * fix secondary form element color * increase dropdown border thickness * fix history textinput * make warning a bit darker * attempt to fix rotate icons * fix line actions bg * fix terminal colors * fix broken history colors * fix textinput label padding * fix bottom negative margin * updates for prompt colors. darken magenta, grey out the whites slightly, and sneak in a change for git co. * clean up prompt.tsx * fixing wobbly icons * center svg icon, simplify meta-line1 --- public/themes/default.css | 99 ++++++++------------- public/themes/light.css | 22 +++-- public/themes/term-default.css | 6 +- public/themes/term-light.css | 2 - src/app/app.less | 16 ++-- src/app/app.tsx | 3 +- src/app/assets/icons/globe.svg | 9 +- src/app/assets/icons/rotate-sharp-solid.svg | 1 + src/app/clientsettings/clientsettings.tsx | 24 +++-- src/app/common/elements/button.less | 22 ++--- src/app/common/elements/checkbox.less | 8 +- src/app/common/elements/cmdstrcode.less | 2 - src/app/common/elements/dropdown.less | 20 +++-- src/app/common/elements/mainview.less | 2 +- src/app/common/elements/textfield.less | 15 ++-- src/app/common/elements/tooltip.less | 2 +- src/app/common/modals/screensettings.less | 4 + src/app/common/modals/screensettings.tsx | 34 ++++--- src/app/common/prompt/prompt.less | 62 +++++-------- src/app/common/prompt/prompt.tsx | 41 +++++---- src/app/history/history.less | 5 +- src/app/line/line.less | 75 +++++++++------- src/app/line/linecomps.tsx | 17 ++-- src/app/workspace/cmdinput/cmdinput.less | 16 ++-- src/app/workspace/cmdinput/cmdinput.tsx | 7 +- src/app/workspace/screen/screenview.less | 4 + src/plugins/code/code.less | 2 +- wavesrv/pkg/cmdrunner/shparse.go | 2 +- 28 files changed, 253 insertions(+), 269 deletions(-) create mode 100644 src/app/assets/icons/rotate-sharp-solid.svg diff --git a/public/themes/default.css b/public/themes/default.css index 353893b29..89d4e7fbb 100644 --- a/public/themes/default.css +++ b/public/themes/default.css @@ -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,44 +92,20 @@ --form-element-border-color: rgba(241, 246, 243, 0.15); --form-element-bg-color: var(--app-bg-color); - --form-element-text-color: #ffffff; - --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; - - /* 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); @@ -136,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); @@ -176,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); @@ -190,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 */ @@ -205,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); diff --git a/public/themes/light.css b/public/themes/light.css index 19fefa4af..ca3b101c3 100644 --- a/public/themes/light.css +++ b/public/themes/light.css @@ -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); /* modal colors */ @@ -53,16 +56,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; diff --git a/public/themes/term-default.css b/public/themes/term-default.css index f0108a0de..0037124b2 100644 --- a/public/themes/term-default.css +++ b/public/themes/term-default.css @@ -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; diff --git a/public/themes/term-light.css b/public/themes/term-light.css index 27c01dd0e..2cffb2ee5 100644 --- a/public/themes/term-light.css +++ b/public/themes/term-light.css @@ -5,7 +5,5 @@ /* term colors */ --term-foreground: #000000; --term-background: #fefefe; - --term-black: #fefefe; - --term-white: #000000; --term-cmdtext: #000000; } diff --git a/src/app/app.less b/src/app/app.less index 59541aa26..71d22ecc5 100644 --- a/src/app/app.less +++ b/src/app/app.less @@ -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); } } diff --git a/src/app/app.tsx b/src/app/app.tsx index 63f36b92c..de6910e6e 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -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 (
diff --git a/src/app/assets/icons/globe.svg b/src/app/assets/icons/globe.svg index 75a6930c1..8f1966a0b 100644 --- a/src/app/assets/icons/globe.svg +++ b/src/app/assets/icons/globe.svg @@ -1,5 +1,6 @@ - - - - + + + + \ No newline at end of file diff --git a/src/app/assets/icons/rotate-sharp-solid.svg b/src/app/assets/icons/rotate-sharp-solid.svg new file mode 100644 index 000000000..e46ae88ca --- /dev/null +++ b/src/app/assets/icons/rotate-sharp-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/clientsettings/clientsettings.tsx b/src/app/clientsettings/clientsettings.tsx index 495b56be6..8c08cccd3 100644 --- a/src/app/clientsettings/clientsettings.tsx +++ b/src/app/clientsettings/clientsettings.tsx @@ -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 />
- -
-
Theme
-
- -
+
+
Theme
+
+
- +
Client ID
{cdata.clientid}
diff --git a/src/app/common/elements/button.less b/src/app/common/elements/button.less index 9d7a825d2..d37b0034e 100644 --- a/src/app/common/elements/button.less +++ b/src/app/common/elements/button.less @@ -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); } } } diff --git a/src/app/common/elements/checkbox.less b/src/app/common/elements/checkbox.less index 25e4cca67..3afbecfbb 100644 --- a/src/app/common/elements/checkbox.less +++ b/src/app/common/elements/checkbox.less @@ -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); diff --git a/src/app/common/elements/cmdstrcode.less b/src/app/common/elements/cmdstrcode.less index f6a54243f..50e830eb4 100644 --- a/src/app/common/elements/cmdstrcode.less +++ b/src/app/common/elements/cmdstrcode.less @@ -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); diff --git a/src/app/common/elements/dropdown.less b/src/app/common/elements/dropdown.less index 681a11a93..70e4bbb2d 100644 --- a/src/app/common/elements/dropdown.less +++ b/src/app/common/elements/dropdown.less @@ -3,9 +3,9 @@ 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); + background: var(--form-element-bg-color); &.no-label { height: 34px; @@ -17,7 +17,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 { @@ -88,11 +88,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); } } @@ -109,13 +113,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 { diff --git a/src/app/common/elements/mainview.less b/src/app/common/elements/mainview.less index 8433234d8..5e9e306cf 100644 --- a/src/app/common/elements/mainview.less +++ b/src/app/common/elements/mainview.less @@ -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; } diff --git a/src/app/common/elements/textfield.less b/src/app/common/elements/textfield.less index 66d78624c..343868176 100644 --- a/src/app/common/elements/textfield.less +++ b/src/app/common/elements/textfield.less @@ -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; diff --git a/src/app/common/elements/tooltip.less b/src/app/common/elements/tooltip.less index aafa80927..44ca20d5d 100644 --- a/src/app/common/elements/tooltip.less +++ b/src/app/common/elements/tooltip.less @@ -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; } } diff --git a/src/app/common/modals/screensettings.less b/src/app/common/modals/screensettings.less index 897c48d22..92e78ca42 100644 --- a/src/app/common/modals/screensettings.less +++ b/src/app/common/modals/screensettings.less @@ -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); + } } } diff --git a/src/app/common/modals/screensettings.tsx b/src/app/common/modals/screensettings.tsx index 4d9249018..d1813f68b 100644 --- a/src/app/common/modals/screensettings.tsx +++ b/src/app/common/modals/screensettings.tsx @@ -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 = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" }); errorMessage: OV = 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 ( diff --git a/src/app/common/prompt/prompt.less b/src/app/common/prompt/prompt.less index 824a2ac03..5678baaa1 100644 --- a/src/app/common/prompt/prompt.less +++ b/src/app/common/prompt/prompt.less @@ -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); + } +} diff --git a/src/app/common/prompt/prompt.tsx b/src/app/common/prompt/prompt.tsx index 21cef0946..04fdb276f 100644 --- a/src/app/common/prompt/prompt.tsx +++ b/src/app/common/prompt/prompt.tsx @@ -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 { 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 { @mobxReact.observer class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record; color: boolean }, {}> { render() { - let rptr = this.props.rptr; + const rptr = this.props.rptr; if (rptr == null || isBlank(rptr.remoteid)) { return  ; } - 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 = {cwd}; + const cwdElem = {cwd}; let remoteElem = null; if (remoteStr != "local") { remoteElem = ( @@ -107,7 +106,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record git:({branchName}){" "} @@ -115,8 +114,8 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record venv:({venv}){" "} @@ -124,7 +123,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record conda:({condaEnv}){" "} diff --git a/src/app/history/history.less b/src/app/history/history.less index 6c8ed2fda..77cbd5669 100644 --- a/src/app/history/history.less +++ b/src/app/history/history.less @@ -108,7 +108,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; @@ -135,9 +135,6 @@ flex-direction: row; align-items: center; margin-left: 15px; - - .fromts-text { - } } .reset-button { diff --git a/src/app/line/line.less b/src/app/line/line.less index d5e24691f..9351e0348 100644 --- a/src/app/line/line.less +++ b/src/app/line/line.less @@ -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; } diff --git a/src/app/line/linecomps.tsx b/src/app/line/linecomps.tsx index d2c645f3a..56b6893eb 100644 --- a/src/app/line/linecomps.tsx +++ b/src/app/line/linecomps.tsx @@ -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 = ; iconTitle = "error"; } else if (status == "running" || status == "detached") { - icon = ; + icon = ; iconTitle = "running"; } else { icon = ; iconTitle = "unknown"; } return ( -
- {lineNumStr} -
{icon}
-
+ <> +
{lineNumStr}
+
+ {icon} +
+ ); } } diff --git a/src/app/workspace/cmdinput/cmdinput.less b/src/app/workspace/cmdinput/cmdinput.less index e99f3ad5b..540758ef9 100644 --- a/src/app/workspace/cmdinput/cmdinput.less +++ b/src/app/workspace/cmdinput/cmdinput.less @@ -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 { diff --git a/src/app/workspace/cmdinput/cmdinput.tsx b/src/app/workspace/cmdinput/cmdinput.tsx index 29c81950a..6cac9b5a9 100644 --- a/src/app/workspace/cmdinput/cmdinput.tsx +++ b/src/app/workspace/cmdinput/cmdinput.tsx @@ -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} + {numRunningLines}{" "} + + +
diff --git a/src/app/workspace/screen/screenview.less b/src/app/workspace/screen/screenview.less index c3c1e3990..17ade0bd9 100644 --- a/src/app/workspace/screen/screenview.less +++ b/src/app/workspace/screen/screenview.less @@ -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); + } } } diff --git a/src/plugins/code/code.less b/src/plugins/code/code.less index 6da14f5d4..742bc160e 100644 --- a/src/plugins/code/code.less +++ b/src/plugins/code/code.less @@ -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; diff --git a/wavesrv/pkg/cmdrunner/shparse.go b/wavesrv/pkg/cmdrunner/shparse.go index 2be936260..4544d9552 100644 --- a/wavesrv/pkg/cmdrunner/shparse.go +++ b/wavesrv/pkg/cmdrunner/shparse.go @@ -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 } } From c9b2c4b24cba963e62c40c0a5d9ca677ddda252b Mon Sep 17 00:00:00 2001 From: sawka Date: Thu, 7 Mar 2024 22:05:19 -0800 Subject: [PATCH 3/5] poll for clientdata every 200ms (instead of every second) to improve startup speed. also only show window after ready-to-show. --- src/electron/emain.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/electron/emain.ts b/src/electron/emain.ts index 14819c07b..807513eb6 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -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 { - 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); } From 0b9834171dcc895c54e20c4225cce0acbb2311ca Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 7 Mar 2024 22:08:08 -0800 Subject: [PATCH 4/5] poll for clientdata every 200ms (instead of every second) to improve startup speed. also only show window after ready-to-show. (#410) From 2a5857bc3d31551a8db84515452e7d8b92e9cc12 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:37:00 -0800 Subject: [PATCH 5/5] SSH UI Quick Fixes (#408) * fix: set golbal ssh config to correct path This adds the missing "etc" directory to the path for the global config file. * chore: update auth mode tooltip This just changes the text to be slightly more accurate to the current behavior. * feat: add box to disable waveshell install modal This hooks in to the existing don't show this again code that pops up when creating a modal. * refactor: remove install modal in remote creation There used to be a modal that popped up while installing a remote that informed the user that waveshell gets installed on their remote. Since we have a new modal that pops up at the time of install, the older modal can be removed. * fix: allow user to cancel ssh dial The new ssh code broke dial for invalid urls since the context did not cancel the dial or any associated user input. This change reconnects the context along with the context for installing waveshell. * style: widen the rconndetail modal The rconndetail modal is currently narrower than the xtermjs element which results in awkward scrolling if a line is long. This change makes the width auto so it can size itself as needed. * add a max-width for safety --- src/app/common/elements/index.tsx | 1 - .../elements/showwaveshellinstallprompt.tsx | 28 ----- src/app/common/modals/createremoteconn.tsx | 33 ++---- src/app/common/modals/userinput.less | 10 +- src/app/common/modals/userinput.tsx | 47 +++++--- .../common/modals/viewremoteconndetail.less | 3 +- src/app/connections/connections.tsx | 9 +- src/types/custom.d.ts | 2 + wavesrv/pkg/cmdrunner/cmdrunner.go | 2 +- wavesrv/pkg/remote/remote.go | 112 ++++++++++++------ wavesrv/pkg/remote/sshclient.go | 47 +++++--- wavesrv/pkg/userinput/userinput.go | 12 +- 12 files changed, 165 insertions(+), 141 deletions(-) delete mode 100644 src/app/common/elements/showwaveshellinstallprompt.tsx diff --git a/src/app/common/elements/index.tsx b/src/app/common/elements/index.tsx index 30895855a..7096d3284 100644 --- a/src/app/common/elements/index.tsx +++ b/src/app/common/elements/index.tsx @@ -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"; diff --git a/src/app/common/elements/showwaveshellinstallprompt.tsx b/src/app/common/elements/showwaveshellinstallprompt.tsx deleted file mode 100644 index 647cd74d4..000000000 --- a/src/app/common/elements/showwaveshellinstallprompt.tsx +++ /dev/null @@ -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 }; diff --git a/src/app/common/modals/createremoteconn.tsx b/src/app/common/modals/createremoteconn.tsx index 297e67973..56f4e6ab5 100644 --- a/src/app/common/modals/createremoteconn.tsx +++ b/src/app/common/modals/createremoteconn.tsx @@ -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={
  • - none - no authentication, or authentication is already - configured in your ssh config. + none - no authentication details are stored.
  • - key - use a private key. + key - provide a custom private key for authentication.
  • - password - use a password. + password - provide a password (to save) for + authentication.
  • - key+password - use a key with a passphrase. + key+passphrase - provide a custom private key with a + passphrase (to save) for authentication.
} @@ -374,7 +361,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
Error: {this.getErrorStr()}
- + ); } diff --git a/src/app/common/modals/userinput.less b/src/app/common/modals/userinput.less index f86c11ca0..fd6b3e6fa 100644 --- a/src/app/common/modals/userinput.less +++ b/src/app/common/modals/userinput.less @@ -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; + } } } } diff --git a/src/app/common/modals/userinput.tsx b/src/app/common/modals/userinput.tsx index 47a3c55a8..19e0bbee0 100644 --- a/src/app/common/modals/userinput.tsx +++ b/src/app/common/modals/userinput.tsx @@ -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) => {
-
- - - - {userInputRequest.querytext} +
+
+ + + + {userInputRequest.querytext} +
+ + + handleTextKeyDown(e)} + /> + +
- - - handleTextKeyDown(e)} - /> - - + + (checkboxStatus.current = !checkboxStatus.current)} + label={userInputRequest.checkboxmsg} + className="checkbox-text" + /> +
diff --git a/src/app/common/modals/viewremoteconndetail.less b/src/app/common/modals/viewremoteconndetail.less index aee472d89..44d474178 100644 --- a/src/app/common/modals/viewremoteconndetail.less +++ b/src/app/common/modals/viewremoteconndetail.less @@ -1,5 +1,6 @@ .rconndetail-modal { - width: 631px; + width: auto; + max-width: 80vw; min-height: 565px; .wave-modal-content { diff --git a/src/app/connections/connections.tsx b/src/app/connections/connections.tsx index f5c715b8f..9cc734620 100644 --- a/src/app/connections/connections.tsx +++ b/src/app/connections/connections.tsx @@ -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 diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 0c5018b87..4395ca3bc 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -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"; diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 9291203aa..3af7379ba 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -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) diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index 9d51f9d73..07f61dec9 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -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 diff --git a/wavesrv/pkg/remote/sshclient.go b/wavesrv/pkg/remote/sshclient.go index ed770ca27..7eff78ef3 100644 --- a/wavesrv/pkg/remote/sshclient.go +++ b/wavesrv/pkg/remote/sshclient.go @@ -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 { diff --git a/wavesrv/pkg/userinput/userinput.go b/wavesrv/pkg/userinput/userinput.go index 0ab0fe410..50212acb6 100644 --- a/wavesrv/pkg/userinput/userinput.go +++ b/wavesrv/pkg/userinput/userinput.go @@ -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 {