@@ -19,22 +19,184 @@ import { join } from "path"
1919import stripAnsi from "strip-ansi"
2020import { Arguments } from "@kui-shell/core"
2121
22- import kubeEvents from "./kube"
23- import torchEvents from "./torch"
22+ import parseKubeEvents , { collateEvent as collateKubeEvent , KubeEvent } from "./kube"
23+ import parseTorchEvents , { collateEvent as collateTorchEvent , TorchEvent } from "./torch"
2424
2525import { expand } from "../../lib/util"
2626import Grid from "../../components/Grid"
2727
28+ interface EventState {
29+ kubeEvents : KubeEvent [ ]
30+ torchEvents : TorchEvent [ ]
31+ }
32+
33+ type State = EventState & {
34+ nKubeEvents : number
35+ nTorchEvents : number
36+ catastrophicError ?: Error
37+ }
38+
39+ type Props = EventState & {
40+ /** Follow kube events? */
41+ onKube ?( eventType : "data" , cb : ( data : any ) => void ) : void
42+
43+ /** Follow torch events? */
44+ onTorch ?( eventType : "data" , cb : ( data : any ) => void ) : void
45+
46+ /** Stop watching? */
47+ unwatch ?( ) : void
48+ }
49+
50+ class Events extends React . PureComponent < Props , State > {
51+ public constructor ( props : Props ) {
52+ super ( props )
53+
54+ const kubeEvents = props . kubeEvents || [ ]
55+ const torchEvents = props . torchEvents || [ ]
56+ this . state = {
57+ kubeEvents,
58+ torchEvents,
59+ nKubeEvents : kubeEvents . length ,
60+ nTorchEvents : torchEvents . length ,
61+ }
62+
63+ // reduce any initial flood of events
64+ let queueFlushHysteresis = 300
65+ setTimeout ( ( ) => ( queueFlushHysteresis = 0 ) , 5000 )
66+
67+ if ( props . onKube ) {
68+ let queue : string [ ] = [ ]
69+ let flushTO : ReturnType < typeof setTimeout >
70+
71+ props . onKube ( "data" , ( line ) => {
72+ if ( typeof line === "string" ) {
73+ queue . push ( stripAnsi ( line ) )
74+
75+ if ( flushTO ) {
76+ clearTimeout ( flushTO )
77+ }
78+
79+ flushTO = setTimeout ( ( ) => {
80+ const toBeProcessed = queue
81+ queue = [ ]
82+ this . setState ( ( curState ) => {
83+ toBeProcessed . forEach ( ( line ) => collateKubeEvent ( curState . kubeEvents , line ) )
84+ return {
85+ nKubeEvents : curState . kubeEvents . length ,
86+ }
87+ } )
88+ } , queueFlushHysteresis )
89+ }
90+ } )
91+ }
92+ if ( props . onTorch ) {
93+ let queue : string [ ] = [ ]
94+ let flushTO : ReturnType < typeof setTimeout >
95+
96+ props . onTorch ( "data" , ( line ) => {
97+ if ( typeof line === "string" ) {
98+ queue . push ( stripAnsi ( line ) )
99+
100+ if ( flushTO ) {
101+ clearTimeout ( flushTO )
102+ }
103+
104+ flushTO = setTimeout ( ( ) => {
105+ const toBeProcessed = queue
106+ queue = [ ]
107+ this . setState ( ( curState ) => {
108+ toBeProcessed . forEach ( ( line ) => collateTorchEvent ( curState . torchEvents , line ) )
109+ return {
110+ nTorchEvents : curState . torchEvents . length ,
111+ }
112+ } )
113+ } , queueFlushHysteresis )
114+ }
115+ } )
116+ }
117+ }
118+
119+ public static getDerivedStateFromError ( error : Error ) {
120+ return { catastrophicError : error }
121+ }
122+
123+ public componentDidCatch ( error : Error , errorInfo : React . ErrorInfo ) {
124+ console . error ( "catastrophic error in Scalar" , error , errorInfo )
125+ }
126+
127+ public componentWillUnmount ( ) {
128+ if ( this . props . unwatch ) {
129+ this . props . unwatch ( )
130+ }
131+ }
132+
133+ private get events ( ) {
134+ return [ ...this . state . kubeEvents , ...this . state . torchEvents ]
135+ . filter ( ( _ ) => ! _ . hidden )
136+ . sort ( ( a , b ) => a . timestamp - b . timestamp )
137+ }
138+
139+ public render ( ) {
140+ if ( this . state . catastrophicError ) {
141+ return "Internal Error"
142+ } else {
143+ return < Grid events = { this . events } />
144+ }
145+ }
146+ }
147+
28148async function eventsUI ( filepath : string , REPL : Arguments [ "REPL" ] ) {
29- const [ kube , logs ] = await Promise . all ( [
30- REPL . qexec < string > ( `vfs fslice ${ join ( expand ( filepath ) , "events/kubernetes.txt" ) } 0` ) . then ( stripAnsi ) ,
31- REPL . qexec < string > ( `vfs fslice ${ join ( expand ( filepath ) , "logs/job.txt" ) } 0` ) . then ( stripAnsi ) ,
32- ] )
33-
34- const events = [ ...kubeEvents ( kube ) , ...torchEvents ( logs ) ]
35- . filter ( ( _ ) => ! _ . hidden )
36- . sort ( ( a , b ) => a . timestamp - b . timestamp )
37- return < Grid events = { events } />
149+ const jobFilepath = join ( expand ( filepath ) , "logs/job.txt" )
150+ const kubeFilepath = join ( expand ( filepath ) , "events/kubernetes.txt" )
151+
152+ if ( process . env . FOLLOW ) {
153+ const [ TailFile , split2 ] = await Promise . all ( [
154+ import ( "@logdna/tail-file" ) . then ( ( _ ) => _ . default ) ,
155+ import ( "split2" ) . then ( ( _ ) => _ . default ) ,
156+ ] )
157+
158+ const kubeTail = new TailFile ( kubeFilepath , {
159+ startPos : 0 ,
160+ pollFileIntervalMs : 500 ,
161+ } )
162+ kubeTail . on ( "tail_error" , ( err ) => {
163+ console . error ( err )
164+ } )
165+
166+ const jobTail = new TailFile ( jobFilepath , {
167+ startPos : 0 ,
168+ pollFileIntervalMs : 500 ,
169+ } )
170+ jobTail . on ( "tail_error" , ( err ) => {
171+ console . error ( err )
172+ } )
173+
174+ kubeTail . start ( )
175+ jobTail . start ( )
176+
177+ const kubeSplitter = kubeTail . pipe ( split2 ( ) )
178+ const torchSplitter = jobTail . pipe ( split2 ( ) )
179+
180+ return (
181+ < Events
182+ kubeEvents = { [ ] }
183+ torchEvents = { [ ] }
184+ onKube = { kubeSplitter . on . bind ( kubeSplitter ) }
185+ onTorch = { torchSplitter . on . bind ( torchSplitter ) }
186+ unwatch = { ( ) => {
187+ kubeTail . quit ( )
188+ jobTail . quit ( )
189+ } }
190+ />
191+ )
192+ } else {
193+ const [ kube , torch ] = await Promise . all ( [
194+ REPL . qexec < string > ( `vfs fslice ${ kubeFilepath } 0` ) . then ( stripAnsi ) . then ( parseKubeEvents ) ,
195+ REPL . qexec < string > ( `vfs fslice ${ jobFilepath } 0` ) . then ( stripAnsi ) . then ( parseTorchEvents ) ,
196+ ] )
197+
198+ return < Events kubeEvents = { kube } torchEvents = { torch } />
199+ }
38200}
39201
40202export default async function eventsCmd ( args : Arguments ) {
0 commit comments