Skip to content

Commit

Permalink
Open path in terminal
Browse files Browse the repository at this point in the history
Allow opening of a path from a URL option, path=path.
If the terminal is busy, then warn the user and prompt them to
continue.

Signed-off-by: Ashley Cui <[email protected]>
  • Loading branch information
ashley-cui committed Oct 30, 2024
1 parent c36f441 commit b3cb9a4
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 5 deletions.
4 changes: 4 additions & 0 deletions pkg/systemd/overview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ class OverviewPage extends React.Component {
this.state.hostnameData.OperatingSystemPrettyName &&
<div className="ct-overview-header-subheading" id="system_information_os_text">{cockpit.format(_("running $0"), this.state.hostnameData.OperatingSystemPrettyName)}</div>}
</div>
{/* DEV ARTIFACT: FOR TESTING */}
<button ref={this.asdf}
className="pf-v5-c-button pf-m-secondary asdf"
onClick={() => cockpit.jump("/system/terminal#/?path=/home%2Fadmin%2Fa%2Fb")}>{_("asdf")}</button>
<div className='ct-overview-header-actions'>
{ show_superuser && <SuperuserIndicator proxy={this.superuser} /> }
{ "\n" }
Expand Down
109 changes: 105 additions & 4 deletions pkg/systemd/terminal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { createRoot } from "react-dom/client";
import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
import { NumberInput } from "@patternfly/react-core/dist/esm/components/NumberInput/index.js";
import { Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from "@patternfly/react-core/dist/esm/components/Toolbar/index.js";
import { Modal } from "@patternfly/react-core/dist/esm/components/Modal/index.js";

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import Modal.
import { Button } from '@patternfly/react-core/dist/esm/components/Button';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import Button.
import { Alert, AlertActionCloseButton, AlertActionLink } from "@patternfly/react-core/dist/esm/components/Alert/index.js";



import "./terminal.scss";

Expand All @@ -26,17 +31,21 @@ const _ = cockpit.gettext;
* Spawns the user's shell in the user's home directory.
*/
class UserTerminal extends React.Component {
createChannel(user) {
return cockpit.channel({
createChannel(user, dir) {
const ch = cockpit.channel({
payload: "stream",
spawn: [user.shell || "/bin/bash"],
environ: [
"TERM=xterm-256color",
],
directory: user.home || "/",
directory: dir || user.home || "/",
pty: true,
binary: true,
pid: null,
});
ch.addEventListener("ready", (_, msg) => this.setState({ pid: msg.pid }), { once: true });
ch.addEventListener("close", () => this.setState({ pid: null }), { once: true });
return ch;
}

constructor(props) {
Expand Down Expand Up @@ -66,12 +75,17 @@ const _ = cockpit.gettext;
title: 'Terminal',
theme: theme || "black-theme",
size: parseInt(size) || 16,
changePathBusy: false,
pid: null,
pathError: null,
};
this.onTitleChanged = this.onTitleChanged.bind(this);
this.onResetClick = this.onResetClick.bind(this);
this.onThemeChanged = this.onThemeChanged.bind(this);
this.onPlus = this.onPlus.bind(this);
this.onMinus = this.onMinus.bind(this);
this.onModal = this.onModal.bind(this);
this.onNavigate = this.onNavigate.bind(this);

this.terminalRef = React.createRef();
this.resetButtonRef = React.createRef();
Expand All @@ -81,8 +95,35 @@ const _ = cockpit.gettext;
}

async componentDidMount() {
cockpit.addEventListener("locationchanged", this.onNavigate);
const user = await cockpit.user();
this.setState({ user, channel: this.createChannel(user) });
var dir
if (cockpit.location.options.path) {
const exists = await this.dirExists();
if (exists){
dir = cockpit.location.options.path;
} else{
this.setState({ pathError: cockpit.location.options.path })
}
}
this.setState({ user, channel: this.createChannel(user, dir)});
}

async dirExists(){
var exists
const cmmd = "test -d " + cockpit.location.options.path
await cockpit.script(cmmd, [], {err: "message"})
.then(() => {
exists = true
})
.catch((err) => {
exists = false
})
return exists
}

componentWillUnmount() {
cockpit.removeEventListener("locationchanged", this.onNavigate);
}

onTitleChanged(title) {
Expand All @@ -95,6 +136,42 @@ const _ = cockpit.gettext;
document.cookie = cookie;
}

onModal(){
this.setState({ changePathBusy: false });
this.setState(prevState => ({ channel: this.createChannel(prevState.user, cockpit.location.options.path)}));
}

async onNavigate(){
// Clear old path errors
this.setState({ pathError: null })

// If there's no path to change to, then we're done here
if (!cockpit.location.options.path) {
return
}

// Check if path we're changing to exists
const exists = await this.dirExists();
if (!exists){
// Show error and clear location option for path
this.setState({ pathError: cockpit.location.options.path })
// cockpit.location.replace("")
} else if (this.state.pid !== null){
// Check if current shell has a process running in it, ie it's busy
const cmmd = "grep -qr '^PPid:[[:space:]]*" + this.state.pid + "$' /proc/*/status";
cockpit.script(cmmd, [], {err: "message"})
.then(() => {
// it's busy: show a busy modal
this.setState({ changePathBusy: true })
})
.catch((err) => {
// it's not busy: change the path immediately
this.setState(prevState => ({ channel: this.createChannel(prevState.user, cockpit.location.options.path) }));
})
}

}

onPlus() {
this.setState((state, _) => {
localStorage.setItem('terminal:font-size', state.size + 1);
Expand Down Expand Up @@ -186,6 +263,30 @@ const _ = cockpit.gettext;
</ToolbarContent>
</Toolbar>
</div>
<div className={"warn"}>
{this.state.pathError && <Alert isInline variant='danger'
title={'Error opening directory: ' + this.state.pathError + ' does not exist' }
actionClose={<AlertActionCloseButton onClose={() => this.setState({ pathError: null })}>
</AlertActionCloseButton>}>
</Alert>}
{this.state.changePathBusy && <Alert title={_("There is still a process running in this terminal. Changing the directory will kill it.")}
variant="warning"
actionClose={<AlertActionCloseButton onClose={() => this.setState({ changePathBusy: false })}></AlertActionCloseButton>}
actionLinks={
<React.Fragment>
<AlertActionLink onClick={this.onModal}>
Continue
</AlertActionLink>
<AlertActionLink // eslint-disable-next-line no-console
onClick={() => this.setState({ changePathBusy: false })}
>
Cancel
</AlertActionLink>
</React.Fragment>
}>
</Alert>
}
</div>
<div className={"terminal-body " + this.state.theme} id="the-terminal">
{terminal}
</div>
Expand Down
2 changes: 1 addition & 1 deletion pkg/systemd/terminal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.console-ct-container {
block-size: 100%;
display: grid;
grid-template-rows: auto 1fr;
grid-template-rows: auto auto 1fr;
overflow: hidden;
}

Expand Down

0 comments on commit b3cb9a4

Please sign in to comment.