Skip to content

Commit

Permalink
feat: improve command terminal UI (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
leo220yuyaodog committed May 1, 2024
1 parent af310a7 commit 46ce235
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 71 deletions.
8 changes: 8 additions & 0 deletions controllers/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ func (c *ApiController) ExecCommand() {
}
}

// GetExecOutput
// @Title GetExecOutput
// @Tag Command API
// @Description get the output of the command
// @Param id query string true "The id ( owner/name ) of the command"
// @Success 200 {string} The Response object
// @router /get-exec-output [get]

func (c *ApiController) GetExecOutput() {
id := c.Input().Get("id")

Expand Down
9 changes: 6 additions & 3 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
"react-device-detect": "1.17.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.3",
"react-highlight-words": "^0.20.0",
"react-i18next": "^11.8.7",
"react-router-dom": "^5.3.3",
"react-scripts": "5.0.1",
"react-highlight-words": "^0.20.0"
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
"xterm-addon-web-links": "^0.9.0"
},
"scripts": {
"start": "set PORT=18001 && craco start",
Expand Down Expand Up @@ -50,9 +53,9 @@
"devDependencies": {
"@babel/core": "^7.19.3",
"@babel/eslint-parser": "^7.19.1",
"babel-preset-react-app": "^10.0.0",
"eslint": "^8.25.0",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-unused-imports": "^2.0.0",
"babel-preset-react-app": "^10.0.0"
"eslint-plugin-unused-imports": "^2.0.0"
}
}
111 changes: 43 additions & 68 deletions web/src/CommandEditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import * as CommandBackend from "./backend/CommandBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import * as AssetBackend from "./backend/AssetBackend";
import "xterm/css/xterm.css";
import {Terminal} from "xterm";
import {FitAddon} from "xterm-addon-fit";
import {WebLinksAddon} from "xterm-addon-web-links";

class CommandEditPage extends React.Component {
constructor(props) {
Expand All @@ -30,28 +34,40 @@ class CommandEditPage extends React.Component {
owner: props.account.owner,
commandName: props.match.params.commandName !== undefined ? props.match.params.commandName : "",
mode: props.location.mode !== undefined ? props.location.mode : "edit",
results: [],
terminals: new Map(),
};
this.scrollState = {};
}

UNSAFE_componentWillMount() {
componentDidMount() {
this.getCommand();
this.getAssets();
}

componentDidUpdate(prevProps, prevState) {
const scrollThreshold = 5;
this.state.results.forEach((result) => {
const textarea = document.getElementById(`textarea-${result.title}`);
if (textarea !== null) {
if (this.scrollState[result.title] !== undefined && Math.abs(textarea.scrollTop - this.scrollState[result.title]) > scrollThreshold) {
return;
if (prevState.command?.assets !== this.state.command?.assets) {
this.state.command.assets.forEach((asset) => {
if (this.state.terminals.get(asset) === undefined) {
const term = new Terminal({
fontFamily: "monaco, Consolas, \"Lucida Console\", monospace",
fontSize: 15,
rendererType: "canvas",
convertEol: true,
cursorBlink: true,
cursorStyle: "block",
rightClickSelectsWord: true,
});

const webLinksAddon = new WebLinksAddon();
const fitAddon = new FitAddon();
term.loadAddon(webLinksAddon);
term.loadAddon(fitAddon);

term.open(document.getElementById(`terminal-${asset}`));
fitAddon.fit();
this.state.terminals.set(asset, term);
}
textarea.scrollTop = textarea.scrollHeight;
this.scrollState[result.title] = textarea.scrollTop;
}
});
});
}
}

getCommand() {
Expand All @@ -60,9 +76,6 @@ class CommandEditPage extends React.Component {
if (res.status === "ok") {
this.setState({
command: res.data,
results: res.data.assets.map(asset => {
return {title: asset, text: ""};
}),
});
} else {
Setting.showMessage("error", `Failed to get command: ${res.msg}`);
Expand Down Expand Up @@ -160,18 +173,7 @@ class CommandEditPage extends React.Component {
<Select virtual={false} style={{width: "100%"}} mode="multiple" value={command.assets}
options={this.state.assets.filter(asset => asset.type === "SSH").map(asset => Setting.getOption(asset.displayName, asset.name))}
onChange={value => {
const results = [];
value.forEach((asset) => {
if (this.state.results.find(result => result.title === asset) !== undefined) {
results.push(this.state.results.find(result => result.title === asset));
} else {
results.push({title: asset, text: ""});
}
});
this.updateCommandField("assets", value);
this.setState({
results: results,
});
}}
/>
</Col>
Expand All @@ -188,25 +190,11 @@ class CommandEditPage extends React.Component {
if (jsonData.text === "") {
jsonData.text = "\n";
}
const results = this.state.results;
if (this.state.results.find(result => result.title === asset) === undefined) {
results.push({title: asset, text: jsonData.text});
} else {
results.find(result => result.title === asset).text += jsonData.text + "\n";
}
this.setState({
results: results,
});
const terminal = this.state.terminals.get(asset);
terminal.write(jsonData.text + "\n");
}, (error) => {
const results = this.state.results;
if (this.state.results.find(result => result.title === asset) === undefined) {
results.push({title: asset, text: error});
} else {
results.find(result => result.title === asset).text += error;
}
this.setState({
results: results,
});
const terminal = this.state.terminals.get(asset);
terminal.write(error + "\n");
});
}
);
Expand All @@ -225,41 +213,28 @@ class CommandEditPage extends React.Component {
gutter: 16,
column: 2,
}}
dataSource={this.state.results}
renderItem={(item, index) => (
dataSource={this.state.command?.assets}
renderItem={(item) => (
<List.Item>
<Card title={item.title} size="small" extra={
<Card title={item} size="small" extra={
<Button type="primary" onClick={() => {
CommandBackend.execCommand(this.state.owner, this.state.commandName, item.title, (data) => {
CommandBackend.execCommand(this.state.owner, this.state.commandName, item, (data) => {
const jsonData = JSON.parse(data);
if (jsonData.text === "") {
jsonData.text = "\n";
}
const results = this.state.results;
if (this.state.results.find(result => result.title === item.title) === undefined) {
results.push({title: item.title, text: jsonData.text});
} else {
results.find(result => result.title === item.title).text += jsonData.text + "\n";
}
this.setState({
results: results,
});
const terminal = this.state.terminals.get(item);
terminal.write(jsonData.text + "\n");
}, (error) => {
const results = this.state.results;
if (this.state.results.find(result => result.title === item.title) === undefined) {
results.push({title: item.title, text: error});
} else {
results.find(result => result.title === item.title).text += error;
}
this.setState({
results: results,
});
const terminal = this.state.terminals.get(item);
terminal.write(error + "\n");
});
}}>
{i18next.t("command:Run")}
</Button>
}>
<Input.TextArea id={`textarea-${item.title}`} value={item.text} rows={8} readOnly />
<div id={`terminal-${item}`}
/>
</Card>
</List.Item>
)}
Expand Down
15 changes: 15 additions & 0 deletions web/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10322,6 +10322,21 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==

xterm-addon-fit@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz#48ca99015385141918f955ca7819e85f3691d35f"
integrity sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==

xterm-addon-web-links@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.9.0.tgz#c65b18588d1f613e703eb6feb7f129e7ff1c63e7"
integrity sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q==

xterm@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46"
integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==

y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
Expand Down

0 comments on commit 46ce235

Please sign in to comment.