mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
temp fix to enable arrow up/down keybindings
This commit is contained in:
commit
4758b7351d
7
.github/workflows/build-helper.yml
vendored
7
.github/workflows/build-helper.yml
vendored
@ -45,14 +45,15 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: "yarn"
|
||||
- name: Install yarn
|
||||
run: |
|
||||
corepack enable
|
||||
yarn install
|
||||
- name: Set Version
|
||||
id: set-version
|
||||
run: |
|
||||
VERSION=$(node -e 'console.log(require("./version.js"))')
|
||||
echo "WAVETERM_VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
- name: Install Yarn Dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
||||
run: scripthaus run ${{ matrix.scripthaus }}
|
||||
env:
|
||||
|
121
.github/workflows/regression.yml
vendored
121
.github/workflows/regression.yml
vendored
@ -1,65 +1,66 @@
|
||||
name: TestDriver.ai Regression Testing
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: 0 21 * * *
|
||||
workflow_dispatch: null
|
||||
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: 0 21 * * *
|
||||
workflow_dispatch: null
|
||||
|
||||
permissions:
|
||||
contents: read # To allow the action to read repository contents
|
||||
pull-requests: write # To allow the action to create/update pull request comments
|
||||
contents: read # To allow the action to read repository contents
|
||||
pull-requests: write # To allow the action to create/update pull request comments
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: TestDriver
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dashcamio/testdriver@main
|
||||
id: testdriver
|
||||
with:
|
||||
version: v2.10.2
|
||||
prerun: |
|
||||
rm ~/Desktop/WITH-LOVE-FROM-AMERICA.txt
|
||||
cd ~/actions-runner/_work/testdriver/testdriver/
|
||||
brew install go
|
||||
brew tap scripthaus-dev/scripthaus
|
||||
brew install scripthaus
|
||||
npm install -g yarn
|
||||
scripthaus run build-backend
|
||||
echo "Yarn"
|
||||
yarn
|
||||
echo "Rebuild"
|
||||
scripthaus run electron-rebuild
|
||||
echo "Webpack"
|
||||
scripthaus run webpack-build
|
||||
echo "Starting Electron"
|
||||
scripthaus run electron 1>/dev/null 2>&1 &
|
||||
echo "Electron Done"
|
||||
exit
|
||||
prompt: |
|
||||
1. wait 10 seconds
|
||||
1. click "Continue"
|
||||
1. click "Create new tab"
|
||||
1. validate that overlapping text does not appear in the application
|
||||
1. focus the Wave input with the keyboard shorcut Command + I
|
||||
1. type 'ls' into the input
|
||||
1. press return
|
||||
1. validate Wave shows the result of 'ls'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: peter-evans/create-or-update-comment@v4
|
||||
if: ${{always()}}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
## TestDriver Summary
|
||||
${{ steps.testdriver.outputs.markdown }}
|
||||
${{ steps.testdriver.outputs.summary }}
|
||||
reactions: |
|
||||
+1
|
||||
-1
|
||||
test:
|
||||
name: TestDriver
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dashcamio/testdriver@main
|
||||
id: testdriver
|
||||
with:
|
||||
version: v2.12.5
|
||||
prerun: |
|
||||
rm ~/Desktop/WITH-LOVE-FROM-AMERICA.txt
|
||||
cd ~/actions-runner/_work/testdriver/testdriver/
|
||||
brew install go
|
||||
brew tap scripthaus-dev/scripthaus
|
||||
brew install corepack
|
||||
brew install scripthaus
|
||||
corepack enable
|
||||
yarn install
|
||||
scripthaus run build-backend
|
||||
echo "Yarn"
|
||||
yarn
|
||||
echo "Rebuild"
|
||||
scripthaus run electron-rebuild
|
||||
echo "Webpack"
|
||||
scripthaus run webpack-build
|
||||
echo "Starting Electron"
|
||||
scripthaus run electron 1>/dev/null 2>&1 &
|
||||
echo "Electron Done"
|
||||
exit
|
||||
prompt: |
|
||||
1. wait 10 seconds
|
||||
1. click "Get Started"
|
||||
1. validate that overlapping text does not appear in the application
|
||||
1. focus the Wave input with the keyboard shorcut Command + I
|
||||
1. type 'ls' into the input
|
||||
1. press return
|
||||
1. validate Wave shows the result of 'ls'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: peter-evans/create-or-update-comment@v4
|
||||
if: ${{always()}}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
## TestDriver Summary
|
||||
${{ steps.testdriver.outputs.markdown }}
|
||||
${{ steps.testdriver.outputs.summary }}
|
||||
reactions: |
|
||||
+1
|
||||
-1
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -22,3 +22,12 @@ test/
|
||||
.vscode/
|
||||
make/
|
||||
waveterm-builds.zip
|
||||
|
||||
# Yarn Modern
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
@ -3,7 +3,8 @@ cd ~/actions-runner/_work/testdriver/testdriver/
|
||||
brew install go
|
||||
brew tap scripthaus-dev/scripthaus
|
||||
brew install scripthaus
|
||||
npm install -g yarn
|
||||
corepack enable
|
||||
yarn install
|
||||
scripthaus run build-backend
|
||||
echo "Yarn"
|
||||
yarn
|
||||
|
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
30
BUILD.md
30
BUILD.md
@ -11,35 +11,43 @@ If you install the production version of Wave, you'll see a semi-transparent gra
|
||||
|
||||
Download and install Go (must be at least go 1.18):
|
||||
|
||||
```
|
||||
```sh
|
||||
brew install go
|
||||
```
|
||||
|
||||
Download and install ScriptHaus (to run the build commands):
|
||||
|
||||
```
|
||||
```sh
|
||||
brew tap scripthaus-dev/scripthaus
|
||||
brew install scripthaus
|
||||
```
|
||||
|
||||
You also need a relatively modern nodejs with npm and yarn installed.
|
||||
|
||||
- Node can be installed from [https://nodejs.org](https://nodejs.org).
|
||||
- npm can install yarn using:
|
||||
Node can be installed from [https://nodejs.org](https://nodejs.org).
|
||||
|
||||
```
|
||||
npm install -g yarn
|
||||
We use Yarn Modern to manage our packages. The recommended way to install Yarn Modern is using Corepack, a new utility shipped by NodeJS that lets you manage your package manager versioning as you would any packages.
|
||||
|
||||
If you installed NodeJS from the official feed (via the website or using NVM), this should come preinstalled. If you use Homebrew or some other feed, you may need to manually install Corepack using `npm install -g corepack`.
|
||||
|
||||
For more information on Corepack, check out [this link](https://yarnpkg.com/corepack).
|
||||
|
||||
Once you've verified that you have Corepack installed, run the following script to set up Yarn for the repository:
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Clone the Repo
|
||||
|
||||
```
|
||||
```sh
|
||||
git clone git@github.com:wavetermdev/waveterm.git
|
||||
```
|
||||
|
||||
## Building WaveShell / WaveSrv
|
||||
|
||||
```
|
||||
```sh
|
||||
scripthaus run build-backend
|
||||
```
|
||||
|
||||
@ -49,7 +57,7 @@ This builds the Golang backends for Wave. The binaries will put in waveshell/bin
|
||||
|
||||
Install modules (we use yarn):
|
||||
|
||||
```
|
||||
```sh
|
||||
yarn
|
||||
```
|
||||
|
||||
@ -57,7 +65,7 @@ yarn
|
||||
|
||||
We use webpack to build both the React and Electron App Wrapper code. They are both run together using:
|
||||
|
||||
```
|
||||
```sh
|
||||
scripthaus run webpack-watch
|
||||
```
|
||||
|
||||
@ -65,7 +73,7 @@ scripthaus run webpack-watch
|
||||
|
||||
Now that webpack is running (and watching for file changes) we can finally run the WaveTerm Dev Client! To start the client run:
|
||||
|
||||
```
|
||||
```sh
|
||||
scripthaus run electron
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Open-Source Acknowledgements
|
||||
|
||||
We make use of many amazing open-source projects to build Wave Terminal. We automatically generate license reports via FOSSA to comply with the license distribution requirements of our dependencies. Below is a summary of the licenses used by our product. Clicking on the image will take you to the full report on FOSSA's website.
|
||||
We make use of many amazing open-source projects to build Wave Terminal. We automatically generate license reports via FOSSA to comply with the license distribution requirements of our dependencies. Below is a summary of the licenses used by our product. For a full report, see [here](https://app.fossa.com/reports/24d13570-624b-4450-8c22-756e513060c9?full=true) (the page may take 20-30s to load).
|
||||
|
||||
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwavetermdev%2Fwaveterm.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwavetermdev%2Fwaveterm?ref=badge_large)
|
||||
|
14710
package-lock.json
generated
Normal file
14710
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
||||
},
|
||||
"productName": "Wave",
|
||||
"description": "An Open-Source, AI-Native, Terminal Built for Seamless Workflows",
|
||||
"version": "0.7.3",
|
||||
"version": "0.7.5",
|
||||
"main": "dist/emain.js",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@ -26,7 +26,7 @@
|
||||
"@tanstack/react-table": "^8.10.3",
|
||||
"autobind-decorator": "^2.4.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"classnames": "^2.3.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.3",
|
||||
"dompurify": "^3.0.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
@ -71,7 +71,6 @@
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@electron/rebuild": "^3.6.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/papaparse": "^5.3.10",
|
||||
@ -108,5 +107,6 @@
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.1.1"
|
||||
}
|
||||
|
@ -115,15 +115,3 @@ scripthaus run fullbuild-waveshell
|
||||
echo building wavesrv
|
||||
scripthaus run build-wavesrv
|
||||
```
|
||||
|
||||
```bash
|
||||
# @scripthaus command generate-license-disclaimers
|
||||
DISCLAIMER_DIR="./acknowledgements"
|
||||
DISCLAIMER_OUTPUT_DIR="$DISCLAIMER_DIR/disclaimers"
|
||||
if [ -d "$DISCLAIMER_OUTPUT_DIR" ]; then
|
||||
rm -rf "$DISCLAIMER_OUTPUT_DIR"
|
||||
fi
|
||||
mkdir "$DISCLAIMER_OUTPUT_DIR"
|
||||
go run github.com/google/go-licenses@latest report ./wavesrv/... ./waveshell/... --template "$DISCLAIMER_DIR/go_licenses_report.tpl" --ignore github.com/wavetermdev/waveterm > "$DISCLAIMER_OUTPUT_DIR/backend.md"
|
||||
yarn licenses generate-disclaimer > "$DISCLAIMER_OUTPUT_DIR/frontend.md"
|
||||
```
|
||||
|
@ -4,7 +4,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
@ -133,7 +133,7 @@ class App extends React.Component<{}, {}> {
|
||||
const rightSidebarCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
const activeMainView = GlobalModel.activeMainView.get();
|
||||
const lightDarkClass = GlobalModel.isDarkTheme.get() ? "is-dark" : "is-light";
|
||||
const mainClassName = cn(
|
||||
const mainClassName = clsx(
|
||||
"platform-" + platform,
|
||||
{
|
||||
"mainsidebar-collapsed": mainSidebarCollapsed,
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { CmdStrCode, Markdown } from "@/common/elements";
|
||||
|
||||
@ -152,11 +152,11 @@ class Bookmark extends React.Component<BookmarkProps, {}> {
|
||||
return (
|
||||
<div
|
||||
data-bookmarkid={bm.bookmarkid}
|
||||
className={cn("bookmark focus-parent is-editing", {
|
||||
className={clsx("bookmark focus-parent is-editing", {
|
||||
"pending-delete": model.pendingDelete.get() == bm.bookmarkid,
|
||||
})}
|
||||
>
|
||||
<div className={cn("focus-indicator", { active: isSelected })} />
|
||||
<div className={clsx("focus-indicator", { active: isSelected })} />
|
||||
<div className="bookmark-edit">
|
||||
<div className="field">
|
||||
<label className="label">Description (markdown)</label>
|
||||
@ -198,12 +198,12 @@ class Bookmark extends React.Component<BookmarkProps, {}> {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={cn("bookmark focus-parent", {
|
||||
className={clsx("bookmark focus-parent", {
|
||||
"pending-delete": model.pendingDelete.get() == bm.bookmarkid,
|
||||
})}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<div className={cn("focus-indicator", { active: isSelected })} />
|
||||
<div className={clsx("focus-indicator", { active: isSelected })} />
|
||||
<div className="bookmark-id-div">{bm.bookmarkid.substr(0, 8)}</div>
|
||||
<div className="bookmark-content">
|
||||
<If condition={hasDesc}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import "./button.less";
|
||||
|
||||
@ -35,7 +35,7 @@ class Button extends React.Component<ButtonProps> {
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn("wave-button", { disabled }, { "term-inline": termInline }, className)}
|
||||
className={clsx("wave-button", { disabled }, { "term-inline": termInline }, className)}
|
||||
onClick={this.handleClick}
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import "./checkbox.less";
|
||||
|
||||
@ -49,7 +49,7 @@ class Checkbox extends React.Component<
|
||||
const checkboxId = id || this.generatedId;
|
||||
|
||||
return (
|
||||
<div className={cn("checkbox", className)}>
|
||||
<div className={clsx("checkbox", className)}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={checkboxId}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
|
||||
import { ReactComponent as CheckIcon } from "@/assets/icons/line/check.svg";
|
||||
@ -41,7 +41,7 @@ class CmdStrCode extends React.Component<
|
||||
render() {
|
||||
let { isCopied, cmdstr, fontSize, limitHeight } = this.props;
|
||||
return (
|
||||
<div className={cn("cmdstr-code", { "is-large": fontSize == "large" }, { "limit-height": limitHeight })}>
|
||||
<div className={clsx("cmdstr-code", { "is-large": fontSize == "large" }, { "limit-height": limitHeight })}>
|
||||
<If condition={isCopied}>
|
||||
<div key="copied" className="copied-indicator">
|
||||
<div>copied</div>
|
||||
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, createRef } from "react";
|
||||
import * as mobx from "mobx";
|
||||
import ReactDOM from "react-dom";
|
||||
import dayjs from "dayjs";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { Button } from "@/elements";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel } from "@/models";
|
||||
@ -102,7 +102,7 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
return (
|
||||
<div className="day-picker-header">
|
||||
<div
|
||||
className={cn({ fade: showYearAccordion })}
|
||||
className={clsx({ fade: showYearAccordion })}
|
||||
onClick={() => {
|
||||
if (!showYearAccordion) {
|
||||
setExpandedYear(selDate.year()); // Set expandedYear when opening accordion
|
||||
@ -111,7 +111,7 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
}}
|
||||
>
|
||||
{selDate.format("MMMM YYYY")}
|
||||
<span className={cn("dropdown-arrow", { fade: showYearAccordion })}></span>
|
||||
<span className={clsx("dropdown-arrow", { fade: showYearAccordion })}></span>
|
||||
</div>
|
||||
<If condition={!showYearAccordion}>
|
||||
<div className="arrows">
|
||||
@ -250,14 +250,14 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
</div>
|
||||
<If condition={expandedYear === year}>
|
||||
<div
|
||||
className={cn("month-container", {
|
||||
className={clsx("month-container", {
|
||||
expanded: expandedYear === year,
|
||||
})}
|
||||
>
|
||||
{Array.from({ length: 12 }, (_, i) => i + 1).map((month) => (
|
||||
<div
|
||||
key={month}
|
||||
className={cn("month", {
|
||||
className={clsx("month", {
|
||||
selected: year === currentYear && month === selDate.month() + 1,
|
||||
})}
|
||||
onClick={() => handleMonthYearSelect(month, year)}
|
||||
|
@ -4,7 +4,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import ReactDOM from "react-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
@ -239,11 +239,11 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
|
||||
const dropdownMenu = isOpen
|
||||
? ReactDOM.createPortal(
|
||||
<div className={cn("wave-dropdown-menu")} ref={this.menuRef} style={this.calculatePosition()}>
|
||||
<div className={clsx("wave-dropdown-menu")} ref={this.menuRef} style={this.calculatePosition()}>
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn("wave-dropdown-item unselectable", {
|
||||
className={clsx("wave-dropdown-item unselectable", {
|
||||
"wave-dropdown-item-highlighted": index === highlightedIndex,
|
||||
})}
|
||||
onClick={(e) => this.handleSelect(option, e)}
|
||||
@ -265,7 +265,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={cn("wave-dropdown", className, {
|
||||
className={clsx("wave-dropdown", className, {
|
||||
"wave-dropdown-error": isError,
|
||||
"no-label": !label,
|
||||
})}
|
||||
@ -279,7 +279,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
|
||||
<If condition={label}>
|
||||
<div
|
||||
className={cn("wave-dropdown-label unselectable", {
|
||||
className={clsx("wave-dropdown-label unselectable", {
|
||||
float: shouldLabelFloat,
|
||||
"offset-left": decoration?.startDecoration,
|
||||
})}
|
||||
@ -288,14 +288,14 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
className={cn("wave-dropdown-display unselectable truncate", {
|
||||
className={clsx("wave-dropdown-display unselectable truncate", {
|
||||
"offset-left": decoration?.startDecoration,
|
||||
})}
|
||||
style={selectedOptionLabelStyle}
|
||||
>
|
||||
{selectedOptionLabel}
|
||||
</div>
|
||||
<div className={cn("wave-dropdown-arrow", { "wave-dropdown-arrow-rotate": isOpen })}>
|
||||
<div className={clsx("wave-dropdown-arrow", { "wave-dropdown-arrow-rotate": isOpen })}>
|
||||
<i className="fa-sharp fa-solid fa-chevron-down"></i>
|
||||
</div>
|
||||
{dropdownMenu}
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
import { GlobalModel } from "@/models";
|
||||
@ -121,7 +121,7 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
render() {
|
||||
if (this.isEditing.get()) {
|
||||
return (
|
||||
<div className={cn("settings-input inline-edit", "edit-active")}>
|
||||
<div className={clsx("settings-input inline-edit", "edit-active")}>
|
||||
<div className="field has-addons">
|
||||
<div className="control">
|
||||
<input
|
||||
@ -163,7 +163,7 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div onClick={this.clickEdit} className={cn("settings-input inline-edit", "edit-not-active")}>
|
||||
<div onClick={this.clickEdit} className={clsx("settings-input inline-edit", "edit-not-active")}>
|
||||
{this.props.text}
|
||||
<If condition={this.props.showIcon}>
|
||||
<i className="fa-sharp fa-solid fa-pen" />
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import "./inputdecoration.less";
|
||||
|
||||
@ -18,7 +18,7 @@ class InputDecoration extends React.Component<InputDecorationProps, {}> {
|
||||
const { children, position = "end" } = this.props;
|
||||
return (
|
||||
<div
|
||||
className={cn("wave-input-decoration", {
|
||||
className={clsx("wave-input-decoration", {
|
||||
"start-position": position === "start",
|
||||
"end-position": position === "end",
|
||||
})}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { ButtonProps } from "./button";
|
||||
|
||||
interface LinkButtonProps extends ButtonProps {
|
||||
@ -16,7 +16,7 @@ class LinkButton extends React.Component<LinkButtonProps> {
|
||||
const { leftIcon, rightIcon, children, className, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<a {...rest} className={cn(`wave-button link-button`, className)}>
|
||||
<a {...rest} className={clsx(`wave-button link-button`, className)}>
|
||||
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||
{children}
|
||||
{rightIcon && <span className="icon-right">{rightIcon}</span>}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
import "./mainview.less";
|
||||
@ -24,7 +24,7 @@ class MainView extends React.Component<{
|
||||
const maxWidthSubtractor = sidebarModel.getCollapsed() ? 0 : sidebarModel.getWidth();
|
||||
return (
|
||||
<div
|
||||
className={cn("mainview", this.props.className)}
|
||||
className={clsx("mainview", this.props.className)}
|
||||
style={{ maxWidth: `calc(100vw - ${maxWidthSubtractor}px)` }}
|
||||
>
|
||||
<div className="header-container">
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
@ -22,7 +22,7 @@ function LinkRenderer(props: any): any {
|
||||
}
|
||||
|
||||
function HeaderRenderer(props: any, hnum: number): any {
|
||||
return <div className={cn("title", "is-" + hnum)}>{props.children}</div>;
|
||||
return <div className={clsx("title", "is-" + hnum)}>{props.children}</div>;
|
||||
}
|
||||
|
||||
function CodeRenderer(props: any): any {
|
||||
@ -53,7 +53,7 @@ class CodeBlockMarkdown extends React.Component<
|
||||
console.log("this.blockIndex", this.blockIndex);
|
||||
let selected = this.blockIndex == this.props.codeSelectSelectedIndex;
|
||||
return (
|
||||
<pre ref={this.blockRef} className={cn({ selected: selected })} onClick={this.handleClick}>
|
||||
<pre ref={this.blockRef} className={clsx({ selected: selected })} onClick={this.handleClick}>
|
||||
{this.props.children}
|
||||
</pre>
|
||||
);
|
||||
@ -108,7 +108,7 @@ class Markdown extends React.Component<
|
||||
pre: (props) => this.codeBlockRenderer(props, codeSelect, curCodeSelectIndex, this.curUuid),
|
||||
};
|
||||
return (
|
||||
<div className={cn("markdown content", this.props.extraClassName)} style={this.props.style}>
|
||||
<div className={clsx("markdown content", this.props.extraClassName)} style={this.props.style}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{text}
|
||||
</ReactMarkdown>
|
||||
|
@ -4,7 +4,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { TextFieldState, TextField } from "./textfield";
|
||||
|
||||
@ -48,7 +48,7 @@ class PasswordField extends TextField {
|
||||
|
||||
// The input should always receive the real value
|
||||
const inputProps = {
|
||||
className: cn("wave-textfield-inner-input", { "offset-left": decoration?.startDecoration }),
|
||||
className: clsx("wave-textfield-inner-input", { "offset-left": decoration?.startDecoration }),
|
||||
ref: this.inputRef,
|
||||
id: label,
|
||||
value: inputValue, // Always use the real value here
|
||||
@ -63,7 +63,7 @@ class PasswordField extends TextField {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(`wave-textfield wave-password ${className || ""}`, {
|
||||
className={clsx(`wave-textfield wave-password ${className || ""}`, {
|
||||
focused: focused,
|
||||
error: error,
|
||||
"no-label": !label,
|
||||
@ -72,7 +72,7 @@ class PasswordField extends TextField {
|
||||
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
|
||||
<div className="wave-textfield-inner">
|
||||
<label
|
||||
className={cn("wave-textfield-inner-label", {
|
||||
className={clsx("wave-textfield-inner-label", {
|
||||
float: this.state.hasContent || this.state.focused || placeholder,
|
||||
"offset-left": decoration?.startDecoration,
|
||||
})}
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalCommandRunner, SidebarModel } from "@/models";
|
||||
import { MagicLayout } from "@/app/magiclayout";
|
||||
|
||||
@ -142,7 +142,7 @@ class ResizableSidebar extends React.Component<ResizableSidebarProps> {
|
||||
const isCollapsed = model.getCollapsed();
|
||||
|
||||
return (
|
||||
<div className={cn("sidebar", className, { collapsed: isCollapsed })} style={{ width, minWidth: width }}>
|
||||
<div className={clsx("sidebar", className, { collapsed: isCollapsed })} style={{ width, minWidth: width }}>
|
||||
<div className="sidebar-content">{children(this.toggleCollapsed)}</div>
|
||||
<div
|
||||
className="sidebar-handle"
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import { isBlank } from "@/util/util";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
class TabIcon extends React.Component<{ icon: string; color: string }> {
|
||||
render() {
|
||||
@ -20,7 +20,7 @@ class TabIcon extends React.Component<{ icon: string; color: string }> {
|
||||
color = "green";
|
||||
}
|
||||
return (
|
||||
<div className={cn("tabicon", "color-" + color)}>
|
||||
<div className={clsx("tabicon", "color-" + color)}>
|
||||
<i className={iconClass} />
|
||||
</div>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
|
||||
import "./textfield.less";
|
||||
@ -140,7 +140,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("wave-textfield", className, {
|
||||
className={clsx("wave-textfield", className, {
|
||||
focused: focused,
|
||||
error: error,
|
||||
disabled: disabled,
|
||||
@ -154,7 +154,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
<div className="wave-textfield-inner">
|
||||
<If condition={label}>
|
||||
<label
|
||||
className={cn("wave-textfield-inner-label", {
|
||||
className={clsx("wave-textfield-inner-label", {
|
||||
float: this.state.hasContent || this.state.focused || placeholder,
|
||||
"offset-left": decoration?.startDecoration,
|
||||
})}
|
||||
@ -164,7 +164,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
</label>
|
||||
</If>
|
||||
<input
|
||||
className={cn("wave-textfield-inner-input", "wave-input", {
|
||||
className={clsx("wave-textfield-inner-input", "wave-input", {
|
||||
"offset-left": decoration?.startDecoration,
|
||||
})}
|
||||
ref={this.inputRef}
|
||||
|
@ -4,7 +4,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import "./tooltip.less";
|
||||
@ -63,7 +63,7 @@ class Tooltip extends React.Component<TooltipProps, TooltipState> {
|
||||
const style = this.calculatePosition();
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<div className={cn("wave-tooltip", this.props.className)} style={style}>
|
||||
<div className={clsx("wave-tooltip", this.props.className)} style={style}>
|
||||
{this.props.icon && <div className="wave-tooltip-icon">{this.props.icon}</div>}
|
||||
<div className="wave-tooltip-message">{this.props.message}</div>
|
||||
</div>,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Component, ReactNode } from "react";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
@ -43,7 +43,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
const { plugin } = this.props;
|
||||
|
||||
return (
|
||||
<div className={cn("load-error-text", { "view-error": !plugin })}>
|
||||
<div className={clsx("load-error-text", { "view-error": !plugin })}>
|
||||
<div>{`${error?.message}`}</div>
|
||||
{plugin && <div>An error occurred in the {plugin} plugin</div>}
|
||||
</div>
|
||||
|
@ -1,9 +1,6 @@
|
||||
import React from "react";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { ReactComponent as SpinnerIndicator } from "@/assets/icons/spinner-indicator.svg";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as appconst from "@/app/appconst";
|
||||
|
||||
import { ReactComponent as RotateIconSvg } from "@/assets/icons/line/rotate.svg";
|
||||
@ -15,77 +12,52 @@ interface PositionalIconProps {
|
||||
divRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class FrontIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={this.props.divRef}
|
||||
className={cn("front-icon", "positional-icon", this.props.className)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<div className="positional-icon-inner">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export const FrontIcon: React.FC<PositionalIconProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
ref={props.divRef}
|
||||
className={clsx("front-icon", "positional-icon", props.className)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<div className="positional-icon-inner">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export class CenteredIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={this.props.divRef}
|
||||
className={cn("centered-icon", "positional-icon", this.props.className)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<div className="positional-icon-inner">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export const CenteredIcon: React.FC<PositionalIconProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
ref={props.divRef}
|
||||
className={clsx("centered-icon", "positional-icon", props.className)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<div className="positional-icon-inner">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface ActionsIconProps {
|
||||
onClick: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class ActionsIcon extends React.Component<ActionsIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<CenteredIcon className="actions" onClick={this.props.onClick}>
|
||||
<div className="icon hoverEffect fa-sharp fa-solid fa-1x fa-ellipsis-vertical"></div>
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
export const ActionsIcon: React.FC<ActionsIconProps> = (props) => {
|
||||
return (
|
||||
<CenteredIcon className="actions" onClick={props.onClick}>
|
||||
<div className="icon hoverEffect fa-sharp fa-solid fa-1x fa-ellipsis-vertical"></div>
|
||||
</CenteredIcon>
|
||||
);
|
||||
};
|
||||
|
||||
class SyncSpin extends React.Component<{
|
||||
classRef?: React.RefObject<HTMLDivElement>;
|
||||
export const SyncSpin: React.FC<{
|
||||
classRef?: React.RefObject<Element>;
|
||||
children?: React.ReactNode;
|
||||
shouldSync?: boolean;
|
||||
}> {
|
||||
listenerAdded: boolean = false;
|
||||
}> = (props) => {
|
||||
const { classRef, children, shouldSync } = props;
|
||||
const [listenerAdded, setListenerAdded] = React.useState(false);
|
||||
|
||||
componentDidMount() {
|
||||
this.syncSpinner();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.syncSpinner();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
const classRef = this.props.classRef;
|
||||
if (classRef.current != null && this.listenerAdded) {
|
||||
const elem = classRef.current;
|
||||
const svgElem = elem.querySelector("svg");
|
||||
if (svgElem != null) {
|
||||
svgElem.removeEventListener("animationstart", this.handleAnimationStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleAnimationStart(e: AnimationEvent) {
|
||||
const classRef = this.props.classRef;
|
||||
const handleAnimationStart = (e: AnimationEvent) => {
|
||||
const classRef = props.classRef;
|
||||
if (classRef.current == null) {
|
||||
return;
|
||||
}
|
||||
@ -98,10 +70,9 @@ class SyncSpin extends React.Component<{
|
||||
return;
|
||||
}
|
||||
animArr[0].startTime = 0;
|
||||
}
|
||||
};
|
||||
|
||||
syncSpinner() {
|
||||
const { classRef, shouldSync } = this.props;
|
||||
React.useEffect(() => {
|
||||
const shouldSyncVal = shouldSync ?? true;
|
||||
if (!shouldSyncVal || classRef.current == null) {
|
||||
return;
|
||||
@ -111,21 +82,24 @@ class SyncSpin extends React.Component<{
|
||||
if (svgElem == null) {
|
||||
return;
|
||||
}
|
||||
if (!this.listenerAdded) {
|
||||
svgElem.addEventListener("animationstart", this.handleAnimationStart);
|
||||
this.listenerAdded = true;
|
||||
if (!listenerAdded) {
|
||||
svgElem.addEventListener("animationstart", handleAnimationStart);
|
||||
setListenerAdded(true);
|
||||
}
|
||||
const animArr = svgElem.getAnimations();
|
||||
if (animArr == null || animArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
animArr[0].startTime = 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
if (listenerAdded) {
|
||||
svgElem.removeEventListener("animationstart", handleAnimationStart);
|
||||
setListenerAdded(false);
|
||||
}
|
||||
};
|
||||
});
|
||||
return children;
|
||||
};
|
||||
|
||||
interface StatusIndicatorProps {
|
||||
/**
|
||||
@ -142,97 +116,79 @@ interface StatusIndicatorProps {
|
||||
/**
|
||||
* This component is used to show the status of a command. It will show a spinner around the status indicator if there are running commands. It will also delay showing the spinner for a short time to prevent flickering.
|
||||
*/
|
||||
@mobxReact.observer
|
||||
export class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
||||
iconRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
spinnerVisible: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
timeout: NodeJS.Timeout;
|
||||
export const StatusIndicator: React.FC<StatusIndicatorProps> = (props) => {
|
||||
const { level, className, runningCommands } = props;
|
||||
const iconRef = React.useRef<HTMLDivElement>();
|
||||
const [spinnerVisible, setSpinnerVisible] = React.useState(false);
|
||||
const [timeoutState, setTimeoutState] = React.useState<NodeJS.Timeout>(undefined);
|
||||
|
||||
clearSpinnerTimeout() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
const clearSpinnerTimeout = () => {
|
||||
if (timeoutState) {
|
||||
clearTimeout(timeoutState);
|
||||
setTimeoutState(undefined);
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.spinnerVisible.set(false);
|
||||
})();
|
||||
}
|
||||
setSpinnerVisible(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* This will apply a delay after there is a running command before showing the spinner. This prevents flickering for commands that return quickly.
|
||||
*/
|
||||
updateMountCallback() {
|
||||
const runningCommands = this.props.runningCommands ?? false;
|
||||
if (runningCommands && !this.timeout) {
|
||||
this.timeout = setTimeout(
|
||||
mobx.action(() => {
|
||||
this.spinnerVisible.set(true);
|
||||
}),
|
||||
100
|
||||
React.useEffect(() => {
|
||||
if (runningCommands && !timeoutState) {
|
||||
console.log("show spinner");
|
||||
setTimeoutState(
|
||||
setTimeout(() => {
|
||||
setSpinnerVisible(true);
|
||||
}, 100)
|
||||
);
|
||||
} else if (!runningCommands) {
|
||||
this.clearSpinnerTimeout();
|
||||
console.log("clear spinner");
|
||||
clearSpinnerTimeout();
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
clearSpinnerTimeout();
|
||||
};
|
||||
}, [runningCommands]);
|
||||
|
||||
componentDidUpdate(): void {
|
||||
this.updateMountCallback();
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.updateMountCallback();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.clearSpinnerTimeout();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { level, className, runningCommands } = this.props;
|
||||
const spinnerVisible = this.spinnerVisible.get();
|
||||
let statusIndicator = null;
|
||||
if (level != appconst.StatusIndicatorLevel.None || spinnerVisible) {
|
||||
let indicatorLevelClass = null;
|
||||
switch (level) {
|
||||
case appconst.StatusIndicatorLevel.Output:
|
||||
indicatorLevelClass = "output";
|
||||
break;
|
||||
case appconst.StatusIndicatorLevel.Success:
|
||||
indicatorLevelClass = "success";
|
||||
break;
|
||||
case appconst.StatusIndicatorLevel.Error:
|
||||
indicatorLevelClass = "error";
|
||||
break;
|
||||
}
|
||||
|
||||
const spinnerVisibleClass = spinnerVisible ? "spinner-visible" : null;
|
||||
statusIndicator = (
|
||||
<CenteredIcon
|
||||
divRef={this.iconRef}
|
||||
className={cn(className, indicatorLevelClass, spinnerVisibleClass, "status-indicator")}
|
||||
>
|
||||
<SpinnerIndicator className={spinnerVisible ? "spin" : null} />
|
||||
</CenteredIcon>
|
||||
);
|
||||
let statusIndicator = null;
|
||||
if (level != appconst.StatusIndicatorLevel.None || spinnerVisible) {
|
||||
let indicatorLevelClass = null;
|
||||
switch (level) {
|
||||
case appconst.StatusIndicatorLevel.Output:
|
||||
indicatorLevelClass = "output";
|
||||
break;
|
||||
case appconst.StatusIndicatorLevel.Success:
|
||||
indicatorLevelClass = "success";
|
||||
break;
|
||||
case appconst.StatusIndicatorLevel.Error:
|
||||
indicatorLevelClass = "error";
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<SyncSpin classRef={this.iconRef} shouldSync={runningCommands}>
|
||||
{statusIndicator}
|
||||
</SyncSpin>
|
||||
|
||||
const spinnerVisibleClass = spinnerVisible ? "spinner-visible" : null;
|
||||
statusIndicator = (
|
||||
<CenteredIcon
|
||||
divRef={iconRef}
|
||||
className={clsx(className, indicatorLevelClass, spinnerVisibleClass, "status-indicator")}
|
||||
>
|
||||
<SpinnerIndicator className={spinnerVisible ? "spin" : null} />
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SyncSpin classRef={iconRef} shouldSync={runningCommands}>
|
||||
{statusIndicator}
|
||||
</SyncSpin>
|
||||
);
|
||||
};
|
||||
|
||||
export class RotateIcon extends React.Component<{
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}> {
|
||||
iconRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
render() {
|
||||
return (
|
||||
<SyncSpin classRef={this.iconRef}>
|
||||
<RotateIconSvg className={this.props.className ?? ""} />
|
||||
</SyncSpin>
|
||||
);
|
||||
}
|
||||
}
|
||||
export const RotateIcon: React.FC<{ className?: string; onClick?: React.MouseEventHandler<SVGSVGElement> }> = (
|
||||
props
|
||||
) => {
|
||||
const iconRef = React.useRef<SVGSVGElement>();
|
||||
return (
|
||||
<SyncSpin classRef={iconRef}>
|
||||
<RotateIconSvg ref={iconRef} className={props.className ?? ""} onClick={props.onClick} />
|
||||
</SyncSpin>
|
||||
);
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ import { GlobalModel } from "@/models";
|
||||
import { Modal, LinkButton } from "@/elements";
|
||||
import * as util from "@/util/util";
|
||||
import * as appconst from "@/app/appconst";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
|
||||
import logo from "@/assets/waveterm-logo-with-bg.svg";
|
||||
@ -36,7 +36,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
const isUpToDate = !showUpdateStatus || GlobalModel.appUpdateStatus.get() !== "ready";
|
||||
|
||||
return (
|
||||
<div className={cn("status", { outdated: !isUpToDate })}>
|
||||
<div className={clsx("status", { outdated: !isUpToDate })}>
|
||||
<If condition={!isUpToDate}>
|
||||
<div>
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation" />
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
|
||||
import { SettingsError, Modal, Dropdown, Tooltip } from "@/elements";
|
||||
import * as util from "@/util/util";
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||
import { Modal, TextField, InputDecoration, Tooltip } from "@/elements";
|
||||
import * as util from "@/util/util";
|
||||
@ -287,7 +287,7 @@ class TabSwitcherModal extends React.Component<{}, {}> {
|
||||
<div
|
||||
key={option.sessionId + "/" + option.screenId}
|
||||
ref={this.optionRefs[index]}
|
||||
className={cn("search-option unselectable", {
|
||||
className={clsx("search-option unselectable", {
|
||||
"focused-option": this.focusedIdx.get() === index,
|
||||
})}
|
||||
onClick={() => this.handleSelect(index)}
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "@/models";
|
||||
import { Modal, Tooltip, Button, Status } from "@/elements";
|
||||
import * as util from "@/util/util";
|
||||
@ -140,12 +140,12 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
renderInstallStatus(remote: 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";
|
||||
if (remote.needswaveshellupgrade) {
|
||||
statusStr = "waveshell " + remote.waveshellversion + " - needs upgrade";
|
||||
} else if (util.isBlank(remote.waveshellversion)) {
|
||||
statusStr = "waveshell unknown";
|
||||
} else {
|
||||
statusStr = "mshell " + remote.mshellversion + " - current";
|
||||
statusStr = "waveshell " + remote.waveshellversion + " - current";
|
||||
}
|
||||
} else {
|
||||
statusStr = remote.installstatus;
|
||||
@ -231,7 +231,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
} else if (remote.status == "disconnected") {
|
||||
buttons.push(connectButton);
|
||||
} else if (remote.status == "error") {
|
||||
if (remote.needsmshellupgrade) {
|
||||
if (remote.needswaveshellupgrade) {
|
||||
if (remote.installstatus == "connecting") {
|
||||
buttons.push(cancelInstallButton);
|
||||
} else {
|
||||
@ -270,7 +270,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
} else if (remote.status == "error") {
|
||||
if (remote.noinitpk) {
|
||||
message = "Error, could not connect.";
|
||||
} else if (remote.needsmshellupgrade) {
|
||||
} else if (remote.needswaveshellupgrade) {
|
||||
if (remote.installstatus == "connecting") {
|
||||
message = "Installing...";
|
||||
} else {
|
||||
@ -370,7 +370,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div
|
||||
key="term"
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"terminal-wrapper",
|
||||
{ focus: isTermFocused },
|
||||
remote != null ? "status-" + remote.status : null
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { isBlank } from "@/util/util";
|
||||
|
||||
import "./prompt.less";
|
||||
@ -108,7 +108,7 @@ class Prompt extends React.Component<
|
||||
let remoteElem = null;
|
||||
if (remoteStr != "local") {
|
||||
remoteElem = (
|
||||
<span title={remoteTitle} className={cn("term-prompt-remote", remoteColorClass)}>
|
||||
<span title={remoteTitle} className={clsx("term-prompt-remote", remoteColorClass)}>
|
||||
[{remoteStr}]{" "}
|
||||
</span>
|
||||
);
|
||||
@ -124,10 +124,10 @@ class Prompt extends React.Component<
|
||||
render() {
|
||||
const rptr = this.props.rptr;
|
||||
if (rptr == null || isBlank(rptr.remoteid)) {
|
||||
return <span className={cn("term-prompt", "color-green")}> </span>;
|
||||
return <span className={clsx("term-prompt", "color-green")}> </span>;
|
||||
}
|
||||
let { remoteElem, isRoot } = this.getRemoteElem();
|
||||
let termClassNames = cn(
|
||||
let termClassNames = clsx(
|
||||
"term-prompt",
|
||||
{ "term-prompt-color": this.props.color },
|
||||
{ "term-prompt-isroot": isRoot }
|
||||
@ -172,16 +172,16 @@ class Prompt extends React.Component<
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (!isBlank(festate["K8SCONTEXT"])) {
|
||||
const k8sContext = festate["K8SCONTEXT"];
|
||||
const k8sNs = festate["K8SNAMESPACE"];
|
||||
k8sElem = (
|
||||
<span title="k8s context:namespace" className="term-prompt-k8s">
|
||||
k8s:({k8sContext}
|
||||
{isBlank(k8sNs) ? "" : ":" + k8sNs}){" "}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
// if (!isBlank(festate["K8SCONTEXT"])) {
|
||||
// const k8sContext = festate["K8SCONTEXT"];
|
||||
// const k8sNs = festate["K8SNAMESPACE"];
|
||||
// k8sElem = (
|
||||
// <span title="k8s context:namespace" className="term-prompt-k8s">
|
||||
// k8s:({k8sContext}
|
||||
// {isBlank(k8sNs) ? "" : ":" + k8sNs}){" "}
|
||||
// </span>
|
||||
// );
|
||||
// }
|
||||
return (
|
||||
<span className={termClassNames}>
|
||||
{remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem} {k8sElem}
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, RemotesModel, GlobalCommandRunner } from "@/models";
|
||||
import { Button, Status } from "@/common/elements";
|
||||
import * as util from "@/util/util";
|
||||
@ -185,7 +185,7 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
|
||||
<For index="idx" each="item" of={items}>
|
||||
<tr
|
||||
key={item.remoteid}
|
||||
className={cn("connections-item", {
|
||||
className={clsx("connections-item", {
|
||||
hovered: this.state.hoveredItemId === item.remoteid,
|
||||
})}
|
||||
onClick={() => this.handleRead(item.remoteid)} // Moved onClick here
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
@ -137,7 +137,7 @@ class HistoryCmdStr extends React.Component<
|
||||
render() {
|
||||
const { cmdstr, fontSize, limitHeight } = this.props;
|
||||
return (
|
||||
<div className={cn("cmdstr-code", { "is-large": fontSize == "large" }, { "limit-height": limitHeight })}>
|
||||
<div className={clsx("cmdstr-code", { "is-large": fontSize == "large" }, { "limit-height": limitHeight })}>
|
||||
<div key="code" className="code-div">
|
||||
<code>{cmdstr}</code>
|
||||
</div>
|
||||
@ -490,7 +490,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div key="control1" className={cn("control-bar", "is-top", { "is-hidden": items.length == 0 })}>
|
||||
<div key="control1" className={clsx("control-bar", "is-top", { "is-hidden": items.length == 0 })}>
|
||||
<div className="control-checkbox" onClick={this.handleControlCheckbox} title="Toggle Selection">
|
||||
<HistoryCheckbox
|
||||
checked={numSelected > 0 && numSelected == items.length}
|
||||
@ -498,7 +498,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"control-button delete-button",
|
||||
{ "is-disabled": numSelected == 0 },
|
||||
{ "is-active": hvm.deleteActive.get() }
|
||||
@ -515,14 +515,14 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
Showing {offset + 1}-{offset + items.length}
|
||||
</div>
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": offset == 0 })}
|
||||
className={clsx("showing-btn", { "is-disabled": offset == 0 })}
|
||||
onClick={offset != 0 ? this.handlePrev : null}
|
||||
>
|
||||
<ChevronLeftIcon className="icon" />
|
||||
</div>
|
||||
<div className="btn-spacer" />
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": !hasMore })}
|
||||
className={clsx("showing-btn", { "is-disabled": !hasMore })}
|
||||
onClick={hasMore ? this.handleNext : null}
|
||||
>
|
||||
<ChevronRightIcon className="icon" />
|
||||
@ -538,7 +538,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
<For index="idx" each="item" of={items}>
|
||||
<div
|
||||
key={item.historyid}
|
||||
className={cn("row history-item", {
|
||||
className={clsx("row history-item", {
|
||||
"is-selected": hvm.selectedItems.get(item.historyid),
|
||||
})}
|
||||
>
|
||||
@ -608,21 +608,21 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div
|
||||
key="control2"
|
||||
className={cn("control-bar", "is-bottom", { "is-hidden": items.length == 0 || !hasMore })}
|
||||
className={clsx("control-bar", "is-bottom", { "is-hidden": items.length == 0 || !hasMore })}
|
||||
>
|
||||
<div className="spacer" />
|
||||
<div className="showing-text">
|
||||
Showing {offset + 1}-{offset + items.length}
|
||||
</div>
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": offset == 0 })}
|
||||
className={clsx("showing-btn", { "is-disabled": offset == 0 })}
|
||||
onClick={offset != 0 ? this.handlePrev : null}
|
||||
>
|
||||
<ChevronLeftIcon className="icon" />
|
||||
</div>
|
||||
<div className="btn-spacer" />
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": !hasMore })}
|
||||
className={clsx("showing-btn", { "is-disabled": !hasMore })}
|
||||
onClick={hasMore ? this.handleNext : null}
|
||||
>
|
||||
<ChevronRightIcon className="icon" />
|
||||
|
@ -11,7 +11,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { Choose, If, Otherwise, When } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, Cmd } from "@/models";
|
||||
import { termHeightFromRows } from "@/util/textmeasure";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { getTermPtyData } from "@/util/modelutil";
|
||||
|
||||
import { renderCmdText } from "@/common/elements";
|
||||
@ -172,7 +172,7 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
<div
|
||||
key="bookmark"
|
||||
title="Bookmark"
|
||||
className={cn("line-icon", "line-bookmark")}
|
||||
className={clsx("line-icon", "line-bookmark")}
|
||||
onClick={this.clickBookmark}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-bookmark fa-fw" />
|
||||
@ -180,7 +180,7 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
<div
|
||||
key="minimize"
|
||||
title={`${isMinimized ? "Show Output" : "Hide Output"}`}
|
||||
className={cn("line-icon", isMinimized ? "active" : "")}
|
||||
className={clsx("line-icon", isMinimized ? "active" : "")}
|
||||
onClick={this.clickMinimize}
|
||||
>
|
||||
<Choose>
|
||||
@ -220,7 +220,7 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
<div
|
||||
key="bookmark"
|
||||
title="Bookmark"
|
||||
className={cn("line-icon", "line-bookmark")}
|
||||
className={clsx("line-icon", "line-bookmark")}
|
||||
onClick={this.clickBookmark}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-bookmark fa-fw" />
|
||||
@ -254,7 +254,7 @@ class LineHeader extends React.Component<{ screen: LineContainerType; line: Line
|
||||
<React.Fragment>
|
||||
<div
|
||||
key="meta2"
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"meta meta-line2 cmdtext-expanded no-highlight-scrollbar scrollbar-hide-until-hover",
|
||||
{
|
||||
"is-multiline": isMultiLine,
|
||||
@ -304,7 +304,7 @@ class LineHeader extends React.Component<{ screen: LineContainerType; line: Line
|
||||
const { line, cmd } = this.props;
|
||||
const hidePrompt = getIsHidePrompt(line);
|
||||
return (
|
||||
<div key="header" className={cn("line-header", { "hide-prompt": hidePrompt })}>
|
||||
<div key="header" className={clsx("line-header", { "hide-prompt": hidePrompt })}>
|
||||
{this.renderMeta1(cmd)}
|
||||
<If condition={!hidePrompt}>{this.renderCmdText(cmd)}</If>
|
||||
</div>
|
||||
@ -349,7 +349,7 @@ class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRigh
|
||||
return (
|
||||
<>
|
||||
<div className="linenum">{lineNumStr}</div>
|
||||
<div title={iconTitle} className={cn("status-icon", "status-" + status)}>
|
||||
<div title={iconTitle} className={clsx("status-icon", "status-" + status)}>
|
||||
{icon}
|
||||
</div>
|
||||
</>
|
||||
@ -567,7 +567,7 @@ class LineCmd extends React.Component<
|
||||
const { screen, line, width } = this.props;
|
||||
contentHeight = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
|
||||
}
|
||||
const mainDivCn = cn("line", "line-cmd");
|
||||
const mainDivCn = clsx("line", "line-cmd");
|
||||
if (DebugHeightProblems && line.linenum >= MinLine && line.linenum <= MaxLine) {
|
||||
heightLog[line.linenum] = heightLog[line.linenum] || {};
|
||||
heightLog[line.linenum].contentHeight = contentHeight;
|
||||
@ -582,7 +582,7 @@ class LineCmd extends React.Component<
|
||||
>
|
||||
<LineHeader screen={screen} line={line} cmd={cmd} />
|
||||
<div
|
||||
className={cn("line-content", { "zero-height": contentHeight == 0 })}
|
||||
className={clsx("line-content", { "zero-height": contentHeight == 0 })}
|
||||
style={{ height: contentHeight }}
|
||||
/>
|
||||
</div>
|
||||
@ -801,7 +801,7 @@ class LineCmd extends React.Component<
|
||||
.get();
|
||||
const isRunning = cmd.isRunning();
|
||||
const cmdError = cmdShouldMarkError(cmd);
|
||||
const mainDivCn = cn(
|
||||
const mainDivCn = clsx(
|
||||
"line",
|
||||
"line-cmd",
|
||||
{ selected: isSelected },
|
||||
@ -830,7 +830,7 @@ class LineCmd extends React.Component<
|
||||
onContextMenu={this.handleContextMenu}
|
||||
>
|
||||
<If condition={isSelected || cmdError}>
|
||||
<div key="mask" className={cn("line-mask", { "error-mask": cmdError })}></div>
|
||||
<div key="mask" className={clsx("line-mask", { "error-mask": cmdError })}></div>
|
||||
</If>
|
||||
<LineActions screen={screen} line={line} cmd={cmd} />
|
||||
<LineHeader screen={screen} line={line} cmd={cmd} />
|
||||
@ -971,7 +971,7 @@ class LineText extends React.Component<
|
||||
name: "computed-isSelected",
|
||||
})
|
||||
.get();
|
||||
const mainClass = cn("line", "line-text", "focus-parent", { selected: isSelected });
|
||||
const mainClass = clsx("line", "line-text", "focus-parent", { selected: isSelected });
|
||||
return (
|
||||
<div
|
||||
className={mainClass}
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
@ -496,7 +496,7 @@ class LinesView extends React.Component<
|
||||
// let lineElem = <Line key={line.lineid} line={line} screen={screen} width={width} visible={this.visibleMap.get(lineNumStr)} staticRender={this.staticRender.get()} onHeightChange={this.onHeightChange} overrideCollapsed={this.collapsedMap.get(lineNumStr)} topBorder={topBorder} renderMode={renderMode}/>;
|
||||
lineElements.push(lineElem);
|
||||
}
|
||||
let linesClass = cn("lines", renderMode == "normal" ? "lines-expanded" : "lines-collapsed", "wide-scrollbar");
|
||||
let linesClass = clsx("lines", renderMode == "normal" ? "lines-expanded" : "lines-collapsed", "wide-scrollbar");
|
||||
return (
|
||||
<div key="lines" className={linesClass} onScroll={this.scrollHandler} ref={this.linesRef}>
|
||||
<div className="lines-spacer"></div>
|
||||
|
@ -165,6 +165,11 @@ class AIChat extends React.Component<{}, {}> {
|
||||
chatWindowRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
termFontSize: number = 14;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
mobx.makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
|
||||
@ -223,8 +228,7 @@ class AIChat extends React.Component<{}, {}> {
|
||||
return { numLines, linePos };
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
@boundMethod
|
||||
@mobx.action.bound
|
||||
onTextAreaFocused(e: any) {
|
||||
GlobalModel.inputModel.setAuxViewFocus(true);
|
||||
GlobalModel.inputModel.setActiveAuxView(appconst.InputAuxView_AIChat);
|
||||
@ -299,8 +303,7 @@ class AIChat extends React.Component<{}, {}> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
@boundMethod
|
||||
@mobx.action.bound
|
||||
onKeyDown(e: any) {}
|
||||
|
||||
renderError(err: string): any {
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
|
||||
@ -38,7 +38,7 @@ class SideBarItem extends React.Component<{
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cn("item", "unselectable", "hoverEffect", this.props.className)}
|
||||
className={clsx("item", "unselectable", "hoverEffect", this.props.className)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<FrontIcon>{this.props.frontIcon}</FrontIcon>
|
||||
@ -201,7 +201,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
return (
|
||||
<SideBarItem
|
||||
key={session.sessionId}
|
||||
className={cn({ bold: isActive, highlight: showHighlight })}
|
||||
className={clsx({ bold: isActive, highlight: showHighlight })}
|
||||
frontIcon={<span className="index">{index + 1}</span>}
|
||||
contents={session.name.get()}
|
||||
endIcons={[
|
||||
@ -269,7 +269,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
<SideBarItem
|
||||
key="history"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-clock-rotate-left icon" />}
|
||||
className={cn({ highlight: historyActive })}
|
||||
className={clsx({ highlight: historyActive })}
|
||||
contents="History"
|
||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="H" />]}
|
||||
onClick={this.handleHistoryClick}
|
||||
@ -278,7 +278,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
<SideBarItem
|
||||
key="connections"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-globe icon " />}
|
||||
className={cn({ highlight: connectionsActive })}
|
||||
className={clsx({ highlight: connectionsActive })}
|
||||
contents="Connections"
|
||||
onClick={this.handleConnectionsClick}
|
||||
/>
|
||||
@ -325,7 +325,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
<SideBarItem
|
||||
key="settings"
|
||||
frontIcon={<SettingsIcon className="icon" />}
|
||||
className={cn({ highlight: settingsActive })}
|
||||
className={clsx({ highlight: settingsActive })}
|
||||
contents="Settings"
|
||||
onClick={this.handleSettingsClick}
|
||||
/>
|
||||
|
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { Choose, If, Otherwise, When } from "tsx-control-statements/components";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
@ -24,7 +24,7 @@ export const AuxiliaryCmdView: React.FC<AuxiliaryCmdViewProps> = observer((props
|
||||
const { title, className, iconClass, titleBarContents, children, onClose, onScrollbarInitialized } = props;
|
||||
|
||||
return (
|
||||
<div className={cn("auxview", className)}>
|
||||
<div className={clsx("auxview", className)}>
|
||||
<If condition={title || onClose || titleBarContents || iconClass}>
|
||||
<div className="auxview-titlebar">
|
||||
<If condition={iconClass != null}>
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { Choose, If, When } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
|
||||
@ -86,9 +86,9 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
e.stopPropagation();
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (inputModel.getActiveAuxView() === appconst.InputAuxView_AIChat) {
|
||||
// inputModel.closeAuxView();
|
||||
inputModel.closeAuxView();
|
||||
} else {
|
||||
// inputModel.openAIAssistantChat();
|
||||
inputModel.openAIAssistantChat();
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,16 +185,16 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={this.cmdInputRef} className={cn("cmd-input", hasOpenView, { active: focusVal })}>
|
||||
<div ref={this.cmdInputRef} className={clsx("cmd-input", hasOpenView, { active: focusVal })}>
|
||||
<Choose>
|
||||
<When condition={openView === appconst.InputAuxView_History}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<HistoryInfo />
|
||||
</When>
|
||||
{/* <When condition={openView === appconst.InputAuxView_AIChat}>
|
||||
<When condition={openView === appconst.InputAuxView_AIChat}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<AIChat />
|
||||
</When> */}
|
||||
</When>
|
||||
<When condition={openView === appconst.InputAuxView_Info}>
|
||||
<InfoMsg key="infomsg" />
|
||||
</When>
|
||||
@ -239,7 +239,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
<If condition={numRunningLines > 0}>
|
||||
<div
|
||||
key="running"
|
||||
className={cn("cmdinput-icon", "running-cmds", { active: filterRunning })}
|
||||
className={clsx("cmdinput-icon", "running-cmds", { active: filterRunning })}
|
||||
title="Filter for Running Commands"
|
||||
onClick={() => this.toggleFilter(screen)}
|
||||
>
|
||||
@ -277,7 +277,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
</If>
|
||||
<div
|
||||
key="input"
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"cmd-input-field field has-addons",
|
||||
inputMode != null ? "inputmode-" + inputMode : null
|
||||
)}
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
@ -132,7 +132,7 @@ class HItem extends React.Component<
|
||||
return (
|
||||
<div
|
||||
key={hitem.historynum}
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"history-item",
|
||||
{ "is-selected": isSelected },
|
||||
{ "history-haderror": hitem.haderror },
|
||||
@ -256,7 +256,7 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
onScrollbarInitialized={this.handleScrollbarInitialized}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"history-items",
|
||||
{ "show-remotes": !opts.limitRemote },
|
||||
{ "show-sessions": opts.queryType == "global" }
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
@ -64,7 +64,11 @@ class InfoMsg extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
return (
|
||||
<AuxiliaryCmdView title={titleStr} className="cmd-input-info">
|
||||
<AuxiliaryCmdView
|
||||
title={titleStr}
|
||||
className="cmd-input-info"
|
||||
onClose={() => GlobalModel.inputModel.closeAuxView()}
|
||||
>
|
||||
<If condition={infoMsg?.infomsg}>
|
||||
<div key="infomsg" className="info-msg">
|
||||
<If condition={infoMsg.infomsghtml}>
|
||||
@ -86,7 +90,7 @@ class InfoMsg extends React.Component<{}, {}> {
|
||||
<div
|
||||
onClick={() => this.handleCompClick(istr)}
|
||||
key={idx}
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"info-comp",
|
||||
{ "has-space": this.hasSpace(istr) },
|
||||
{ "metacmd-comp": istr.startsWith("^") }
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import * as util from "@/util/util";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
|
||||
import { getMonoFontSize } from "@/util/textmeasure";
|
||||
import * as appconst from "@/app/appconst";
|
||||
@ -617,8 +617,9 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const renderCmdInputKeybindings = inputModel.shouldRenderAuxViewKeybindings(null);
|
||||
const renderCmdInputKeybindings =
|
||||
inputModel.shouldRenderAuxViewKeybindings(null) ||
|
||||
inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_Info);
|
||||
const renderHistoryKeybindings = inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_History);
|
||||
console.log("renderCmdInputKeybindings", renderCmdInputKeybindings);
|
||||
console.log("renderHistoryKeybindings", renderHistoryKeybindings);
|
||||
@ -654,7 +655,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
onSelect={this.onSelect}
|
||||
placeholder="Type here..."
|
||||
maxLength={MaxInputLength}
|
||||
className={cn("textarea", { "display-disabled": auxViewFocused })}
|
||||
className={clsx("textarea", { "display-disabled": auxViewFocused })}
|
||||
></textarea>
|
||||
<input
|
||||
key="history"
|
||||
|
@ -3,7 +3,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalCommandRunner, GlobalModel, Screen } from "@/models";
|
||||
import { TextField, Dropdown } from "@/elements";
|
||||
import { getRemoteStrWithAlias } from "@/common/prompt/prompt";
|
||||
@ -183,7 +183,7 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
|
||||
startDecoration: (
|
||||
<div className="lefticon">
|
||||
<GlobeIcon className="globe-icon" />
|
||||
<StatusCircleIcon className={cn("status-icon", "status-" + curRemote.status)} />
|
||||
<StatusCircleIcon className={clsx("status-icon", "status-" + curRemote.status)} />
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import dayjs from "dayjs";
|
||||
import { GlobalCommandRunner, ForwardLineContainer, GlobalModel, ScreenLines, Screen, Session } from "@/models";
|
||||
@ -116,7 +116,7 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
|
||||
<div className="screen-view" ref={this.screenViewRef}>
|
||||
<div className="window-view" style={{ width: "100%" }}>
|
||||
<div key="lines" className="lines"></div>
|
||||
<div key="window-empty" className={cn("window-empty")}>
|
||||
<div key="window-empty" className={clsx("window-empty")}>
|
||||
<div className="flex-centered-column">
|
||||
<code className="text-standard">[no workspace]</code>
|
||||
<If condition={sessionCount == 0}>
|
||||
@ -136,7 +136,7 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
|
||||
<div className="screen-view" ref={this.screenViewRef}>
|
||||
<div className="window-view" style={{ width: "100%" }}>
|
||||
<div key="lines" className="lines"></div>
|
||||
<div key="window-empty" className={cn("window-empty")}>
|
||||
<div key="window-empty" className={clsx("window-empty")}>
|
||||
<div className="flex-centered-column">
|
||||
<code className="text-standard">[no active tab]</code>
|
||||
<If condition={screens.length == 0}>
|
||||
@ -479,7 +479,7 @@ class ScreenWindowView extends React.Component<ScreenWindowViewProps, {}> {
|
||||
return (
|
||||
<div className="window-view" ref={this.windowViewRef} data-screenid={screen.screenId} style={{ width }}>
|
||||
<div key="lines" className="lines"></div>
|
||||
<div key="window-empty" className={cn("window-empty", { "should-fade": fade })}>
|
||||
<div key="window-empty" className={clsx("window-empty", { "should-fade": fade })}>
|
||||
<div className="text-standard">{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -559,7 +559,7 @@ class ScreenWindowView extends React.Component<ScreenWindowViewProps, {}> {
|
||||
<If condition={lines.length == 0 && screen.nextLineNum.get() != 1}>
|
||||
<div className="window-empty" ref={this.windowViewRef} data-screenid={screen.screenId}>
|
||||
<div key="lines" className="lines"></div>
|
||||
<div key="window-empty" className={cn("window-empty")}>
|
||||
<div key="window-empty" className={clsx("window-empty")}>
|
||||
<div>
|
||||
<code className="text-standard">
|
||||
[workspace="{session.name.get()}" tab="{screen.name.get()}"]
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
|
||||
import { ActionsIcon, StatusIndicator, CenteredIcon } from "@/common/icons/icons";
|
||||
import * as constants from "@/app/appconst";
|
||||
@ -131,7 +131,7 @@ class ScreenTab extends React.Component<
|
||||
value={screen}
|
||||
id={"screentab-" + screen.screenId}
|
||||
data-screenid={screen.screenId}
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"screen-tab",
|
||||
{ "is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get() },
|
||||
"color-" + screen.getTabColor()
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { For, If } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { GlobalModel, GlobalCommandRunner, Session, Screen } from "@/models";
|
||||
import { ReactComponent as AddIcon } from "@/assets/icons/add.svg";
|
||||
import { Reorder } from "framer-motion";
|
||||
|
@ -4,7 +4,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
@ -234,7 +234,7 @@ class WorkspaceView extends React.Component<{}, {}> {
|
||||
return (
|
||||
<div
|
||||
ref={this.sessionRef}
|
||||
className={cn("mainview", "session-view", { "is-hidden": isHidden })}
|
||||
className={clsx("mainview", "session-view", { "is-hidden": isHidden })}
|
||||
id={sessionId}
|
||||
data-sessionid={sessionId}
|
||||
style={{
|
||||
@ -246,7 +246,7 @@ class WorkspaceView extends React.Component<{}, {}> {
|
||||
</If>
|
||||
<ScreenTabs key={"tabs-" + sessionId} session={session} />
|
||||
<If condition={activeScreen != null}>
|
||||
<div key="pulldown" className={cn("tab-settings-pulldown", { closed: !showTabSettings })}>
|
||||
<div key="pulldown" className={clsx("tab-settings-pulldown", { closed: !showTabSettings })}>
|
||||
<Button className="close-button secondary ghost" onClick={this.toggleTabSettings}>
|
||||
<i className="fa-solid fa-sharp fa-xmark-large" />
|
||||
</Button>
|
||||
|
@ -14,7 +14,7 @@ import * as waveutil from "../util/util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { handleJsonFetchResponse, fireAndForget } from "@/util/util";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { checkKeyPressed, adaptFromElectronKeyEvent, setKeyUtilPlatform } from "@/util/keyutil";
|
||||
import { adaptFromElectronKeyEvent, setKeyUtilPlatform } from "@/util/keyutil";
|
||||
import { platform } from "os";
|
||||
|
||||
const WaveAppPathVarName = "WAVETERM_APP_PATH";
|
||||
@ -22,7 +22,6 @@ const WaveDevVarName = "WAVETERM_DEV";
|
||||
const AuthKeyFile = "waveterm.authkey";
|
||||
const DevServerEndpoint = "http://127.0.0.1:8090";
|
||||
const ProdServerEndpoint = "http://127.0.0.1:1619";
|
||||
const startTs = Date.now();
|
||||
|
||||
const isDev = process.env[WaveDevVarName] != null;
|
||||
const waveHome = getWaveHomeDir();
|
||||
@ -35,7 +34,6 @@ let wasActive = true;
|
||||
let wasInFg = true;
|
||||
let currentGlobalShortcut: string | null = null;
|
||||
let initialClientData: ClientDataType = null;
|
||||
let MainWindow: Electron.BrowserWindow | null = null;
|
||||
|
||||
checkPromptMigrate();
|
||||
ensureDir(waveHome);
|
||||
@ -201,14 +199,15 @@ function readAuthKey(): string {
|
||||
}
|
||||
const reloadAcceleratorKey = unamePlatform == "darwin" ? "Option+R" : "Super+R";
|
||||
const cmdOrAlt = process.platform === "darwin" ? "Cmd" : "Alt";
|
||||
|
||||
let viewSubMenu: Electron.MenuItemConstructorOptions[] = [];
|
||||
viewSubMenu.push({ role: "reload", accelerator: reloadAcceleratorKey });
|
||||
viewSubMenu.push({ role: "toggleDevTools" });
|
||||
if (isDev) {
|
||||
viewSubMenu.push({
|
||||
label: "Toggle Dev UI",
|
||||
click: () => {
|
||||
MainWindow?.webContents.send("toggle-devui");
|
||||
click: (_, window) => {
|
||||
window?.webContents.send("toggle-devui");
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -216,36 +215,33 @@ viewSubMenu.push({ type: "separator" });
|
||||
viewSubMenu.push({
|
||||
label: "Actual Size",
|
||||
accelerator: cmdOrAlt + "+0",
|
||||
click: () => {
|
||||
if (MainWindow == null) {
|
||||
return;
|
||||
}
|
||||
MainWindow.webContents.setZoomFactor(1);
|
||||
MainWindow.webContents.send("zoom-changed");
|
||||
click: (_, window) => {
|
||||
window?.webContents.setZoomFactor(1);
|
||||
window?.webContents.send("zoom-changed");
|
||||
},
|
||||
});
|
||||
viewSubMenu.push({
|
||||
label: "Zoom In",
|
||||
accelerator: cmdOrAlt + "+Plus",
|
||||
click: () => {
|
||||
if (MainWindow == null) {
|
||||
click: (_, window) => {
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
const zoomFactor = MainWindow.webContents.getZoomFactor();
|
||||
MainWindow.webContents.setZoomFactor(zoomFactor * 1.1);
|
||||
MainWindow.webContents.send("zoom-changed");
|
||||
const zoomFactor = window.webContents.getZoomFactor();
|
||||
window.webContents.setZoomFactor(zoomFactor * 1.1);
|
||||
window.webContents.send("zoom-changed");
|
||||
},
|
||||
});
|
||||
viewSubMenu.push({
|
||||
label: "Zoom Out",
|
||||
accelerator: cmdOrAlt + "+-",
|
||||
click: () => {
|
||||
if (MainWindow == null) {
|
||||
click: (_, window) => {
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
const zoomFactor = MainWindow.webContents.getZoomFactor();
|
||||
MainWindow.webContents.setZoomFactor(zoomFactor / 1.1);
|
||||
MainWindow.webContents.send("zoom-changed");
|
||||
const zoomFactor = window.webContents.getZoomFactor();
|
||||
window.webContents.setZoomFactor(zoomFactor / 1.1);
|
||||
window.webContents.send("zoom-changed");
|
||||
},
|
||||
});
|
||||
viewSubMenu.push({ type: "separator" });
|
||||
@ -256,8 +252,8 @@ const menuTemplate: Electron.MenuItemConstructorOptions[] = [
|
||||
submenu: [
|
||||
{
|
||||
label: "About Wave Terminal",
|
||||
click: () => {
|
||||
MainWindow?.webContents.send("menu-item-about");
|
||||
click: (_, window) => {
|
||||
window?.webContents.send("menu-item-about");
|
||||
},
|
||||
},
|
||||
{ type: "separator" },
|
||||
@ -326,7 +322,7 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
|
||||
console.log("frame navigation canceled");
|
||||
}
|
||||
|
||||
function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWindow {
|
||||
function createWindow(clientData: ClientDataType | null): Electron.BrowserWindow {
|
||||
const bounds = calcBounds(clientData);
|
||||
setKeyUtilPlatform(platform());
|
||||
const win = new electron.BrowserWindow({
|
||||
@ -374,9 +370,6 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
|
||||
wasInFg = true;
|
||||
wasActive = true;
|
||||
});
|
||||
win.on("close", () => {
|
||||
MainWindow = null;
|
||||
});
|
||||
win.webContents.on("zoom-changed", (e) => {
|
||||
win.webContents.send("zoom-changed");
|
||||
});
|
||||
@ -400,7 +393,6 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
|
||||
console.log("window-open denied", url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
@ -475,8 +467,9 @@ app.on("window-all-closed", () => {
|
||||
});
|
||||
|
||||
electron.ipcMain.on("toggle-developer-tools", (event) => {
|
||||
if (MainWindow != null) {
|
||||
MainWindow.webContents.toggleDevTools();
|
||||
const window = getWindowForEvent(event);
|
||||
if (window != null) {
|
||||
window.webContents.toggleDevTools();
|
||||
}
|
||||
event.returnValue = true;
|
||||
});
|
||||
@ -488,8 +481,8 @@ function convertMenuDefArrToMenu(menuDefArr: ElectronContextMenuItem[]): electro
|
||||
role: menuDef.role as any,
|
||||
label: menuDef.label,
|
||||
type: menuDef.type,
|
||||
click: () => {
|
||||
MainWindow?.webContents.send("contextmenu-click", menuDef.id);
|
||||
click: (_, window) => {
|
||||
window?.webContents.send("contextmenu-click", menuDef.id);
|
||||
},
|
||||
};
|
||||
if (menuDef.submenu != null) {
|
||||
@ -501,6 +494,11 @@ function convertMenuDefArrToMenu(menuDefArr: ElectronContextMenuItem[]): electro
|
||||
return electron.Menu.buildFromTemplate(menuItems);
|
||||
}
|
||||
|
||||
function getWindowForEvent(event: Electron.IpcMainEvent): Electron.BrowserWindow {
|
||||
const windowId = event.sender.id;
|
||||
return electron.BrowserWindow.fromId(windowId);
|
||||
}
|
||||
|
||||
electron.ipcMain.on("contextmenu-show", (event, menuDefArr: ElectronContextMenuItem[], { x, y }) => {
|
||||
if (menuDefArr == null || menuDefArr.length == 0) {
|
||||
return;
|
||||
@ -511,8 +509,9 @@ electron.ipcMain.on("contextmenu-show", (event, menuDefArr: ElectronContextMenuI
|
||||
});
|
||||
|
||||
electron.ipcMain.on("hide-window", (event) => {
|
||||
if (MainWindow != null) {
|
||||
MainWindow.hide();
|
||||
const window = getWindowForEvent(event);
|
||||
if (window) {
|
||||
window.hide();
|
||||
}
|
||||
event.returnValue = true;
|
||||
});
|
||||
@ -553,8 +552,9 @@ electron.ipcMain.on("restart-server", (event) => {
|
||||
});
|
||||
|
||||
electron.ipcMain.on("reload-window", (event) => {
|
||||
if (MainWindow != null) {
|
||||
MainWindow.reload();
|
||||
const window = getWindowForEvent(event);
|
||||
if (window) {
|
||||
window.reload();
|
||||
}
|
||||
event.returnValue = true;
|
||||
});
|
||||
@ -593,9 +593,9 @@ electron.ipcMain.on("set-nativethemesource", (event, themeSource: "system" | "li
|
||||
});
|
||||
|
||||
electron.nativeTheme.on("updated", () => {
|
||||
if (MainWindow != null) {
|
||||
MainWindow.webContents.send("nativetheme-updated");
|
||||
}
|
||||
electron.BrowserWindow.getAllWindows().forEach((win) => {
|
||||
win.webContents.send("nativetheme-updated");
|
||||
});
|
||||
});
|
||||
|
||||
function readLastLinesOfFile(filePath: string, lineCount: number) {
|
||||
@ -659,13 +659,13 @@ async function getClientData(willRetry: boolean, retryNum: number): Promise<Clie
|
||||
}
|
||||
|
||||
function sendWSSC() {
|
||||
if (MainWindow != null) {
|
||||
electron.BrowserWindow.getAllWindows().forEach((win) => {
|
||||
if (waveSrvProc == null) {
|
||||
MainWindow.webContents.send("wavesrv-status-change", false);
|
||||
return;
|
||||
win.webContents.send("wavesrv-status-change", false);
|
||||
} else {
|
||||
win.webContents.send("wavesrv-status-change", true, waveSrvProc.pid);
|
||||
}
|
||||
MainWindow.webContents.send("wavesrv-status-change", true, waveSrvProc.pid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function runWaveSrv() {
|
||||
@ -733,7 +733,7 @@ electron.ipcMain.on("context-editmenu", (_, { x, y }, opts) => {
|
||||
menu.popup({ x, y });
|
||||
});
|
||||
|
||||
async function createMainWindowWrap() {
|
||||
async function createWindowWrap() {
|
||||
let clientData: ClientDataType | null = null;
|
||||
try {
|
||||
clientData = await getClientDataPoll(1);
|
||||
@ -741,9 +741,9 @@ async function createMainWindowWrap() {
|
||||
} catch (e) {
|
||||
console.log("error getting wavesrv clientdata", e.toString());
|
||||
}
|
||||
MainWindow = createMainWindow(clientData);
|
||||
if (clientData && clientData.winsize.fullscreen) {
|
||||
MainWindow.setFullScreen(true);
|
||||
const win = createWindow(clientData);
|
||||
if (clientData?.winsize.fullscreen) {
|
||||
win.setFullScreen(true);
|
||||
}
|
||||
configureAutoUpdaterStartup(clientData);
|
||||
}
|
||||
@ -762,7 +762,7 @@ function logActiveState() {
|
||||
console.log("error logging active state", err);
|
||||
});
|
||||
// for next iteration
|
||||
wasInFg = MainWindow != null && MainWindow.isFocused();
|
||||
wasInFg = electron.BrowserWindow.getFocusedWindow()?.isFocused() ?? false;
|
||||
wasActive = false;
|
||||
}
|
||||
|
||||
@ -788,9 +788,13 @@ function reregisterGlobalShortcut(shortcut: string) {
|
||||
currentGlobalShortcut = null;
|
||||
return;
|
||||
}
|
||||
const ok = electron.globalShortcut.register(shortcut, () => {
|
||||
const ok = electron.globalShortcut.register(shortcut, async () => {
|
||||
console.log("global shortcut triggered, showing window");
|
||||
MainWindow?.show();
|
||||
if (electron.BrowserWindow.getAllWindows().length == 0) {
|
||||
await createWindowWrap();
|
||||
}
|
||||
const winToShow = electron.BrowserWindow.getFocusedWindow() ?? electron.BrowserWindow.getAllWindows()[0];
|
||||
winToShow?.show();
|
||||
});
|
||||
console.log("registered global shortcut", shortcut, ok ? "ok" : "failed");
|
||||
if (!ok) {
|
||||
@ -802,10 +806,12 @@ function reregisterGlobalShortcut(shortcut: string) {
|
||||
|
||||
// ====== AUTO-UPDATER ====== //
|
||||
let autoUpdateLock = false;
|
||||
let autoUpdateEnabled = false;
|
||||
let autoUpdateInterval: NodeJS.Timeout | null = null;
|
||||
let availableUpdateReleaseName: string | null = null;
|
||||
let availableUpdateReleaseNotes: string | null = null;
|
||||
let appUpdateStatus = "unavailable";
|
||||
let lastUpdateCheck: Date = null;
|
||||
|
||||
/**
|
||||
* Sets the app update status and sends it to the main window
|
||||
@ -813,8 +819,22 @@ let appUpdateStatus = "unavailable";
|
||||
*/
|
||||
function setAppUpdateStatus(status: string) {
|
||||
appUpdateStatus = status;
|
||||
if (MainWindow != null) {
|
||||
MainWindow.webContents.send("app-update-status", appUpdateStatus);
|
||||
electron.BrowserWindow.getAllWindows().forEach((window) => {
|
||||
window.webContents.send("app-update-status", appUpdateStatus);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an hour has passed since the last update check, and if so, checks for updates using the `autoUpdater` object
|
||||
*/
|
||||
function checkForUpdates() {
|
||||
if (!autoUpdateEnabled) {
|
||||
return;
|
||||
}
|
||||
const now = new Date();
|
||||
if (!lastUpdateCheck || Math.abs(now.getTime() - lastUpdateCheck.getTime()) > 3600000) {
|
||||
fireAndForget(() => autoUpdater.checkForUpdates());
|
||||
lastUpdateCheck = now;
|
||||
}
|
||||
}
|
||||
|
||||
@ -861,14 +881,16 @@ function initUpdater(): NodeJS.Timeout {
|
||||
body: "A new version of Wave Terminal is ready to install.",
|
||||
});
|
||||
updateNotification.on("click", () => {
|
||||
fireAndForget(installAppUpdate);
|
||||
fireAndForget(() => installAppUpdate());
|
||||
});
|
||||
updateNotification.show();
|
||||
});
|
||||
|
||||
// check for updates right away and keep checking later
|
||||
autoUpdater.checkForUpdates();
|
||||
return setInterval(() => fireAndForget(autoUpdater.checkForUpdates), 3600000); // 1 hour in ms
|
||||
checkForUpdates();
|
||||
return setInterval(() => {
|
||||
checkForUpdates();
|
||||
}, 600000); // intervals are unreliable when an app is suspended so we will check every 10 mins if an hour has passed.
|
||||
}
|
||||
|
||||
/**
|
||||
@ -883,12 +905,17 @@ async function installAppUpdate() {
|
||||
detail: "A new version has been downloaded. Restart the application to apply the updates.",
|
||||
};
|
||||
|
||||
await electron.dialog.showMessageBox(MainWindow, dialogOpts).then(({ response }) => {
|
||||
if (response === 0) autoUpdater.quitAndInstall();
|
||||
});
|
||||
const allWindows = electron.BrowserWindow.getAllWindows();
|
||||
if (allWindows.length > 0) {
|
||||
await electron.dialog
|
||||
.showMessageBox(electron.BrowserWindow.getFocusedWindow() ?? allWindows[0], dialogOpts)
|
||||
.then(({ response }) => {
|
||||
if (response === 0) autoUpdater.quitAndInstall();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
electron.ipcMain.on("install-app-update", () => fireAndForget(installAppUpdate));
|
||||
electron.ipcMain.on("install-app-update", () => fireAndForget(() => installAppUpdate()));
|
||||
electron.ipcMain.on("get-app-update-status", (event) => {
|
||||
event.returnValue = appUpdateStatus;
|
||||
});
|
||||
@ -919,15 +946,22 @@ function configureAutoUpdater(enabled: boolean) {
|
||||
console.log("auto-update configuration already in progress, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
autoUpdateEnabled = enabled;
|
||||
autoUpdateLock = true;
|
||||
|
||||
if (enabled && autoUpdateInterval == null) {
|
||||
if (autoUpdateEnabled && autoUpdateInterval == null) {
|
||||
lastUpdateCheck = null;
|
||||
try {
|
||||
console.log("configuring auto updater");
|
||||
autoUpdateInterval = initUpdater();
|
||||
} catch (e) {
|
||||
console.log("error configuring auto updater", e.toString());
|
||||
}
|
||||
} else if (!autoUpdateEnabled && autoUpdateInterval != null) {
|
||||
console.log("disabling auto updater");
|
||||
clearInterval(autoUpdateInterval);
|
||||
autoUpdateInterval = null;
|
||||
}
|
||||
autoUpdateLock = false;
|
||||
}
|
||||
@ -950,11 +984,13 @@ function configureAutoUpdater(enabled: boolean) {
|
||||
}
|
||||
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
|
||||
await app.whenReady();
|
||||
await createMainWindowWrap();
|
||||
await createWindowWrap();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
||||
createMainWindowWrap().then();
|
||||
createWindowWrap().then();
|
||||
}
|
||||
checkForUpdates();
|
||||
});
|
||||
})();
|
||||
|
||||
|
@ -6,7 +6,7 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import Editor, { Monaco } from "@monaco-editor/react";
|
||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { Markdown, Button } from "@/elements";
|
||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||
@ -579,7 +579,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
<div className="flex-spacer" />
|
||||
<div className="code-statusbar">
|
||||
<If condition={message != null}>
|
||||
<div className={cn("code-message", { error: message.status == "error" })}>
|
||||
<div className={clsx("code-message", { error: message.status == "error" })}>
|
||||
{this.state.message.text}
|
||||
</div>
|
||||
</If>
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import * as util from "@/util/util";
|
||||
import { GlobalModel } from "@/models";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
class SimpleBlobRendererModel {
|
||||
context: RendererContext;
|
||||
@ -247,7 +247,7 @@ class SimpleBlobRenderer extends React.Component<
|
||||
return (
|
||||
<div
|
||||
ref={this.wrapperDivRef}
|
||||
className={cn("renderer-loading", { "zero-height": height == 0 })}
|
||||
className={clsx("renderer-loading", { "zero-height": height == 0 })}
|
||||
style={{ minHeight: height, fontSize: model.opts.termFontSize }}
|
||||
>
|
||||
loading content <i className="fa fa-ellipsis fa-fade" />
|
||||
@ -260,7 +260,7 @@ class SimpleBlobRenderer extends React.Component<
|
||||
}
|
||||
let { festate, cmdstr, exitcode } = this.props.initParams.rawCmd;
|
||||
return (
|
||||
<div ref={this.wrapperDivRef} className={cn("sr-wrapper", { "zero-height": model.savedHeight == 0 })}>
|
||||
<div ref={this.wrapperDivRef} className={clsx("sr-wrapper", { "zero-height": model.savedHeight == 0 })}>
|
||||
<Comp
|
||||
cwd={festate.cwd}
|
||||
cmdstr={cmdstr}
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
import { useTableNav } from "@table-nav/react";
|
||||
import SortUpIcon from "./img/sort-up-solid.svg";
|
||||
import SortDownIcon from "./img/sort-down-solid.svg";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import "./csv.less";
|
||||
|
||||
@ -190,7 +190,7 @@ const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("csv-renderer", { show: tableLoaded })}
|
||||
className={clsx("csv-renderer", { show: tableLoaded })}
|
||||
style={{ height: tableLoaded ? "auto" : savedHeight }}
|
||||
>
|
||||
<table className="probe">
|
||||
|
@ -10,7 +10,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { termHeightFromRows } from "@/util/textmeasure";
|
||||
import cn from "classnames";
|
||||
import { clsx } from "clsx";
|
||||
import * as lineutil from "@/app/line/lineutil";
|
||||
|
||||
import "./terminal.less";
|
||||
@ -207,7 +207,7 @@ class TerminalRenderer extends React.Component<{
|
||||
<div
|
||||
ref={this.elemRef}
|
||||
key="term-wrap"
|
||||
className={cn(
|
||||
className={clsx(
|
||||
"terminal-wrapper",
|
||||
{ focus: isFocused },
|
||||
{ "cmd-done": !cmd.isRunning() },
|
||||
|
4
src/types/custom.d.ts
vendored
4
src/types/custom.d.ts
vendored
@ -121,8 +121,8 @@ declare global {
|
||||
sshconfigsrc: string;
|
||||
archived: boolean;
|
||||
uname: string;
|
||||
mshellversion: string;
|
||||
needsmshellupgrade: boolean;
|
||||
waveshellversion: string;
|
||||
needswaveshellupgrade: boolean;
|
||||
noinitpk: boolean;
|
||||
authtype: string;
|
||||
waitingforpassword: boolean;
|
||||
|
@ -28,7 +28,7 @@ func readFullRunPacket(packetParser *packet.PacketParser) (*packet.RunPacketType
|
||||
return runPacket, nil
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid packet '%s' sent to mshell", pk.GetType())
|
||||
return nil, fmt.Errorf("invalid packet '%s' sent to waveshell", pk.GetType())
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no run packet received")
|
||||
@ -97,7 +97,7 @@ func handleSingle() {
|
||||
|
||||
func handleUsage() {
|
||||
usage := `
|
||||
mshell is a helper program for wave terminal. it is used to execute commands
|
||||
waveshell is a helper program for wave terminal. it is used to execute commands
|
||||
|
||||
Options:
|
||||
--help - prints this message
|
||||
@ -106,7 +106,7 @@ Options:
|
||||
--single - run a single command (connected to multiplexer)
|
||||
--single --version - return an init packet with version info
|
||||
|
||||
mshell does not open any external ports and does not require any additional permissions.
|
||||
waveshell does not open any external ports and does not require any additional permissions.
|
||||
it communicates exclusively through stdin/stdout with an attached process
|
||||
via a JSON packet format.
|
||||
`
|
||||
@ -124,7 +124,7 @@ func main() {
|
||||
handleUsage()
|
||||
return
|
||||
} else if firstArg == "--version" {
|
||||
fmt.Printf("mshell %s+%s\n", base.MShellVersion, base.BuildTime)
|
||||
fmt.Printf("waveshell %s+%s\n", base.WaveshellVersion, base.BuildTime)
|
||||
return
|
||||
} else if firstArg == "--single" || firstArg == "--single-from-server" {
|
||||
base.ProcessType = base.ProcessType_WaveShellSingle
|
||||
|
@ -20,18 +20,18 @@ import (
|
||||
)
|
||||
|
||||
const HomeVarName = "HOME"
|
||||
const DefaultMShellHome = "~/.mshell"
|
||||
const DefaultMShellName = "mshell"
|
||||
const MShellPathVarName = "MSHELL_PATH"
|
||||
const MShellHomeVarName = "MSHELL_HOME"
|
||||
const MShellInstallBinVarName = "MSHELL_INSTALLBIN_PATH"
|
||||
const DefaultWaveshellHome = "~/.mshell"
|
||||
const DefaultWaveshellName = "mshell"
|
||||
const WaveshellPathVarName = "MSHELL_PATH"
|
||||
const WaveshellHomeVarName = "MSHELL_HOME"
|
||||
const WaveshellInstallBinVarName = "MSHELL_INSTALLBIN_PATH"
|
||||
const SSHCommandVarName = "SSH_COMMAND"
|
||||
const MShellDebugVarName = "MSHELL_DEBUG"
|
||||
const WaveshellDebugVarName = "MSHELL_DEBUG"
|
||||
const SessionsDirBaseName = "sessions"
|
||||
const RcFilesDirBaseName = "rcfiles"
|
||||
const MShellVersion = "v0.7.0"
|
||||
const WaveshellVersion = "v0.7.0"
|
||||
const RemoteIdFile = "remoteid"
|
||||
const DefaultMShellInstallBinDir = "/opt/mshell/bin"
|
||||
const DefaultWaveshellInstallBinDir = "/opt/mshell/bin"
|
||||
const LogFileName = "mshell.log"
|
||||
const ForceDebugLog = false
|
||||
|
||||
@ -90,7 +90,7 @@ func Logf(fmtStr string, args ...interface{}) {
|
||||
}
|
||||
|
||||
func InitDebugLog(prefix string) {
|
||||
homeDir := GetMShellHomeDir()
|
||||
homeDir := GetWaveshellHomeDir()
|
||||
err := os.MkdirAll(homeDir, 0777)
|
||||
if err != nil {
|
||||
return
|
||||
@ -163,7 +163,7 @@ func (ckey CommandKey) Validate(typeStr string) error {
|
||||
}
|
||||
|
||||
func HasDebugFlag(envMap map[string]string, flagName string) bool {
|
||||
msDebug := envMap[MShellDebugVarName]
|
||||
msDebug := envMap[WaveshellDebugVarName]
|
||||
flags := strings.Split(msDebug, ",")
|
||||
for _, flag := range flags {
|
||||
if strings.TrimSpace(flag) == flagName {
|
||||
@ -174,13 +174,13 @@ func HasDebugFlag(envMap map[string]string, flagName string) bool {
|
||||
}
|
||||
|
||||
func GetDebugRcFileName() string {
|
||||
msHome := GetMShellHomeDir()
|
||||
return path.Join(msHome, DebugRcFileName)
|
||||
wsHome := GetWaveshellHomeDir()
|
||||
return path.Join(wsHome, DebugRcFileName)
|
||||
}
|
||||
|
||||
func GetDebugReturnStateFileName() string {
|
||||
msHome := GetMShellHomeDir()
|
||||
return path.Join(msHome, DebugReturnStateFileName)
|
||||
wsHome := GetWaveshellHomeDir()
|
||||
return path.Join(wsHome, DebugReturnStateFileName)
|
||||
}
|
||||
|
||||
func GetHomeDir() string {
|
||||
@ -191,16 +191,16 @@ func GetHomeDir() string {
|
||||
return homeVar
|
||||
}
|
||||
|
||||
func GetMShellHomeDir() string {
|
||||
homeVar := os.Getenv(MShellHomeVarName)
|
||||
func GetWaveshellHomeDir() string {
|
||||
homeVar := os.Getenv(WaveshellHomeVarName)
|
||||
if homeVar != "" {
|
||||
return homeVar
|
||||
}
|
||||
return ExpandHomeDir(DefaultMShellHome)
|
||||
return ExpandHomeDir(DefaultWaveshellHome)
|
||||
}
|
||||
|
||||
func EnsureRcFilesDir() (string, error) {
|
||||
mhome := GetMShellHomeDir()
|
||||
mhome := GetWaveshellHomeDir()
|
||||
dirName := path.Join(mhome, RcFilesDirBaseName)
|
||||
err := CacheEnsureDir(dirName, RcFilesDirBaseName, 0700, "rcfiles dir")
|
||||
if err != nil {
|
||||
@ -209,18 +209,18 @@ func EnsureRcFilesDir() (string, error) {
|
||||
return dirName, nil
|
||||
}
|
||||
|
||||
func GetMShellPath() (string, error) {
|
||||
msPath := os.Getenv(MShellPathVarName) // use MSHELL_PATH
|
||||
if msPath != "" {
|
||||
return exec.LookPath(msPath)
|
||||
func GetWaveshellPath() (string, error) {
|
||||
wsPath := os.Getenv(WaveshellPathVarName) // use MSHELL_PATH -- will require rename
|
||||
if wsPath != "" {
|
||||
return exec.LookPath(wsPath)
|
||||
}
|
||||
mhome := GetMShellHomeDir()
|
||||
userMShellPath := path.Join(mhome, DefaultMShellName) // look in ~/.mshell
|
||||
msPath, err := exec.LookPath(userMShellPath)
|
||||
mhome := GetWaveshellHomeDir()
|
||||
userWaveshellPath := path.Join(mhome, DefaultWaveshellName) // look in ~/.mshell -- will require rename
|
||||
wsPath, err := exec.LookPath(userWaveshellPath)
|
||||
if err == nil {
|
||||
return msPath, nil
|
||||
return wsPath, nil
|
||||
}
|
||||
return exec.LookPath(DefaultMShellName) // standard path lookup for 'mshell'
|
||||
return exec.LookPath(DefaultWaveshellName) // standard path lookup for 'mshell'-- will require rename
|
||||
}
|
||||
|
||||
func ExpandHomeDir(pathStr string) string {
|
||||
@ -239,9 +239,9 @@ func ValidGoArch(goos string, goarch string) bool {
|
||||
}
|
||||
|
||||
func GoArchOptFile(version string, goos string, goarch string) string {
|
||||
installBinDir := os.Getenv(MShellInstallBinVarName)
|
||||
installBinDir := os.Getenv(WaveshellInstallBinVarName)
|
||||
if installBinDir == "" {
|
||||
installBinDir = DefaultMShellInstallBinDir
|
||||
installBinDir = DefaultWaveshellInstallBinDir
|
||||
}
|
||||
versionStr := semver.MajorMinor(version)
|
||||
if versionStr == "" {
|
||||
@ -252,22 +252,22 @@ func GoArchOptFile(version string, goos string, goarch string) string {
|
||||
}
|
||||
|
||||
func GetRemoteId() (string, error) {
|
||||
mhome := GetMShellHomeDir()
|
||||
homeInfo, err := os.Stat(mhome)
|
||||
wsHome := GetWaveshellHomeDir()
|
||||
homeInfo, err := os.Stat(wsHome)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
err = os.MkdirAll(mhome, 0777)
|
||||
err = os.MkdirAll(wsHome, 0777)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot make mshell home directory[%s]: %w", mhome, err)
|
||||
return "", fmt.Errorf("cannot make waveshell home directory[%s]: %w", wsHome, err)
|
||||
}
|
||||
homeInfo, err = os.Stat(mhome)
|
||||
homeInfo, err = os.Stat(wsHome)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot stat mshell home directory[%s]: %w", mhome, err)
|
||||
return "", fmt.Errorf("cannot stat waveshell home directory[%s]: %w", wsHome, err)
|
||||
}
|
||||
if !homeInfo.IsDir() {
|
||||
return "", fmt.Errorf("mshell home directory[%s] is not a directory", mhome)
|
||||
return "", fmt.Errorf("waveshell home directory[%s] is not a directory", wsHome)
|
||||
}
|
||||
remoteIdFile := path.Join(mhome, RemoteIdFile)
|
||||
remoteIdFile := path.Join(wsHome, RemoteIdFile)
|
||||
fd, err := os.Open(remoteIdFile)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// write the file
|
||||
|
@ -687,18 +687,18 @@ func FmtMessagePacket(fmtStr string, args ...interface{}) *MessagePacketType {
|
||||
}
|
||||
|
||||
type InitPacketType struct {
|
||||
Type string `json:"type"`
|
||||
RespId string `json:"respid,omitempty"`
|
||||
Version string `json:"version"`
|
||||
BuildTime string `json:"buildtime,omitempty"`
|
||||
MShellHomeDir string `json:"mshellhomedir,omitempty"`
|
||||
HomeDir string `json:"homedir,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
HostName string `json:"hostname,omitempty"`
|
||||
NotFound bool `json:"notfound,omitempty"`
|
||||
UName string `json:"uname,omitempty"`
|
||||
Shell string `json:"shell,omitempty"`
|
||||
RemoteId string `json:"remoteid,omitempty"`
|
||||
Type string `json:"type"`
|
||||
RespId string `json:"respid,omitempty"`
|
||||
Version string `json:"version"`
|
||||
BuildTime string `json:"buildtime,omitempty"`
|
||||
WaveshellHomeDir string `json:"waveshellhomedir,omitempty"`
|
||||
HomeDir string `json:"homedir,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
HostName string `json:"hostname,omitempty"`
|
||||
NotFound bool `json:"notfound,omitempty"`
|
||||
UName string `json:"uname,omitempty"`
|
||||
Shell string `json:"shell,omitempty"`
|
||||
RemoteId string `json:"remoteid,omitempty"`
|
||||
}
|
||||
|
||||
func (*InitPacketType) GetType() string {
|
||||
@ -772,12 +772,12 @@ func MakeCmdDonePacket(ck base.CommandKey) *CmdDonePacketType {
|
||||
}
|
||||
|
||||
type CmdStartPacketType struct {
|
||||
Type string `json:"type"`
|
||||
RespId string `json:"respid,omitempty"`
|
||||
Ts int64 `json:"ts"`
|
||||
CK base.CommandKey `json:"ck"`
|
||||
Pid int `json:"pid,omitempty"`
|
||||
MShellPid int `json:"mshellpid,omitempty"`
|
||||
Type string `json:"type"`
|
||||
RespId string `json:"respid,omitempty"`
|
||||
Ts int64 `json:"ts"`
|
||||
CK base.CommandKey `json:"ck"`
|
||||
Pid int `json:"pid,omitempty"`
|
||||
WaveshellPid int `json:"waveshellpid,omitempty"`
|
||||
}
|
||||
|
||||
func (*CmdStartPacketType) GetType() string {
|
||||
|
@ -455,7 +455,7 @@ func (m *MServer) writeFile(pk *packet.WriteFilePacketType, wfc *WriteFileContex
|
||||
}
|
||||
var writeFd *os.File
|
||||
if pk.UseTemp {
|
||||
writeFd, err = os.CreateTemp("", "mshell.writefile.*") // "" means make this file in standard TempDir
|
||||
writeFd, err = os.CreateTemp("", "waveshell.writefile.*") // "" means make this file in standard TempDir
|
||||
if err != nil {
|
||||
resp := packet.MakeWriteFileReadyPacket(pk.ReqId)
|
||||
resp.Error = fmt.Sprintf("cannot create temp file: %v", err)
|
||||
@ -754,14 +754,14 @@ func (m *MServer) runCommand(runPacket *packet.RunPacketType) {
|
||||
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("test error"))
|
||||
return
|
||||
}
|
||||
ecmd, err := shexec.MakeMShellSingleCmd()
|
||||
ecmd, err := shexec.MakeWaveshellSingleCmd()
|
||||
if err != nil {
|
||||
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("server run packets require valid ck: %s", err))
|
||||
return
|
||||
}
|
||||
cproc, err := shexec.MakeClientProc(context.Background(), shexec.CmdWrap{Cmd: ecmd})
|
||||
if err != nil {
|
||||
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("starting mshell client: %s", err))
|
||||
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("starting waveshell client: %s", err))
|
||||
return
|
||||
}
|
||||
m.Lock.Lock()
|
||||
@ -833,7 +833,7 @@ func (server *MServer) runReadLoop() {
|
||||
}
|
||||
continue
|
||||
}
|
||||
server.Sender.SendMessageFmt("invalid packet '%s' sent to mshell server", packet.AsString(pk))
|
||||
server.Sender.SendMessageFmt("invalid packet '%s' sent to waveshell server", packet.AsString(pk))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ const FirstExtraFilesFdNum = 3
|
||||
func StreamCommandWithExtraFd(ctx context.Context, ecmd *exec.Cmd, outputCh chan []byte, extraFdNum int, endBytes []byte, stdinDataCh chan []byte) ([]byte, error) {
|
||||
defer close(outputCh)
|
||||
ecmd.Env = os.Environ()
|
||||
shellutil.UpdateCmdEnv(ecmd, shellutil.MShellEnvVars(shellutil.DefaultTermType))
|
||||
shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellEnvVars(shellutil.DefaultTermType))
|
||||
cmdPty, cmdTty, err := pty.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening new pty: %w", err)
|
||||
@ -232,7 +232,7 @@ func StreamCommandWithExtraFd(ctx context.Context, ecmd *exec.Cmd, outputCh chan
|
||||
|
||||
func RunSimpleCmdInPty(ecmd *exec.Cmd, endBytes []byte) ([]byte, error) {
|
||||
ecmd.Env = os.Environ()
|
||||
shellutil.UpdateCmdEnv(ecmd, shellutil.MShellEnvVars(shellutil.DefaultTermType))
|
||||
shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellEnvVars(shellutil.DefaultTermType))
|
||||
cmdPty, cmdTty, err := pty.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening new pty: %w", err)
|
||||
@ -311,8 +311,8 @@ func parseExtVarOutput(pvarBytes []byte, promptOutput string, zmodsOutput string
|
||||
|
||||
// for debugging (not for production use)
|
||||
func writeStateToFile(shellType string, outputBytes []byte) error {
|
||||
msHome := base.GetMShellHomeDir()
|
||||
stateFileName := path.Join(msHome, shellType+"-state.txt")
|
||||
wsHome := base.GetWaveshellHomeDir()
|
||||
stateFileName := path.Join(wsHome, shellType+"-state.txt")
|
||||
os.WriteFile(stateFileName, outputBytes, 0644)
|
||||
return nil
|
||||
}
|
||||
|
@ -531,8 +531,6 @@ for var in "${(@k)dis_functions_source}"; do
|
||||
done
|
||||
printf "[%SECTIONSEP%]";
|
||||
[%GITBRANCH%]
|
||||
[%K8SCONTEXT%]
|
||||
[%K8SNAMESPACE%]
|
||||
printf "[%SECTIONSEP%]";
|
||||
print -P "$PS1"
|
||||
printf "[%SECTIONSEP%]";
|
||||
|
@ -15,13 +15,13 @@ const DefaultTermType = "xterm-256color"
|
||||
const DefaultTermRows = 24
|
||||
const DefaultTermCols = 80
|
||||
|
||||
func MShellEnvVars(termType string) map[string]string {
|
||||
func WaveshellEnvVars(termType string) map[string]string {
|
||||
rtn := make(map[string]string)
|
||||
if termType != "" {
|
||||
rtn["TERM"] = termType
|
||||
}
|
||||
rtn["WAVESHELL"], _ = os.Executable()
|
||||
rtn["WAVESHELL_VERSION"] = base.MShellVersion
|
||||
rtn["WAVESHELL_VERSION"] = base.WaveshellVersion
|
||||
return rtn
|
||||
}
|
||||
|
||||
|
@ -170,8 +170,8 @@ type WaveshellLaunchError struct {
|
||||
func (wle WaveshellLaunchError) Error() string {
|
||||
if wle.InitPk.NotFound {
|
||||
return "waveshell client not found"
|
||||
} else if semver.MajorMinor(wle.InitPk.Version) != semver.MajorMinor(base.MShellVersion) {
|
||||
return fmt.Sprintf("invalid remote waveshell version '%s', must be '=%s'", wle.InitPk.Version, semver.MajorMinor(base.MShellVersion))
|
||||
} else if semver.MajorMinor(wle.InitPk.Version) != semver.MajorMinor(base.WaveshellVersion) {
|
||||
return fmt.Sprintf("invalid remote waveshell version '%s', must be '=%s'", wle.InitPk.Version, semver.MajorMinor(base.WaveshellVersion))
|
||||
}
|
||||
return fmt.Sprintf("invalid waveshell: init packet=%v", *wle.InitPk)
|
||||
}
|
||||
@ -232,7 +232,7 @@ func MakeClientProc(ctx context.Context, ecmd ConnInterface) (*ClientProc, error
|
||||
cproc.Close()
|
||||
return nil, WaveshellLaunchError{InitPk: initPk}
|
||||
}
|
||||
if semver.MajorMinor(initPk.Version) != semver.MajorMinor(base.MShellVersion) {
|
||||
if semver.MajorMinor(initPk.Version) != semver.MajorMinor(base.WaveshellVersion) {
|
||||
cproc.Close()
|
||||
return nil, WaveshellLaunchError{InitPk: initPk}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ fi
|
||||
`
|
||||
|
||||
func MakeClientCommandStr() string {
|
||||
return strings.ReplaceAll(ClientCommandFmt, "[%VERSION%]", semver.MajorMinor(base.MShellVersion))
|
||||
return strings.ReplaceAll(ClientCommandFmt, "[%VERSION%]", semver.MajorMinor(base.WaveshellVersion))
|
||||
}
|
||||
|
||||
const InstallCommandFmt = `
|
||||
@ -88,10 +88,10 @@ fi
|
||||
`
|
||||
|
||||
func MakeInstallCommandStr() string {
|
||||
return strings.ReplaceAll(InstallCommandFmt, "[%VERSION%]", semver.MajorMinor(base.MShellVersion))
|
||||
return strings.ReplaceAll(InstallCommandFmt, "[%VERSION%]", semver.MajorMinor(base.WaveshellVersion))
|
||||
}
|
||||
|
||||
type MShellBinaryReaderFn func(version string, goos string, goarch string) (io.ReadCloser, error)
|
||||
type WaveshellBinaryReaderFn func(version string, goos string, goarch string) (io.ReadCloser, error)
|
||||
|
||||
type ReturnStateBuf struct {
|
||||
Lock *sync.Mutex
|
||||
@ -277,7 +277,7 @@ func (c *ShExecType) MakeCmdStartPacket(reqId string) *packet.CmdStartPacketType
|
||||
startPacket.Ts = time.Now().UnixMilli()
|
||||
startPacket.CK = c.CK
|
||||
startPacket.Pid = c.Cmd.Process.Pid
|
||||
startPacket.MShellPid = os.Getpid()
|
||||
startPacket.WaveshellPid = os.Getpid()
|
||||
return startPacket
|
||||
}
|
||||
|
||||
@ -295,7 +295,7 @@ func MakeSimpleStaticWriterPipe(data []byte) (*os.File, error) {
|
||||
}
|
||||
|
||||
func MakeRunnerExec(ck base.CommandKey) (*exec.Cmd, error) {
|
||||
msPath, err := base.GetMShellPath()
|
||||
msPath, err := base.GetWaveshellPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -317,7 +317,7 @@ func MakeDetachedExecCmd(pk *packet.RunPacketType, cmdTty *os.File) (*exec.Cmd,
|
||||
ecmd.Env = os.Environ()
|
||||
}
|
||||
shellutil.UpdateCmdEnv(ecmd, shellenv.EnvMapFromState(state))
|
||||
shellutil.UpdateCmdEnv(ecmd, shellutil.MShellEnvVars(getTermType(pk)))
|
||||
shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellEnvVars(getTermType(pk)))
|
||||
if state.Cwd != "" {
|
||||
ecmd.Dir = base.ExpandHomeDir(state.Cwd)
|
||||
}
|
||||
@ -470,10 +470,10 @@ type ClientOpts struct {
|
||||
UsePty bool
|
||||
}
|
||||
|
||||
func MakeMShellSingleCmd() (*exec.Cmd, error) {
|
||||
func MakeWaveshellSingleCmd() (*exec.Cmd, error) {
|
||||
execFile, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find local mshell executable: %w", err)
|
||||
return nil, fmt.Errorf("cannot find local waveshell executable: %w", err)
|
||||
}
|
||||
ecmd := exec.Command(execFile, "--single-from-server")
|
||||
return ecmd, nil
|
||||
@ -528,31 +528,6 @@ func (opts SSHOpts) MakeSSHExecCmd(remoteCommand string, sapi shellapi.ShellApi)
|
||||
}
|
||||
}
|
||||
|
||||
func (opts SSHOpts) MakeMShellSSHOpts() string {
|
||||
var moreSSHOpts []string
|
||||
if opts.SSHIdentity != "" {
|
||||
identityOpt := fmt.Sprintf("-i %s", shellescape.Quote(opts.SSHIdentity))
|
||||
moreSSHOpts = append(moreSSHOpts, identityOpt)
|
||||
}
|
||||
if opts.SSHUser != "" {
|
||||
userOpt := fmt.Sprintf("-l %s", shellescape.Quote(opts.SSHUser))
|
||||
moreSSHOpts = append(moreSSHOpts, userOpt)
|
||||
}
|
||||
if opts.SSHPort != 0 {
|
||||
portOpt := fmt.Sprintf("-p %d", opts.SSHPort)
|
||||
moreSSHOpts = append(moreSSHOpts, portOpt)
|
||||
}
|
||||
if opts.SSHOptsStr != "" {
|
||||
optsOpt := fmt.Sprintf("--ssh-opts %s", shellescape.Quote(opts.SSHOptsStr))
|
||||
moreSSHOpts = append(moreSSHOpts, optsOpt)
|
||||
}
|
||||
if opts.SSHHost != "" {
|
||||
sshArg := fmt.Sprintf("--ssh %s", shellescape.Quote(opts.SSHHost))
|
||||
moreSSHOpts = append(moreSSHOpts, sshArg)
|
||||
}
|
||||
return strings.Join(moreSSHOpts, " ")
|
||||
}
|
||||
|
||||
func GetTerminalSize() (int, int, error) {
|
||||
fd, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
@ -610,19 +585,19 @@ func ValidateRemoteFds(rfds []packet.RemoteFd) error {
|
||||
dupMap := make(map[int]bool)
|
||||
for _, rfd := range rfds {
|
||||
if rfd.FdNum < 0 {
|
||||
return fmt.Errorf("mshell negative fd numbers fd=%d", rfd.FdNum)
|
||||
return fmt.Errorf("waveshell negative fd numbers fd=%d", rfd.FdNum)
|
||||
}
|
||||
if rfd.FdNum < FirstExtraFilesFdNum {
|
||||
return fmt.Errorf("mshell does not support re-opening fd=%d (0, 1, and 2, are always open)", rfd.FdNum)
|
||||
return fmt.Errorf("waveshell does not support re-opening fd=%d (0, 1, and 2, are always open)", rfd.FdNum)
|
||||
}
|
||||
if rfd.FdNum > MaxFdNum {
|
||||
return fmt.Errorf("mshell does not support opening fd numbers above %d", MaxFdNum)
|
||||
return fmt.Errorf("waveshell does not support opening fd numbers above %d", MaxFdNum)
|
||||
}
|
||||
if dupMap[rfd.FdNum] {
|
||||
return fmt.Errorf("mshell got duplicate entries for fd=%d", rfd.FdNum)
|
||||
return fmt.Errorf("waveshell got duplicate entries for fd=%d", rfd.FdNum)
|
||||
}
|
||||
if rfd.Read && rfd.Write {
|
||||
return fmt.Errorf("mshell does not support opening fd numbers for reading and writing, fd=%d", rfd.FdNum)
|
||||
return fmt.Errorf("waveshell does not support opening fd numbers for reading and writing, fd=%d", rfd.FdNum)
|
||||
}
|
||||
if !rfd.Read && !rfd.Write {
|
||||
return fmt.Errorf("invalid fd=%d, neither reading or writing mode specified", rfd.FdNum)
|
||||
@ -632,14 +607,14 @@ func ValidateRemoteFds(rfds []packet.RemoteFd) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendMShellBinary(input io.WriteCloser, mshellStream io.Reader) {
|
||||
func sendWaveshellBinary(input io.WriteCloser, waveshellStream io.Reader) {
|
||||
go func() {
|
||||
defer input.Close()
|
||||
io.Copy(input, mshellStream)
|
||||
io.Copy(input, waveshellStream)
|
||||
}()
|
||||
}
|
||||
|
||||
func RunInstallFromCmd(ctx context.Context, ecmd ConnInterface, tryDetect bool, mshellStream io.Reader, mshellReaderFn MShellBinaryReaderFn, msgFn func(string)) error {
|
||||
func RunInstallFromCmd(ctx context.Context, ecmd ConnInterface, tryDetect bool, waveshellStream io.Reader, waveshellReaderFn WaveshellBinaryReaderFn, msgFn func(string)) error {
|
||||
inputWriter, err := ecmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating stdin pipe: %v", err)
|
||||
@ -655,8 +630,8 @@ func RunInstallFromCmd(ctx context.Context, ecmd ConnInterface, tryDetect bool,
|
||||
go func() {
|
||||
io.Copy(os.Stderr, stderrReader)
|
||||
}()
|
||||
if mshellStream != nil {
|
||||
sendMShellBinary(inputWriter, mshellStream)
|
||||
if waveshellStream != nil {
|
||||
sendWaveshellBinary(inputWriter, waveshellStream)
|
||||
}
|
||||
packetParser := packet.MakePacketParser(stdoutReader, nil)
|
||||
err = ecmd.Start()
|
||||
@ -686,24 +661,24 @@ func RunInstallFromCmd(ctx context.Context, ecmd ConnInterface, tryDetect bool,
|
||||
}
|
||||
goos, goarch, err := DetectGoArch(initPacket.UName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("arch cannot be detected (might be incompatible with mshell): %w", err)
|
||||
return fmt.Errorf("arch cannot be detected (might be incompatible with waveshell): %w", err)
|
||||
}
|
||||
msgStr := fmt.Sprintf("mshell detected remote architecture as '%s.%s'\n", goos, goarch)
|
||||
msgStr := fmt.Sprintf("waveshell detected remote architecture as '%s.%s'\n", goos, goarch)
|
||||
msgFn(msgStr)
|
||||
detectedMSS, err := mshellReaderFn(base.MShellVersion, goos, goarch)
|
||||
detectedMSS, err := waveshellReaderFn(base.WaveshellVersion, goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer detectedMSS.Close()
|
||||
sendMShellBinary(inputWriter, detectedMSS)
|
||||
sendWaveshellBinary(inputWriter, detectedMSS)
|
||||
continue
|
||||
}
|
||||
if pk.GetType() == packet.InitPacketStr && !firstInit {
|
||||
initPacket := pk.(*packet.InitPacketType)
|
||||
if initPacket.Version == base.MShellVersion {
|
||||
if initPacket.Version == base.WaveshellVersion {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid version '%s' received from client, expecting '%s'", initPacket.Version, base.MShellVersion)
|
||||
return fmt.Errorf("invalid version '%s' received from client, expecting '%s'", initPacket.Version, base.WaveshellVersion)
|
||||
}
|
||||
if pk.GetType() == packet.RawPacketStr {
|
||||
rawPk := pk.(*packet.RawPacketType)
|
||||
@ -770,7 +745,7 @@ func DetectGoArch(uname string) (string, string, error) {
|
||||
osVal := strings.TrimSpace(strings.ToLower(fields[0]))
|
||||
archVal := strings.TrimSpace(strings.ToLower(fields[1]))
|
||||
if osVal != "darwin" && osVal != "linux" {
|
||||
return "", "", fmt.Errorf("invalid uname OS '%s', mshell only supports OS X (darwin) and linux", osVal)
|
||||
return "", "", fmt.Errorf("invalid uname OS '%s', waveshell only supports OS X (darwin) and linux", osVal)
|
||||
}
|
||||
goos := osVal
|
||||
goarch := ""
|
||||
@ -780,7 +755,7 @@ func DetectGoArch(uname string) (string, string, error) {
|
||||
goarch = "arm64"
|
||||
}
|
||||
if goarch == "" {
|
||||
return "", "", fmt.Errorf("invalid uname machine type '%s', mshell only supports aarch64 (amd64) and x86_64 (amd64)", archVal)
|
||||
return "", "", fmt.Errorf("invalid uname machine type '%s', waveshell only supports aarch64 (amd64) and x86_64 (amd64)", archVal)
|
||||
}
|
||||
if !base.ValidGoArch(goos, goarch) {
|
||||
return "", "", fmt.Errorf("invalid arch detected %s.%s", goos, goarch)
|
||||
@ -975,7 +950,7 @@ func RunCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender, fro
|
||||
cmdTty.Close()
|
||||
}()
|
||||
cmd.CmdPty = cmdPty
|
||||
shellutil.UpdateCmdEnv(cmd.Cmd, shellutil.MShellEnvVars(getTermType(pk)))
|
||||
shellutil.UpdateCmdEnv(cmd.Cmd, shellutil.WaveshellEnvVars(getTermType(pk)))
|
||||
}
|
||||
if cmdTty != nil {
|
||||
cmd.Cmd.Stdin = cmdTty
|
||||
@ -1151,8 +1126,8 @@ func (rs *ReturnStateBuf) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// in detached run mode, we don't want mshell to die from signals
|
||||
// since we want mshell to persist even if the mshell --server is terminated
|
||||
// in detached run mode, we don't want waveshell to die from signals since
|
||||
// we want waveshell to persist even if the waveshell --server is terminated
|
||||
func SetupSignalsForDetach() {
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGPIPE)
|
||||
@ -1163,8 +1138,8 @@ func SetupSignalsForDetach() {
|
||||
}()
|
||||
}
|
||||
|
||||
// in detached run mode, we don't want mshell to die from signals
|
||||
// since we want mshell to persist even if the mshell --server is terminated
|
||||
// in detached run mode, we don't want waveshell to die from signals since
|
||||
// we want waveshell to persist even if the waveshell --server is terminated
|
||||
func IgnoreSigPipe() {
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGPIPE)
|
||||
@ -1241,10 +1216,10 @@ func (c *ShExecType) WaitForCommand() *packet.CmdDonePacketType {
|
||||
|
||||
func MakeInitPacket() *packet.InitPacketType {
|
||||
initPacket := packet.MakeInitPacket()
|
||||
initPacket.Version = base.MShellVersion
|
||||
initPacket.Version = base.WaveshellVersion
|
||||
initPacket.BuildTime = base.BuildTime
|
||||
initPacket.HomeDir = base.GetHomeDir()
|
||||
initPacket.MShellHomeDir = base.GetMShellHomeDir()
|
||||
initPacket.WaveshellHomeDir = base.GetWaveshellHomeDir()
|
||||
if user, _ := user.Current(); user != nil {
|
||||
initPacket.User = user.Username
|
||||
}
|
||||
|
@ -452,12 +452,12 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJsonError(w, fmt.Errorf("invalid line, no remote"))
|
||||
return
|
||||
}
|
||||
msh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if wsh == nil {
|
||||
WriteJsonError(w, fmt.Errorf("invalid line, cannot resolve remote"))
|
||||
return
|
||||
}
|
||||
rrState := msh.GetRemoteRuntimeState()
|
||||
rrState := wsh.GetRemoteRuntimeState()
|
||||
fullPath, err := rrState.ExpandHomeDir(params.Path)
|
||||
if err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("error expanding homedir: %v", err))
|
||||
@ -472,7 +472,7 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
writePk.Path = filepath.Join(cwd, fullPath)
|
||||
}
|
||||
iter, err := msh.PacketRpcIter(r.Context(), writePk)
|
||||
iter, err := wsh.PacketRpcIter(r.Context(), writePk)
|
||||
if err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("error: %v", err))
|
||||
return
|
||||
@ -502,7 +502,7 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
|
||||
} else if err != nil {
|
||||
dataErr := fmt.Errorf("error reading file data: %v", err)
|
||||
dataPk.Error = dataErr.Error()
|
||||
msh.SendFileData(dataPk)
|
||||
wsh.SendFileData(dataPk)
|
||||
WriteJsonError(w, dataErr)
|
||||
return
|
||||
}
|
||||
@ -510,7 +510,7 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
|
||||
dataPk.Data = make([]byte, nr)
|
||||
copy(dataPk.Data, bufSlice[0:nr])
|
||||
}
|
||||
msh.SendFileData(dataPk)
|
||||
wsh.SendFileData(dataPk)
|
||||
if dataPk.Eof {
|
||||
break
|
||||
}
|
||||
@ -581,13 +581,13 @@ func HandleReadFile(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("invalid line, no remote"))
|
||||
return
|
||||
}
|
||||
msh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if wsh == nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("invalid line, cannot resolve remote"))
|
||||
return
|
||||
}
|
||||
rrState := msh.GetRemoteRuntimeState()
|
||||
rrState := wsh.GetRemoteRuntimeState()
|
||||
fullPath, err := rrState.ExpandHomeDir(path)
|
||||
if err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("error expanding homedir: %v", err))
|
||||
@ -601,7 +601,7 @@ func HandleReadFile(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
streamPk.Path = filepath.Join(cwd, fullPath)
|
||||
}
|
||||
iter, err := msh.StreamFile(r.Context(), streamPk)
|
||||
iter, err := wsh.StreamFile(r.Context(), streamPk)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(fmt.Sprintf("error trying to stream file: %v", err)))
|
||||
@ -733,7 +733,6 @@ func HandleRunEphemeralCommand(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJsonError(w, fmt.Errorf(ErrorDecodingJson, err))
|
||||
return
|
||||
}
|
||||
log.Printf("Running ephemeral command: %v\n", commandPk)
|
||||
|
||||
if commandPk.EphemeralOpts == nil {
|
||||
commandPk.EphemeralOpts = &ephemeral.EphemeralRunOpts{}
|
||||
@ -1129,6 +1128,11 @@ func main() {
|
||||
log.Printf("[error] migrate up: %v\n", err)
|
||||
return
|
||||
}
|
||||
// err = blockstore.MigrateBlockstore()
|
||||
// if err != nil {
|
||||
// log.Printf("[error] migrate blockstore: %v\n", err)
|
||||
// return
|
||||
// }
|
||||
clientData, err := sstore.EnsureClientData(context.Background())
|
||||
if err != nil {
|
||||
log.Printf("[error] ensuring client data: %v\n", err)
|
||||
|
1
wavesrv/db/blockstore-migrations/000001_init.down.sql
Normal file
1
wavesrv/db/blockstore-migrations/000001_init.down.sql
Normal file
@ -0,0 +1 @@
|
||||
-- nothing
|
19
wavesrv/db/blockstore-migrations/000001_init.up.sql
Normal file
19
wavesrv/db/blockstore-migrations/000001_init.up.sql
Normal file
@ -0,0 +1,19 @@
|
||||
CREATE TABLE block_file (
|
||||
blockid varchar(36) NOT NULL,
|
||||
name varchar(200) NOT NULL,
|
||||
maxsize bigint NOT NULL,
|
||||
circular boolean NOT NULL,
|
||||
size bigint NOT NULL,
|
||||
createdts bigint NOT NULL,
|
||||
modts bigint NOT NULL,
|
||||
meta json NOT NULL,
|
||||
PRIMARY KEY (blockid, name)
|
||||
);
|
||||
|
||||
CREATE TABLE block_data (
|
||||
blockid varchar(36) NOT NULL,
|
||||
name varchar(200) NOT NULL,
|
||||
partidx int NOT NULL,
|
||||
data blob NOT NULL,
|
||||
PRIMARY KEY(blockid, name, partidx)
|
||||
);
|
@ -10,3 +10,6 @@ import "embed"
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var MigrationFS embed.FS
|
||||
|
||||
//go:embed blockstore-migrations/*.sql
|
||||
var BlockstoreMigrationFS embed.FS
|
||||
|
@ -5,7 +5,6 @@ go 1.22
|
||||
toolchain go1.22.0
|
||||
|
||||
require (
|
||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9
|
||||
github.com/alessio/shellescape v1.4.1
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
|
||||
github.com/creack/pty v1.1.18
|
||||
|
@ -1,5 +1,3 @@
|
||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs=
|
||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
|
||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs=
|
||||
@ -57,7 +55,6 @@ github.com/sawka/txwrap v0.1.2 h1:v8xS0Z1LE7/6vMZA81PYihI+0TSR6Zm1MalzzBIuXKc=
|
||||
github.com/sawka/txwrap v0.1.2/go.mod h1:T3nlw2gVpuolo6/XEetvBbk1oMXnY978YmBFy1UyHvw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/wavetermdev/ssh_config v0.0.0-20240306041034-17e2087ebde2 h1:onqZrJVap1sm15AiIGTfWzdr6cEF0KdtddeuuOVhzyY=
|
||||
@ -74,8 +71,6 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
type FileOptsType struct {
|
||||
@ -32,7 +30,11 @@ type FileInfo struct {
|
||||
Meta FileMeta
|
||||
}
|
||||
|
||||
const MaxBlockSize = int64(128 * units.Kilobyte)
|
||||
const UnitsKB = 1024 * 1024
|
||||
const UnitsMB = 1024 * UnitsKB
|
||||
const UnitsGB = 1024 * UnitsMB
|
||||
|
||||
const MaxBlockSize = int64(128 * UnitsKB)
|
||||
const DefaultFlushTimeout = 1 * time.Second
|
||||
|
||||
type CacheEntry struct {
|
||||
@ -79,16 +81,23 @@ type BlockStore interface {
|
||||
GetAllBlockIds(ctx context.Context) []string
|
||||
}
|
||||
|
||||
var cache map[string]*CacheEntry = make(map[string]*CacheEntry)
|
||||
var blockstoreCache map[string]*CacheEntry = make(map[string]*CacheEntry)
|
||||
var globalLock *sync.Mutex = &sync.Mutex{}
|
||||
var appendLock *sync.Mutex = &sync.Mutex{}
|
||||
var flushTimeout = DefaultFlushTimeout
|
||||
var lastWriteTime time.Time
|
||||
|
||||
// for testing
|
||||
func clearCache() {
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
blockstoreCache = make(map[string]*CacheEntry)
|
||||
}
|
||||
|
||||
func InsertFileIntoDB(ctx context.Context, fileInfo FileInfo) error {
|
||||
metaJson, err := json.Marshal(fileInfo.Meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error writing file %s to db: %v", fileInfo.Name, err)
|
||||
return fmt.Errorf("error writing file %s to db: %v", fileInfo.Name, err)
|
||||
}
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `INSERT INTO block_file VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
@ -96,7 +105,7 @@ func InsertFileIntoDB(ctx context.Context, fileInfo FileInfo) error {
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
return fmt.Errorf("Error writing file %s to db: %v", fileInfo.Name, txErr)
|
||||
return fmt.Errorf("error writing file %s to db: %v", fileInfo.Name, txErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -104,7 +113,7 @@ func InsertFileIntoDB(ctx context.Context, fileInfo FileInfo) error {
|
||||
func WriteFileToDB(ctx context.Context, fileInfo FileInfo) error {
|
||||
metaJson, err := json.Marshal(fileInfo.Meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error writing file %s to db: %v", fileInfo.Name, err)
|
||||
return fmt.Errorf("error writing file %s to db: %v", fileInfo.Name, err)
|
||||
}
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `UPDATE block_file SET blockid = ?, name = ?, maxsize = ?, circular = ?, size = ?, createdts = ?, modts = ?, meta = ? where blockid = ? and name = ?`
|
||||
@ -112,7 +121,7 @@ func WriteFileToDB(ctx context.Context, fileInfo FileInfo) error {
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
return fmt.Errorf("Error writing file %s to db: %v", fileInfo.Name, txErr)
|
||||
return fmt.Errorf("error writing file %s to db: %v", fileInfo.Name, txErr)
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -125,7 +134,7 @@ func WriteDataBlockToDB(ctx context.Context, blockId string, name string, index
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
return fmt.Errorf("Error writing data block to db: %v", txErr)
|
||||
return fmt.Errorf("error writing data block to db: %v", txErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -152,7 +161,7 @@ func WriteToCacheBlockNum(ctx context.Context, blockId string, name string, p []
|
||||
defer cacheEntry.Lock.Unlock()
|
||||
block, err := GetCacheBlock(ctx, blockId, name, cacheNum, pullFromDB)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("Error getting cache block: %v", err)
|
||||
return 0, 0, fmt.Errorf("error getting cache block: %v", err)
|
||||
}
|
||||
var bytesWritten = 0
|
||||
blockLen := len(block.data)
|
||||
@ -192,7 +201,7 @@ func ReadFromCacheBlock(ctx context.Context, blockId string, name string, block
|
||||
}
|
||||
}()
|
||||
if pos > len(block.data) {
|
||||
return 0, fmt.Errorf("Reading past end of cache block, should never happen")
|
||||
return 0, fmt.Errorf("reading past end of cache block, should never happen")
|
||||
}
|
||||
bytesWritten := 0
|
||||
index := pos
|
||||
@ -216,7 +225,7 @@ func ReadFromCacheBlock(ctx context.Context, blockId string, name string, block
|
||||
return bytesWritten, nil
|
||||
}
|
||||
|
||||
const MaxSizeError = "Hit Max Size"
|
||||
const MaxSizeError = "MaxSizeError"
|
||||
|
||||
func WriteToCacheBuf(buf *[]byte, p []byte, pos int, length int, maxWrite int64) (int, error) {
|
||||
bytesToWrite := length
|
||||
@ -260,7 +269,7 @@ func GetValuesFromCacheId(cacheId string) (blockId string, name string) {
|
||||
func GetCacheEntry(ctx context.Context, blockId string, name string) (*CacheEntry, bool) {
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
if curCacheEntry, found := cache[GetCacheId(blockId, name)]; found {
|
||||
if curCacheEntry, found := blockstoreCache[GetCacheId(blockId, name)]; found {
|
||||
return curCacheEntry, true
|
||||
} else {
|
||||
return nil, false
|
||||
@ -279,7 +288,7 @@ func GetCacheEntryOrPopulate(ctx context.Context, blockId string, name string) (
|
||||
if cacheEntry, found := GetCacheEntry(ctx, blockId, name); found {
|
||||
return cacheEntry, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("Error getting cache entry %v %v", blockId, name)
|
||||
return nil, fmt.Errorf("error getting cache entry %v %v", blockId, name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,16 +297,16 @@ func GetCacheEntryOrPopulate(ctx context.Context, blockId string, name string) (
|
||||
func SetCacheEntry(ctx context.Context, cacheId string, cacheEntry *CacheEntry) {
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
if _, found := cache[cacheId]; found {
|
||||
if _, found := blockstoreCache[cacheId]; found {
|
||||
return
|
||||
}
|
||||
cache[cacheId] = cacheEntry
|
||||
blockstoreCache[cacheId] = cacheEntry
|
||||
}
|
||||
|
||||
func DeleteCacheEntry(ctx context.Context, blockId string, name string) {
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
delete(cache, GetCacheId(blockId, name))
|
||||
delete(blockstoreCache, GetCacheId(blockId, name))
|
||||
}
|
||||
|
||||
func GetCacheBlock(ctx context.Context, blockId string, name string, cacheNum int, pullFromDB bool) (*CacheBlock, error) {
|
||||
@ -392,7 +401,7 @@ func WriteAtHelper(ctx context.Context, blockId string, name string, p []byte, o
|
||||
}
|
||||
fInfo, err := Stat(ctx, blockId, name)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Write At err: %v", err)
|
||||
return 0, fmt.Errorf("WriteAt err: %v", err)
|
||||
}
|
||||
if off > fInfo.Opts.MaxSize && fInfo.Opts.Circular {
|
||||
numOver := off / fInfo.Opts.MaxSize
|
||||
@ -416,12 +425,12 @@ func WriteAtHelper(ctx context.Context, blockId string, name string, p []byte, o
|
||||
b, err := WriteAtHelper(ctx, blockId, name, p, 0, false)
|
||||
bytesWritten += b
|
||||
if err != nil {
|
||||
return bytesWritten, fmt.Errorf("Write to cache error: %v", err)
|
||||
return bytesWritten, fmt.Errorf("write to cache error: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
return bytesWritten, fmt.Errorf("Write to cache error: %v", err)
|
||||
return bytesWritten, fmt.Errorf("write to cache error: %v", err)
|
||||
}
|
||||
}
|
||||
if len(p) == b {
|
||||
@ -452,7 +461,7 @@ func GetAllBlockSizes(dataBlocks []*CacheBlock) (int, int) {
|
||||
}
|
||||
|
||||
func FlushCache(ctx context.Context) error {
|
||||
for _, cacheEntry := range cache {
|
||||
for _, cacheEntry := range blockstoreCache {
|
||||
err := WriteFileToDB(ctx, *cacheEntry.Info)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -485,14 +494,14 @@ func ReadAt(ctx context.Context, blockId string, name string, p *[]byte, off int
|
||||
bytesRead := 0
|
||||
fInfo, err := Stat(ctx, blockId, name)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Read At err: %v", err)
|
||||
return 0, fmt.Errorf("ReadAt err: %v", err)
|
||||
}
|
||||
if off > fInfo.Opts.MaxSize && fInfo.Opts.Circular {
|
||||
numOver := off / fInfo.Opts.MaxSize
|
||||
off = off - (numOver * fInfo.Opts.MaxSize)
|
||||
}
|
||||
if off > fInfo.Size {
|
||||
return 0, fmt.Errorf("Read At error: tried to read past the end of the file")
|
||||
return 0, fmt.Errorf("ReadAt error: tried to read past the end of the file")
|
||||
}
|
||||
endReadPos := math.Min(float64(int64(len(*p))+off), float64(fInfo.Size))
|
||||
bytesToRead := int64(endReadPos) - off
|
||||
@ -505,7 +514,7 @@ func ReadAt(ctx context.Context, blockId string, name string, p *[]byte, off int
|
||||
for index := curCacheNum; index < curCacheNum+numCaches; index++ {
|
||||
curCacheBlock, err := GetCacheBlock(ctx, blockId, name, index, true)
|
||||
if err != nil {
|
||||
return bytesRead, fmt.Errorf("Error getting cache block: %v", err)
|
||||
return bytesRead, fmt.Errorf("error getting cache block: %v", err)
|
||||
}
|
||||
cacheOffset := off - (int64(index) * MaxBlockSize)
|
||||
if cacheOffset < 0 {
|
||||
@ -540,7 +549,7 @@ func ReadAt(ctx context.Context, blockId string, name string, p *[]byte, off int
|
||||
break
|
||||
}
|
||||
} else {
|
||||
return bytesRead, fmt.Errorf("Read from cache error: %v", err)
|
||||
return bytesRead, fmt.Errorf("read from cache error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -552,7 +561,7 @@ func AppendData(ctx context.Context, blockId string, name string, p []byte) (int
|
||||
defer appendLock.Unlock()
|
||||
fInfo, err := Stat(ctx, blockId, name)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Append stat error: %v", err)
|
||||
return 0, fmt.Errorf("append stat error: %v", err)
|
||||
}
|
||||
return WriteAt(ctx, blockId, name, p, fInfo.Size)
|
||||
}
|
||||
@ -564,12 +573,12 @@ func DeleteFile(ctx context.Context, blockId string, name string) error {
|
||||
}
|
||||
|
||||
func DeleteBlock(ctx context.Context, blockId string) error {
|
||||
for cacheId, _ := range cache {
|
||||
for cacheId := range blockstoreCache {
|
||||
curBlockId, name := GetValuesFromCacheId(cacheId)
|
||||
if curBlockId == blockId {
|
||||
err := DeleteFile(ctx, blockId, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting %v %v: %v", blockId, name, err)
|
||||
return fmt.Errorf("error deleting %v %v: %v", blockId, name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,16 @@ import (
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/sawka/txwrap"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||
|
||||
dbfs "github.com/wavetermdev/waveterm/wavesrv/db"
|
||||
)
|
||||
|
||||
const DBFileName = "blockstore.db"
|
||||
@ -21,12 +26,64 @@ type SingleConnDBGetter struct {
|
||||
SingleConnLock *sync.Mutex
|
||||
}
|
||||
|
||||
var dbWrap *SingleConnDBGetter
|
||||
var dbWrap *SingleConnDBGetter = &SingleConnDBGetter{SingleConnLock: &sync.Mutex{}}
|
||||
|
||||
type TxWrap = txwrap.TxWrap
|
||||
|
||||
func InitDBState() {
|
||||
dbWrap = &SingleConnDBGetter{SingleConnLock: &sync.Mutex{}}
|
||||
func MakeBlockstoreMigrate() (*migrate.Migrate, error) {
|
||||
fsVar, err := iofs.New(dbfs.BlockstoreMigrationFS, "blockstore-migrations")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening iofs: %w", err)
|
||||
}
|
||||
dbUrl := fmt.Sprintf("sqlite3://%s", GetDBName())
|
||||
m, err := migrate.NewWithSourceInstance("iofs", fsVar, dbUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("making blockstore migration db[%s]: %w", GetDBName(), err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func MigrateBlockstore() error {
|
||||
log.Printf("migrate blockstore\n")
|
||||
m, err := MakeBlockstoreMigrate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
curVersion, dirty, err := GetMigrateVersion(m)
|
||||
if dirty {
|
||||
return fmt.Errorf("cannot migrate up, database is dirty")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get current migration version: %v", err)
|
||||
}
|
||||
defer m.Close()
|
||||
err = m.Up()
|
||||
if err != nil && err != migrate.ErrNoChange {
|
||||
return fmt.Errorf("migrating blockstore: %w", err)
|
||||
}
|
||||
newVersion, _, err := GetMigrateVersion(m)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get new migration version: %v", err)
|
||||
}
|
||||
if newVersion != curVersion {
|
||||
log.Printf("[db] blockstore migration done, version %d -> %d\n", curVersion, newVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMigrateVersion(m *migrate.Migrate) (uint, bool, error) {
|
||||
if m == nil {
|
||||
var err error
|
||||
m, err = MakeBlockstoreMigrate()
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
}
|
||||
curVersion, dirty, err := m.Version()
|
||||
if err == migrate.ErrNilVersion {
|
||||
return 0, false, nil
|
||||
}
|
||||
return curVersion, dirty, err
|
||||
}
|
||||
|
||||
func (dbg *SingleConnDBGetter) GetDB(ctx context.Context) (*sqlx.DB, error) {
|
||||
@ -62,8 +119,12 @@ func WithTxRtn[RT any](ctx context.Context, fn func(tx *TxWrap) (RT, error)) (RT
|
||||
var globalDBLock = &sync.Mutex{}
|
||||
var globalDB *sqlx.DB
|
||||
var globalDBErr error
|
||||
var overrideDBName string
|
||||
|
||||
func GetDBName() string {
|
||||
if overrideDBName != "" {
|
||||
return overrideDBName
|
||||
}
|
||||
scHome := scbase.GetWaveHomeDir()
|
||||
return path.Join(scHome, DBFileName)
|
||||
}
|
||||
|
@ -6,15 +6,17 @@ import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
)
|
||||
|
||||
const testOverrideDBName = "test-blockstore.db"
|
||||
const bigFileSize = 10 * UnitsMB
|
||||
|
||||
type TestBlockType struct {
|
||||
BlockId string
|
||||
Name string
|
||||
@ -22,6 +24,22 @@ type TestBlockType struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func initTestDb(t *testing.T) {
|
||||
log.Printf("initTestDb: %v", t.Name())
|
||||
os.Remove(testOverrideDBName)
|
||||
overrideDBName = testOverrideDBName
|
||||
err := MigrateBlockstore()
|
||||
if err != nil {
|
||||
t.Fatalf("MigrateBlockstore error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanupTestDB(t *testing.T) {
|
||||
clearCache()
|
||||
CloseDB()
|
||||
os.Remove(testOverrideDBName)
|
||||
}
|
||||
|
||||
func (b *TestBlockType) ToMap() map[string]interface{} {
|
||||
rtn := make(map[string]interface{})
|
||||
return rtn
|
||||
@ -35,22 +53,17 @@ func (b *TestBlockType) FromMap(m map[string]interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func Cleanup(t *testing.T, ctx context.Context) {
|
||||
DeleteBlock(ctx, "test-block-id")
|
||||
}
|
||||
|
||||
func CleanupName(t *testing.T, ctx context.Context, blockId string) {
|
||||
DeleteBlock(ctx, blockId)
|
||||
}
|
||||
|
||||
func TestGetDB(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
GetDBTimeout := 10 * time.Second
|
||||
ctx, _ := context.WithTimeout(context.Background(), GetDBTimeout)
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), GetDBTimeout)
|
||||
defer cancelFn()
|
||||
_, err := GetDB(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("TestInitDB error: %v", err)
|
||||
}
|
||||
CloseDB()
|
||||
}
|
||||
|
||||
func SimpleAssert(t *testing.T, condition bool, description string) {
|
||||
@ -82,9 +95,11 @@ func InsertIntoBlockData(t *testing.T, ctx context.Context, blockId string, name
|
||||
}
|
||||
|
||||
func TestTx(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
SetFlushTimeout(2 * time.Minute)
|
||||
InitDBState()
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `INSERT into block_data values ('test-block-id', 'test-file-name', 0, 256)`
|
||||
tx.Exec(query)
|
||||
@ -127,11 +142,13 @@ func TestTx(t *testing.T) {
|
||||
if txErr != nil {
|
||||
t.Errorf("TestTx error deleting test entries: %v", txErr)
|
||||
}
|
||||
CloseDB()
|
||||
}
|
||||
|
||||
func TestMultipleChunks(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
InitDBState()
|
||||
InsertIntoBlockData(t, ctx, "test-block-id", "file-1", 0, make([]byte, 5))
|
||||
InsertIntoBlockData(t, ctx, "test-block-id", "file-1", 1, make([]byte, 5))
|
||||
InsertIntoBlockData(t, ctx, "test-block-id", "file-1", 2, make([]byte, 5))
|
||||
@ -178,7 +195,9 @@ func TestMultipleChunks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMakeFile(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
@ -205,7 +224,7 @@ func TestMakeFile(t *testing.T) {
|
||||
log.Printf("cur file info: %v", curFileInfo)
|
||||
SimpleAssert(t, curFileInfo.Name == "file-1", "correct file name")
|
||||
SimpleAssert(t, curFileInfo.Meta["test-descriptor"] == true, "meta correct")
|
||||
curCacheEntry := cache[GetCacheId("test-block-id", "file-1")]
|
||||
curCacheEntry := blockstoreCache[GetCacheId("test-block-id", "file-1")]
|
||||
curFileInfo = curCacheEntry.Info
|
||||
log.Printf("cache entry: %v", curCacheEntry)
|
||||
SimpleAssert(t, curFileInfo.Name == "file-1", "cache correct file name")
|
||||
@ -218,15 +237,16 @@ func TestMakeFile(t *testing.T) {
|
||||
if txErr != nil {
|
||||
t.Errorf("TestTx error deleting test entries: %v", txErr)
|
||||
}
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteAt(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -244,7 +264,10 @@ func TestWriteAt(t *testing.T) {
|
||||
} else {
|
||||
log.Printf("Write at no errors: %v", bytesWritten)
|
||||
}
|
||||
SimpleAssert(t, bytesWritten == len(testBytesToWrite), "Correct num bytes written")
|
||||
if bytesWritten != len(testBytesToWrite) {
|
||||
t.Errorf("WriteAt error: towrite:%d written:%d err:%v\n", len(testBytesToWrite), bytesWritten, err)
|
||||
return
|
||||
}
|
||||
cacheData, err = GetCacheBlock(ctx, "test-block-id", "file-1", 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("Error getting cache: %v", err)
|
||||
@ -313,15 +336,16 @@ func TestWriteAt(t *testing.T) {
|
||||
}
|
||||
log.Printf("Got stat: %v", fInfo)
|
||||
SimpleAssert(t, int64(len(cacheData.data)) == fInfo.Size, "Correct fInfo size")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteAtLeftPad(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -349,14 +373,16 @@ func TestWriteAtLeftPad(t *testing.T) {
|
||||
}
|
||||
log.Printf("Got stat: %v %v %v", fInfo, fInfo.Size, len(cacheData.data))
|
||||
SimpleAssert(t, int64(len(cacheData.data)) == fInfo.Size, "Correct fInfo size")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestReadAt(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -399,14 +425,16 @@ func TestReadAt(t *testing.T) {
|
||||
}
|
||||
SimpleAssert(t, bytesRead == (11-4), "Correct num bytes read")
|
||||
log.Printf("bytes read: %v string: %s", read, string(read))
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestFlushCache(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -461,17 +489,16 @@ func TestFlushCache(t *testing.T) {
|
||||
t.Errorf("get data from db error: %v", txErr)
|
||||
}
|
||||
log.Printf("DB Data: %v", dbData)
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
var largeDataFlushFullWriteSize int64 = int64(1024 * units.Megabyte)
|
||||
var largeDataFlushFullWriteSize int64 = 64 * UnitsKB
|
||||
|
||||
func WriteLargeDataFlush(t *testing.T, ctx context.Context) {
|
||||
writeSize := int64(64 - 16)
|
||||
fullWriteSize := largeDataFlushFullWriteSize
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -524,6 +551,9 @@ func WriteLargeDataFlush(t *testing.T, ctx context.Context) {
|
||||
SimpleAssert(t, bytes.Equal(readHashBuf, hashBuf), "hashes are equal")
|
||||
}
|
||||
func TestWriteAtMaxSize(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
@ -544,11 +574,12 @@ func TestWriteAtMaxSize(t *testing.T) {
|
||||
log.Printf("readbuf: %v\n", readBuf)
|
||||
SimpleAssert(t, bytesRead == 4, "Correct num bytes read")
|
||||
SimpleAssert(t, bytes.Equal(readBuf[:4], readTest), "Correct bytes read")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteAtMaxSizeMultipleBlocks(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
@ -569,11 +600,12 @@ func TestWriteAtMaxSizeMultipleBlocks(t *testing.T) {
|
||||
log.Printf("readbuf multiple: %v %v %v\n", readBuf, bytesRead, bytesWritten)
|
||||
SimpleAssert(t, bytesRead == 4, "Correct num bytes read")
|
||||
SimpleAssert(t, bytes.Equal(readBuf[:4], readTest), "Correct bytes read")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteAtCircular(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
@ -603,11 +635,12 @@ func TestWriteAtCircular(t *testing.T) {
|
||||
SimpleAssert(t, bytesRead == 7, "Correct num bytes read")
|
||||
SimpleAssert(t, bytes.Equal(readBuf[:7], readTest), "Correct bytes read")
|
||||
log.Printf("readbuf circular %v %v, %v", readBuf, string(readBuf), bytesRead)
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteAtCircularWierdOffset(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
@ -646,11 +679,12 @@ func TestWriteAtCircularWierdOffset(t *testing.T) {
|
||||
SimpleAssert(t, bytesRead == 7, "Correct num bytes read")
|
||||
SimpleAssert(t, bytes.Equal(readBuf[:7], readTest), "Correct bytes read")
|
||||
log.Printf("readbuf circular %v %v, %v", readBuf, string(readBuf), bytesRead)
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
@ -691,7 +725,6 @@ func TestAppend(t *testing.T) {
|
||||
}
|
||||
SimpleAssert(t, bytesRead == bytesWritten+4, "Correct num bytes read")
|
||||
SimpleAssert(t, bytes.Equal(readBuf, readTestBytes), "Correct bytes read")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func AppendSyncWorker(t *testing.T, ctx context.Context, wg *sync.WaitGroup) {
|
||||
@ -705,13 +738,15 @@ func AppendSyncWorker(t *testing.T, ctx context.Context, wg *sync.WaitGroup) {
|
||||
SimpleAssert(t, bytesWritten == 1, "Correct bytes written")
|
||||
}
|
||||
func TestAppendSync(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
numWorkers := 10
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id-sync", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -729,15 +764,6 @@ func TestAppendSync(t *testing.T) {
|
||||
}
|
||||
log.Printf("read buf : %v", readBuf)
|
||||
SimpleAssert(t, bytesRead == numWorkers, "Correct bytes read")
|
||||
CleanupName(t, ctx, "test-block-id-sync")
|
||||
}
|
||||
|
||||
func TestAppendSyncMultiple(t *testing.T) {
|
||||
numTests := 100
|
||||
for index := 0; index < numTests; index++ {
|
||||
TestAppendSync(t)
|
||||
log.Printf("finished test: %v", index)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteAtSyncWorker(t *testing.T, ctx context.Context, wg *sync.WaitGroup, index int64) {
|
||||
@ -753,13 +779,15 @@ func WriteAtSyncWorker(t *testing.T, ctx context.Context, wg *sync.WaitGroup, in
|
||||
}
|
||||
|
||||
func TestWriteAtSync(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
numWorkers := 10
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id-sync", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -777,22 +805,16 @@ func TestWriteAtSync(t *testing.T) {
|
||||
}
|
||||
log.Printf("read buf : %v", readBuf)
|
||||
SimpleAssert(t, bytesRead == numWorkers, "Correct num bytes read")
|
||||
CleanupName(t, ctx, "test-block-id-sync")
|
||||
}
|
||||
|
||||
func TestWriteAtSyncMultiple(t *testing.T) {
|
||||
numTests := 100
|
||||
for index := 0; index < numTests; index++ {
|
||||
TestWriteAtSync(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
testBytesToWrite := []byte{'T', 'E', 'S', 'T', 'M', 'E', 'S', 'S', 'A', 'G', 'E'}
|
||||
bytesWritten, err := WriteFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts, testBytesToWrite)
|
||||
if err != nil {
|
||||
@ -807,15 +829,16 @@ func TestWriteFile(t *testing.T) {
|
||||
SimpleAssert(t, bytesRead == bytesWritten, "Correct num bytes read")
|
||||
log.Printf("bytes read: %v string: %s", read, string(read))
|
||||
SimpleAssert(t, bytes.Equal(read, testBytesToWrite), "Correct bytes read")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteMeta(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -843,15 +866,16 @@ func TestWriteMeta(t *testing.T) {
|
||||
}
|
||||
log.Printf("meta: %v", fInfo.Meta)
|
||||
SimpleAssert(t, fInfo.Meta["second-test-descriptor"] == "test1", "Retrieved second meta correctly")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestGetAllBlockIds(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
err = MakeFile(ctx, "test-block-id-2", "file-1", fileMeta, fileOpts)
|
||||
err = MakeFile(ctx, "test-block-id-2", "file-2", fileMeta, fileOpts)
|
||||
@ -864,16 +888,17 @@ func TestGetAllBlockIds(t *testing.T) {
|
||||
testBlockIdArr := []string{"test-block-id", "test-block-id-2", "test-block-id-3"}
|
||||
for idx, val := range blockIds {
|
||||
SimpleAssert(t, testBlockIdArr[idx] == val, "Correct blockid value")
|
||||
CleanupName(t, ctx, val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListFiles(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
err = MakeFile(ctx, "test-block-id-2", "file-1", fileMeta, fileOpts)
|
||||
err = MakeFile(ctx, "test-block-id-2", "file-2", fileMeta, fileOpts)
|
||||
@ -893,19 +918,18 @@ func TestListFiles(t *testing.T) {
|
||||
for idx, val := range files {
|
||||
SimpleAssert(t, val.Name == blockid_1_files[idx], "Correct file name")
|
||||
}
|
||||
CleanupName(t, ctx, "test-block-id")
|
||||
CleanupName(t, ctx, "test-block-id-2")
|
||||
CleanupName(t, ctx, "test-block-id-3")
|
||||
}
|
||||
|
||||
func TestFlushTimer(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
testFlushTimeout := 10 * time.Second
|
||||
SetFlushTimeout(testFlushTimeout)
|
||||
InitDBState()
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -958,22 +982,12 @@ func TestFlushTimer(t *testing.T) {
|
||||
t.Errorf("get data from db error: %v", txErr)
|
||||
}
|
||||
log.Printf("DB Data: %v", dbData)
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestFlushTimerMultiple(t *testing.T) {
|
||||
testFlushTimeout := 1 * time.Second
|
||||
SetFlushTimeout(testFlushTimeout)
|
||||
numTests := 10
|
||||
for index := 0; index < numTests; index++ {
|
||||
TestWriteAt(t)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// time consuming test
|
||||
|
||||
func TestWriteAtMiddle(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
WriteLargeDataFlush(t, ctx)
|
||||
testBytesToWrite := []byte{'T', 'E', 'S', 'T', 'M', 'E', 'S', 'S', 'A', 'G', 'E'}
|
||||
@ -989,23 +1003,26 @@ func TestWriteAtMiddle(t *testing.T) {
|
||||
log.Printf("readBuf: %v %v", readBuf, string(readBuf))
|
||||
SimpleAssert(t, bytesRead == bytesWritten, "Correct num bytes read")
|
||||
SimpleAssert(t, bytes.Equal(readBuf, testBytesToWrite), "read correct bytes")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteLargeDataFlush(t *testing.T) {
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
WriteLargeDataFlush(t, ctx)
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
func TestWriteLargeDataNoFlush(t *testing.T) {
|
||||
InitDBState()
|
||||
initTestDb(t)
|
||||
defer cleanupTestDB(t)
|
||||
|
||||
writeSize := int64(64 - 16)
|
||||
fullWriteSize := int64(1024 * units.Megabyte)
|
||||
fullWriteSize := int64(64 * UnitsKB)
|
||||
ctx := context.Background()
|
||||
fileMeta := make(FileMeta)
|
||||
fileMeta["test-descriptor"] = true
|
||||
fileOpts := FileOptsType{MaxSize: int64(5 * units.Gigabyte), Circular: false, IJson: false}
|
||||
fileOpts := FileOptsType{MaxSize: bigFileSize, Circular: false, IJson: false}
|
||||
err := MakeFile(ctx, "test-block-id", "file-1", fileMeta, fileOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeFile error: %v", err)
|
||||
@ -1028,11 +1045,13 @@ func TestWriteLargeDataNoFlush(t *testing.T) {
|
||||
copy(hashBuf, hash.Sum(nil))
|
||||
bytesWritten, err := WriteAt(ctx, "test-block-id", "file-1", writeBuf, writeIndex)
|
||||
if int64(bytesWritten) != writeSize {
|
||||
log.Printf("write issue: %v %v \n", bytesWritten, writeSize)
|
||||
t.Errorf("write issue: %v %v %v err:%v\n", bytesWritten, writeSize, writeIndex, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("error: %v", err)
|
||||
t.Errorf("Write At error: %v\n", err)
|
||||
return
|
||||
}
|
||||
writeIndex += int64(bytesWritten)
|
||||
}
|
||||
@ -1060,7 +1079,6 @@ func TestWriteLargeDataNoFlush(t *testing.T) {
|
||||
}
|
||||
log.Printf("final hash: %v readBuf: %v, bytesRead: %v", readHashBuf, readBuf, readIndex)
|
||||
SimpleAssert(t, bytes.Equal(readHashBuf, hashBuf), "hashes are equal")
|
||||
Cleanup(t, ctx)
|
||||
}
|
||||
|
||||
// saving this code for later
|
||||
|
@ -1311,7 +1311,7 @@ func checkForWriteFinished(ctx context.Context, iter *packet.RpcResponseIter) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCopyLocalFileToRemote(ctx context.Context, cmd *sstore.CmdType, remote_msh *remote.MShellProc, localPath string, destPath string, outputPos int64) {
|
||||
func doCopyLocalFileToRemote(ctx context.Context, cmd *sstore.CmdType, remoteWsh *remote.WaveshellProc, localPath string, destPath string, outputPos int64) {
|
||||
var exitSuccess bool
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
@ -1326,7 +1326,7 @@ func doCopyLocalFileToRemote(ctx context.Context, cmd *sstore.CmdType, remote_ms
|
||||
writePk := packet.MakeWriteFilePacket()
|
||||
writePk.ReqId = uuid.New().String()
|
||||
writePk.Path = destPath
|
||||
iter, err := remote_msh.WriteFile(ctx, writePk)
|
||||
iter, err := remoteWsh.WriteFile(ctx, writePk)
|
||||
if err != nil {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Error starting file write: %v\r\n", err), &outputPos)
|
||||
return
|
||||
@ -1358,7 +1358,7 @@ func doCopyLocalFileToRemote(ctx context.Context, cmd *sstore.CmdType, remote_ms
|
||||
} else if err != nil {
|
||||
dataErr := fmt.Sprintf("error reading file data: %v", err)
|
||||
dataPk.Error = dataErr
|
||||
remote_msh.SendFileData(dataPk)
|
||||
remoteWsh.SendFileData(dataPk)
|
||||
writeStringToPty(ctx, cmd, dataErr, &outputPos)
|
||||
return
|
||||
}
|
||||
@ -1373,7 +1373,7 @@ func doCopyLocalFileToRemote(ctx context.Context, cmd *sstore.CmdType, remote_ms
|
||||
lastFileTransferPercentage = fileTransferPercentage
|
||||
}
|
||||
}
|
||||
remote_msh.SendFileData(dataPk)
|
||||
remoteWsh.SendFileData(dataPk)
|
||||
if dataPk.Eof {
|
||||
break
|
||||
}
|
||||
@ -1405,7 +1405,7 @@ func getStatusBarString(filePercentageInt int) string {
|
||||
return statusBarString
|
||||
}
|
||||
|
||||
func doCopyRemoteFileToRemote(ctx context.Context, cmd *sstore.CmdType, sourceMsh *remote.MShellProc, destMsh *remote.MShellProc, sourcePath string, destPath string, outputPos int64) {
|
||||
func doCopyRemoteFileToRemote(ctx context.Context, cmd *sstore.CmdType, sourceWsh *remote.WaveshellProc, destWsh *remote.WaveshellProc, sourcePath string, destPath string, outputPos int64) {
|
||||
var exitSuccess bool
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
@ -1414,7 +1414,7 @@ func doCopyRemoteFileToRemote(ctx context.Context, cmd *sstore.CmdType, sourceMs
|
||||
streamPk := packet.MakeStreamFilePacket()
|
||||
streamPk.ReqId = uuid.New().String()
|
||||
streamPk.Path = sourcePath
|
||||
sourceStreamIter, err := sourceMsh.StreamFile(ctx, streamPk)
|
||||
sourceStreamIter, err := sourceWsh.StreamFile(ctx, streamPk)
|
||||
if err != nil {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Error getting file data packet: %v\r\n", err), &outputPos)
|
||||
return
|
||||
@ -1443,7 +1443,7 @@ func doCopyRemoteFileToRemote(ctx context.Context, cmd *sstore.CmdType, sourceMs
|
||||
writePk := packet.MakeWriteFilePacket()
|
||||
writePk.ReqId = uuid.New().String()
|
||||
writePk.Path = destPath
|
||||
destWriteIter, err := destMsh.WriteFile(ctx, writePk)
|
||||
destWriteIter, err := destWsh.WriteFile(ctx, writePk)
|
||||
if err != nil {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Error starting file write: %v\r\n", err), &outputPos)
|
||||
return
|
||||
@ -1482,7 +1482,7 @@ func doCopyRemoteFileToRemote(ctx context.Context, cmd *sstore.CmdType, sourceMs
|
||||
writeDataPk.Type = dataPk.Type
|
||||
writeDataPk.Data = make([]byte, int64(len(dataPk.Data)))
|
||||
copy(writeDataPk.Data, dataPk.Data)
|
||||
err = destMsh.SendFileData(writeDataPk)
|
||||
err = destWsh.SendFileData(writeDataPk)
|
||||
if err != nil {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("error sending file to dest: %v\r\n", err), &outputPos)
|
||||
return
|
||||
@ -1542,7 +1542,7 @@ func doCopyLocalFileToLocal(ctx context.Context, cmd *sstore.CmdType, sourcePath
|
||||
exitSuccess = true
|
||||
}
|
||||
|
||||
func doCopyRemoteFileToLocal(ctx context.Context, cmd *sstore.CmdType, remote_msh *remote.MShellProc, sourcePath string, localPath string, outputPos int64) {
|
||||
func doCopyRemoteFileToLocal(ctx context.Context, cmd *sstore.CmdType, remoteWsh *remote.WaveshellProc, sourcePath string, localPath string, outputPos int64) {
|
||||
var exitSuccess bool
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
@ -1551,7 +1551,7 @@ func doCopyRemoteFileToLocal(ctx context.Context, cmd *sstore.CmdType, remote_ms
|
||||
streamPk := packet.MakeStreamFilePacket()
|
||||
streamPk.ReqId = uuid.New().String()
|
||||
streamPk.Path = sourcePath
|
||||
iter, err := remote_msh.StreamFile(ctx, streamPk)
|
||||
iter, err := remoteWsh.StreamFile(ctx, streamPk)
|
||||
if err != nil {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Error getting file data packet: %v\r\n", err), &outputPos)
|
||||
return
|
||||
@ -1700,11 +1700,11 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
|
||||
var sourceFullPath string
|
||||
var destFullPath string
|
||||
sourceMsh := sourceRemoteId.MShell
|
||||
if sourceMsh == nil {
|
||||
return nil, fmt.Errorf("failure getting source remote mshell")
|
||||
sourceWsh := sourceRemoteId.Waveshell
|
||||
if sourceWsh == nil {
|
||||
return nil, fmt.Errorf("failure getting source remote waveshell")
|
||||
}
|
||||
sourceRRState := sourceMsh.GetRemoteRuntimeState()
|
||||
sourceRRState := sourceWsh.GetRemoteRuntimeState()
|
||||
sourcePathWithHome, err := sourceRRState.ExpandHomeDir(sourcePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("expand home dir err: %v", err)
|
||||
@ -1720,11 +1720,11 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
sourceFileName := filepath.Base(sourceFullPath)
|
||||
destPath = filepath.Join(destPath, sourceFileName)
|
||||
}
|
||||
destMsh := destRemoteId.MShell
|
||||
if destMsh == nil {
|
||||
return nil, fmt.Errorf("failure getting dest remote mshell")
|
||||
destWsh := destRemoteId.Waveshell
|
||||
if destWsh == nil {
|
||||
return nil, fmt.Errorf("failure getting dest remote waveshell")
|
||||
}
|
||||
destRRState := destMsh.GetRemoteRuntimeState()
|
||||
destRRState := destWsh.GetRemoteRuntimeState()
|
||||
destPathWithHome, err := destRRState.ExpandHomeDir(destPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("expand home dir err: %v", err)
|
||||
@ -1757,7 +1757,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive))
|
||||
if destRemote != ConnectedRemote && destRemoteId != nil && !destRemoteId.RState.IsConnected() {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Attempting to autoconnect to remote %v\r\n", destRemote), &outputPos)
|
||||
err = destRemoteId.MShell.TryAutoConnect()
|
||||
err = destRemoteId.Waveshell.TryAutoConnect()
|
||||
if err != nil {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Couldn't connect to remote %v\r\n", sourceRemote), &outputPos)
|
||||
} else {
|
||||
@ -1766,7 +1766,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
}
|
||||
if sourceRemote != LocalRemote && sourceRemoteId != nil && !sourceRemoteId.RState.IsConnected() {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Attempting to autoconnect to remote %v\r\n", sourceRemote), &outputPos)
|
||||
err = sourceRemoteId.MShell.TryAutoConnect()
|
||||
err = sourceRemoteId.Waveshell.TryAutoConnect()
|
||||
if err != nil {
|
||||
writeStringToPty(ctx, cmd, fmt.Sprintf("Couldn't connect to remote %v\r\n", sourceRemote), &outputPos)
|
||||
} else {
|
||||
@ -1778,11 +1778,11 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
if destRemote == LocalRemote && sourceRemote == LocalRemote {
|
||||
go doCopyLocalFileToLocal(context.Background(), cmd, sourceFullPath, destFullPath, outputPos)
|
||||
} else if destRemote == LocalRemote && sourceRemote != LocalRemote {
|
||||
go doCopyRemoteFileToLocal(context.Background(), cmd, sourceMsh, sourceFullPath, destFullPath, outputPos)
|
||||
go doCopyRemoteFileToLocal(context.Background(), cmd, sourceWsh, sourceFullPath, destFullPath, outputPos)
|
||||
} else if destRemote != LocalRemote && sourceRemote == LocalRemote {
|
||||
go doCopyLocalFileToRemote(context.Background(), cmd, destMsh, sourceFullPath, destFullPath, outputPos)
|
||||
go doCopyLocalFileToRemote(context.Background(), cmd, destWsh, sourceFullPath, destFullPath, outputPos)
|
||||
} else if destRemote != LocalRemote && sourceRemote != LocalRemote {
|
||||
go doCopyRemoteFileToRemote(context.Background(), cmd, sourceMsh, destMsh, sourceFullPath, destFullPath, outputPos)
|
||||
go doCopyRemoteFileToRemote(context.Background(), cmd, sourceWsh, destWsh, sourceFullPath, destFullPath, outputPos)
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
@ -1792,8 +1792,8 @@ func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mshell := ids.Remote.MShell
|
||||
go mshell.RunInstall(false)
|
||||
wshell := ids.Remote.Waveshell
|
||||
go wshell.RunInstall(false)
|
||||
return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
|
||||
}
|
||||
|
||||
@ -1802,8 +1802,8 @@ func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacke
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mshell := ids.Remote.MShell
|
||||
go mshell.CancelInstall()
|
||||
wshell := ids.Remote.Waveshell
|
||||
go wshell.CancelInstall()
|
||||
return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
|
||||
}
|
||||
|
||||
@ -1812,7 +1812,7 @@ func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go ids.Remote.MShell.Launch(true)
|
||||
go ids.Remote.Waveshell.Launch(true)
|
||||
return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
|
||||
}
|
||||
|
||||
@ -1822,7 +1822,7 @@ func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy
|
||||
return nil, err
|
||||
}
|
||||
force := resolveBool(pk.Kwargs["force"], false)
|
||||
go ids.Remote.MShell.Disconnect(force)
|
||||
go ids.Remote.Waveshell.Disconnect(force)
|
||||
return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
|
||||
}
|
||||
|
||||
@ -2082,7 +2082,7 @@ func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
}
|
||||
visualEdit := resolveBool(pk.Kwargs["visual"], false)
|
||||
isSubmitted := resolveBool(pk.Kwargs["submit"], false)
|
||||
editArgs, err := parseRemoteEditArgs(false, pk, ids.Remote.MShell.IsLocal())
|
||||
editArgs, err := parseRemoteEditArgs(false, pk, ids.Remote.Waveshell.IsLocal())
|
||||
if err != nil {
|
||||
return makeRemoteEditErrorReturn_edit(ids, visualEdit, fmt.Errorf("/remote:new %v", err))
|
||||
}
|
||||
@ -2092,7 +2092,7 @@ func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
if !visualEdit && len(editArgs.EditMap) == 0 {
|
||||
return nil, fmt.Errorf("/remote:set no updates, can set %s. (set visual=1 to edit in UI)", formatStrs(RemoteSetArgs, "or", false))
|
||||
}
|
||||
err = ids.Remote.MShell.UpdateRemote(ctx, editArgs.EditMap)
|
||||
err = ids.Remote.Waveshell.UpdateRemote(ctx, editArgs.EditMap)
|
||||
if err != nil {
|
||||
return makeRemoteEditErrorReturn_edit(ids, visualEdit, fmt.Errorf("/remote:new error updating remote: %v", err))
|
||||
}
|
||||
@ -2367,19 +2367,19 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
|
||||
editMap[sstore.RemoteField_SSHKey] = hostInfo.SshKeyFile
|
||||
}
|
||||
editMap[sstore.RemoteField_ShellPref] = hostInfo.ShellPref
|
||||
msh := remote.GetRemoteById(previouslyImportedRemote.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(previouslyImportedRemote.RemoteId)
|
||||
if wsh == nil {
|
||||
remoteChangeList["updateErr"] = append(remoteChangeList["updateErr"], hostInfo.CanonicalName)
|
||||
log.Printf("strange, msh for remote %s [%s] not found\n", hostInfo.CanonicalName, previouslyImportedRemote.RemoteId)
|
||||
log.Printf("strange, wsh for remote %s [%s] not found\n", hostInfo.CanonicalName, previouslyImportedRemote.RemoteId)
|
||||
continue
|
||||
}
|
||||
|
||||
if msh.Remote.ConnectMode == hostInfo.ConnectMode && msh.Remote.SSHOpts.SSHIdentity == hostInfo.SshKeyFile && msh.Remote.RemoteAlias == hostInfo.Host && msh.Remote.ShellPref == hostInfo.ShellPref {
|
||||
if wsh.Remote.ConnectMode == hostInfo.ConnectMode && wsh.Remote.SSHOpts.SSHIdentity == hostInfo.SshKeyFile && wsh.Remote.RemoteAlias == hostInfo.Host && wsh.Remote.ShellPref == hostInfo.ShellPref {
|
||||
// silently skip this one. it didn't fail, but no changes were needed
|
||||
continue
|
||||
}
|
||||
|
||||
err := msh.UpdateRemote(ctx, editMap)
|
||||
err := wsh.UpdateRemote(ctx, editMap)
|
||||
if err != nil {
|
||||
remoteChangeList["updateErr"] = append(remoteChangeList["updateErr"], hostInfo.CanonicalName)
|
||||
log.Printf("error updating remote[%s]: %v\n", hostInfo.CanonicalName, err)
|
||||
@ -2548,11 +2548,11 @@ func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids re
|
||||
}
|
||||
for _, ri := range riArr {
|
||||
rptr := sstore.RemotePtrType{RemoteId: ri.RemoteId, Name: ri.Name}
|
||||
msh := remote.GetRemoteById(ri.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(ri.RemoteId)
|
||||
if wsh == nil {
|
||||
continue
|
||||
}
|
||||
baseDisplayName := msh.GetDisplayName()
|
||||
baseDisplayName := wsh.GetDisplayName()
|
||||
displayName := rptr.GetDisplayName(baseDisplayName)
|
||||
cwdStr := "-"
|
||||
if ri.FeState["cwd"] != "" {
|
||||
@ -3006,17 +3006,17 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.Upd
|
||||
if rstate.Archived {
|
||||
return nil, fmt.Errorf("/%s error: remote %q cannot switch to archived remote", GetCmdStr(pk), newRemote)
|
||||
}
|
||||
newMsh := remote.GetRemoteById(rptr.RemoteId)
|
||||
if newMsh == nil {
|
||||
return nil, fmt.Errorf("/%s error: remote %q not found (msh)", GetCmdStr(pk), newRemote)
|
||||
newWsh := remote.GetRemoteById(rptr.RemoteId)
|
||||
if newWsh == nil {
|
||||
return nil, fmt.Errorf("/%s error: remote %q not found (wsh)", GetCmdStr(pk), newRemote)
|
||||
}
|
||||
if !newMsh.IsConnected() {
|
||||
err := newMsh.TryAutoConnect()
|
||||
if !newWsh.IsConnected() {
|
||||
err := newWsh.TryAutoConnect()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%q is disconnected, auto-connect failed: %w", rstate.GetBaseDisplayName(), err)
|
||||
}
|
||||
if !newMsh.IsConnected() {
|
||||
if newMsh.GetRemoteCopy().ConnectMode == sstore.ConnectModeManual {
|
||||
if !newWsh.IsConnected() {
|
||||
if newWsh.GetRemoteCopy().ConnectMode == sstore.ConnectModeManual {
|
||||
return nil, fmt.Errorf("%q is disconnected (must manually connect)", rstate.GetBaseDisplayName())
|
||||
}
|
||||
return nil, fmt.Errorf("%q is disconnected", rstate.GetBaseDisplayName())
|
||||
@ -3057,7 +3057,7 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.Upd
|
||||
ScreenId: ids.ScreenId,
|
||||
RPtr: *rptr,
|
||||
}
|
||||
go doAsyncResetCommand(newMsh, opts, cmd)
|
||||
go doAsyncResetCommand(newWsh, opts, cmd)
|
||||
return update, nil
|
||||
} else {
|
||||
outputStr := fmt.Sprintf("reconnected to %s", GetFullRemoteDisplayName(rptr, rstate))
|
||||
@ -3298,7 +3298,7 @@ func doCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix str
|
||||
cgPacket.CompType = compType
|
||||
cgPacket.Prefix = prefix
|
||||
cgPacket.Cwd = ids.Remote.FeState["cwd"]
|
||||
resp, err := ids.Remote.MShell.PacketRpc(ctx, cgPacket)
|
||||
resp, err := ids.Remote.Waveshell.PacketRpc(ctx, cgPacket)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@ -3942,7 +3942,7 @@ func ClearSudoCache(ctx context.Context, pk *scpacket.FeCommandPacketType) (rtnU
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids.Remote.MShell.ClearCachedSudoPw()
|
||||
ids.Remote.Waveshell.ClearCachedSudoPw()
|
||||
pluralize := ""
|
||||
|
||||
clearAll := resolveBool(pk.Kwargs["all"], false)
|
||||
@ -3966,7 +3966,7 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ids.Remote.MShell.IsConnected() {
|
||||
if !ids.Remote.Waveshell.IsConnected() {
|
||||
return nil, fmt.Errorf("cannot reinit, remote is not connected")
|
||||
}
|
||||
verbose := resolveBool(pk.Kwargs["verbose"], false)
|
||||
@ -3994,7 +3994,7 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
|
||||
ScreenId: ids.ScreenId,
|
||||
RPtr: ids.Remote.RemotePtr,
|
||||
}
|
||||
go doAsyncResetCommand(ids.Remote.MShell, opts, cmd)
|
||||
go doAsyncResetCommand(ids.Remote.Waveshell, opts, cmd)
|
||||
return update, nil
|
||||
}
|
||||
|
||||
@ -4007,7 +4007,7 @@ type connectOptsType struct {
|
||||
}
|
||||
|
||||
// this does the asynchroneous part of the connection reset
|
||||
func doAsyncResetCommand(msh *remote.MShellProc, opts connectOptsType, cmd *sstore.CmdType) {
|
||||
func doAsyncResetCommand(wsh *remote.WaveshellProc, opts connectOptsType, cmd *sstore.CmdType) {
|
||||
ctx, cancelFn := context.WithCancel(context.Background())
|
||||
defer cancelFn()
|
||||
startTime := time.Now()
|
||||
@ -4025,7 +4025,7 @@ func doAsyncResetCommand(msh *remote.MShellProc, opts connectOptsType, cmd *ssto
|
||||
writeStringToPty(ctx, cmd, string(data), &outputPos)
|
||||
}
|
||||
origStatePtr, _ := sstore.GetRemoteStatePtr(ctx, opts.SessionId, opts.ScreenId, opts.RPtr)
|
||||
ssPk, err := msh.ReInit(ctx, base.MakeCommandKey(cmd.ScreenId, cmd.LineId), opts.ShellType, dataFn, opts.Verbose)
|
||||
ssPk, err := wsh.ReInit(ctx, base.MakeCommandKey(cmd.ScreenId, cmd.LineId), opts.ShellType, dataFn, opts.Verbose)
|
||||
if err != nil {
|
||||
rtnErr = err
|
||||
return
|
||||
@ -4296,11 +4296,11 @@ func resizeRunningCommand(ctx context.Context, cmd *sstore.CmdType, newCols int)
|
||||
feInput := scpacket.MakeFeInputPacket()
|
||||
feInput.CK = base.MakeCommandKey(cmd.ScreenId, cmd.LineId)
|
||||
feInput.WinSize = &packet.WinSize{Rows: int(cmd.TermOpts.Rows), Cols: newCols}
|
||||
msh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if wsh == nil {
|
||||
return fmt.Errorf("cannot resize, cmd remote not found")
|
||||
}
|
||||
err := msh.HandleFeInput(feInput)
|
||||
err := wsh.HandleFeInput(feInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -4421,12 +4421,12 @@ func LineRestartCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
|
||||
if cmd.Status == sstore.CmdStatusRunning || cmd.Status == sstore.CmdStatusDetached {
|
||||
killCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
err = ids.Remote.MShell.KillRunningCommandAndWait(killCtx, base.MakeCommandKey(ids.ScreenId, lineId))
|
||||
err = ids.Remote.Waveshell.KillRunningCommandAndWait(killCtx, base.MakeCommandKey(ids.ScreenId, lineId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ids.Remote.MShell.ResetDataPos(base.MakeCommandKey(ids.ScreenId, lineId))
|
||||
ids.Remote.Waveshell.ResetDataPos(base.MakeCommandKey(ids.ScreenId, lineId))
|
||||
err = sstore.ClearCmdPtyFile(ctx, ids.ScreenId, lineId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error clearing existing pty file: %v", err)
|
||||
@ -5065,8 +5065,8 @@ func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
return nil, err
|
||||
}
|
||||
streamPk.StatOnly = true
|
||||
msh := ids.Remote.MShell
|
||||
iter, err := msh.StreamFile(ctx, streamPk)
|
||||
wsh := ids.Remote.Waveshell
|
||||
iter, err := wsh.StreamFile(ctx, streamPk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/view:stat error: %v", err)
|
||||
}
|
||||
@ -5116,8 +5116,8 @@ func ViewTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msh := ids.Remote.MShell
|
||||
iter, err := msh.StreamFile(ctx, streamPk)
|
||||
wsh := ids.Remote.Waveshell
|
||||
iter, err := wsh.StreamFile(ctx, streamPk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/view:test error: %v", err)
|
||||
}
|
||||
@ -5413,8 +5413,8 @@ func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
} else {
|
||||
writePk.Path = filepath.Join(cwd, fileArg)
|
||||
}
|
||||
msh := ids.Remote.MShell
|
||||
iter, err := msh.PacketRpcIter(ctx, writePk)
|
||||
wsh := ids.Remote.Waveshell
|
||||
iter, err := wsh.PacketRpcIter(ctx, writePk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/edit:test error: %v", err)
|
||||
}
|
||||
@ -5433,7 +5433,7 @@ func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
|
||||
dataPk := packet.MakeFileDataPacket(writePk.ReqId)
|
||||
dataPk.Data = []byte(content)
|
||||
dataPk.Eof = true
|
||||
err = msh.SendFileData(dataPk)
|
||||
err = wsh.SendFileData(dataPk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/edit:test error sending data packet: %v", err)
|
||||
}
|
||||
@ -5500,17 +5500,17 @@ func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus
|
||||
if !sigNameRe.MatchString(sigArg) {
|
||||
return nil, fmt.Errorf("invalid signal name/number: %q", sigArg)
|
||||
}
|
||||
msh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
||||
if wsh == nil {
|
||||
return nil, fmt.Errorf("cannot send signal, no remote found for command")
|
||||
}
|
||||
if !msh.IsConnected() {
|
||||
if !wsh.IsConnected() {
|
||||
return nil, fmt.Errorf("cannot send signal, remote is not connected")
|
||||
}
|
||||
inputPk := scpacket.MakeFeInputPacket()
|
||||
inputPk.CK = base.MakeCommandKey(cmd.ScreenId, cmd.LineId)
|
||||
inputPk.SigName = sigArg
|
||||
err = msh.HandleFeInput(inputPk)
|
||||
err = wsh.HandleFeInput(inputPk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot send signal: %v", err)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ type resolvedIds struct {
|
||||
type ResolvedRemote struct {
|
||||
DisplayName string
|
||||
RemotePtr sstore.RemotePtrType
|
||||
MShell *remote.MShellProc
|
||||
Waveshell *remote.WaveshellProc
|
||||
RState remote.RemoteRuntimeState
|
||||
RemoteCopy *sstore.RemoteType
|
||||
ShellType string // default remote shell preference
|
||||
@ -201,11 +201,11 @@ func resolveRemoteArg(remoteArg string) (*sstore.RemotePtrType, error) {
|
||||
if rrUser != "" {
|
||||
return nil, fmt.Errorf("remoteusers not supported")
|
||||
}
|
||||
msh := remote.GetRemoteByArg(rrRemote)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteByArg(rrRemote)
|
||||
if wsh == nil {
|
||||
return nil, nil
|
||||
}
|
||||
rcopy := msh.GetRemoteCopy()
|
||||
rcopy := wsh.GetRemoteCopy()
|
||||
return &sstore.RemotePtrType{RemoteId: rcopy.RemoteId, Name: rrName}, nil
|
||||
}
|
||||
|
||||
@ -269,7 +269,7 @@ func resolveUiIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype i
|
||||
}
|
||||
if rtype&R_RemoteConnected > 0 {
|
||||
if !rtn.Remote.RState.IsConnected() {
|
||||
err = rtn.Remote.MShell.TryAutoConnect()
|
||||
err = rtn.Remote.Waveshell.TryAutoConnect()
|
||||
if err != nil {
|
||||
return rtn, fmt.Errorf("error trying to auto-connect remote [%s]: %w", rtn.Remote.DisplayName, err)
|
||||
}
|
||||
@ -464,18 +464,18 @@ func ResolveRemoteFromPtr(ctx context.Context, rptr *sstore.RemotePtrType, sessi
|
||||
if rptr == nil || rptr.RemoteId == "" {
|
||||
return nil, nil
|
||||
}
|
||||
msh := remote.GetRemoteById(rptr.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(rptr.RemoteId)
|
||||
if wsh == nil {
|
||||
return nil, fmt.Errorf("invalid remote '%s', not found", rptr.RemoteId)
|
||||
}
|
||||
rstate := msh.GetRemoteRuntimeState()
|
||||
rcopy := msh.GetRemoteCopy()
|
||||
rstate := wsh.GetRemoteRuntimeState()
|
||||
rcopy := wsh.GetRemoteCopy()
|
||||
displayName := rstate.GetDisplayName(rptr)
|
||||
rtn := &ResolvedRemote{
|
||||
DisplayName: displayName,
|
||||
RemotePtr: *rptr,
|
||||
RState: rstate,
|
||||
MShell: msh,
|
||||
Waveshell: wsh,
|
||||
RemoteCopy: &rcopy,
|
||||
StatePtr: nil,
|
||||
FeState: nil,
|
||||
@ -488,7 +488,7 @@ func ResolveRemoteFromPtr(ctx context.Context, rptr *sstore.RemotePtrType, sessi
|
||||
// continue with state set to nil
|
||||
} else {
|
||||
if ri == nil {
|
||||
rtn.ShellType = msh.GetShellPref()
|
||||
rtn.ShellType = wsh.GetShellPref()
|
||||
rtn.StatePtr = nil
|
||||
rtn.FeState = nil
|
||||
} else {
|
||||
|
@ -65,8 +65,8 @@ func doCompGen(ctx context.Context, prefix string, compType string, compCtx Comp
|
||||
if !packet.IsValidCompGenType(compType) {
|
||||
return nil, fmt.Errorf("/_compgen invalid type '%s'", compType)
|
||||
}
|
||||
msh := remote.GetRemoteById(compCtx.RemotePtr.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(compCtx.RemotePtr.RemoteId)
|
||||
if wsh == nil {
|
||||
return nil, fmt.Errorf("invalid remote '%s', not found", compCtx.RemotePtr)
|
||||
}
|
||||
cgPacket := packet.MakeCompGenPacket()
|
||||
@ -74,7 +74,7 @@ func doCompGen(ctx context.Context, prefix string, compType string, compCtx Comp
|
||||
cgPacket.CompType = compType
|
||||
cgPacket.Prefix = prefix
|
||||
cgPacket.Cwd = compCtx.Cwd
|
||||
resp, err := msh.PacketRpc(ctx, cgPacket)
|
||||
resp, err := wsh.PacketRpc(ctx, cgPacket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -36,7 +36,7 @@ const WaveDirName = ".waveterm" // must match emain.ts
|
||||
const WaveDevDirName = ".waveterm-dev" // must match emain.ts
|
||||
const WaveAppPathVarName = "WAVETERM_APP_PATH"
|
||||
const WaveAuthKeyFileName = "waveterm.authkey"
|
||||
const MShellVersion = "v0.7.0" // must match base.MShellVersion
|
||||
const WaveshellVersion = "v0.7.0" // must match base.WaveshellVersion
|
||||
|
||||
// initialized by InitialzeWaveAuthKey (called by main-server)
|
||||
var WaveAuthKey string
|
||||
@ -73,7 +73,7 @@ func GetWaveHomeDir() string {
|
||||
return scHome
|
||||
}
|
||||
|
||||
func MShellBinaryDir() string {
|
||||
func WaveshellBinaryDir() string {
|
||||
appPath := os.Getenv(WaveAppPathVarName)
|
||||
if appPath == "" {
|
||||
appPath = "."
|
||||
@ -81,32 +81,32 @@ func MShellBinaryDir() string {
|
||||
return filepath.Join(appPath, "bin", "mshell")
|
||||
}
|
||||
|
||||
func MShellBinaryPath(version string, goos string, goarch string) (string, error) {
|
||||
func WaveshellBinaryPath(version string, goos string, goarch string) (string, error) {
|
||||
if !base.ValidGoArch(goos, goarch) {
|
||||
return "", fmt.Errorf("invalid goos/goarch combination: %s/%s", goos, goarch)
|
||||
}
|
||||
binaryDir := MShellBinaryDir()
|
||||
binaryDir := WaveshellBinaryDir()
|
||||
versionStr := semver.MajorMinor(version)
|
||||
if versionStr == "" {
|
||||
return "", fmt.Errorf("invalid mshell version: %q", version)
|
||||
return "", fmt.Errorf("invalid waveshell version: %q", version)
|
||||
}
|
||||
fileName := fmt.Sprintf("mshell-%s-%s.%s", versionStr, goos, goarch)
|
||||
fullFileName := filepath.Join(binaryDir, fileName)
|
||||
return fullFileName, nil
|
||||
}
|
||||
|
||||
func LocalMShellBinaryPath() (string, error) {
|
||||
return MShellBinaryPath(MShellVersion, runtime.GOOS, runtime.GOARCH)
|
||||
func LocalWaveshellBinaryPath() (string, error) {
|
||||
return WaveshellBinaryPath(WaveshellVersion, runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func MShellBinaryReader(version string, goos string, goarch string) (io.ReadCloser, error) {
|
||||
mshellPath, err := MShellBinaryPath(version, goos, goarch)
|
||||
func WaveshellBinaryReader(version string, goos string, goarch string) (io.ReadCloser, error) {
|
||||
waveshellPath, err := WaveshellBinaryPath(version, goos, goarch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd, err := os.Open(mshellPath)
|
||||
fd, err := os.Open(waveshellPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open mshell binary %q: %v", mshellPath, err)
|
||||
return nil, fmt.Errorf("cannot open waveshell binary %q: %v", waveshellPath, err)
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
@ -326,9 +326,9 @@ func sendCmdInput(pk *scpacket.FeInputPacketType) error {
|
||||
if pk.Remote.RemoteId == "" {
|
||||
return fmt.Errorf("input must set remoteid")
|
||||
}
|
||||
msh := remote.GetRemoteById(pk.Remote.RemoteId)
|
||||
if msh == nil {
|
||||
wsh := remote.GetRemoteById(pk.Remote.RemoteId)
|
||||
if wsh == nil {
|
||||
return fmt.Errorf("remote %s not found", pk.Remote.RemoteId)
|
||||
}
|
||||
return msh.HandleFeInput(pk)
|
||||
return wsh.HandleFeInput(pk)
|
||||
}
|
||||
|
@ -751,10 +751,10 @@ func UpdateCmdForRestart(ctx context.Context, ck base.CommandKey, ts int64, cmdP
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateCmdStartInfo(ctx context.Context, ck base.CommandKey, cmdPid int, mshellPid int) error {
|
||||
func UpdateCmdStartInfo(ctx context.Context, ck base.CommandKey, cmdPid int, waveshellPid int) error {
|
||||
return WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `UPDATE cmd SET cmdpid = ?, remotepid = ? WHERE screenid = ? AND lineid = ?`
|
||||
tx.Exec(query, cmdPid, mshellPid, ck.GetGroupId(), lineIdFromCK(ck))
|
||||
tx.Exec(query, cmdPid, waveshellPid, ck.GetGroupId(), lineIdFromCK(ck))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
sh2db "github.com/wavetermdev/waveterm/wavesrv/db"
|
||||
dbfs "github.com/wavetermdev/waveterm/wavesrv/db"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
)
|
||||
@ -29,7 +29,7 @@ const CmdLineSpecialMigration = 20
|
||||
const RISpecialMigration = 30
|
||||
|
||||
func MakeMigrate() (*migrate.Migrate, error) {
|
||||
fsVar, err := iofs.New(sh2db.MigrationFS, "migrations")
|
||||
fsVar, err := iofs.New(dbfs.MigrationFS, "migrations")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening iofs: %w", err)
|
||||
}
|
||||
|
@ -758,34 +758,34 @@ const (
|
||||
)
|
||||
|
||||
type RemoteRuntimeState struct {
|
||||
RemoteType string `json:"remotetype"`
|
||||
RemoteId string `json:"remoteid"`
|
||||
RemoteAlias string `json:"remotealias,omitempty"`
|
||||
RemoteCanonicalName string `json:"remotecanonicalname"`
|
||||
RemoteVars map[string]string `json:"remotevars"`
|
||||
Status string `json:"status"`
|
||||
ConnectTimeout int `json:"connecttimeout,omitempty"`
|
||||
CountdownActive bool `json:"countdownactive"`
|
||||
ErrorStr string `json:"errorstr,omitempty"`
|
||||
InstallStatus string `json:"installstatus"`
|
||||
InstallErrorStr string `json:"installerrorstr,omitempty"`
|
||||
NeedsMShellUpgrade bool `json:"needsmshellupgrade,omitempty"`
|
||||
NoInitPk bool `json:"noinitpk,omitempty"`
|
||||
AuthType string `json:"authtype,omitempty"`
|
||||
ConnectMode string `json:"connectmode"`
|
||||
AutoInstall bool `json:"autoinstall"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
RemoteIdx int64 `json:"remoteidx"`
|
||||
SSHConfigSrc string `json:"sshconfigsrc"`
|
||||
UName string `json:"uname"`
|
||||
MShellVersion string `json:"mshellversion"`
|
||||
WaitingForPassword bool `json:"waitingforpassword,omitempty"`
|
||||
Local bool `json:"local,omitempty"`
|
||||
IsSudo bool `json:"issudo,omitempty"`
|
||||
RemoteOpts *RemoteOptsType `json:"remoteopts,omitempty"`
|
||||
CanComplete bool `json:"cancomplete,omitempty"`
|
||||
ShellPref string `json:"shellpref,omitempty"`
|
||||
DefaultShellType string `json:"defaultshelltype,omitempty"`
|
||||
RemoteType string `json:"remotetype"`
|
||||
RemoteId string `json:"remoteid"`
|
||||
RemoteAlias string `json:"remotealias,omitempty"`
|
||||
RemoteCanonicalName string `json:"remotecanonicalname"`
|
||||
RemoteVars map[string]string `json:"remotevars"`
|
||||
Status string `json:"status"`
|
||||
ConnectTimeout int `json:"connecttimeout,omitempty"`
|
||||
CountdownActive bool `json:"countdownactive"`
|
||||
ErrorStr string `json:"errorstr,omitempty"`
|
||||
InstallStatus string `json:"installstatus"`
|
||||
InstallErrorStr string `json:"installerrorstr,omitempty"`
|
||||
NeedsWaveshellUpgrade bool `json:"needswaveshellupgrade,omitempty"`
|
||||
NoInitPk bool `json:"noinitpk,omitempty"`
|
||||
AuthType string `json:"authtype,omitempty"`
|
||||
ConnectMode string `json:"connectmode"`
|
||||
AutoInstall bool `json:"autoinstall"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
RemoteIdx int64 `json:"remoteidx"`
|
||||
SSHConfigSrc string `json:"sshconfigsrc"`
|
||||
UName string `json:"uname"`
|
||||
WaveshellVersion string `json:"waveshellversion"`
|
||||
WaitingForPassword bool `json:"waitingforpassword,omitempty"`
|
||||
Local bool `json:"local,omitempty"`
|
||||
IsSudo bool `json:"issudo,omitempty"`
|
||||
RemoteOpts *RemoteOptsType `json:"remoteopts,omitempty"`
|
||||
CanComplete bool `json:"cancomplete,omitempty"`
|
||||
ShellPref string `json:"shellpref,omitempty"`
|
||||
DefaultShellType string `json:"defaultshelltype,omitempty"`
|
||||
}
|
||||
|
||||
func (state RemoteRuntimeState) IsConnected() bool {
|
||||
|
Loading…
Reference in New Issue
Block a user