Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 00e8786

Browse files
committed
fix: replace Containers with Logs tab
this PR also fixes - a few missing pieces to support Sidecar Toolbar updates from react components - ability for PTY streaming consumers to direct xon/xoff flow control - removes a number of way out of date css rules Fixes #4603
1 parent 7d874a6 commit 00e8786

File tree

31 files changed

+465
-399
lines changed

31 files changed

+465
-399
lines changed

packages/core/src/core/jobs/job.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,10 @@ export interface Abortable {
1818
abort(): void
1919
}
2020

21+
export interface FlowControllable {
22+
xon(): void
23+
xoff(): void
24+
}
25+
2126
/** in the future, a WatchableJob may be more than Abortable, e.g. Suspendable */
2227
export type WatchableJob = Abortable

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export {
7979
isMetadataBearing as isResourceWithMetadata
8080
} from './models/entity'
8181
export { isWatchable, Watchable, Watcher, WatchPusher } from './core/jobs/watchable'
82-
export { Abortable } from './core/jobs/job'
82+
export { Abortable, FlowControllable } from './core/jobs/job'
8383
import { Tab } from './webapp/tab'
8484
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8585
export async function History(tab: Tab) {

packages/core/src/models/execOptions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
import { ExecType } from './command'
1818
import { Tab } from '../webapp/tab'
19-
import { Streamable, StreamableFactory } from './streamable'
19+
import { Stream, Streamable, StreamableFactory } from './streamable'
2020
import { Block } from '../webapp/models/block'
21-
import { Abortable } from '../core/jobs/job'
21+
import { Abortable, FlowControllable } from '../core/jobs/job'
2222

2323
export interface ExecOptions {
2424
/** force execution in a given tab? */
@@ -89,7 +89,7 @@ export interface ExecOptions {
8989
stderr?: (str: string) => any // eslint-disable-line @typescript-eslint/no-explicit-any
9090

9191
/** on job init, pass the job, and get back a stdout */
92-
onInit?: (job: Abortable) => (str: Streamable) => void
92+
onInit?: (job: Abortable & FlowControllable) => Stream | Promise<Stream>
9393

9494
parameters?: any // eslint-disable-line @typescript-eslint/no-explicit-any
9595
entity?: any // eslint-disable-line @typescript-eslint/no-explicit-any

packages/core/src/models/streamable.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ import { ScalarResponse } from './entity'
1919
export type Streamable = ScalarResponse // SimpleEntity | Table | MixedResponse | MultiModalResponse
2020
export default Streamable
2121

22-
export type Stream = (response: Streamable) => Promise<void>
22+
export type Stream = (response: Streamable) => void | Promise<void>
2323

24-
export type StreamableFactory = () => Promise<Stream>
24+
export type StreamableFactory = () => Stream | Promise<Stream>

plugins/plugin-bash-like/src/pty/client.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ const remoteChannelFactory: ChannelFactory = async (tab: Tab) => {
708708
const { proto, port, path, uid, gid } = resp.content
709709
const protoHostPortContextRoot = window.location.href
710710
.replace(/#?\/?$/, '')
711-
.replace(/^http(s?):\/\/([^:/]+)(:\d+)?/, `${proto}://$2${port === -1 ? '$3' : ':'+port}`)
711+
.replace(/^http(s?):\/\/([^:/]+)(:\d+)?/, `${proto}://$2${port === -1 ? '$3' : ':' + port}`)
712712
.replace(/\/(index\.html)?$/, '')
713713
const url = new URL(path, protoHostPortContextRoot).href
714714
debug('websocket url', url, proto, port, path, uid, gid)
@@ -938,11 +938,20 @@ export const doExec = (
938938

939939
if (execOptions.onInit) {
940940
const job = {
941+
xon: () => {
942+
debug('xon requested')
943+
ws.send(JSON.stringify({ type: 'xon', uuid: ourUUID }))
944+
},
945+
xoff: () => {
946+
debug('xoff requested')
947+
ws.send(JSON.stringify({ type: 'xoff', uuid: ourUUID }))
948+
},
941949
abort: () => {
950+
debug('abort requested')
942951
ws.send(JSON.stringify({ type: 'kill', uuid: ourUUID }))
943952
}
944953
}
945-
execOptions.stdout = execOptions.onInit(job)
954+
execOptions.stdout = await execOptions.onInit(job)
946955
}
947956
}
948957

plugins/plugin-bash-like/src/pty/server.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,24 @@ export const onConnection = (exitNow: ExitHandler, uid?: number, gid?: number) =
251251
} = JSON.parse(data)
252252

253253
switch (msg.type) {
254+
case 'xon': {
255+
const shell = msg.uuid && (await shells[msg.uuid])
256+
if (shell) {
257+
const RESUME = '\x11' // this is XON
258+
shell.write(RESUME)
259+
}
260+
break
261+
}
262+
263+
case 'xoff': {
264+
const shell = msg.uuid && (await shells[msg.uuid])
265+
if (shell) {
266+
const PAUSE = '\x13' // this is XOFF
267+
shell.write(PAUSE)
268+
}
269+
break
270+
}
271+
254272
case 'kill': {
255273
const shell = msg.uuid && (await shells[msg.uuid])
256274
if (shell) {

plugins/plugin-client-common/src/components/Content/Eval.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@ import * as React from 'react'
1818
import * as Debug from 'debug'
1919

2020
import {
21-
ParsedOptions,
22-
Tab as KuiTab,
2321
ScalarResource,
2422
SupportedStringContent,
25-
MultiModalResponse,
2623
isCommandStringContent,
2724
FunctionThatProducesContent,
2825
ReactProvider,
@@ -33,18 +30,12 @@ import {
3330
} from '@kui-shell/core'
3431

3532
import { Loading } from '../../'
36-
import KuiMMRContent from './KuiContent'
33+
import KuiMMRContent, { KuiMMRProps } from './KuiContent'
3734

3835
const debug = Debug('plugins/sidecar/Eval')
3936

40-
interface EvalProps {
41-
tab: KuiTab
37+
interface EvalProps extends Omit<KuiMMRProps, 'mode'> {
4238
command: string | FunctionThatProducesContent
43-
args: {
44-
argvNoOptions: string[]
45-
parsedOptions: ParsedOptions
46-
}
47-
response: MultiModalResponse
4839
contentType?: SupportedStringContent
4940
}
5041

@@ -129,6 +120,6 @@ export default class Eval extends React.PureComponent<EvalProps, EvalState> {
129120
contentType: this.state.contentType
130121
}
131122

132-
return <KuiMMRContent tab={this.props.tab} mode={mode} response={this.props.response} args={this.props.args} />
123+
return <KuiMMRContent {...this.props} mode={mode} />
133124
}
134125
}

plugins/plugin-client-common/src/components/Content/KuiContent.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import HTMLString from './HTMLString'
4242
import HTMLDom from './Scalar/HTMLDom'
4343
import RadioTableSpi from '../spi/RadioTable'
4444

45-
interface KuiMMRProps {
45+
export interface KuiMMRProps {
4646
tab: KuiTab
4747
mode: Content
4848
response: MultiModalResponse
@@ -55,7 +55,7 @@ interface KuiMMRProps {
5555

5656
export default class KuiMMRContent extends React.PureComponent<KuiMMRProps> {
5757
public render() {
58-
const { tab, mode, response, willUpdateToolbar, args } = this.props
58+
const { tab, mode, response, willUpdateToolbar } = this.props
5959

6060
if (isStringWithOptionalContentType(mode)) {
6161
if (mode.contentType === 'text/html') {
@@ -74,18 +74,16 @@ export default class KuiMMRContent extends React.PureComponent<KuiMMRProps> {
7474
<Editor
7575
content={mode}
7676
readOnly={false}
77-
willUpdateToolbar={willUpdateToolbar}
77+
willUpdateToolbar={!response.toolbarText && willUpdateToolbar}
7878
response={response}
7979
repl={tab.REPL}
8080
/>
8181
)
8282
}
8383
} else if (isCommandStringContent(mode)) {
84-
return (
85-
<Eval tab={tab} command={mode.contentFrom} contentType={mode.contentType} response={response} args={args} />
86-
)
84+
return <Eval {...this.props} command={mode.contentFrom} contentType={mode.contentType} />
8785
} else if (isFunctionContent(mode)) {
88-
return <Eval tab={tab} command={mode.content} response={response} args={args} />
86+
return <Eval {...this.props} command={mode.content} />
8987
} else if (isScalarContent(mode)) {
9088
if (isReactProvider(mode)) {
9189
return mode.react({ willUpdateToolbar })

plugins/plugin-client-common/src/components/Views/Sidecar/Toolbar.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export type Props = {
3131
}
3232
}
3333

34+
/** helper to ensure exhaustiveness of the switch statement below */
35+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
36+
function assertUnreachable(x: never): never {
37+
throw new Error('Did not expect to get here')
38+
}
39+
3440
export default class Toolbar extends React.PureComponent<Props> {
3541
private icon() {
3642
if (this.props.toolbarText) {
@@ -40,9 +46,13 @@ export default class Toolbar extends React.PureComponent<Props> {
4046
return <Icons icon="Info" />
4147
case 'warning':
4248
return <Icons icon="Warning" />
43-
default:
49+
case 'error':
4450
return <Icons icon="Error" />
4551
}
52+
53+
// this bit of magic ensures exhaustiveness of the switch;
54+
// reference: https://stackoverflow.com/a/39419171
55+
return assertUnreachable(type)
4656
}
4757
}
4858

plugins/plugin-client-common/src/components/spi/Icons/impl/PatternFly.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import {
3535
ArrowLeftIcon as Back,
3636
ArrowRightIcon as Forward,
3737
InfoCircleIcon as Info,
38-
WarningTriangleIcon as Warning,
39-
ExclamationTriangleIcon as Oops,
38+
ExclamationTriangleIcon as Warning,
39+
BombIcon as Oops,
4040
ListIcon as List,
4141
ThIcon as Grid,
4242
CaretLeftIcon as PreviousPage,

0 commit comments

Comments
 (0)