@@ -38,11 +38,18 @@ import EmbedPanelGrafana from '~/modules/compute/components/grafana/EmbedPanelGr
3838interface JobDetailsPanelProps {
3939 job ?: Job
4040 jobMetadata ?: JobMetadata
41- system ?: System
41+ system ?: System ,
42+ stdoutFile ?: File
43+ stderrFile ?: File
4244}
4345
44- const JobDetailsPanel : React . FC < JobDetailsPanelProps > = ( { job, jobMetadata, system } ) => {
46+ const JobDetailsPanel : React . FC < JobDetailsPanelProps > = ( { job, jobMetadata, system, stdoutFile , stderrFile } ) => {
4547 const [ cancelDialogOpen , setCancelDialogOpen ] = useState ( false )
48+ const [ downloadkDialogOpen , setDownloadDialogOpen ] = useState ( false )
49+ const handleDownload = ( ) => {
50+ setDownloadDialogOpen ( true )
51+ }
52+
4653 return (
4754 < >
4855 < JobCancelDialog
@@ -62,7 +69,8 @@ const JobDetailsPanel: React.FC<JobDetailsPanelProps> = ({ job, jobMetadata, sys
6269 </ button >
6370 </ div >
6471 ) }
65- < h3 className = 'text-sm font-semibold mb-3' > Job details</ h3 >
72+
73+ < h3 className = 'font-semibold mb-3' > Job details</ h3 >
6674 < AttributesList >
6775 < AttributesListItem label = 'Job ID' > #{ job ?. jobId } </ AttributesListItem >
6876 < AttributesListItem label = 'Name' > { job ?. name } </ AttributesListItem >
@@ -80,8 +88,8 @@ const JobDetailsPanel: React.FC<JobDetailsPanelProps> = ({ job, jobMetadata, sys
8088 ) }
8189 </ AttributesListItem >
8290 </ AttributesList >
83- < div className = 'mt-4 mb-6 border-b border-gray-900/10' />
84- < h3 className = 'text-sm font-semibold mb-3' > Execution times</ h3 >
91+
92+ < h3 className = 'font-semibold mb-3 mt-9 ' > Execution times</ h3 >
8593 < AttributesList >
8694 < AttributesListItem label = 'Start time' >
8795 { formatDateTimeFromTimestamp ( { timestamp : job ?. time . start } ) }
@@ -93,8 +101,53 @@ const JobDetailsPanel: React.FC<JobDetailsPanelProps> = ({ job, jobMetadata, sys
93101 { formatTime ( { time : job ?. time . elapsed } ) }
94102 </ AttributesListItem >
95103 </ AttributesList >
96- < div className = 'mt-4 mb-6 border-b border-gray-900/10' />
97- < h3 className = 'text-sm font-semibold mb-3' > System and resource details</ h3 >
104+
105+ < h3 className = 'font-semibold mb-3 mt-9' > Files</ h3 >
106+ < AttributesList >
107+ < AttributesListItem label = 'StdOut' > < div className = 'flex' > { jobMetadata ?. standardOutput || 'N/A' }
108+ { stdoutFile && (
109+ < >
110+ < DownloadDialog
111+ system = { system ?. name || '' }
112+ file = { stdoutFile }
113+ open = { downloadkDialogOpen }
114+ onClose = { ( ) => setDownloadDialogOpen ( false ) }
115+ />
116+ < button
117+ onClick = { handleDownload }
118+ title = 'Download STDOUT log'
119+ className = 'w-8 h-8 flex items-center justify-center rounded-md border text-neutral-600 hover:text-neutral-800 hover:bg-neutral-100'
120+ >
121+ < ArrowDownCircleIcon className = 'w-4 h-4' />
122+ </ button >
123+ </ >
124+ ) }
125+ </ div >
126+ </ AttributesListItem >
127+ < AttributesListItem label = 'StdErr' > { jobMetadata ?. standardError || 'N/A' }
128+ { stderrFile && (
129+ < >
130+ < DownloadDialog
131+ system = { system ?. name || '' }
132+ file = { stderrFile }
133+ open = { downloadkDialogOpen }
134+ onClose = { ( ) => setDownloadDialogOpen ( false ) }
135+ />
136+ < button
137+ onClick = { handleDownload }
138+ title = 'Download STDOUT log'
139+ className = 'w-8 h-8 flex items-center justify-center rounded-md border text-neutral-600 hover:text-neutral-800 hover:bg-neutral-100'
140+ >
141+ < ArrowDownCircleIcon className = 'w-4 h-4' />
142+ </ button >
143+ </ >
144+ ) }
145+ </ AttributesListItem >
146+ < AttributesListItem label = 'StdIn' > { jobMetadata ?. standardInput || 'N/A' } </ AttributesListItem >
147+ < AttributesListItem label = 'Working directory' > { job ?. workingDirectory || 'N/A' } </ AttributesListItem >
148+ </ AttributesList >
149+
150+ < h3 className = 'font-semibold mb-3 mt-9' > System and resource details</ h3 >
98151 < AttributesList >
99152 < AttributesListItem label = 'System name' >
100153 < LabelBadge color = { LabelColor . YELLOW } > { system ?. name } </ LabelBadge >
@@ -104,7 +157,6 @@ const JobDetailsPanel: React.FC<JobDetailsPanelProps> = ({ job, jobMetadata, sys
104157 </ AttributesListItem >
105158 < AttributesListItem label = 'Nodes' > { job ?. nodes } </ AttributesListItem >
106159 < AttributesListItem label = 'Partition' > { job ?. partition } </ AttributesListItem >
107- < AttributesListItem label = 'Working directory' > { job ?. workingDirectory } </ AttributesListItem >
108160 </ AttributesList >
109161 </ >
110162 )
@@ -115,10 +167,10 @@ interface JobDetailCenterProps {
115167 jobMetadata ?: JobMetadata
116168 system ?: System
117169 activeTab : OutputTabId
118- stdout : string
170+ stdout ? : string
119171 stdoutFile ?: File
120- stdin : string
121- stderr : string
172+ stdin ? : string
173+ stderr ? : string
122174 stderrFile ?: File
123175 script ?: string
124176 dashboards ?: GrafanaDashboard [ ]
@@ -143,59 +195,49 @@ const JobDetailCenter: React.FC<JobDetailCenterProps> = ({
143195 OUTPUT_TABS . find ( ( t ) => t . id === 'resources' ) ! . enabled = false
144196 }
145197 return (
146- < div className = 'flex flex-1 rounded-xl border bg-white/70 shadow-sm' >
147- < div className = 'flex-1 min-w-0 h-full min-h-0' >
198+ < div className = 'flex-1 rounded-xl border bg-white shadow-sm m-6 pt-2' style = { { marginTop : '20px' } } >
199+ < div className = 'sticky top-16 flex items-center justify-between bg-white p-4 px-3 py-2 shrink-0 z-9 ' >
200+ < select name = 'datasource' value = { activeTab } onChange = { ( e ) => onChangeTab ( e . target . value ) } className = 'flex-none w-64 border-gray-300 focus:border-blue-300 focus:ring-blue-300 rounded-md border py-2 px-3 shadow-sm sm:text-sm focus:outline-none' >
201+ < option value = 'stdout' > Job StdOut</ option >
202+ < option value = 'stderr' > Job StdErr</ option >
203+ < option value = 'stdin' > Job StdIn</ option >
204+ < option value = 'script' > Job Script</ option >
205+ < option value = 'resources' > Dashboards</ option >
206+ </ select >
207+ </ div >
208+ < div className = 'min-w-0 h-full min-h-0 pt-4 ' >
148209 { activeTab === 'stdout' && (
149- < ConsolePane
150- title = 'Job output – STDOUT'
151- content = { stdout }
152- system = { system }
153- filePath = { jobMetadata ?. standardOutput }
154- stdFile = { stdoutFile }
155- />
210+ < ConsolePane content = { stdout } />
156211 ) }
157212 { activeTab === 'stdin' && (
158- < ConsolePane
159- title = 'Job input – STDIN'
160- content = { stdin }
161- filePath = { jobMetadata ?. standardInput }
162- />
213+ < ConsolePane content = { stdin } />
163214 ) }
164215 { activeTab === 'stderr' && (
165- < ConsolePane
166- title = 'Job output – STDERR'
167- content = { stderr }
168- system = { system }
169- filePath = { jobMetadata ?. standardError }
170- stdFile = { stderrFile }
171- />
216+ < ConsolePane content = { stderr } />
172217 ) }
173- { activeTab === 'script' && < ScriptPane script = { script } /> }
218+ { activeTab === 'script' && < ConsolePane content = { script } /> }
174219 { activeTab === 'resources' && dashboards && dashboards . length > 0 && (
175220 < ResourcesPaneMulti job = { job ! } dashboards = { dashboards } title = 'Resources' />
176221 ) }
177222 </ div >
178223 < aside className = 'fixed right-0 top-16 bottom-12 w-[30rem] border-l bg-white overflow-y-auto' >
179- < RightTabs active = { activeTab } onChange = { onChangeTab } />
180224 < div className = 'p-4' >
181- < JobDetailsPanel job = { job } jobMetadata = { jobMetadata } system = { system } />
225+ < JobDetailsPanel job = { job } jobMetadata = { jobMetadata } system = { system } stdoutFile = { stdoutFile } stderrFile = { stderrFile } />
182226 </ div >
183227 </ aside >
184228 </ div >
185229 )
186230}
187231
188232interface ConsolePaneProps {
189- title : string
190- content : string
191- system ?: System
192- filePath ?: string | null
193- stdFile ?: File
233+
234+ content ?: string
235+
194236}
195237
196- const ConsolePane : React . FC < ConsolePaneProps > = ( { title , content, system , filePath , stdFile } ) => {
238+ const ConsolePane : React . FC < ConsolePaneProps > = ( { content} ) => {
197239 const scrollerRef = useRef < HTMLDivElement | null > ( null )
198- const [ downloadkDialogOpen , setDownloadDialogOpen ] = useState ( false )
240+
199241
200242 useEffect ( ( ) => {
201243 const el = scrollerRef . current
@@ -204,59 +246,15 @@ const ConsolePane: React.FC<ConsolePaneProps> = ({ title, content, system, fileP
204246 if ( nearBottom ) el . scrollTop = el . scrollHeight
205247 } , [ content ] )
206248
207- const handleDownload = ( ) => {
208- setDownloadDialogOpen ( true )
209- }
210-
211249 return (
212250 < section className = 'h-full min-h-0 flex flex-col' >
213- < div className = 'flex items-center justify-between border-b bg-white/60 backdrop-blur px-3 py-2 shrink-0' >
214- < div className = 'text-sm font-medium' > { title } </ div >
215- < div className = 'flex items-center gap-2 text-xs' >
216- { stdFile && (
217- < >
218- < DownloadDialog
219- system = { system ?. name || '' }
220- file = { stdFile }
221- open = { downloadkDialogOpen }
222- onClose = { ( ) => setDownloadDialogOpen ( false ) }
223- />
224- < button
225- onClick = { handleDownload }
226- title = 'Download STDOUT log'
227- className = 'w-8 h-8 flex items-center justify-center rounded-md border text-neutral-600 hover:text-neutral-800 hover:bg-neutral-100'
228- >
229- < ArrowDownCircleIcon className = 'w-4 h-4' />
230- </ button >
231- </ >
232- ) }
233- </ div >
234- </ div >
235251 < div className = 'flex-1 bg-black text-neutral-100 font-mono text-[12px] leading-5' >
236- < pre className = 'px-3 py-2 whitespace-pre-wrap' > { content } </ pre >
252+ < pre className = 'px-3 py-2 whitespace-pre-wrap' > { content || '# No data available' } </ pre >
237253 </ div >
238254 </ section >
239255 )
240256}
241257
242- interface ScriptPaneProps {
243- script ?: string
244- }
245-
246- const ScriptPane : React . FC < ScriptPaneProps > = ( { script } ) => {
247- return (
248- < section className = 'h-full min-h-0 flex flex-col' >
249- < div className = 'flex items-center justify-between border-b bg-white/60 backdrop-blur px-3 py-2 shrink-0' >
250- < div className = 'text-sm font-medium' > Script</ div >
251- </ div >
252- < div className = 'flex-1 bg-neutral-50 text-neutral-800' >
253- < pre className = 'px-4 py-3 text-xs leading-5 font-mono whitespace-pre' >
254- { script || '# No script available' }
255- </ pre >
256- </ div >
257- </ section >
258- )
259- }
260258
261259interface ResourcePaneProps {
262260 src ?: string
@@ -427,45 +425,16 @@ let OUTPUT_TABS = [
427425
428426type OutputTabId = ( typeof OUTPUT_TABS ) [ number ] [ 'id' ]
429427
430- interface RightTabsProps {
431- active : OutputTabId
432- onChange : ( id : OutputTabId ) => void
433- }
434-
435- const RightTabs : React . FC < RightTabsProps > = ( { active, onChange } ) => {
436- return (
437- < div className = 'border-b bg-white/70 backdrop-blur p-4 sticky top-0 z-10' >
438- < div className = 'flex flex-col w-full items-stretch gap-2' >
439- { OUTPUT_TABS . map ( ( t ) => (
440- < React . Fragment key = { t . id } >
441- { t . enabled && (
442- < button
443- key = { t . id }
444- type = 'button'
445- onClick = { ( ) => onChange ( t . id ) }
446- data-active = { active === t . id }
447- className = 'text-xs rounded-md border px-2.5 py-1.5 data-[active=true]:bg-neutral-900 data-[active=true]:text-white data-[active=true]:border-neutral-900 data-[active=false]:bg-white data-[active=false]:text-neutral-700 hover:bg-neutral-50'
448- aria-pressed = { active === t . id }
449- >
450- { t . label }
451- </ button >
452- ) }
453- </ React . Fragment >
454- ) ) }
455- </ div >
456- </ div >
457- )
458- }
459428
460429interface JobDetailsLayoutProps {
461430 job ?: Job
462431 jobMetadata ?: JobMetadata
463432 system ?: System
464433 activeTab : OutputTabId
465- stdout : string
434+ stdout ? : string
466435 stdoutFile ?: File
467- stdin : string
468- stderr : string
436+ stdin ? : string
437+ stderr ? : string
469438 stderrFile ?: File
470439 script ?: string
471440 dashboards ?: GrafanaDashboard [ ]
@@ -487,17 +456,7 @@ const JobDetailsLayout: React.FC<JobDetailsLayoutProps> = ({
487456 onChangeTab,
488457} ) => {
489458 return (
490- < div className = 'flex flex-1 flex-col gap-4 pt-6 pb-20 px-8' >
491- < div className = 'flex items-center justify-between' >
492- < div className = 'text-sm text-neutral-500' >
493- Clariden /{ ' ' }
494- < Link to = { `/compute` } className = 'hover:text-gray-900' >
495- Jobs
496- </ Link > { ' ' }
497- / < span className = 'text-neutral-800' > { job ?. jobId } </ span >
498- </ div >
499- { /* <div className='text-xs text-neutral-500'>Last update: just now</div> */ }
500- </ div >
459+ < div className = 'flex flex-1 flex-col gap-4 pb-20' >
501460 < div className = 'flex flex-1 h-full' >
502461 < JobDetailCenter
503462 job = { job }
@@ -647,10 +606,10 @@ const JobDetailsConsoleView: React.FC<JobDetailsConsoleViewProps> = ({
647606 jobMetadata = { jobMetadata || undefined }
648607 system = { system }
649608 activeTab = { activeTab }
650- stdout = { jobStandardOuput ?. output ?. content || '...' }
609+ stdout = { jobStandardOuput ?. output ?. content }
651610 stdoutFile = { jobStandardOutputFile || undefined }
652- stdin = { jobMetadata ?. standardInput || '' }
653- stderr = { jobStandardError ?. output ?. content || '...' }
611+ stdin = { jobMetadata ?. standardInput || undefined }
612+ stderr = { jobStandardError ?. output ?. content }
654613 stderrFile = { jobStandardErrorFile || undefined }
655614 script = { jobMetadata ?. script || undefined }
656615 dashboards = { dashboards }
0 commit comments