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