mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
connections screen and modals (#69)
* init * connections table * view styles * new components. header and status. * action buttons * use Button component in other modals * hook add connection button * RemoteConnDetailModal component * refactor remotes model. read connection modal. * remote conn detail modal layout and styles * fix xterm styles * use correct status message in xterm * tone down color of settings input * clean up * edit remote conn modal * fix buttons gap * change button label * archive and force install features * use classnames * add some class names and also set some widths / maxwidth for the table. too hard to read on large screens. * small style updates * fix some typescript errors, other small fixups * fix type error * move add button to the bottom of the table * more improvements * adjust layout, behavior, and style accrdg to mike's feedback * set table max-width in css * open detail modal after creation of new remote * update some working (remote -> connection). fix typescript error in connections. remove some console.logs * fix a couple of mobx warnings (need to wrap in action)
This commit is contained in:
parent
64203e7823
commit
e95934e2df
@ -49,6 +49,14 @@ textarea {
|
||||
}
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
font-family: @text-s1-font;
|
||||
color: @text-primary;
|
||||
}
|
||||
|
||||
.text-standard {
|
||||
font-size: 12.5px;
|
||||
font-weight: 300;
|
||||
@ -219,7 +227,8 @@ a.a-block {
|
||||
|
||||
.history-view,
|
||||
.bookmarks-view,
|
||||
.plugins-view {
|
||||
.plugins-view,
|
||||
.connections-view {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -465,85 +474,99 @@ a.a-block {
|
||||
}
|
||||
|
||||
.icon.color-red {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-red;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-green {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-green;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-orange {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-orange;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-blue {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-yellow {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-yellow;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-pink {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-pink;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-mint {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-mint;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-cyan {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-cyan;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-violet {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-violet;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-white {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-white;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icon.status-connected {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @status-connected;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icon.status-connecting {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @status-connecting;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icon.status-disconnected {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @status-disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icon.status-error {
|
||||
path, circle {
|
||||
path,
|
||||
circle {
|
||||
fill: @status-error;
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,14 @@ import { WorkspaceView } from "./workspace/workspaceview";
|
||||
import { PluginsView } from "./pluginsview/pluginsview";
|
||||
import { BookmarksView } from "./bookmarks/bookmarks";
|
||||
import { HistoryView } from "./history/history";
|
||||
import { ConnectionsView } from "./connections/connections";
|
||||
import {
|
||||
ScreenSettingsModal,
|
||||
SessionSettingsModal,
|
||||
LineSettingsModal,
|
||||
ClientSettingsModal,
|
||||
} from "./common/modals/settings";
|
||||
import { RemotesModal } from "./connections/connections";
|
||||
import { RemotesModal } from "./connections_deprecated/connections";
|
||||
import { TosModal } from "./common/modals/modals";
|
||||
import { MainSideBar } from "./sidebar/sidebar";
|
||||
import {
|
||||
@ -30,6 +31,8 @@ import {
|
||||
AlertModal,
|
||||
AboutModal,
|
||||
CreateRemoteConnModal,
|
||||
ViewRemoteConnDetailModal,
|
||||
EditRemoteConnModal,
|
||||
} from "./common/modals/modals";
|
||||
import { ErrorBoundary } from "./common/error/errorboundary";
|
||||
import "./app.less";
|
||||
@ -85,14 +88,17 @@ class App extends React.Component<{}, {}> {
|
||||
let sessionSettingsModal = GlobalModel.sessionSettingsModal.get();
|
||||
let lineSettingsModal = GlobalModel.lineSettingsModal.get();
|
||||
let clientSettingsModal = GlobalModel.clientSettingsModal.get();
|
||||
let remotesModel = GlobalModel.remotesModalModel;
|
||||
let remotesModal = remotesModel.isOpen();
|
||||
let remotesModel = GlobalModel.remotesModel;
|
||||
let remotesModalMode = remotesModel.modalMode.get();
|
||||
let selectedRemoteId = remotesModel.selectedRemoteId.get();
|
||||
let selectedRemote = GlobalModel.getRemote(selectedRemoteId);
|
||||
let isAuthEditMode = remotesModel.isAuthEditMode();
|
||||
let remoteEdit = remotesModel.remoteEdit.get();
|
||||
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
|
||||
let hasClientStop = GlobalModel.getHasClientStop();
|
||||
let dcWait = this.dcWait.get();
|
||||
let platform = GlobalModel.getPlatform();
|
||||
|
||||
if (disconnected || hasClientStop) {
|
||||
if (!dcWait) {
|
||||
setTimeout(() => this.updateDcWait(true), 1500);
|
||||
@ -117,7 +123,6 @@ class App extends React.Component<{}, {}> {
|
||||
if (dcWait) {
|
||||
setTimeout(() => this.updateDcWait(false), 0);
|
||||
}
|
||||
//console.log(`GlobalModel.activeMainView.get() = ${GlobalModel.activeMainView.get()}`); // @mike - if I remove this, I cant see plugins
|
||||
return (
|
||||
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
|
||||
<div className="main-content">
|
||||
@ -127,6 +132,7 @@ class App extends React.Component<{}, {}> {
|
||||
<WorkspaceView />
|
||||
<HistoryView />
|
||||
<BookmarksView />
|
||||
<ConnectionsView model={remotesModel} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<AlertModal />
|
||||
@ -136,9 +142,26 @@ class App extends React.Component<{}, {}> {
|
||||
<If condition={GlobalModel.aboutModalOpen.get()}>
|
||||
<AboutModal />
|
||||
</If>
|
||||
<If condition={remoteEdit !== null && !remoteEdit.old}>
|
||||
<If condition={remoteEdit !== null && remotesModalMode === "add"}>
|
||||
<CreateRemoteConnModal model={remotesModel} remoteEdit={remoteEdit} />
|
||||
</If>
|
||||
<If condition={selectedRemote != null}>
|
||||
<If condition={!isAuthEditMode && remotesModalMode === "read"}>
|
||||
<ViewRemoteConnDetailModal
|
||||
key={"remotedetail-" + selectedRemoteId}
|
||||
remote={selectedRemote}
|
||||
model={remotesModel}
|
||||
/>
|
||||
</If>
|
||||
<If condition={remoteEdit !== null && isAuthEditMode && remotesModalMode === "edit"}>
|
||||
<EditRemoteConnModal
|
||||
key={"remotedetail-" + selectedRemoteId}
|
||||
remote={selectedRemote}
|
||||
model={remotesModel}
|
||||
remoteEdit={remoteEdit}
|
||||
/>
|
||||
</If>
|
||||
</If>
|
||||
<If condition={screenSettingsModal != null}>
|
||||
<ScreenSettingsModal
|
||||
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}
|
||||
@ -155,9 +178,6 @@ class App extends React.Component<{}, {}> {
|
||||
<If condition={clientSettingsModal}>
|
||||
<ClientSettingsModal />
|
||||
</If>
|
||||
<If condition={remotesModal}>
|
||||
<RemotesModal model={GlobalModel.remotesModalModel} />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -257,33 +257,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wave-button {
|
||||
display: flex;
|
||||
padding: 6px 16px !important;
|
||||
color: @term-white !important;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 6px !important;
|
||||
height: auto !important;
|
||||
|
||||
&:hover {
|
||||
color: @term-white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.wave-button.is-wave-green {
|
||||
color: @term-bright-white !important;
|
||||
background: @term-green !important;
|
||||
|
||||
&:hover {
|
||||
background-color: @term-green;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset;
|
||||
color: @term-bright-white !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.button.is-plain,
|
||||
.button.is-prompt-cancel {
|
||||
background-color: #222;
|
||||
@ -637,7 +610,8 @@
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
height: 44px;
|
||||
width: 412px;
|
||||
min-width: 412px;
|
||||
width: 100%;
|
||||
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
|
||||
border-radius: 6px;
|
||||
background: var(--element-hover-2, rgba(255, 255, 255, 0.06));
|
||||
@ -801,10 +775,18 @@
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||
|
||||
&:hover {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
&.focused {
|
||||
border-color: @term-green;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: @term-red;
|
||||
}
|
||||
@ -931,3 +913,135 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wave-button {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
|
||||
display: flex;
|
||||
padding: 6px 16px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 6px;
|
||||
height: auto;
|
||||
|
||||
&:hover {
|
||||
color: @term-white;
|
||||
}
|
||||
|
||||
i {
|
||||
fill: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: @term-green;
|
||||
background: none;
|
||||
|
||||
i {
|
||||
fill: @term-green;
|
||||
}
|
||||
|
||||
&.solid {
|
||||
color: @term-bright-white;
|
||||
background: @term-green;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset;
|
||||
|
||||
i {
|
||||
fill: @term-white;
|
||||
}
|
||||
}
|
||||
|
||||
&.outlined {
|
||||
border: 1px solid @term-green;
|
||||
}
|
||||
|
||||
&.ghost {
|
||||
// Styles for .ghost are already defined above
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @term-bright-white;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
color: @term-white;
|
||||
background: none;
|
||||
|
||||
&.solid {
|
||||
background: rgba(255, 255, 255, 0.09);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.outlined {
|
||||
border: 1px solid rgba(255, 255, 255, 0.09);
|
||||
}
|
||||
|
||||
&.ghost {
|
||||
padding: 6px 10px;
|
||||
|
||||
i {
|
||||
fill: @term-green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.color-red {
|
||||
&.solid {
|
||||
border-color: @term-red;
|
||||
background-color: mix(@term-red, @term-white, 50%);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.outlined {
|
||||
color: @term-red;
|
||||
border-color: @term-red;
|
||||
}
|
||||
|
||||
&.ghost {
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.link-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.wave-status-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.dot {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.dot.green {
|
||||
background-color: @status-connected;
|
||||
}
|
||||
|
||||
.dot.red {
|
||||
background-color: @status-error;
|
||||
}
|
||||
|
||||
.dot.gray {
|
||||
background-color: @status-disconnected;
|
||||
}
|
||||
|
||||
.dot.yellow {
|
||||
background-color: @status-connecting;
|
||||
}
|
||||
}
|
||||
|
@ -217,6 +217,112 @@ class Tooltip extends React.Component<TooltipProps, TooltipState> {
|
||||
}
|
||||
}
|
||||
|
||||
type ButtonVariantType = "outlined" | "solid" | "ghost";
|
||||
type ButtonThemeType = "primary" | "secondary";
|
||||
|
||||
interface ButtonProps {
|
||||
theme?: ButtonThemeType;
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
variant?: ButtonVariantType;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
class Button extends React.Component<ButtonProps> {
|
||||
static defaultProps = {
|
||||
theme: "primary",
|
||||
variant: "solid",
|
||||
color: "",
|
||||
};
|
||||
|
||||
@boundMethod
|
||||
handleClick() {
|
||||
if (this.props.onClick && !this.props.disabled) {
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { leftIcon, rightIcon, theme, children, disabled, variant, color } = this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn("wave-button", theme, variant, color, { disabled: disabled })}
|
||||
onClick={this.handleClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||
{children}
|
||||
{rightIcon && <span className="icon-right">{rightIcon}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IconButton extends Button {
|
||||
render() {
|
||||
const { children, theme, variant = "solid", ...rest } = this.props;
|
||||
const className = `wave-button icon-button ${theme} ${variant}`;
|
||||
|
||||
return (
|
||||
<button {...rest} className={className}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default IconButton;
|
||||
|
||||
interface LinkButtonProps extends ButtonProps {
|
||||
href: string;
|
||||
target?: string;
|
||||
}
|
||||
|
||||
class LinkButton extends IconButton {
|
||||
render() {
|
||||
// @ts-ignore
|
||||
const { href, target, leftIcon, rightIcon, children, theme, variant }: LinkButtonProps = this.props;
|
||||
|
||||
return (
|
||||
<a href={href} target={target} className={`wave-button link-button`}>
|
||||
<button {...this.props} className={`icon-button ${theme} ${variant}`}>
|
||||
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||
{children}
|
||||
{rightIcon && <span className="icon-right">{rightIcon}</span>}
|
||||
</button>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
interface StatusProps {
|
||||
status: "green" | "red" | "gray" | "yellow";
|
||||
text: string;
|
||||
}
|
||||
|
||||
class Status extends React.Component<StatusProps> {
|
||||
@boundMethod
|
||||
renderDot() {
|
||||
const { status } = this.props;
|
||||
|
||||
return <div className={`dot ${status}`} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { text } = this.props;
|
||||
|
||||
return (
|
||||
<div className="wave-status-container">
|
||||
{this.renderDot()}
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface TextFieldDecorationProps {
|
||||
startDecoration?: React.ReactNode;
|
||||
endDecoration?: React.ReactNode;
|
||||
@ -232,6 +338,7 @@ interface TextFieldProps {
|
||||
required?: boolean;
|
||||
maxLength?: number;
|
||||
autoFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface TextFieldState {
|
||||
@ -267,6 +374,22 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
}
|
||||
}
|
||||
|
||||
// Method to handle focus at the component level
|
||||
@boundMethod
|
||||
handleComponentFocus() {
|
||||
if (this.inputRef.current && !this.inputRef.current.contains(document.activeElement)) {
|
||||
this.inputRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Method to handle blur at the component level
|
||||
@boundMethod
|
||||
handleComponentBlur() {
|
||||
if (this.inputRef.current && this.inputRef.current.contains(document.activeElement)) {
|
||||
this.inputRef.current.blur();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleFocus() {
|
||||
this.setState({ focused: true });
|
||||
@ -311,14 +434,23 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { label, value, placeholder, decoration, className, maxLength, autoFocus } = this.props;
|
||||
const { label, value, placeholder, decoration, className, maxLength, autoFocus, disabled } = this.props;
|
||||
const { focused, internalValue, error } = this.state;
|
||||
|
||||
// Decide if the input should behave as controlled or uncontrolled
|
||||
const inputValue = value !== undefined ? value : internalValue;
|
||||
|
||||
return (
|
||||
<div className={cn(`wave-textfield ${className || ""}`, { focused: focused, error: error })}>
|
||||
<div
|
||||
className={cn(`wave-textfield ${className || ""}`, {
|
||||
focused: focused,
|
||||
error: error,
|
||||
disabled: disabled,
|
||||
})}
|
||||
onFocus={this.handleComponentFocus}
|
||||
onBlur={this.handleComponentBlur}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
|
||||
<div className="wave-textfield-inner">
|
||||
<label
|
||||
@ -341,6 +473,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
placeholder={placeholder}
|
||||
maxLength={maxLength}
|
||||
autoFocus={autoFocus}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{decoration?.endDecoration && <>{decoration.endDecoration}</>}
|
||||
@ -980,4 +1113,8 @@ export {
|
||||
NumberField,
|
||||
PasswordField,
|
||||
Tooltip,
|
||||
Button,
|
||||
IconButton,
|
||||
LinkButton,
|
||||
Status,
|
||||
};
|
||||
|
@ -460,7 +460,7 @@
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
|
||||
div.button {
|
||||
button:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
@ -468,29 +468,215 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wave-button {
|
||||
display: flex;
|
||||
padding: 6px 16px;
|
||||
align-items: center;
|
||||
gap: var(--sizing-2-xs, 4px);
|
||||
border-radius: 6px;
|
||||
height: auto;
|
||||
.wave-modal.rconndetail-modal {
|
||||
.wave-modal-content.rconndetail-wave-modal-content {
|
||||
width: 631px;
|
||||
min-height: 565px;
|
||||
overflow: visible;
|
||||
|
||||
.wave-modal-content-inner.rconndetail-wave-modal-content-inner {
|
||||
display: flex;
|
||||
padding-bottom: 0px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.rconndetail-wave-modal-body {
|
||||
display: flex;
|
||||
padding: 0px 20px;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-self: stretch;
|
||||
|
||||
.name-header-actions-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
|
||||
.rconndetail-name {
|
||||
color: @term-bright-white;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
|
||||
.wave-button {
|
||||
padding: 4px 15px;
|
||||
font-size: 11px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-detail {
|
||||
.settings-field {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.settings-label {
|
||||
font-weight: bold;
|
||||
width: 12em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settings-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
color: @term-white;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-field:not(:first-child) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
padding: 3px 8px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
border-radius: 6px;
|
||||
background: rgba(241, 246, 243, 0.08);
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
|
||||
.terminal-connectelem {
|
||||
height: 163px !important; // Needed to override plugin height
|
||||
|
||||
.xterm-viewport {
|
||||
display: flex;
|
||||
padding: 6px 10px;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
|
||||
background: #080a08;
|
||||
height: 163px !important; // Needed to override plugin height
|
||||
}
|
||||
|
||||
.xterm-screen {
|
||||
padding: 10px;
|
||||
width: 541px !important; // Needed to override plugin width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rconndetail-wave-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
padding: 0 20px 20px;
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
|
||||
button:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wave-button.color-green {
|
||||
color: @term-bright-white;
|
||||
background: @term-green !important; // !important is needed to override the default button color
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset;
|
||||
.wave-modal.erconn-modal {
|
||||
.wave-modal-content.erconn-wave-modal-content {
|
||||
width: 502px;
|
||||
min-height: 411px;
|
||||
overflow: visible;
|
||||
|
||||
&:hover {
|
||||
color: @term-bright-white;
|
||||
.wave-modal-content-inner.erconn-wave-modal-content-inner {
|
||||
display: flex;
|
||||
padding-bottom: 0px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.erconn-wave-modal-body {
|
||||
display: flex;
|
||||
padding: 0px 20px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.name-actions-section {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
|
||||
.name {
|
||||
color: @term-bright-white;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
|
||||
.wave-button {
|
||||
padding: 4px 15px;
|
||||
font-size: 11px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.erconn-wave-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
padding: 0 20px 20px;
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
|
||||
button:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wave-button.color-standard {
|
||||
color: @term-white;
|
||||
background: var(--overlays-white-6, rgba(255, 255, 255, 0.12));
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
|
||||
&:hover {
|
||||
color: @term-white;
|
||||
|
@ -9,13 +9,14 @@ import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModalModel } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../model/model";
|
||||
import * as T from "../../../types/types";
|
||||
import { Markdown, InfoMessage } from "../common";
|
||||
import * as util from "../../../util/util";
|
||||
import * as textmeasure from "../../../util/textmeasure";
|
||||
import { Toggle, Checkbox } from "../common";
|
||||
import { ClientDataType } from "../../../types/types";
|
||||
import { TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip } from "../common";
|
||||
import { TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip, Button, Status } from "../common";
|
||||
|
||||
import close from "../../assets/icons/close.svg";
|
||||
import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.svg";
|
||||
@ -35,6 +36,10 @@ let BUILD = __WAVETERM_BUILD__;
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
const RemotePtyRows = 9;
|
||||
const RemotePtyCols = 80;
|
||||
const PasswordUnchangedSentinel = "--unchanged--";
|
||||
|
||||
@mobxReact.observer
|
||||
class DisconnectedModal extends React.Component<{}, {}> {
|
||||
logRef: any = React.createRef();
|
||||
@ -335,15 +340,9 @@ class TosModal extends React.Component<{}, {}> {
|
||||
/>
|
||||
</div>
|
||||
<div className="button-wrapper">
|
||||
<button
|
||||
onClick={this.acceptTos}
|
||||
className={cn("button wave-button is-wave-green is-outlined is-small", {
|
||||
"disabled-button": !this.state.isChecked,
|
||||
})}
|
||||
disabled={!this.state.isChecked}
|
||||
>
|
||||
<Button onClick={this.acceptTos} disabled={!this.state.isChecked}>
|
||||
Continue
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
@ -377,7 +376,9 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
// TODO no up-to-date status reporting
|
||||
return (
|
||||
<div className="status updated">
|
||||
<div className="text-selectable">Client Version {VERSION} ({BUILD})</div>
|
||||
<div className="text-selectable">
|
||||
Client Version {VERSION} ({BUILD})
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -388,7 +389,9 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
<i className="fa-sharp fa-solid fa-circle-check" />
|
||||
<span>Up to Date</span>
|
||||
</div>
|
||||
<div className="selectable">Client Version {VERSION} ({BUILD})</div>
|
||||
<div className="selectable">
|
||||
Client Version {VERSION} ({BUILD})
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -398,7 +401,9 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation" />
|
||||
<span>Outdated Version</span>
|
||||
</div>
|
||||
<div className="selectable">Client Version {VERSION} ({BUILD})</div>
|
||||
<div className="selectable">
|
||||
Client Version {VERSION} ({BUILD})
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={this.updateApp} className="button color-green text-secondary">
|
||||
Update
|
||||
@ -427,7 +432,11 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div className="text-wrapper">
|
||||
<div>Wave Terminal</div>
|
||||
<div className="text-standard">Modern Terminal for<br/>Seamless Workflow</div>
|
||||
<div className="text-standard">
|
||||
Modern Terminal for
|
||||
<br />
|
||||
Seamless Workflow
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="wave-modal-section about-section text-standard">
|
||||
@ -473,13 +482,12 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel; remoteEdit: T.RemoteEditType }, {}> {
|
||||
class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remoteEdit: T.RemoteEditType }, {}> {
|
||||
tempAlias: OV<string>;
|
||||
tempHostName: OV<string>;
|
||||
tempPort: OV<string>;
|
||||
tempAuthMode: OV<string>;
|
||||
tempConnectMode: OV<string>;
|
||||
tempManualMode: OV<boolean>;
|
||||
tempPassword: OV<string>;
|
||||
tempKeyFile: OV<string>;
|
||||
errorStr: OV<string>;
|
||||
@ -559,7 +567,6 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
let crRtn = GlobalCommandRunner.screenSetRemote(cname, true, false);
|
||||
crRtn.then((crcrtn) => {
|
||||
if (crcrtn.success) {
|
||||
model.closeModal();
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
@ -572,6 +579,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
this.errorStr.set(crtn.error);
|
||||
})();
|
||||
});
|
||||
model.seRecentConnAdded(true);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@ -595,6 +603,13 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeAuthMode(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempAuthMode.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangePort(value: string): void {
|
||||
mobx.action(() => {
|
||||
@ -609,8 +624,15 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeConnectMode(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempConnectMode.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
render() {
|
||||
let { model, remoteEdit } = this.props;
|
||||
let { model } = this.props;
|
||||
let authMode = this.tempAuthMode.get();
|
||||
|
||||
return (
|
||||
@ -620,7 +642,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
<div className="wave-modal-content-inner crconn-wave-modal-content-inner">
|
||||
<header className="wave-modal-header crconn-wave-modal-header">
|
||||
<div className="wave-modal-title crconn-wave-modal-title">Add Connection</div>
|
||||
<div className="wave-modal-close crconn-wave-modal-close" onClick={model.cancelEditAuth}>
|
||||
<div className="wave-modal-close crconn-wave-modal-close" onClick={model.closeModal}>
|
||||
<img src={close} alt="Close (Escape)" />
|
||||
</div>
|
||||
</header>
|
||||
@ -698,9 +720,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
{ value: "key+password", label: "key+password" },
|
||||
]}
|
||||
value={this.tempAuthMode.get()}
|
||||
onChange={(val: string) => {
|
||||
this.tempAuthMode.set(val);
|
||||
}}
|
||||
onChange={this.handleChangeAuthMode}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
@ -772,9 +792,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
{ value: "manual", label: "manual" },
|
||||
]}
|
||||
value={this.tempConnectMode.get()}
|
||||
onChange={(val: string) => {
|
||||
this.tempConnectMode.set(val);
|
||||
}}
|
||||
onChange={this.handleChangeConnectMode}
|
||||
/>
|
||||
</div>
|
||||
<If condition={!util.isBlank(this.getErrorStr())}>
|
||||
@ -783,15 +801,10 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
</div>
|
||||
<footer className="wave-modal-footer crconn-wave-modal-footer">
|
||||
<div className="action-buttons">
|
||||
<div onClick={model.cancelEditAuth} className="button wave-button is-plain">
|
||||
<Button theme="secondary" onClick={model.closeModal}>
|
||||
Cancel
|
||||
</div>
|
||||
<button
|
||||
onClick={this.submitRemote}
|
||||
className="button wave-button is-wave-green text-standard"
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
</Button>
|
||||
<Button onClick={this.submitRemote}>Connect</Button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
@ -801,4 +814,687 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModalModel;
|
||||
}
|
||||
}
|
||||
|
||||
export { LoadingSpinner, ClientStopModal, AlertModal, DisconnectedModal, TosModal, AboutModal, CreateRemoteConnModal };
|
||||
@mobxReact.observer
|
||||
class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; remote: T.RemoteType }, {}> {
|
||||
termRef: React.RefObject<any> = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
let elem = this.termRef.current;
|
||||
if (elem == null) {
|
||||
console.log("ERROR null term-remote element");
|
||||
return;
|
||||
}
|
||||
this.props.model.createTermWrap(elem);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
let { remote } = this.props;
|
||||
if (remote == null || remote.archived) {
|
||||
this.props.model.deSelectRemote();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.model.disposeTerm();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickTermBlock(): void {
|
||||
if (this.props.model.remoteTermWrap != null) {
|
||||
this.props.model.remoteTermWrap.giveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
getRemoteTypeStr(remote: T.RemoteType): string {
|
||||
if (!util.isBlank(remote.uname)) {
|
||||
let unameStr = remote.uname;
|
||||
unameStr = unameStr.replace("|", ", ");
|
||||
return remote.remotetype + " (" + unameStr + ")";
|
||||
}
|
||||
return remote.remotetype;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
connectRemote(remoteId: string) {
|
||||
GlobalCommandRunner.connectRemote(remoteId);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
disconnectRemote(remoteId: string) {
|
||||
GlobalCommandRunner.disconnectRemote(remoteId);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
installRemote(remoteId: string) {
|
||||
GlobalCommandRunner.installRemote(remoteId);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
cancelInstall(remoteId: string) {
|
||||
GlobalCommandRunner.installCancelRemote(remoteId);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
openEditModal(): void {
|
||||
this.props.model.openEditModal();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
getStatus(status: string) {
|
||||
switch (status) {
|
||||
case "connected":
|
||||
return "green";
|
||||
case "disconnected":
|
||||
return "gray";
|
||||
default:
|
||||
return "red";
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickArchive(): void {
|
||||
let { remote } = this.props;
|
||||
if (remote.status == "connected") {
|
||||
GlobalModel.showAlert({ message: "Cannot delete a connected connection. Disconnect and try again." });
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalModel.showAlert({
|
||||
message: "Are you sure you want to delete this connection?",
|
||||
confirm: true,
|
||||
});
|
||||
prtn.then((confirm) => {
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
GlobalCommandRunner.archiveRemote(remote.remoteid);
|
||||
});
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClose(): void {
|
||||
let { model } = this.props;
|
||||
model.closeModal();
|
||||
model.seRecentConnAdded(false);
|
||||
}
|
||||
|
||||
renderInstallStatus(remote: T.RemoteType): any {
|
||||
let statusStr: string = null;
|
||||
if (remote.installstatus == "disconnected") {
|
||||
if (remote.needsmshellupgrade) {
|
||||
statusStr = "mshell " + remote.mshellversion + " - needs upgrade";
|
||||
} else if (util.isBlank(remote.mshellversion)) {
|
||||
statusStr = "mshell unknown";
|
||||
} else {
|
||||
statusStr = "mshell " + remote.mshellversion + " - current";
|
||||
}
|
||||
} else {
|
||||
statusStr = remote.installstatus;
|
||||
}
|
||||
if (statusStr == null) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div key="install-status" className="settings-field">
|
||||
<div className="settings-label"> Install Status</div>
|
||||
<div className="settings-input">{statusStr}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderHeaderBtns(remote: T.RemoteType): React.ReactNode {
|
||||
let buttons: React.ReactNode[] = [];
|
||||
const archiveButton = (
|
||||
<Button theme="secondary" onClick={() => this.clickArchive()}>
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
const disconnectButton = (
|
||||
<Button theme="secondary" onClick={() => this.disconnectRemote(remote.remoteid)}>
|
||||
Disconnect Now
|
||||
</Button>
|
||||
);
|
||||
const connectButton = (
|
||||
<Button theme="secondary" onClick={() => this.connectRemote(remote.remoteid)}>
|
||||
Connect Now
|
||||
</Button>
|
||||
);
|
||||
const tryReconnectButton = (
|
||||
<Button theme="secondary" onClick={() => this.connectRemote(remote.remoteid)}>
|
||||
Try Reconnect
|
||||
</Button>
|
||||
);
|
||||
let updateAuthButton = (
|
||||
<Button theme="secondary" onClick={() => this.openEditModal()}>
|
||||
Edit
|
||||
</Button>
|
||||
);
|
||||
let cancelInstallButton = (
|
||||
<Button theme="secondary" onClick={() => this.cancelInstall(remote.remoteid)}>
|
||||
Cancel Install
|
||||
</Button>
|
||||
);
|
||||
let installNowButton = (
|
||||
<Button theme="secondary" onClick={() => this.installRemote(remote.remoteid)}>
|
||||
Install Now
|
||||
</Button>
|
||||
);
|
||||
if (remote.local) {
|
||||
installNowButton = <></>;
|
||||
updateAuthButton = <></>;
|
||||
cancelInstallButton = <></>;
|
||||
}
|
||||
buttons = [archiveButton, updateAuthButton];
|
||||
if (remote.status == "connected" || remote.status == "connecting") {
|
||||
buttons.push(disconnectButton);
|
||||
} else if (remote.status == "disconnected") {
|
||||
buttons.push(connectButton);
|
||||
} else if (remote.status == "error") {
|
||||
if (remote.needsmshellupgrade) {
|
||||
if (remote.installstatus == "connecting") {
|
||||
buttons.push(cancelInstallButton);
|
||||
} else {
|
||||
buttons.push(installNowButton);
|
||||
}
|
||||
} else {
|
||||
buttons.push(tryReconnectButton);
|
||||
}
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let button: React.ReactNode = null;
|
||||
|
||||
return (
|
||||
<For each="button" of={buttons} index="i">
|
||||
<div key={i}>{button}</div>
|
||||
</For>
|
||||
);
|
||||
}
|
||||
|
||||
getMessage(remote: T.RemoteType): string {
|
||||
let message = "";
|
||||
if (remote.status == "connected") {
|
||||
message = "Connected and ready to run commands.";
|
||||
} else if (remote.status == "connecting") {
|
||||
message = remote.waitingforpassword ? "Connecting, waiting for user-input..." : "Connecting...";
|
||||
let connectTimeout = remote.connecttimeout ?? 0;
|
||||
message = message + " (" + connectTimeout + "s)";
|
||||
} else if (remote.status == "disconnected") {
|
||||
message = "Disconnected";
|
||||
} else if (remote.status == "error") {
|
||||
if (remote.noinitpk) {
|
||||
message = "Error, could not connect.";
|
||||
} else if (remote.needsmshellupgrade) {
|
||||
if (remote.installstatus == "connecting") {
|
||||
message = "Installing...";
|
||||
} else {
|
||||
message = "Error, needs install.";
|
||||
}
|
||||
} else {
|
||||
message = "Error";
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
render() {
|
||||
let { model, remote } = this.props;
|
||||
let isTermFocused = model.remoteTermWrapFocus.get();
|
||||
let termFontSize = GlobalModel.termFontSize.get();
|
||||
let termWidth = textmeasure.termWidthFromCols(RemotePtyCols, termFontSize);
|
||||
let remoteAliasText = util.isBlank(remote.remotealias) ? "(none)" : remote.remotealias;
|
||||
|
||||
return (
|
||||
<div className={cn("modal wave-modal rconndetail-modal is-active")}>
|
||||
<div className="modal-background wave-modal-background" />
|
||||
<div className="modal-content wave-modal-content rconndetail-wave-modal-content">
|
||||
<div className="wave-modal-content-inner rconndetail-wave-modal-content-inner">
|
||||
<header className="wave-modal-header rconndetail-wave-modal-header">
|
||||
<div className="wave-modal-title rconndetail-wave-modal-title">Connection</div>
|
||||
<div className="wave-modal-close rconndetail-wave-modal-close" onClick={model.closeModal}>
|
||||
<img src={close} alt="Close (Escape)" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="wave-modal-body rconndetail-wave-modal-body">
|
||||
<div className="name-header-actions-wrapper">
|
||||
<div className="name text-primary">{getName(remote)}</div>
|
||||
<div className="header-actions">{this.renderHeaderBtns(remote)}</div>
|
||||
</div>
|
||||
<div className="remote-detail" style={{ overflow: "hidden" }}>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Conn Id</div>
|
||||
<div className="settings-input">{remote.remoteid}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Type</div>
|
||||
<div className="settings-input">{this.getRemoteTypeStr(remote)}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Canonical Name</div>
|
||||
<div className="settings-input">
|
||||
{remote.remotecanonicalname}
|
||||
<If
|
||||
condition={
|
||||
!util.isBlank(remote.remotevars.port) && remote.remotevars.port != "22"
|
||||
}
|
||||
>
|
||||
<span style={{ marginLeft: 5 }}>(port {remote.remotevars.port})</span>
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field" style={{ minHeight: 24 }}>
|
||||
<div className="settings-label">Alias</div>
|
||||
<div className="settings-input">{remoteAliasText}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Auth Type</div>
|
||||
<div className="settings-input">
|
||||
<If condition={!remote.local}>{remote.authtype}</If>
|
||||
<If condition={remote.local}>local</If>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Connect Mode</div>
|
||||
<div className="settings-input">{remote.connectmode}</div>
|
||||
</div>
|
||||
{this.renderInstallStatus(remote)}
|
||||
<div className="flex-spacer" style={{ minHeight: 20 }} />
|
||||
<div className="status">
|
||||
<Status status={this.getStatus(remote.status)} text={this.getMessage(remote)} />
|
||||
</div>
|
||||
<div
|
||||
key="term"
|
||||
className={cn(
|
||||
"terminal-wrapper",
|
||||
{ focus: isTermFocused },
|
||||
remote != null ? "status-" + remote.status : null
|
||||
)}
|
||||
>
|
||||
<If condition={!isTermFocused}>
|
||||
<div key="termblock" className="term-block" onClick={this.clickTermBlock}></div>
|
||||
</If>
|
||||
<If condition={model.showNoInputMsg.get()}>
|
||||
<div key="termtag" className="term-tag">
|
||||
input is only allowed while status is 'connecting'
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
key="terminal"
|
||||
className="terminal-connectelem"
|
||||
ref={this.termRef}
|
||||
data-remoteid={remote.remoteid}
|
||||
style={{
|
||||
height: textmeasure.termHeightFromRows(RemotePtyRows, termFontSize),
|
||||
width: termWidth,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer className="wave-modal-footer rconndetail-wave-modal-footer">
|
||||
<div className="action-buttons">
|
||||
<Button theme="secondary" onClick={model.closeModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={model.closeModal}>Done</Button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class EditRemoteConnModal extends React.Component<
|
||||
{ model: RemotesModel; remote: T.RemoteType; remoteEdit: T.RemoteEditType },
|
||||
{}
|
||||
> {
|
||||
tempAlias: OV<string>;
|
||||
tempAuthMode: OV<string>;
|
||||
tempConnectMode: OV<string>;
|
||||
tempPassword: OV<string>;
|
||||
tempKeyFile: OV<string>;
|
||||
submitted: OV<boolean>;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const { remote, remoteEdit } = this.props;
|
||||
// console.log("remoteEdit", remoteEdit);
|
||||
this.tempAlias = mobx.observable.box(remote.remotealias ?? "", { name: "EditRemoteSettings-alias" });
|
||||
this.tempAuthMode = mobx.observable.box(remote.authtype, { name: "EditRemoteSettings-authMode" });
|
||||
this.tempConnectMode = mobx.observable.box(remote.connectmode, { name: "EditRemoteSettings-connectMode" });
|
||||
this.tempKeyFile = mobx.observable.box(remoteEdit.keystr ?? "", { name: "EditRemoteSettings-keystr" });
|
||||
this.tempPassword = mobx.observable.box(remoteEdit.haspassword ? PasswordUnchangedSentinel : "", {
|
||||
name: "EditRemoteSettings-password",
|
||||
});
|
||||
this.submitted = mobx.observable.box(false, { name: "EditRemoteSettings-submitted" });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
let { remote } = this.props;
|
||||
if (remote == null || remote.archived) {
|
||||
this.props.model.deSelectRemote();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickArchive(): void {
|
||||
let { remote } = this.props;
|
||||
if (remote.status == "connected") {
|
||||
GlobalModel.showAlert({ message: "Cannot delete a connected connection. Disconnect and try again." });
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalModel.showAlert({
|
||||
message: "Are you sure you want to delete this connection?",
|
||||
confirm: true,
|
||||
});
|
||||
prtn.then((confirm) => {
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
GlobalCommandRunner.archiveRemote(remote.remoteid);
|
||||
});
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickForceInstall(): void {
|
||||
let { remote } = this.props;
|
||||
GlobalCommandRunner.installRemote(remote.remoteid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeKeyFile(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempKeyFile.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangePassword(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempPassword.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeAlias(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempAlias.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeConnectMode(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempConnectMode.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeAuthMode(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempAuthMode.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
canResetPw(): boolean {
|
||||
let { remoteEdit } = this.props;
|
||||
if (remoteEdit == null) {
|
||||
return false;
|
||||
}
|
||||
return remoteEdit.haspassword && this.tempPassword.get() != PasswordUnchangedSentinel;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
resetPw(): void {
|
||||
mobx.action(() => {
|
||||
this.tempPassword.set(PasswordUnchangedSentinel);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onFocusPassword(e: any) {
|
||||
if (this.tempPassword.get() == PasswordUnchangedSentinel) {
|
||||
e.target.select();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
submitRemote(): void {
|
||||
let { remote, remoteEdit, model } = this.props;
|
||||
let authMode = this.tempAuthMode.get();
|
||||
let kwargs: Record<string, string> = {};
|
||||
if (!util.isStrEq(this.tempKeyFile.get(), remoteEdit.keystr)) {
|
||||
if (authMode == "key" || authMode == "key+password") {
|
||||
kwargs["key"] = this.tempKeyFile.get();
|
||||
} else {
|
||||
kwargs["key"] = "";
|
||||
}
|
||||
}
|
||||
if (authMode == "password" || authMode == "key+password") {
|
||||
if (this.tempPassword.get() != PasswordUnchangedSentinel) {
|
||||
kwargs["password"] = this.tempPassword.get();
|
||||
}
|
||||
} else {
|
||||
if (remoteEdit.haspassword) {
|
||||
kwargs["password"] = "";
|
||||
}
|
||||
}
|
||||
if (!util.isStrEq(this.tempAlias.get(), remote.remotealias)) {
|
||||
kwargs["alias"] = this.tempAlias.get();
|
||||
}
|
||||
if (!util.isStrEq(this.tempConnectMode.get(), remote.connectmode)) {
|
||||
kwargs["connectmode"] = this.tempConnectMode.get();
|
||||
}
|
||||
if (Object.keys(kwargs).length == 0) {
|
||||
mobx.action(() => {
|
||||
this.submitted.set(true);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
kwargs["visual"] = "1";
|
||||
kwargs["submit"] = "1";
|
||||
GlobalCommandRunner.editRemote(remote.remoteid, kwargs);
|
||||
mobx.action(() => {
|
||||
this.submitted.set(true);
|
||||
})();
|
||||
model.seRecentConnAdded(false);
|
||||
}
|
||||
|
||||
renderAuthModeMessage(): any {
|
||||
let authMode = this.tempAuthMode.get();
|
||||
if (authMode == "none") {
|
||||
return (
|
||||
<span>
|
||||
This connection requires no authentication.
|
||||
<br />
|
||||
Or authentication is already configured in ssh_config.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (authMode == "key") {
|
||||
return <span>Use a public/private keypair.</span>;
|
||||
}
|
||||
if (authMode == "password") {
|
||||
return <span>Use a password.</span>;
|
||||
}
|
||||
if (authMode == "key+password") {
|
||||
return <span>Use a public/private keypair with a passphrase.</span>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
let { model, remote, remoteEdit } = this.props;
|
||||
let authMode = this.tempAuthMode.get();
|
||||
|
||||
if (util.isBlank(remoteEdit.errorstr) && this.submitted.get()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("modal wave-modal erconn-modal is-active")}>
|
||||
<div className="modal-background wave-modal-background" />
|
||||
<div className="modal-content wave-modal-content erconn-wave-modal-content">
|
||||
<div className="wave-modal-content-inner erconn-wave-modal-content-inner">
|
||||
<header className="wave-modal-header erconn-wave-modal-header">
|
||||
<div className="wave-modal-title erconn-wave-modal-title">Edit Connection</div>
|
||||
<div className="wave-modal-close erconn-wave-modal-close" onClick={model.closeModal}>
|
||||
<img src={close} alt="Close (Escape)" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="wave-modal-body erconn-wave-modal-body">
|
||||
<div className="name-actions-section">
|
||||
<div className="name text-primary">{getName(remote)}</div>
|
||||
<div className="header-actions">
|
||||
<Button theme="secondary" onClick={this.clickArchive}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button theme="secondary" onClick={this.clickForceInstall}>
|
||||
Force Install
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="alias-section">
|
||||
<TextField
|
||||
label="Alias"
|
||||
onChange={this.handleChangeAlias}
|
||||
value={this.tempAlias.get()}
|
||||
maxLength={100}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
<Tooltip
|
||||
message={`(Optional) A short alias to use when selecting or displaying this connection.`}
|
||||
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-circle-question" />
|
||||
</Tooltip>
|
||||
</InputDecoration>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="authmode-section">
|
||||
<Dropdown
|
||||
label="Auth Mode"
|
||||
options={[
|
||||
{ value: "none", label: "none" },
|
||||
{ value: "key", label: "key" },
|
||||
{ value: "password", label: "password" },
|
||||
{ value: "key+password", label: "key+password" },
|
||||
]}
|
||||
value={this.tempAuthMode.get()}
|
||||
onChange={this.handleChangeAuthMode}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
<Tooltip
|
||||
message={
|
||||
<ul>
|
||||
<li>
|
||||
<b>none</b> - no authentication, or authentication is
|
||||
already configured in your ssh config.
|
||||
</li>
|
||||
<li>
|
||||
<b>key</b> - use a private key.
|
||||
</li>
|
||||
<li>
|
||||
<b>password</b> - use a password.
|
||||
</li>
|
||||
<li>
|
||||
<b>key+password</b> - use a key with a passphrase.
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-circle-question" />
|
||||
</Tooltip>
|
||||
</InputDecoration>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<If condition={authMode == "key" || authMode == "key+password"}>
|
||||
<TextField
|
||||
label="SSH Keyfile"
|
||||
placeholder="keyfile path"
|
||||
onChange={this.handleChangeKeyFile}
|
||||
value={this.tempKeyFile.get()}
|
||||
maxLength={400}
|
||||
required={true}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
<Tooltip
|
||||
message={`(Required) The path to your ssh key file.`}
|
||||
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-circle-question" />
|
||||
</Tooltip>
|
||||
</InputDecoration>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</If>
|
||||
<If condition={authMode == "password" || authMode == "key+password"}>
|
||||
<PasswordField
|
||||
label={authMode == "password" ? "SSH Password" : "Key Passphrase"}
|
||||
placeholder="password"
|
||||
onChange={this.handleChangePassword}
|
||||
value={this.tempPassword.get()}
|
||||
maxLength={400}
|
||||
/>
|
||||
</If>
|
||||
<div className="connectmode-section">
|
||||
<Dropdown
|
||||
label="Connect Mode"
|
||||
options={[
|
||||
{ value: "startup", label: "startup" },
|
||||
{ value: "key", label: "key" },
|
||||
{ value: "auto", label: "auto" },
|
||||
{ value: "manual", label: "manual" },
|
||||
]}
|
||||
value={this.tempConnectMode.get()}
|
||||
onChange={this.handleChangeConnectMode}
|
||||
/>
|
||||
</div>
|
||||
<If condition={!util.isBlank(remoteEdit.errorstr)}>
|
||||
<div className="settings-field settings-error">Error: {remoteEdit.errorstr}</div>
|
||||
</If>
|
||||
</div>
|
||||
<footer className="wave-modal-footer erconn-wave-modal-footer">
|
||||
<div className="action-buttons">
|
||||
<Button theme="secondary" onClick={() => model.openReadModal(remote.remoteid)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={this.submitRemote}>Save</Button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getName = (remote: T.RemoteType) => {
|
||||
const { remotealias, remotecanonicalname } = remote;
|
||||
return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname;
|
||||
};
|
||||
|
||||
export {
|
||||
LoadingSpinner,
|
||||
ClientStopModal,
|
||||
AlertModal,
|
||||
DisconnectedModal,
|
||||
TosModal,
|
||||
AboutModal,
|
||||
CreateRemoteConnModal,
|
||||
ViewRemoteConnDetailModal,
|
||||
EditRemoteConnModal,
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, TabColors, MinFontSize, MaxFontSize } from "../../../model/model";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common";
|
||||
import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types";
|
||||
import { ConnectionDropdown } from "../../connections/connections";
|
||||
import { ConnectionDropdown } from "../../connections_deprecated/connections";
|
||||
import { PluginModel } from "../../../plugins/plugins";
|
||||
import * as util from "../../../util/util";
|
||||
import { commandRtnHandler } from "../../../util/util";
|
||||
|
@ -1,408 +1,107 @@
|
||||
@import "../../app/common/themes/themes.less";
|
||||
|
||||
.modal.prompt-modal.remotes-modal {
|
||||
.modal-content {
|
||||
min-width: 850px;
|
||||
}
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
fill: @base-color;
|
||||
margin: 0;
|
||||
}
|
||||
.button {
|
||||
svg {
|
||||
float: right;
|
||||
margin-top: 0.3em;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.dropdown,
|
||||
.button {
|
||||
display: inline-flex;
|
||||
}
|
||||
.dropdown .button {
|
||||
border: none !important;
|
||||
}
|
||||
.inner-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
min-height: 45em;
|
||||
max-height: 45em;
|
||||
|
||||
.remotes-menu {
|
||||
flex: 0 0 200px;
|
||||
border-right: 1px solid @disabled-color;
|
||||
overflow-y: auto;
|
||||
|
||||
.remote-menu-item {
|
||||
border-top: 1px solid @disabled-color;
|
||||
padding: 0.5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
|
||||
&.add-remote {
|
||||
padding: 10px 5px 10px 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
background-color: @active-menu-color;
|
||||
|
||||
.remote-name .remote-name-secondary {
|
||||
color: @term-white;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.remote-status-light {
|
||||
width: 2em;
|
||||
margin-top: 0.7em;
|
||||
margin-right: 0.7em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.remote-name {
|
||||
flex-grow: 1;
|
||||
|
||||
.remote-name-primary {
|
||||
font-weight: bold;
|
||||
max-width: 10em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.remote-name-secondary {
|
||||
color: @disabled-color;
|
||||
max-width: 14em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-detail {
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.settings-field {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
* {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-subtitle {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: @term-white;
|
||||
padding: 0.75em 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid #777;
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
&.has-message {
|
||||
margin-top: 0;
|
||||
}
|
||||
box-shadow: none;
|
||||
border: 1px solid #777;
|
||||
border-radius: 0 0 5px 5px;
|
||||
.xterm-rows {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.remote-message {
|
||||
margin-top: 5px;
|
||||
padding: 8px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
background-color: #333;
|
||||
border: 1px solid #777;
|
||||
border-bottom: none;
|
||||
|
||||
.message-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
svg {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.remote-status {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-field {
|
||||
.update-auth-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.update-auth-button {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.hide-hover {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.auth-editing,
|
||||
&.create-remote {
|
||||
.settings-field.align-top {
|
||||
align-items: flex-start;
|
||||
|
||||
.settings-label {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.settings-input {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 12em !important;
|
||||
}
|
||||
|
||||
.settings-field .settings-input .undo-icon {
|
||||
cursor: pointer;
|
||||
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.editremote-dropdown .dropdown-trigger button {
|
||||
width: 120px;
|
||||
justify-content: flex-start;
|
||||
color: @base-color;
|
||||
border: none;
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-field .raw-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.settings-input input {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
width: 250px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dropdown .dropdown-item {
|
||||
padding: 5px 5px 5px 12px;
|
||||
}
|
||||
|
||||
.dropdown .dropdown-content {
|
||||
max-width: 10.6em;
|
||||
}
|
||||
|
||||
.settings-input {
|
||||
.info-message {
|
||||
margin-left: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
.info-message {
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
position: relative;
|
||||
padding: 2px 10px 5px 4px;
|
||||
margin: 5px 5px 10px 5px;
|
||||
box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.3);
|
||||
&.focus {
|
||||
box-shadow: 0 0 3px 3px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.term-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: @term-red;
|
||||
color: @term-white;
|
||||
z-index: 110;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown.conn-dropdown {
|
||||
padding-left: 0;
|
||||
.connections-view {
|
||||
background-color: @background-session;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(241, 246, 243, 0.08);
|
||||
border: 1px solid rgba(241, 246, 243, 0.08);
|
||||
background: var(--element-window, rgba(13, 13, 13, 0.85));
|
||||
|
||||
.conn-dd-trigger {
|
||||
.header {
|
||||
margin: 24px 18px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 413px;
|
||||
padding: 6px 8px 6px 12px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 42px;
|
||||
|
||||
.lefticon {
|
||||
margin-right: 8px;
|
||||
margin-top: 4px;
|
||||
position: relative;
|
||||
|
||||
.status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
stroke-width: 2px;
|
||||
stroke: @status-outline;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
right: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.dd-control {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.globe-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.conntext {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex: 1 0 0;
|
||||
|
||||
.conntext-solo {
|
||||
color: @text-primary;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.conntext-1 {
|
||||
color: @text-primary;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.conntext-2 {
|
||||
color: @text-secondary;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.connections-title {
|
||||
}
|
||||
}
|
||||
|
||||
.conn-dd-menu {
|
||||
.no-items {
|
||||
display: flex;
|
||||
width: 413px;
|
||||
padding: 6px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border-radius: 8px;
|
||||
background-color: @dropdown-menu;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding: 30px 0 30px 0;
|
||||
border: 1px solid white;
|
||||
border-radius: 3px;
|
||||
margin: 20px 50px 20px 20px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
padding: 5px 12px 5px 8px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
border-radius: 6px;
|
||||
.connections-table {
|
||||
margin: 0px 10px 10px 10px;
|
||||
table-layout: fixed;
|
||||
max-width: 970px;
|
||||
|
||||
.status-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 3px;
|
||||
|
||||
svg.status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
colgroup {
|
||||
.first-col {
|
||||
max-width: 650px;
|
||||
}
|
||||
|
||||
.add-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
svg.add-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
path {
|
||||
fill: @text-primary;
|
||||
}
|
||||
}
|
||||
.second-col {
|
||||
max-width: 150px;
|
||||
}
|
||||
.third-col {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-standard {
|
||||
thead {
|
||||
border-radius: var(--sizing-2-xs, 4px);
|
||||
border-top: 1px solid rgba(250, 250, 250, 0.1);
|
||||
border-bottom: 1px solid rgba(241, 246, 243, 0.15);
|
||||
background: var(--opacity-zinc-502, rgba(250, 250, 250, 0.02));
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||
|
||||
th {
|
||||
height: 32px;
|
||||
padding: 5px 15px 5px 10px;
|
||||
color: @text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
color: @text-caption;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
tr.connections-item {
|
||||
border-bottom: 1px solid rgba(241, 246, 243, 0.15);
|
||||
color: @text-secondary;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(241, 246, 243, 0.08);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
|
||||
td.bookmark i {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
height: 40px;
|
||||
padding: 5px 15px 5px 10px;
|
||||
vertical-align: middle;
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.hovered {
|
||||
.action-buttons {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.help-entry {
|
||||
margin: 1em 2em;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
408
src/app/connections_deprecated/connections.less
Normal file
408
src/app/connections_deprecated/connections.less
Normal file
@ -0,0 +1,408 @@
|
||||
@import "../../app/common/themes/themes.less";
|
||||
|
||||
.modal.prompt-modal.remotes-modal {
|
||||
.modal-content {
|
||||
min-width: 850px;
|
||||
}
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
fill: @base-color;
|
||||
margin: 0;
|
||||
}
|
||||
.button {
|
||||
svg {
|
||||
float: right;
|
||||
margin-top: 0.3em;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.dropdown,
|
||||
.button {
|
||||
display: inline-flex;
|
||||
}
|
||||
.dropdown .button {
|
||||
border: none !important;
|
||||
}
|
||||
.inner-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
min-height: 45em;
|
||||
max-height: 45em;
|
||||
|
||||
.remotes-menu {
|
||||
flex: 0 0 200px;
|
||||
border-right: 1px solid @disabled-color;
|
||||
overflow-y: auto;
|
||||
|
||||
.remote-menu-item {
|
||||
border-top: 1px solid @disabled-color;
|
||||
padding: 0.5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
|
||||
&.add-remote {
|
||||
padding: 10px 5px 10px 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
background-color: @active-menu-color;
|
||||
|
||||
.remote-name .remote-name-secondary {
|
||||
color: @term-white;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.remote-status-light {
|
||||
width: 2em;
|
||||
margin-top: 0.7em;
|
||||
margin-right: 0.7em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.remote-name {
|
||||
flex-grow: 1;
|
||||
|
||||
.remote-name-primary {
|
||||
font-weight: bold;
|
||||
max-width: 10em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.remote-name-secondary {
|
||||
color: @disabled-color;
|
||||
max-width: 14em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-detail {
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.settings-field {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
* {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-subtitle {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: @term-white;
|
||||
padding: 0.75em 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid #777;
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
&.has-message {
|
||||
margin-top: 0;
|
||||
}
|
||||
box-shadow: none;
|
||||
border: 1px solid #777;
|
||||
border-radius: 0 0 5px 5px;
|
||||
.xterm-rows {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.remote-message {
|
||||
margin-top: 5px;
|
||||
padding: 8px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
background-color: #333;
|
||||
border: 1px solid #777;
|
||||
border-bottom: none;
|
||||
|
||||
.message-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
svg {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.remote-status {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-field {
|
||||
.update-auth-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.update-auth-button {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.hide-hover {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.auth-editing,
|
||||
&.create-remote {
|
||||
.settings-field.align-top {
|
||||
align-items: flex-start;
|
||||
|
||||
.settings-label {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.settings-input {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 12em !important;
|
||||
}
|
||||
|
||||
.settings-field .settings-input .undo-icon {
|
||||
cursor: pointer;
|
||||
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.editremote-dropdown .dropdown-trigger button {
|
||||
width: 120px;
|
||||
justify-content: flex-start;
|
||||
color: @base-color;
|
||||
border: none;
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-field .raw-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.settings-input input {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
width: 250px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dropdown .dropdown-item {
|
||||
padding: 5px 5px 5px 12px;
|
||||
}
|
||||
|
||||
.dropdown .dropdown-content {
|
||||
max-width: 10.6em;
|
||||
}
|
||||
|
||||
.settings-input {
|
||||
.info-message {
|
||||
margin-left: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
.info-message {
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
position: relative;
|
||||
padding: 2px 10px 5px 4px;
|
||||
margin: 5px 5px 10px 5px;
|
||||
box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.3);
|
||||
&.focus {
|
||||
box-shadow: 0 0 3px 3px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.term-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: @term-red;
|
||||
color: @term-white;
|
||||
z-index: 110;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown.conn-dropdown {
|
||||
padding-left: 0;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(241, 246, 243, 0.08);
|
||||
|
||||
.conn-dd-trigger {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 413px;
|
||||
padding: 6px 8px 6px 12px;
|
||||
align-items: center;
|
||||
height: 42px;
|
||||
|
||||
.lefticon {
|
||||
margin-right: 8px;
|
||||
margin-top: 4px;
|
||||
position: relative;
|
||||
|
||||
.status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
stroke-width: 2px;
|
||||
stroke: @status-outline;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
right: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.dd-control {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.globe-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.conntext {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex: 1 0 0;
|
||||
|
||||
.conntext-solo {
|
||||
color: @text-primary;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.conntext-1 {
|
||||
color: @text-primary;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.conntext-2 {
|
||||
color: @text-secondary;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conn-dd-menu {
|
||||
display: flex;
|
||||
width: 413px;
|
||||
padding: 6px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border-radius: 8px;
|
||||
background-color: @dropdown-menu;
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
padding: 5px 12px 5px 8px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
border-radius: 6px;
|
||||
|
||||
.status-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 3px;
|
||||
|
||||
svg.status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
svg.add-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
path {
|
||||
fill: @text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-standard {
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
color: @text-caption;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(241, 246, 243, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1300
src/app/connections_deprecated/connections.tsx
Normal file
1300
src/app/connections_deprecated/connections.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -110,6 +110,15 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
GlobalCommandRunner.bookmarksView();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleConnectionsClick(): void {
|
||||
if (GlobalModel.activeMainView.get() == "connections") {
|
||||
GlobalModel.showSessionView();
|
||||
return;
|
||||
}
|
||||
GlobalCommandRunner.connectionsView();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleWebSharingClick(): void {
|
||||
if (GlobalModel.activeMainView.get() == "webshare") {
|
||||
@ -126,11 +135,6 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleConnectionsClick(): void {
|
||||
GlobalModel.remotesModalModel.openModal();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
openSessionSettings(e: any, session: Session): void {
|
||||
e.preventDefault();
|
||||
@ -199,14 +203,14 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
<div className="logo">
|
||||
<If condition={isCollapsed}>
|
||||
<div className="logo-container" onClick={this.toggleCollapsed}>
|
||||
<img src="public/logos/wave-logo.png"/>
|
||||
<img src="public/logos/wave-logo.png" />
|
||||
</div>
|
||||
</If>
|
||||
<If condition={!isCollapsed}>
|
||||
<div className="logo-container">
|
||||
<img src="public/logos/wave-dark.png"/>
|
||||
<img src="public/logos/wave-dark.png" />
|
||||
</div>
|
||||
<div className="spacer"/>
|
||||
<div className="spacer" />
|
||||
<div className="collapse-button" onClick={this.toggleCollapsed}>
|
||||
<LeftChevronIcon className="icon" />
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@ import { getRemoteStr } from "../../common/prompt/prompt";
|
||||
import { GlobalModel, ScreenLines, Screen, Session } from "../../../model/model";
|
||||
import { Line } from "../../line/linecomps";
|
||||
import { LinesView } from "../../line/linesview";
|
||||
import { ConnectionDropdown } from "../../connections/connections";
|
||||
import { ConnectionDropdown } from "../../connections_deprecated/connections";
|
||||
import * as util from "../../../util/util";
|
||||
import { TextField, InputDecoration } from "../../common/common";
|
||||
import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg";
|
||||
@ -101,7 +101,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
|
||||
@boundMethod
|
||||
clickNewConnection(): void {
|
||||
GlobalModel.remotesModalModel.openModalForEdit({ remoteedit: true, old: false }, true);
|
||||
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
|
||||
}
|
||||
|
||||
renderTabIconSelector(): React.ReactNode {
|
||||
@ -117,7 +117,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
<div className="text-s1 unselectable">Select the icon</div>
|
||||
<div className="control-iconlist tabicon-list">
|
||||
<div key="square" className="icondiv" title="square" onClick={() => this.selectTabIcon("square")}>
|
||||
<SquareIcon className="icon square-icon"/>
|
||||
<SquareIcon className="icon square-icon" />
|
||||
</div>
|
||||
<For each="icon" of={TabIcons}>
|
||||
<div
|
||||
|
@ -767,7 +767,7 @@ class Screen {
|
||||
e.preventDefault();
|
||||
let p = navigator.clipboard.readText();
|
||||
p.then((text) => {
|
||||
termWrap.dataHandler?.(text);
|
||||
termWrap.dataHandler?.(text, termWrap);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@ -2203,6 +2203,14 @@ class HistoryViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionsViewModel {
|
||||
showConnectionsView(): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.activeMainView.set("connections");
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
class BookmarksModel {
|
||||
bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
|
||||
name: "Bookmarks",
|
||||
@ -2700,6 +2708,202 @@ class RemotesModalModel {
|
||||
}
|
||||
}
|
||||
|
||||
class RemotesModel {
|
||||
modalMode: OV<null | "read" | "add" | "edit"> = mobx.observable.box(null, {
|
||||
name: "RemotesModel-modalMode",
|
||||
});
|
||||
selectedRemoteId: OV<string> = mobx.observable.box(null, {
|
||||
name: "RemotesModel-selectedRemoteId",
|
||||
});
|
||||
remoteTermWrap: TermWrap;
|
||||
remoteTermWrapFocus: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "RemotesModel-remoteTermWrapFocus",
|
||||
});
|
||||
showNoInputMsg: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "RemotesModel-showNoInputMg",
|
||||
});
|
||||
showNoInputTimeoutId: any = null;
|
||||
remoteEdit: OV<RemoteEditType> = mobx.observable.box(null, {
|
||||
name: "RemotesModel-remoteEdit",
|
||||
});
|
||||
recentConnAddedState: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "RemotesModel-recentlyAdded",
|
||||
});
|
||||
|
||||
isOpen(): boolean {
|
||||
return this.modalMode.get() != null;
|
||||
}
|
||||
|
||||
get recentConnAdded(): boolean {
|
||||
return this.recentConnAddedState.get();
|
||||
}
|
||||
|
||||
seRecentConnAdded(value: boolean) {
|
||||
this.recentConnAddedState.set(value);
|
||||
}
|
||||
|
||||
deSelectRemote(): void {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(null);
|
||||
this.remoteEdit.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
openReadModal(remoteId: string): void {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(remoteId);
|
||||
this.remoteEdit.set(null);
|
||||
this.modalMode.set("read");
|
||||
})();
|
||||
}
|
||||
|
||||
openAddModal(redit: RemoteEditType): void {
|
||||
mobx.action(() => {
|
||||
this.remoteEdit.set(redit);
|
||||
this.modalMode.set("add");
|
||||
})();
|
||||
}
|
||||
|
||||
openEditModal(redit?: RemoteEditType): void {
|
||||
if (redit === undefined) {
|
||||
this.startEditAuth();
|
||||
}
|
||||
if (redit != null) {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(redit.remoteid);
|
||||
this.remoteEdit.set(redit);
|
||||
this.modalMode.set("edit");
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
selectRemote(remoteId: string): void {
|
||||
if (this.selectedRemoteId.get() == remoteId) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(remoteId);
|
||||
this.remoteEdit.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
startEditAuth(): void {
|
||||
let remoteId = this.selectedRemoteId.get();
|
||||
if (remoteId != null) {
|
||||
GlobalCommandRunner.openEditRemote(remoteId);
|
||||
}
|
||||
}
|
||||
|
||||
getModalMode(): string {
|
||||
return this.modalMode.get();
|
||||
}
|
||||
|
||||
isAuthEditMode(): boolean {
|
||||
return this.remoteEdit.get() != null;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
this.modalMode.set(null);
|
||||
this.selectedRemoteId.set(null);
|
||||
})();
|
||||
setTimeout(() => GlobalModel.refocus(), 10);
|
||||
}
|
||||
|
||||
disposeTerm(): void {
|
||||
if (this.remoteTermWrap == null) {
|
||||
return;
|
||||
}
|
||||
this.remoteTermWrap.dispose();
|
||||
this.remoteTermWrap = null;
|
||||
mobx.action(() => {
|
||||
this.remoteTermWrapFocus.set(false);
|
||||
})();
|
||||
}
|
||||
|
||||
receiveData(remoteId: string, ptyPos: number, ptyData: Uint8Array, reason?: string) {
|
||||
if (this.remoteTermWrap == null) {
|
||||
return;
|
||||
}
|
||||
if (this.remoteTermWrap.getContextRemoteId() != remoteId) {
|
||||
return;
|
||||
}
|
||||
this.remoteTermWrap.receiveData(ptyPos, ptyData);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setRemoteTermWrapFocus(focus: boolean): void {
|
||||
mobx.action(() => {
|
||||
this.remoteTermWrapFocus.set(focus);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setShowNoInputMsg(val: boolean) {
|
||||
mobx.action(() => {
|
||||
if (this.showNoInputTimeoutId != null) {
|
||||
clearTimeout(this.showNoInputTimeoutId);
|
||||
this.showNoInputTimeoutId = null;
|
||||
}
|
||||
if (val) {
|
||||
this.showNoInputMsg.set(true);
|
||||
this.showNoInputTimeoutId = setTimeout(() => this.setShowNoInputMsg(false), 2000);
|
||||
} else {
|
||||
this.showNoInputMsg.set(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
termKeyHandler(remoteId: string, event: any, termWrap: TermWrap): void {
|
||||
let remote = GlobalModel.getRemote(remoteId);
|
||||
if (remote == null) {
|
||||
return;
|
||||
}
|
||||
if (remote.status != "connecting" && remote.installstatus != "connecting") {
|
||||
this.setShowNoInputMsg(true);
|
||||
return;
|
||||
}
|
||||
let inputPacket: RemoteInputPacketType = {
|
||||
type: "remoteinput",
|
||||
remoteid: remoteId,
|
||||
inputdata64: btoa(event.key),
|
||||
};
|
||||
GlobalModel.sendInputPacket(inputPacket);
|
||||
}
|
||||
|
||||
createTermWrap(elem: HTMLElement): void {
|
||||
this.disposeTerm();
|
||||
let remoteId = this.selectedRemoteId.get();
|
||||
if (remoteId == null) {
|
||||
return;
|
||||
}
|
||||
let termOpts = {
|
||||
rows: RemotePtyRows,
|
||||
cols: RemotePtyCols,
|
||||
flexrows: false,
|
||||
maxptysize: 64 * 1024,
|
||||
};
|
||||
let termWrap = new TermWrap(elem, {
|
||||
termContext: { remoteId: remoteId },
|
||||
usedRows: RemotePtyRows,
|
||||
termOpts: termOpts,
|
||||
winSize: null,
|
||||
keyHandler: (e, termWrap) => {
|
||||
this.termKeyHandler(remoteId, e, termWrap);
|
||||
},
|
||||
focusHandler: this.setRemoteTermWrapFocus.bind(this),
|
||||
isRunning: true,
|
||||
fontSize: GlobalModel.termFontSize.get(),
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: null,
|
||||
});
|
||||
this.remoteTermWrap = termWrap;
|
||||
}
|
||||
}
|
||||
|
||||
class Model {
|
||||
clientId: string;
|
||||
activeSessionId: OV<string> = mobx.observable.box(null, {
|
||||
@ -2729,9 +2933,10 @@ class Model {
|
||||
authKey: string;
|
||||
isDev: boolean;
|
||||
platform: string;
|
||||
activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare"> = mobx.observable.box("session", {
|
||||
name: "activeMainView",
|
||||
});
|
||||
activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare" | "connections"> =
|
||||
mobx.observable.box("session", {
|
||||
name: "activeMainView",
|
||||
});
|
||||
termFontSize: CV<number>;
|
||||
alertMessage: OV<AlertMessageType> = mobx.observable.box(null, {
|
||||
name: "alertMessage",
|
||||
@ -2753,11 +2958,13 @@ class Model {
|
||||
name: "lineSettingsModal",
|
||||
}); // linenum
|
||||
remotesModalModel: RemotesModalModel;
|
||||
remotesModel: RemotesModel;
|
||||
|
||||
inputModel: InputModel;
|
||||
pluginsModel: PluginsModel;
|
||||
bookmarksModel: BookmarksModel;
|
||||
historyViewModel: HistoryViewModel;
|
||||
connectionViewModel: ConnectionsViewModel;
|
||||
clientData: OV<ClientDataType> = mobx.observable.box(null, {
|
||||
name: "clientData",
|
||||
});
|
||||
@ -2777,7 +2984,9 @@ class Model {
|
||||
this.pluginsModel = new PluginsModel();
|
||||
this.bookmarksModel = new BookmarksModel();
|
||||
this.historyViewModel = new HistoryViewModel();
|
||||
this.connectionViewModel = new ConnectionsViewModel();
|
||||
this.remotesModalModel = new RemotesModalModel();
|
||||
this.remotesModel = new RemotesModel();
|
||||
let isWaveSrvRunning = getApi().getWaveSrvStatus();
|
||||
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
|
||||
name: "model-wavesrv-running",
|
||||
@ -3003,8 +3212,8 @@ class Model {
|
||||
GlobalModel.screenSettingsModal.set(null);
|
||||
didSomething = true;
|
||||
}
|
||||
if (GlobalModel.remotesModalModel.isOpen()) {
|
||||
GlobalModel.remotesModalModel.closeModal();
|
||||
if (GlobalModel.remotesModel.isOpen()) {
|
||||
GlobalModel.remotesModel.closeModal();
|
||||
didSomething = true;
|
||||
}
|
||||
if (GlobalModel.clientSettingsModal.get()) {
|
||||
@ -3213,7 +3422,7 @@ class Model {
|
||||
} else {
|
||||
// remote update
|
||||
let ptyData = base64ToArray(ptyMsg.ptydata64);
|
||||
this.remotesModalModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData);
|
||||
this.remotesModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -3277,6 +3486,9 @@ class Model {
|
||||
this.remotes.clear();
|
||||
}
|
||||
this.updateRemotes(update.remotes);
|
||||
if (update.remotes?.length && this.remotesModel.recentConnAddedState.get()) {
|
||||
this.remotesModel.openReadModal(update.remotes[0].remoteid);
|
||||
}
|
||||
}
|
||||
if ("mainview" in update) {
|
||||
if (update.mainview == "plugins") {
|
||||
@ -3302,12 +3514,8 @@ class Model {
|
||||
}
|
||||
if (interactive && "remoteview" in update) {
|
||||
let rview: RemoteViewType = update.remoteview;
|
||||
if (rview.remoteshowall) {
|
||||
this.remotesModalModel.openModal();
|
||||
} else if (rview.remoteedit != null) {
|
||||
this.remotesModalModel.openModalForEdit({ ...rview.remoteedit, old: true }, false);
|
||||
} else if (rview.ptyremoteid) {
|
||||
this.remotesModalModel.openModal(rview.ptyremoteid);
|
||||
if (rview.remoteedit != null) {
|
||||
this.remotesModel.openEditModal({ ...rview.remoteedit });
|
||||
}
|
||||
}
|
||||
if ("cmdline" in update) {
|
||||
@ -4037,6 +4245,10 @@ class CommandRunner {
|
||||
GlobalModel.submitCommand("bookmarks", "show", null, { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
connectionsView() {
|
||||
GlobalModel.connectionViewModel.showConnectionsView();
|
||||
}
|
||||
|
||||
historyView(params: HistorySearchParams) {
|
||||
let kwargs = { nohist: "1" };
|
||||
kwargs["offset"] = String(params.offset);
|
||||
@ -4214,6 +4426,7 @@ export {
|
||||
RemoteColors,
|
||||
getTermPtyData,
|
||||
RemotesModalModel,
|
||||
RemotesModel,
|
||||
MinFontSize,
|
||||
MaxFontSize,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user