mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-31 18:18:02 +01:00
modals system (#106)
* 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 * new modal component. migrate about modal to new modal component. * migrate create remote conn modal to modal component * working modals stack * 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) * register create conn modal * follow model naming convention * register edit remote conn modal * reset * reset * reset * reset * use remotes model methods and wrap pushModal calls in mobx action * only close connect modal after update for remotes returns * register alert modal * fix type error in app.tsx * migrate remote detail and alert modal to base modal component * Revert "fix conflicts" This reverts commit962da77918
, reversing changes made to34cbe34ba5
. * only wrapper ModalProvider with mobx provider * change archive label to delete * fix error where isOpen method does not exist * remove registry modal * rename ModalStoreModel to ModalsModal * fix issue where edit remote conn modal doesn't show * simplify modal component * grab remoteModel from within the remote modals * fix edit modal * minor change * cleanup * more cleanup * change confirm wording to 'delete' instead of 'archive'. remove or-equals since isBlank is designed to check for exactly that. * undo some of the strict typescript fixes * undo more typescript fixes * cleanup * fix import * revert build.md change
This commit is contained in:
parent
fc79da776c
commit
23b6bb29e7
@ -22,18 +22,9 @@ import {
|
||||
LineSettingsModal,
|
||||
ClientSettingsModal,
|
||||
} from "./common/modals/settings";
|
||||
import { RemotesModal } from "./connections_deprecated/connections";
|
||||
import { TosModal } from "./common/modals/modals";
|
||||
import { MainSideBar } from "./sidebar/sidebar";
|
||||
import {
|
||||
DisconnectedModal,
|
||||
ClientStopModal,
|
||||
AlertModal,
|
||||
AboutModal,
|
||||
CreateRemoteConnModal,
|
||||
ViewRemoteConnDetailModal,
|
||||
EditRemoteConnModal,
|
||||
} from "./common/modals/modals";
|
||||
import { DisconnectedModal, ClientStopModal, ModalsProvider } from "./common/modals/modals";
|
||||
import { ErrorBoundary } from "./common/error/errorboundary";
|
||||
import "./app.less";
|
||||
|
||||
@ -67,7 +58,7 @@ class App extends React.Component<{}, {}> {
|
||||
opts.showCut = true;
|
||||
}
|
||||
let sel = window.getSelection();
|
||||
if (!isBlank(sel.toString())) {
|
||||
if (!isBlank(sel?.toString())) {
|
||||
GlobalModel.contextEditMenu(e, opts);
|
||||
} else {
|
||||
if (isInNonTermInput) {
|
||||
@ -89,11 +80,6 @@ class App extends React.Component<{}, {}> {
|
||||
let lineSettingsModal = GlobalModel.lineSettingsModal.get();
|
||||
let clientSettingsModal = GlobalModel.clientSettingsModal.get();
|
||||
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();
|
||||
@ -135,33 +121,10 @@ class App extends React.Component<{}, {}> {
|
||||
<ConnectionsView model={remotesModel} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<AlertModal />
|
||||
<If condition={GlobalModel.needsTos()}>
|
||||
<TosModal />
|
||||
</If>
|
||||
<If condition={GlobalModel.aboutModalOpen.get()}>
|
||||
<AboutModal />
|
||||
</If>
|
||||
<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>
|
||||
<ModalsProvider />
|
||||
<If condition={screenSettingsModal != null}>
|
||||
<ScreenSettingsModal
|
||||
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}
|
||||
|
5
src/app/appconst.ts
Normal file
5
src/app/appconst.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const ABOUT = "about";
|
||||
export const CREATE_REMOTE = "createRemote";
|
||||
export const VIEW_REMOTE = "viewRemote";
|
||||
export const EDIT_REMOTE = "editRemote";
|
||||
export const ALERT = "alert";
|
@ -1045,3 +1045,76 @@
|
||||
background-color: @status-connecting;
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 500;
|
||||
|
||||
.wave-modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(21, 23, 21, 0.7);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
border-radius: 10px;
|
||||
background: #151715;
|
||||
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45),
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||
|
||||
.wave-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.wave-modal-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 14px 12px 20px;
|
||||
justify-content: space-between;
|
||||
line-height: 20px;
|
||||
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
|
||||
|
||||
button {
|
||||
i {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal-body {
|
||||
width: 100%;
|
||||
padding: 0px 20px;
|
||||
}
|
||||
|
||||
.wave-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
padding: 0 20px 20px;
|
||||
|
||||
button:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -349,7 +349,6 @@ interface TextFieldState {
|
||||
hasContent: boolean;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
inputRef: React.RefObject<HTMLInputElement>;
|
||||
state: TextFieldState;
|
||||
@ -1097,6 +1096,68 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
}
|
||||
}
|
||||
|
||||
interface ModalHeaderProps {
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const ModalHeader: React.FC<ModalHeaderProps> = ({ onClose, title }) => (
|
||||
<div className="wave-modal-header">
|
||||
{<div>{title}</div>}
|
||||
<IconButton theme="secondary" variant="ghost" onClick={onClose}>
|
||||
<i className="fa-sharp fa-solid fa-xmark"></i>
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface ModalFooterProps {
|
||||
onCancel?: () => void;
|
||||
onOk?: () => void;
|
||||
cancelLabel?: string;
|
||||
okLabel?: string;
|
||||
}
|
||||
|
||||
const ModalFooter: React.FC<ModalFooterProps> = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }) => (
|
||||
<div className="wave-modal-footer">
|
||||
<Button theme="secondary" onClick={onCancel}>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button onClick={onOk}>{okLabel}</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface ModalProps {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
onClickBackdrop?: () => void;
|
||||
}
|
||||
|
||||
class Modal extends React.Component<ModalProps> {
|
||||
static Header = ModalHeader;
|
||||
static Footer = ModalFooter;
|
||||
|
||||
renderBackdrop(onClick: (() => void) | undefined) {
|
||||
return <div className="wave-modal-backdrop" onClick={onClick}></div>;
|
||||
}
|
||||
|
||||
renderModal() {
|
||||
const { className, children } = this.props;
|
||||
|
||||
return (
|
||||
<div className="wave-modal-container">
|
||||
{this.renderBackdrop(this.props.onClickBackdrop)}
|
||||
<div className={`wave-modal ${className}`}>
|
||||
<div className="wave-modal-content">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return ReactDOM.createPortal(this.renderModal(), document.getElementById("app") as HTMLElement);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
CmdStrCode,
|
||||
Toggle,
|
||||
@ -1117,4 +1178,5 @@ export {
|
||||
IconButton,
|
||||
LinkButton,
|
||||
Status,
|
||||
Modal,
|
||||
};
|
||||
|
@ -59,22 +59,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modal.alert-modal {
|
||||
z-index: 205;
|
||||
|
||||
footer {
|
||||
justify-content: center;
|
||||
|
||||
.button {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal.settings-modal {
|
||||
footer {
|
||||
justify-content: center;
|
||||
@ -181,59 +165,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modal.wave-modal {
|
||||
.wave-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
border-radius: 10px;
|
||||
background: var(--olive-dark-1, #151715);
|
||||
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45),
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||
|
||||
.wave-modal-content-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
|
||||
.wave-modal-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
justify-content: space-between;
|
||||
line-height: 20px;
|
||||
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
|
||||
|
||||
.wave-modal-title {
|
||||
color: @term-bright-white;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.wave-modal-close {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
width: 87%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal.tos-modal {
|
||||
.modal-content.wave-modal-content {
|
||||
padding: 32px 48px;
|
||||
@ -323,14 +254,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modal.about-modal {
|
||||
.about-wave-modal-content {
|
||||
width: 401px;
|
||||
.about-modal {
|
||||
width: 382px;
|
||||
|
||||
.about-wave-modal-body {
|
||||
.wave-modal-content {
|
||||
gap: 24px;
|
||||
|
||||
.wave-modal-body {
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
|
||||
.about-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
|
||||
.wave-modal-section {
|
||||
.logo-wrapper {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
@ -403,7 +346,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal-section:nth-child(3) {
|
||||
.about-section:nth-child(3) {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
@ -418,7 +361,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal-section:last-child {
|
||||
.about-section:last-child {
|
||||
margin-bottom: 24px;
|
||||
color: @term-white;
|
||||
}
|
||||
@ -426,21 +369,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal.crconn-modal {
|
||||
.wave-modal-content.crconn-wave-modal-content {
|
||||
.crconn-modal {
|
||||
width: 452px;
|
||||
min-height: 411px;
|
||||
overflow: visible;
|
||||
|
||||
.wave-modal-content-inner.crconn-wave-modal-content-inner {
|
||||
display: flex;
|
||||
padding-bottom: 0px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
flex-shrink: 0;
|
||||
.wave-modal-content {
|
||||
gap: 24px;
|
||||
|
||||
.crconn-wave-modal-body {
|
||||
.wave-modal-body {
|
||||
display: flex;
|
||||
padding: 0px 20px;
|
||||
flex-direction: column;
|
||||
@ -450,31 +386,71 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.crconn-wave-modal-footer {
|
||||
.erconn-modal {
|
||||
width: 502px;
|
||||
min-height: 411px;
|
||||
|
||||
.wave-modal-content {
|
||||
gap: 20px;
|
||||
|
||||
.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;
|
||||
width: 100%;
|
||||
padding: 0 20px 20px;
|
||||
align-items: flex-start;
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
|
||||
button:first-child {
|
||||
.wave-button {
|
||||
padding: 4px 15px;
|
||||
font-size: 11px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wave-modal.rconndetail-modal {
|
||||
.wave-modal-content.rconndetail-wave-modal-content {
|
||||
.alert-modal {
|
||||
.wave-modal-content {
|
||||
.wave-modal-body {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rconndetail-modal {
|
||||
width: 631px;
|
||||
min-height: 565px;
|
||||
overflow: visible;
|
||||
|
||||
.wave-modal-content-inner.rconndetail-wave-modal-content-inner {
|
||||
.wave-modal-content {
|
||||
display: flex;
|
||||
padding-bottom: 0px;
|
||||
flex-direction: column;
|
||||
@ -482,7 +458,7 @@
|
||||
gap: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.rconndetail-wave-modal-body {
|
||||
.wave-modal-body {
|
||||
display: flex;
|
||||
padding: 0px 20px;
|
||||
align-items: flex-start;
|
||||
@ -583,95 +559,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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-modal.erconn-modal {
|
||||
.wave-modal-content.erconn-wave-modal-content {
|
||||
width: 502px;
|
||||
min-height: 411px;
|
||||
overflow: visible;
|
||||
|
||||
.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 {
|
||||
|
@ -11,21 +11,18 @@ import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../model/model";
|
||||
import * as T from "../../../types/types";
|
||||
import { Markdown, InfoMessage } from "../common";
|
||||
import { Markdown } from "../common";
|
||||
import * as util from "../../../util/util";
|
||||
import * as textmeasure from "../../../util/textmeasure";
|
||||
import { Toggle, Checkbox } from "../common";
|
||||
import { Toggle, Modal } from "../common";
|
||||
import { ClientDataType } from "../../../types/types";
|
||||
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";
|
||||
import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg";
|
||||
import shield from "../../assets/icons/shield_check.svg";
|
||||
import help from "../../assets/icons/help_filled.svg";
|
||||
import github from "../../assets/icons/github.svg";
|
||||
import logo from "../../assets/waveterm-logo-with-bg.svg";
|
||||
import { ReactComponent as AngleDownIcon } from "../../assets/icons/history/angle-down.svg";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
@ -40,6 +37,19 @@ const RemotePtyRows = 9;
|
||||
const RemotePtyCols = 80;
|
||||
const PasswordUnchangedSentinel = "--unchanged--";
|
||||
|
||||
@mobxReact.observer
|
||||
class ModalsProvider extends React.Component {
|
||||
renderModals() {
|
||||
const modals = GlobalModel.modalsModel.activeModals;
|
||||
|
||||
return modals.map((ModalComponent, index) => <ModalComponent key={index} />);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>{this.renderModals()}</>;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class DisconnectedModal extends React.Component<{}, {}> {
|
||||
logRef: any = React.createRef();
|
||||
@ -188,49 +198,30 @@ class AlertModal extends React.Component<{}, {}> {
|
||||
|
||||
render() {
|
||||
let message = GlobalModel.alertMessage.get();
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
let title = message.title ?? (message.confirm ? "Confirm" : "Alert");
|
||||
let isConfirm = message.confirm;
|
||||
let title = message?.title ?? (message?.confirm ? "Confirm" : "Alert");
|
||||
let isConfirm = message?.confirm ?? false;
|
||||
|
||||
return (
|
||||
<div className="modal prompt-modal is-active alert-modal">
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<p className="modal-title">
|
||||
<WarningIcon className="icon" />
|
||||
{title}
|
||||
</p>
|
||||
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
|
||||
<XmarkIcon />
|
||||
</div>
|
||||
</header>
|
||||
<If condition={message.markdown}>
|
||||
<Markdown text={message.message} extraClassName="inner-content" />
|
||||
<Modal className="alert-modal">
|
||||
<Modal.Header onClose={this.closeModal} title={title} />
|
||||
<div className="wave-modal-body">
|
||||
<If condition={message?.markdown}>
|
||||
<Markdown text={message?.message ?? ""} />
|
||||
</If>
|
||||
<If condition={!message.markdown}>
|
||||
<div className="inner-content content">
|
||||
<p>{message.message}</p>
|
||||
<If condition={!message?.markdown}>{message?.message}</If>
|
||||
</div>
|
||||
</If>
|
||||
<footer>
|
||||
<div className="wave-modal-footer">
|
||||
<If condition={isConfirm}>
|
||||
<div onClick={this.closeModal} className="button is-prompt-cancel is-outlined is-small">
|
||||
<Button theme="secondary" onClick={this.closeModal}>
|
||||
Cancel
|
||||
</div>
|
||||
<div onClick={this.handleOK} className="button is-wave-green is-outlined is-small">
|
||||
OK
|
||||
</div>
|
||||
</Button>
|
||||
<Button onClick={this.handleOK}>Ok</Button>
|
||||
</If>
|
||||
<If condition={!isConfirm}>
|
||||
<div onClick={this.handleOK} className="button is-wave-green is-small">
|
||||
OK
|
||||
</div>
|
||||
<Button onClick={this.handleOK}>Ok</Button>
|
||||
</If>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -342,7 +333,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.aboutModalOpen.set(false);
|
||||
GlobalModel.modalsModel.popModal();
|
||||
})();
|
||||
}
|
||||
|
||||
@ -400,18 +391,10 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={cn("modal about-modal wave-modal is-active")}>
|
||||
<div className="modal-background wave-modal-background" />
|
||||
<div className="modal-content wave-modal-content about-wave-modal-content">
|
||||
<div className="modal-content-inner wave-modal-content-inner about-wave-modal-content-inner">
|
||||
<header className="wave-modal-header about-wave-modal-header">
|
||||
<div className="wave-modal-title about-wave-modal-title">About</div>
|
||||
<div className="wave-modal-close about-wave-modal-close" onClick={this.closeModal}>
|
||||
<img src={close} alt="Close (Escape)" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="wave-modal-body about-wave-modal-body">
|
||||
<section className="wave-modal-section about-section">
|
||||
<Modal className="about-modal">
|
||||
<Modal.Header onClose={this.closeModal} title="About" />
|
||||
<div className="wave-modal-body">
|
||||
<div className="about-section">
|
||||
<div className="logo-wrapper">
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
@ -423,11 +406,9 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
Seamless Workflow
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="wave-modal-section about-section text-standard">
|
||||
{this.getStatus(this.isUpToDate())}
|
||||
</section>
|
||||
<section className="wave-modal-section about-section">
|
||||
</div>
|
||||
<div className="about-section text-standard">{this.getStatus(this.isUpToDate())}</div>
|
||||
<div className="about-section">
|
||||
<a
|
||||
className="wave-button wave-button-link color-standard"
|
||||
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
|
||||
@ -446,28 +427,22 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
</a>
|
||||
<a
|
||||
className="wave-button wave-button-link color-standard"
|
||||
href={util.makeExternLink(
|
||||
"https://github.com/wavetermdev/waveterm/blob/main/LICENSE",
|
||||
)}
|
||||
href={util.makeExternLink("https://github.com/wavetermdev/waveterm/blob/main/LICENSE")}
|
||||
target="_blank"
|
||||
>
|
||||
<i className="fa-sharp fa-light fa-book-blank"></i>
|
||||
License
|
||||
</a>
|
||||
</section>
|
||||
<section className="wave-modal-section about-section text-standard">
|
||||
© 2023 Command Line Inc.
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="about-section text-standard">© 2023 Command Line Inc.</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remoteEdit: T.RemoteEditType }, {}> {
|
||||
class CreateRemoteConnModal extends React.Component<{}, {}> {
|
||||
tempAlias: OV<string>;
|
||||
tempHostName: OV<string>;
|
||||
tempPort: OV<string>;
|
||||
@ -476,10 +451,13 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
tempPassword: OV<string>;
|
||||
tempKeyFile: OV<string>;
|
||||
errorStr: OV<string>;
|
||||
remoteEdit: T.RemoteEditType;
|
||||
model: RemotesModel;
|
||||
|
||||
constructor(props: any) {
|
||||
constructor(props: { remotesModel?: RemotesModel }) {
|
||||
super(props);
|
||||
let { remoteEdit } = this.props;
|
||||
this.model = GlobalModel.remotesModel;
|
||||
this.remoteEdit = this.model.remoteEdit.get();
|
||||
this.tempAlias = mobx.observable.box("", { name: "CreateRemote-alias" });
|
||||
this.tempHostName = mobx.observable.box("", { name: "CreateRemote-hostName" });
|
||||
this.tempPort = mobx.observable.box("", { name: "CreateRemote-port" });
|
||||
@ -487,7 +465,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
this.tempConnectMode = mobx.observable.box("auto", { name: "CreateRemote-connectMode" });
|
||||
this.tempKeyFile = mobx.observable.box("", { name: "CreateRemote-keystr" });
|
||||
this.tempPassword = mobx.observable.box("", { name: "CreateRemote-password" });
|
||||
this.errorStr = mobx.observable.box(remoteEdit.errorstr, { name: "CreateRemote-errorStr" });
|
||||
this.errorStr = mobx.observable.box(this.remoteEdit?.errorstr ?? null, { name: "CreateRemote-errorStr" });
|
||||
}
|
||||
|
||||
remoteCName(): string {
|
||||
@ -505,7 +483,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
if (this.errorStr.get() != null) {
|
||||
return this.errorStr.get();
|
||||
}
|
||||
return this.props.remoteEdit.errorstr;
|
||||
return this.remoteEdit?.errorstr ?? null;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@ -545,7 +523,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
kwargs["connectmode"] = this.tempConnectMode.get();
|
||||
kwargs["visual"] = "1";
|
||||
kwargs["submit"] = "1";
|
||||
let model = this.props.model;
|
||||
let model = this.model;
|
||||
let prtn = GlobalCommandRunner.createRemote(cname, kwargs, false);
|
||||
prtn.then((crtn) => {
|
||||
if (crtn.success) {
|
||||
@ -555,13 +533,13 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.errorStr.set(crcrtn.error);
|
||||
this.errorStr.set(crcrtn.error ?? null);
|
||||
})();
|
||||
});
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.errorStr.set(crtn.error);
|
||||
this.errorStr.set(crtn.error ?? null);
|
||||
})();
|
||||
});
|
||||
model.seRecentConnAdded(true);
|
||||
@ -617,21 +595,16 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
}
|
||||
|
||||
render() {
|
||||
let { model } = this.props;
|
||||
let authMode = this.tempAuthMode.get();
|
||||
|
||||
if (this.remoteEdit == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("modal wave-modal crconn-modal is-active")}>
|
||||
<div className="modal-background wave-modal-background" />
|
||||
<div className="modal-content wave-modal-content crconn-wave-modal-content">
|
||||
<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.closeModal}>
|
||||
<img src={close} alt="Close (Escape)" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="wave-modal-body crconn-wave-modal-body">
|
||||
<Modal className="crconn-modal">
|
||||
<Modal.Header title="Add Connection" onClose={this.model.closeModal} />
|
||||
<div className="wave-modal-body">
|
||||
<div className="user-section">
|
||||
<TextField
|
||||
label="user@host"
|
||||
@ -705,7 +678,9 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
{ value: "key+password", label: "key+password" },
|
||||
]}
|
||||
value={this.tempAuthMode.get()}
|
||||
onChange={this.handleChangeAuthMode}
|
||||
onChange={(val: string) => {
|
||||
this.tempAuthMode.set(val);
|
||||
}}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
@ -713,8 +688,8 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
message={
|
||||
<ul>
|
||||
<li>
|
||||
<b>none</b> - no authentication, or authentication is
|
||||
already configured in your ssh config.
|
||||
<b>none</b> - no authentication, or authentication is already
|
||||
configured in your ssh config.
|
||||
</li>
|
||||
<li>
|
||||
<b>key</b> - use a private key.
|
||||
@ -777,31 +752,36 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
|
||||
{ value: "manual", label: "manual" },
|
||||
]}
|
||||
value={this.tempConnectMode.get()}
|
||||
onChange={this.handleChangeConnectMode}
|
||||
onChange={(val: string) => {
|
||||
this.tempConnectMode.set(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<If condition={!util.isBlank(this.getErrorStr())}>
|
||||
<If condition={!util.isBlank(this.getErrorStr() as string)}>
|
||||
<div className="settings-field settings-error">Error: {this.getErrorStr()}</div>
|
||||
</If>
|
||||
</div>
|
||||
<footer className="wave-modal-footer crconn-wave-modal-footer">
|
||||
<div className="action-buttons">
|
||||
<Button theme="secondary" onClick={model.closeModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={this.submitRemote}>Connect</Button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal.Footer onCancel={this.model.closeModal} onOk={this.submitRemote} okLabel="Connect" />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; remote: T.RemoteType }, {}> {
|
||||
class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
termRef: React.RefObject<any> = React.createRef();
|
||||
model: RemotesModel;
|
||||
|
||||
constructor(props: { remotesModel?: RemotesModel }) {
|
||||
super(props);
|
||||
this.model = GlobalModel.remotesModel;
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get selectedRemote(): T.RemoteType {
|
||||
const selectedRemoteId = this.model.selectedRemoteId.get();
|
||||
return GlobalModel.getRemote(selectedRemoteId);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let elem = this.termRef.current;
|
||||
@ -809,24 +789,23 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
console.log("ERROR null term-remote element");
|
||||
return;
|
||||
}
|
||||
this.props.model.createTermWrap(elem);
|
||||
this.model.createTermWrap(elem);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
let { remote } = this.props;
|
||||
if (remote == null || remote.archived) {
|
||||
this.props.model.deSelectRemote();
|
||||
if (this.selectedRemote == null || this.selectedRemote.archived) {
|
||||
this.model.deSelectRemote();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.model.disposeTerm();
|
||||
this.model.disposeTerm();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickTermBlock(): void {
|
||||
if (this.props.model.remoteTermWrap != null) {
|
||||
this.props.model.remoteTermWrap.giveFocus();
|
||||
if (this.model.remoteTermWrap != null) {
|
||||
this.model.remoteTermWrap.giveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -861,7 +840,7 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
|
||||
@boundMethod
|
||||
openEditModal(): void {
|
||||
this.props.model.openEditModal();
|
||||
GlobalModel.remotesModel.openEditModal();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@ -878,9 +857,8 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
|
||||
@boundMethod
|
||||
clickArchive(): void {
|
||||
let { remote } = this.props;
|
||||
if (remote.status == "connected") {
|
||||
GlobalModel.showAlert({ message: "Cannot delete a connected connection. Disconnect and try again." });
|
||||
if (this.selectedRemote && this.selectedRemote.status == "connected") {
|
||||
GlobalModel.showAlert({ message: "Cannot delete when connected. Disconnect and try again." });
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalModel.showAlert({
|
||||
@ -891,15 +869,16 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
GlobalCommandRunner.archiveRemote(remote.remoteid);
|
||||
if (this.selectedRemote) {
|
||||
GlobalCommandRunner.archiveRemote(this.selectedRemote.remoteid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClose(): void {
|
||||
let { model } = this.props;
|
||||
model.closeModal();
|
||||
model.seRecentConnAdded(false);
|
||||
this.model.closeModal();
|
||||
this.model.seRecentConnAdded(false);
|
||||
}
|
||||
|
||||
renderInstallStatus(remote: T.RemoteType): any {
|
||||
@ -1023,24 +1002,22 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
}
|
||||
|
||||
render() {
|
||||
let { model, remote } = this.props;
|
||||
let isTermFocused = model.remoteTermWrapFocus.get();
|
||||
let remote = this.selectedRemote;
|
||||
|
||||
if (remote == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let model = this.model;
|
||||
let isTermFocused = this.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">
|
||||
<Modal className="rconndetail-modal">
|
||||
<Modal.Header title="Connection" onClose={this.model.closeModal} />
|
||||
<div className="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>
|
||||
@ -1058,11 +1035,7 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
<div className="settings-label">Canonical Name</div>
|
||||
<div className="settings-input">
|
||||
{remote.remotecanonicalname}
|
||||
<If
|
||||
condition={
|
||||
!util.isBlank(remote.remotevars.port) && remote.remotevars.port != "22"
|
||||
}
|
||||
>
|
||||
<If condition={!util.isBlank(remote.remotevars.port) && remote.remotevars.port != "22"}>
|
||||
<span style={{ marginLeft: 5 }}>(port {remote.remotevars.port})</span>
|
||||
</If>
|
||||
</div>
|
||||
@ -1092,7 +1065,7 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
className={cn(
|
||||
"terminal-wrapper",
|
||||
{ focus: isTermFocused },
|
||||
remote != null ? "status-" + remote.status : null,
|
||||
remote != null ? "status-" + remote.status : null
|
||||
)}
|
||||
>
|
||||
<If condition={!isTermFocused}>
|
||||
@ -1116,121 +1089,141 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
|
||||
</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>
|
||||
<Modal.Footer onOk={this.model.closeModal} onCancel={this.model.closeModal} okLabel="Done" />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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>;
|
||||
class EditRemoteConnModal extends React.Component<{}, {}> {
|
||||
internalTempAlias: OV<string>;
|
||||
internalTempKeyFile: OV<string>;
|
||||
internalTempPassword: OV<string>;
|
||||
model: RemotesModel;
|
||||
|
||||
constructor(props: any) {
|
||||
constructor(props: { remotesModel?: RemotesModel }) {
|
||||
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.model = GlobalModel.remotesModel;
|
||||
this.internalTempAlias = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempAlias" });
|
||||
this.internalTempKeyFile = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempKeyFile" });
|
||||
this.internalTempPassword = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempPassword" });
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get selectedRemoteId() {
|
||||
return this.model.selectedRemoteId.get();
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get selectedRemote(): T.RemoteType {
|
||||
return GlobalModel.getRemote(this.selectedRemoteId);
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get remoteEdit(): T.RemoteEditType {
|
||||
return this.model.remoteEdit.get();
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get isAuthEditMode(): boolean {
|
||||
return this.model.isAuthEditMode();
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get tempAuthMode(): mobx.IObservableValue<string> {
|
||||
return mobx.observable.box(this.selectedRemote?.authtype, {
|
||||
name: "EditRemoteConnModal-authMode",
|
||||
});
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get tempConnectMode(): mobx.IObservableValue<string> {
|
||||
return mobx.observable.box(this.selectedRemote?.connectmode, {
|
||||
name: "EditRemoteConnModal-connectMode",
|
||||
});
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get tempAlias(): mobx.IObservableValue<string> {
|
||||
return mobx.observable.box(this.internalTempAlias.get() || this.selectedRemote.remotealias, {
|
||||
name: "EditRemoteConnModal-alias",
|
||||
});
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get tempKeyFile(): mobx.IObservableValue<string> {
|
||||
return mobx.observable.box(this.internalTempKeyFile.get() || this.remoteEdit?.keystr, {
|
||||
name: "EditRemoteConnModal-keystr",
|
||||
});
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get tempPassword(): mobx.IObservableValue<string> {
|
||||
const oldPassword = this.remoteEdit?.haspassword ? PasswordUnchangedSentinel : "";
|
||||
const newPassword = this.internalTempPassword.get() || oldPassword;
|
||||
return mobx.observable.box(newPassword, {
|
||||
name: "EditRemoteConnModal-password",
|
||||
});
|
||||
this.submitted = mobx.observable.box(false, { name: "EditRemoteSettings-submitted" });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
let { remote } = this.props;
|
||||
if (remote == null || remote.archived) {
|
||||
this.props.model.deSelectRemote();
|
||||
if (this.selectedRemote == null || this.selectedRemote.archived) {
|
||||
this.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." });
|
||||
if (this.selectedRemote?.status == "connected") {
|
||||
GlobalModel.showAlert({ message: "Cannot delete while connected. 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);
|
||||
GlobalCommandRunner.archiveRemote(this.selectedRemote?.remoteid);
|
||||
});
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickForceInstall(): void {
|
||||
let { remote } = this.props;
|
||||
GlobalCommandRunner.installRemote(remote.remoteid);
|
||||
GlobalCommandRunner.installRemote(this.selectedRemote?.remoteid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeKeyFile(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempKeyFile.set(value);
|
||||
this.internalTempKeyFile.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangePassword(value: string): void {
|
||||
mobx.action(() => {
|
||||
this.tempPassword.set(value);
|
||||
this.internalTempPassword.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);
|
||||
this.internalTempAlias.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
canResetPw(): boolean {
|
||||
let { remoteEdit } = this.props;
|
||||
if (remoteEdit == null) {
|
||||
if (this.remoteEdit == null) {
|
||||
return false;
|
||||
}
|
||||
return remoteEdit.haspassword && this.tempPassword.get() != PasswordUnchangedSentinel;
|
||||
return Boolean(this.remoteEdit.haspassword) && this.tempPassword.get() != PasswordUnchangedSentinel;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@ -1249,10 +1242,9 @@ class EditRemoteConnModal extends React.Component<
|
||||
|
||||
@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 (!util.isStrEq(this.tempKeyFile.get(), this.remoteEdit?.keystr)) {
|
||||
if (authMode == "key" || authMode == "key+password") {
|
||||
kwargs["key"] = this.tempKeyFile.get();
|
||||
} else {
|
||||
@ -1264,29 +1256,20 @@ class EditRemoteConnModal extends React.Component<
|
||||
kwargs["password"] = this.tempPassword.get();
|
||||
}
|
||||
} else {
|
||||
if (remoteEdit.haspassword) {
|
||||
if (this.remoteEdit?.haspassword) {
|
||||
kwargs["password"] = "";
|
||||
}
|
||||
}
|
||||
if (!util.isStrEq(this.tempAlias.get(), remote.remotealias)) {
|
||||
if (!util.isStrEq(this.tempAlias.get(), this.selectedRemote?.remotealias)) {
|
||||
kwargs["alias"] = this.tempAlias.get();
|
||||
}
|
||||
if (!util.isStrEq(this.tempConnectMode.get(), remote.connectmode)) {
|
||||
if (!util.isStrEq(this.tempConnectMode.get(), this.selectedRemote?.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);
|
||||
GlobalCommandRunner.editRemote(this.selectedRemote?.remoteid, kwargs);
|
||||
this.model.closeModal();
|
||||
}
|
||||
|
||||
renderAuthModeMessage(): any {
|
||||
@ -1313,27 +1296,18 @@ class EditRemoteConnModal extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
let { model, remote, remoteEdit } = this.props;
|
||||
let authMode = this.tempAuthMode.get();
|
||||
|
||||
if (util.isBlank(remoteEdit.errorstr) && this.submitted.get()) {
|
||||
if (this.remoteEdit === null || !this.isAuthEditMode) {
|
||||
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">
|
||||
<Modal className="erconn-modal">
|
||||
<Modal.Header title="Edit Connection" onClose={this.model.closeModal} />
|
||||
<div className="wave-modal-body">
|
||||
<div className="name-actions-section">
|
||||
<div className="name text-primary">{getName(remote)}</div>
|
||||
<div className="name text-primary">{getName(this.selectedRemote)}</div>
|
||||
<div className="header-actions">
|
||||
<Button theme="secondary" onClick={this.clickArchive}>
|
||||
Delete
|
||||
@ -1373,7 +1347,9 @@ class EditRemoteConnModal extends React.Component<
|
||||
{ value: "key+password", label: "key+password" },
|
||||
]}
|
||||
value={this.tempAuthMode.get()}
|
||||
onChange={this.handleChangeAuthMode}
|
||||
onChange={(val: string) => {
|
||||
this.tempAuthMode.set(val);
|
||||
}}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
@ -1381,8 +1357,8 @@ class EditRemoteConnModal extends React.Component<
|
||||
message={
|
||||
<ul>
|
||||
<li>
|
||||
<b>none</b> - no authentication, or authentication is
|
||||
already configured in your ssh config.
|
||||
<b>none</b> - no authentication, or authentication is already
|
||||
configured in your ssh config.
|
||||
</li>
|
||||
<li>
|
||||
<b>key</b> - use a private key.
|
||||
@ -1445,29 +1421,25 @@ class EditRemoteConnModal extends React.Component<
|
||||
{ value: "manual", label: "manual" },
|
||||
]}
|
||||
value={this.tempConnectMode.get()}
|
||||
onChange={this.handleChangeConnectMode}
|
||||
onChange={(val: string) => {
|
||||
this.tempConnectMode.set(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<If condition={!util.isBlank(remoteEdit.errorstr)}>
|
||||
<div className="settings-field settings-error">Error: {remoteEdit.errorstr}</div>
|
||||
<If condition={!util.isBlank(this.remoteEdit?.errorstr)}>
|
||||
<div className="settings-field settings-error">Error: {this.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>
|
||||
<Modal.Footer onOk={this.submitRemote} onCancel={this.model.closeModal} okLabel="Save" />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getName = (remote: T.RemoteType) => {
|
||||
const getName = (remote: T.RemoteType): string => {
|
||||
if (remote == null) {
|
||||
return "";
|
||||
}
|
||||
const { remotealias, remotecanonicalname } = remote;
|
||||
return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname;
|
||||
};
|
||||
@ -1482,4 +1454,5 @@ export {
|
||||
CreateRemoteConnModal,
|
||||
ViewRemoteConnDetailModal,
|
||||
EditRemoteConnModal,
|
||||
ModalsProvider,
|
||||
};
|
||||
|
22
src/app/common/modals/modalsRegistry.tsx
Normal file
22
src/app/common/modals/modalsRegistry.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
AboutModal,
|
||||
CreateRemoteConnModal,
|
||||
ViewRemoteConnDetailModal,
|
||||
EditRemoteConnModal,
|
||||
AlertModal,
|
||||
} from "./modals";
|
||||
import * as constants from "../../appconst";
|
||||
|
||||
const modalsRegistry: { [key: string]: () => React.ReactElement } = {
|
||||
[constants.ABOUT]: () => <AboutModal />,
|
||||
[constants.CREATE_REMOTE]: () => <CreateRemoteConnModal />,
|
||||
[constants.VIEW_REMOTE]: () => <ViewRemoteConnDetailModal />,
|
||||
[constants.EDIT_REMOTE]: () => <EditRemoteConnModal />,
|
||||
[constants.ALERT]: () => <AlertModal />,
|
||||
};
|
||||
|
||||
export { modalsRegistry };
|
@ -20,7 +20,7 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
class ConnectionsView extends React.Component<{ model: RemotesModel }, { hoveredItemId: string }> {
|
||||
tableRef: React.RefObject<any> = React.createRef();
|
||||
tableWidth: OV<number> = mobx.observable.box(0, { name: "tableWidth" });
|
||||
tableRszObs: ResizeObserver;
|
||||
tableRszObs: ResizeObserver = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -105,7 +105,6 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
|
||||
}
|
||||
|
||||
let items = util.sortAndFilterRemotes(GlobalModel.remotes.slice());
|
||||
let remote = this.props.model.selectedRemoteId.get();
|
||||
let item: T.RemoteType = null;
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
@ -65,7 +66,6 @@ import type {
|
||||
import * as T from "../types/types";
|
||||
import { WSControl } from "./ws";
|
||||
import {
|
||||
measureText,
|
||||
getMonoFontSize,
|
||||
windowWidthToCols,
|
||||
windowHeightToRows,
|
||||
@ -76,8 +76,9 @@ import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil";
|
||||
import { sortAndFilterRemotes } from "../util/util";
|
||||
import { MagicLayout } from "../app/magiclayout";
|
||||
import { modalsRegistry } from "../app/common/modals/modalsRegistry";
|
||||
import * as constants from "../app/appconst";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(localizedFormat);
|
||||
@ -2709,13 +2710,10 @@ 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;
|
||||
remoteTermWrap: TermWrap = null;
|
||||
remoteTermWrapFocus: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "RemotesModel-remoteTermWrapFocus",
|
||||
});
|
||||
@ -2730,10 +2728,6 @@ class RemotesModel {
|
||||
name: "RemotesModel-recentlyAdded",
|
||||
});
|
||||
|
||||
isOpen(): boolean {
|
||||
return this.modalMode.get() != null;
|
||||
}
|
||||
|
||||
get recentConnAdded(): boolean {
|
||||
return this.recentConnAddedState.get();
|
||||
}
|
||||
@ -2753,26 +2747,26 @@ class RemotesModel {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(remoteId);
|
||||
this.remoteEdit.set(null);
|
||||
this.modalMode.set("read");
|
||||
GlobalModel.modalsModel.pushModal(constants.VIEW_REMOTE);
|
||||
})();
|
||||
}
|
||||
|
||||
openAddModal(redit: RemoteEditType): void {
|
||||
mobx.action(() => {
|
||||
this.remoteEdit.set(redit);
|
||||
this.modalMode.set("add");
|
||||
GlobalModel.modalsModel.pushModal(constants.CREATE_REMOTE);
|
||||
})();
|
||||
}
|
||||
|
||||
openEditModal(redit?: RemoteEditType): void {
|
||||
if (redit === undefined) {
|
||||
if (redit == null) {
|
||||
this.startEditAuth();
|
||||
}
|
||||
if (redit != null) {
|
||||
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
|
||||
} else {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(redit.remoteid);
|
||||
this.selectedRemoteId.set(redit?.remoteid);
|
||||
this.remoteEdit.set(redit);
|
||||
this.modalMode.set("edit");
|
||||
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
|
||||
})();
|
||||
}
|
||||
}
|
||||
@ -2795,10 +2789,6 @@ class RemotesModel {
|
||||
}
|
||||
}
|
||||
|
||||
getModalMode(): string {
|
||||
return this.modalMode.get();
|
||||
}
|
||||
|
||||
isAuthEditMode(): boolean {
|
||||
return this.remoteEdit.get() != null;
|
||||
}
|
||||
@ -2806,8 +2796,7 @@ class RemotesModel {
|
||||
@boundMethod
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
this.modalMode.set(null);
|
||||
this.selectedRemoteId.set(null);
|
||||
GlobalModel.modalsModel.popModal();
|
||||
})();
|
||||
setTimeout(() => GlobalModel.refocus(), 10);
|
||||
}
|
||||
@ -2904,6 +2893,32 @@ class RemotesModel {
|
||||
}
|
||||
}
|
||||
|
||||
class ModalsModel {
|
||||
store: Array<{ id: string; component: React.ComponentType }> = [];
|
||||
|
||||
constructor() {
|
||||
mobx.makeAutoObservable(this);
|
||||
}
|
||||
|
||||
pushModal(modalId: string) {
|
||||
const modalFactory = modalsRegistry[modalId];
|
||||
|
||||
if (modalFactory && !this.store.some((modal) => modal.id === modalId)) {
|
||||
this.store.push({ id: modalId, component: modalFactory });
|
||||
}
|
||||
}
|
||||
|
||||
popModal() {
|
||||
this.store.pop();
|
||||
}
|
||||
|
||||
get activeModals() {
|
||||
return this.store.slice().map((modal) => {
|
||||
return modal.component;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Model {
|
||||
clientId: string;
|
||||
activeSessionId: OV<string> = mobx.observable.box(null, {
|
||||
@ -2965,6 +2980,7 @@ class Model {
|
||||
bookmarksModel: BookmarksModel;
|
||||
historyViewModel: HistoryViewModel;
|
||||
connectionViewModel: ConnectionsViewModel;
|
||||
modalsModel: ModalsModel;
|
||||
clientData: OV<ClientDataType> = mobx.observable.box(null, {
|
||||
name: "clientData",
|
||||
});
|
||||
@ -2987,6 +3003,7 @@ class Model {
|
||||
this.connectionViewModel = new ConnectionsViewModel();
|
||||
this.remotesModalModel = new RemotesModalModel();
|
||||
this.remotesModel = new RemotesModel();
|
||||
this.modalsModel = new ModalsModel();
|
||||
let isWaveSrvRunning = getApi().getWaveSrvStatus();
|
||||
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
|
||||
name: "model-wavesrv-running",
|
||||
@ -3075,6 +3092,7 @@ class Model {
|
||||
showAlert(alertMessage: AlertMessageType): Promise<boolean> {
|
||||
mobx.action(() => {
|
||||
this.alertMessage.set(alertMessage);
|
||||
GlobalModel.modalsModel.pushModal(constants.ALERT);
|
||||
})();
|
||||
let prtn = new Promise<boolean>((resolve, reject) => {
|
||||
this.alertPromiseResolver = resolve;
|
||||
@ -3085,6 +3103,7 @@ class Model {
|
||||
cancelAlert(): void {
|
||||
mobx.action(() => {
|
||||
this.alertMessage.set(null);
|
||||
GlobalModel.modalsModel.popModal();
|
||||
})();
|
||||
if (this.alertPromiseResolver != null) {
|
||||
this.alertPromiseResolver(false);
|
||||
@ -3095,6 +3114,7 @@ class Model {
|
||||
confirmAlert(): void {
|
||||
mobx.action(() => {
|
||||
this.alertMessage.set(null);
|
||||
GlobalModel.modalsModel.popModal();
|
||||
})();
|
||||
if (this.alertPromiseResolver != null) {
|
||||
this.alertPromiseResolver(true);
|
||||
@ -3212,10 +3232,6 @@ class Model {
|
||||
GlobalModel.screenSettingsModal.set(null);
|
||||
didSomething = true;
|
||||
}
|
||||
if (GlobalModel.remotesModel.isOpen()) {
|
||||
GlobalModel.remotesModel.closeModal();
|
||||
didSomething = true;
|
||||
}
|
||||
if (GlobalModel.clientSettingsModal.get()) {
|
||||
GlobalModel.clientSettingsModal.set(false);
|
||||
didSomething = true;
|
||||
@ -3355,7 +3371,7 @@ class Model {
|
||||
|
||||
onMenuItemAbout(): void {
|
||||
mobx.action(() => {
|
||||
this.aboutModalOpen.set(true);
|
||||
this.modalsModel.pushModal(constants.ABOUT);
|
||||
})();
|
||||
}
|
||||
|
||||
@ -3486,8 +3502,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 (update.remotes && update.remotes.length && this.remotesModel.recentConnAddedState.get()) {
|
||||
GlobalModel.remotesModel.closeModal();
|
||||
GlobalModel.remotesModel.openReadModal(update.remotes![0].remoteid);
|
||||
}
|
||||
}
|
||||
if ("mainview" in update) {
|
||||
@ -3737,7 +3754,7 @@ class Model {
|
||||
submitCommand(
|
||||
metaCmd: string,
|
||||
metaSubCmd: string,
|
||||
args: string[] | null,
|
||||
args: string[],
|
||||
kwargs: Record<string, string>,
|
||||
interactive: boolean
|
||||
): Promise<CommandRtnType> {
|
||||
@ -3816,13 +3833,11 @@ class Model {
|
||||
}
|
||||
|
||||
getRemote(remoteId: string): RemoteType {
|
||||
for (let i = 0; i < this.remotes.length; i++) {
|
||||
if (this.remotes[i].remoteid == remoteId) {
|
||||
return this.remotes[i];
|
||||
}
|
||||
}
|
||||
if (remoteId == null) {
|
||||
return null;
|
||||
}
|
||||
return this.remotes.find((remote) => remote.remoteid === remoteId);
|
||||
}
|
||||
|
||||
getRemoteNames(): Record<string, string> {
|
||||
let rtn: Record<string, string> = {};
|
||||
|
@ -168,7 +168,7 @@ type FeCmdPacketType = {
|
||||
type: string;
|
||||
metacmd: string;
|
||||
metasubcmd?: string;
|
||||
args: string[] | null;
|
||||
args: string[];
|
||||
kwargs: Record<string, string>;
|
||||
rawstr?: string;
|
||||
uicontext: UIContextType;
|
||||
|
Loading…
Reference in New Issue
Block a user