WIP new process details view

This commit is contained in:
Ingo Oppermann 2023-12-08 17:36:57 +01:00
parent f4c9fbe61a
commit 159d00f000
No known key found for this signature in database
GPG Key ID: 2AB32426E9DD229E
5 changed files with 429 additions and 121 deletions

View File

@ -1,120 +0,0 @@
import React from 'react';
import { Trans } from '@lingui/macro';
import makeStyles from '@mui/styles/makeStyles';
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import Modal from '@mui/material/Modal';
import Typography from '@mui/material/Typography';
import ModalContent from '../ModalContent';
import Progress from '../Progress';
import Textarea from '../Textarea';
const useStyles = makeStyles((theme) => ({
title: {
marginBottom: '-.3em',
marginTop: '0em',
fontWeight: 'bold',
},
textarea: {
marginBottom: '-1em',
},
box: {
backgroundColor: theme.palette.background.modalbox,
borderRadius: 4,
padding: '1em',
},
banner: {
marginBottom: '-1em',
},
logging: {
marginTop: '.15em',
},
}));
const initLogdata = (logdata) => {
if (!logdata) {
logdata = {};
}
return {
prelude: [],
log: [],
...logdata,
};
};
const formatLogline = (entry) => {
let line = '@' + entry[0] + ' ';
const matches = entry[1].match(/^\[([0-9A-Za-z]+) @ 0x[0-9a-f]+\]/i);
if (matches !== null) {
let t = '[' + matches[1];
for (let i = 0; i < 10 - matches[1].length; i++) {
t += ' ';
}
t += ']';
line += entry[1].replace(matches[0], t);
} else {
line += entry[1];
}
return line;
};
const Component = function (props) {
const classes = useStyles();
const logdata = initLogdata(props.logdata);
return (
<Modal open={props.open} onClose={props.onClose} className="modal">
<ModalContent title={props.title} onClose={props.onClose} onHelp={props.onHelp}>
<Grid container spacing={1}>
<Grid item xs={12} md={8} lg={10}>
<Grid container spacing={3}>
<Grid item xs={12}>
<div className={classes.box}>
<Grid container spacing={1}>
<Grid item xs={12} className={classes.banner}>
<Typography variant="body1" className={classes.title}>
<Trans>Banner</Trans>
</Typography>
<Textarea rows={9} value={logdata.prelude.join('\n')} scrollTo="bottom" readOnly allowCopy />
</Grid>
<Grid item xs={12} marginTop={2}>
<Divider />
</Grid>
<Grid item xs={12} className={classes.logging}>
<Typography variant="body1" className={classes.title}>
<Trans>Logging</Trans>
</Typography>
<Textarea rows={16} value={logdata.log.map(formatLogline).join('\n')} scrollTo="bottom" readOnly allowCopy />
</Grid>
</Grid>
</div>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} md={4} lg={2}>
{props.progress !== null && <Progress {...props.progress} />}
</Grid>
</Grid>
</ModalContent>
</Modal>
);
};
export default Component;
Component.defaultProps = {
open: false,
title: '',
progress: {},
logdata: {
prelude: [],
log: [],
},
onClose: null,
onHelp: null,
};

View File

@ -0,0 +1,269 @@
import React from 'react';
import { Trans } from '@lingui/macro';
import makeStyles from '@mui/styles/makeStyles';
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import Duration from '../../Duration';
import Number from '../../Number';
const useStyles = makeStyles((theme) => ({
box: {
backgroundColor: theme.palette.background.modalbox,
borderRadius: 4,
padding: '1em',
height: '100%',
},
}));
function init(props) {
const initProps = {
time: 0,
fps: 0,
bitrate: 0,
q: -1,
speed: 0,
drop: 0,
dup: 0,
frames: 0,
cpu: 0,
memory: 0,
...props,
};
return initProps;
}
function IO(props) {
if (props.progress === null) {
return (
<Typography variant="body2" gutterBottom>
<Trans>Not available</Trans>
</Typography>
);
}
const progress = props.progress;
return (
<Grid container>
<Grid item xs={12}>
<Typography variant="body2" gutterBottom>
{progress.type}: {progress.codec}{' '}
{progress.type === 'video' ? (
<React.Fragment>
{progress.width}x{progress.height} {progress.pix_fmt}
</React.Fragment>
) : (
<React.Fragment>
{progress.layout} {progress.sampling_hz} Hz
</React.Fragment>
)}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="h4">
<strong>
<Number value={progress.bitrate_kbit} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>kbit/s</Trans>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="h4">
<strong>
<Number value={progress.fps} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>FPS</Trans>
</Typography>
</Grid>
</Grid>
);
}
IO.defaultProps = {
progress: null,
};
export default function Progress(props) {
const classes = useStyles();
const progress = init(props);
console.log(progress);
let input_video = null;
let input_audio = null;
let output_video = null;
let output_audio = null;
for (let i = 0; i < progress.inputs.length; i++) {
if (progress.inputs[i].type === 'video') {
input_video = progress.inputs[i];
} else if (progress.inputs[i].type === 'audio') {
input_audio = progress.inputs[i];
}
}
for (let i = 0; i < progress.outputs.length; i++) {
if (progress.outputs[i].type === 'video') {
output_video = progress.outputs[i];
} else if (progress.outputs[i].type === 'audio') {
output_audio = progress.outputs[i];
}
}
return (
<Grid container>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Duration seconds={progress.time} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Uptime</Trans>
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Number value={progress.bitrate} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>kbit/s</Trans>
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Number value={progress.q} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Quality</Trans>
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Number value={progress.speed} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Speed</Trans>
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Number value={!isNaN((props.drop * 100) / props.frames) || 0} digits={2} minDigits={2} />%
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Frame drops</Trans>
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Number value={progress.dup} digits={0} minDigits={0} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Dup. frames</Trans>
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Number value={progress.cpu} digits={2} minDigits={2} />%
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>CPU</Trans>
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h4">
<strong>
<Number value={progress.memory / 1024 / 1024} digits={0} minDigits={0} /> MB
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Memory</Trans>
</Typography>
</Grid>
{input_video || input_audio ? (
<React.Fragment>
<Grid item xs={12}>
<Typography variant="body2" gutterBottom>
<Trans>Inputs</Trans>
</Typography>
</Grid>
{input_video && (
<Grid item xs={12}>
<IO progress={input_video}></IO>
</Grid>
)}
{input_audio && (
<Grid item xs={12}>
<IO progress={input_audio}></IO>
</Grid>
)}
</React.Fragment>
) : (
<Grid item xs={12}>
<Typography variant="body2" gutterBottom>
<Trans>No inputs</Trans>
</Typography>
</Grid>
)}
{output_video || output_audio ? (
<React.Fragment>
<Grid item xs={12}>
<Typography variant="body2" gutterBottom>
<Trans>Outputs</Trans>
</Typography>
</Grid>
{output_video && (
<Grid item xs={12}>
<IO progress={output_video}></IO>
</Grid>
)}
{output_audio && (
<Grid item xs={12}>
<IO progress={output_audio}></IO>
</Grid>
)}
</React.Fragment>
) : (
<Grid item xs={12}>
<Typography variant="body2" gutterBottom>
<Trans>No outputs</Trans>
</Typography>
</Grid>
)}
</Grid>
);
}
Progress.defaultProps = {
time: 0,
fps: 0,
bitrate: 0,
q: -1,
speed: 0,
drop: 0,
dup: 0,
frame: 0,
cpu: 0,
memory: 0,
};

View File

@ -0,0 +1,148 @@
import React from 'react';
import { Trans } from '@lingui/macro';
import makeStyles from '@mui/styles/makeStyles';
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import Modal from '@mui/material/Modal';
import Typography from '@mui/material/Typography';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import ModalContent from '../../ModalContent';
import Progress from './Progress';
import Textarea from '../../Textarea';
import TabPanel from '../../TabPanel';
import TabsVerticalGrid from '../../TabsVerticalGrid';
const useStyles = makeStyles((theme) => ({
title: {
marginBottom: '-.3em',
marginTop: '0em',
fontWeight: 'bold',
},
textarea: {
marginBottom: '-1em',
},
box: {
backgroundColor: theme.palette.background.modalbox,
borderRadius: 4,
padding: '1em',
},
banner: {
marginBottom: '-1em',
},
logging: {
marginTop: '.15em',
},
}));
const initLogdata = (logdata) => {
if (!logdata) {
logdata = {};
}
return {
command: [],
prelude: [],
log: [],
...logdata,
};
};
const formatLogline = (entry) => {
let line = '@' + entry[0] + ' ';
const matches = entry[1].match(/^\[([0-9A-Za-z]+) @ 0x[0-9a-f]+\]/i);
if (matches !== null) {
let t = '[' + matches[1];
for (let i = 0; i < 10 - matches[1].length; i++) {
t += ' ';
}
t += ']';
line += entry[1].replace(matches[0], t);
} else {
line += entry[1];
}
return line;
};
const Component = function (props) {
const [$tab, setTab] = React.useState('vitals');
const classes = useStyles();
const logdata = initLogdata(props.logdata);
logdata.command = props.progress.command;
const handleChangeTab = (event, value) => {
setTab(value);
};
return (
<Modal open={props.open} onClose={props.onClose} className="modal">
<ModalContent title={props.title} onClose={props.onClose} onHelp={props.onHelp}>
<Grid container spacing={1}>
<TabsVerticalGrid>
<Tabs orientation="vertical" variant="scrollable" value={$tab} onChange={handleChangeTab}>
<Tab className="tab" label={<Trans>Vitals</Trans>} value="vitals" />
<Tab className="tab" label={<Trans>Log</Trans>} value="log" />
</Tabs>
<TabPanel value={$tab} index="vitals" className="panel">
<Grid container spacing={3}>
<Grid item xs={12}>
{props.progress !== null && <Progress {...props.progress} />}
</Grid>
</Grid>
</TabPanel>
<TabPanel value={$tab} index="log" className="panel">
<Grid container spacing={3}>
<Grid item xs={12}>
<div className={classes.box}>
<Grid container spacing={1}>
<Grid item xs={12} className={classes.banner}>
<Typography variant="body1" className={classes.title}>
<Trans>Command</Trans>
</Typography>
<Textarea rows={1} value={'ffmpeg ' + logdata.command.join(' ')} scrollTo="bottom" readOnly allowCopy />
</Grid>
<Grid item xs={12} className={classes.banner}>
<Typography variant="body1" className={classes.title}>
<Trans>Banner</Trans>
</Typography>
<Textarea rows={9} value={logdata.prelude.join('\n')} scrollTo="bottom" readOnly allowCopy />
</Grid>
<Grid item xs={12} className={classes.logging}>
<Typography variant="body1" className={classes.title}>
<Trans>Logging</Trans>
</Typography>
<Textarea rows={16} value={logdata.log.map(formatLogline).join('\n')} scrollTo="bottom" readOnly allowCopy />
</Grid>
</Grid>
</div>
</Grid>
</Grid>
</TabPanel>
</TabsVerticalGrid>
</Grid>
</ModalContent>
</Modal>
);
};
export default Component;
Component.defaultProps = {
open: false,
title: '',
progress: {},
logdata: {
command: [],
prelude: [],
log: [],
},
onClose: null,
onHelp: null,
};

View File

@ -1,7 +1,7 @@
import { i18n } from '@lingui/core';
import { t } from '@lingui/macro';
import { v4 as uuidv4 } from 'uuid';
import { jwtDecode } from "jwt-decode";
import { jwtDecode } from 'jwt-decode';
import Handlebars from 'handlebars/dist/cjs/handlebars';
import SemverSatisfies from 'semver/functions/satisfies';
import SemverGt from 'semver/functions/gt';
@ -3328,6 +3328,11 @@ class Restreamer {
frames: 0,
drop: 0,
dup: 0,
command: [],
cpu: 0.0,
memory: 0,
inputs: [],
outputs: [],
};
if (state === null) {
@ -3336,6 +3341,7 @@ class Restreamer {
progress.valid = true;
progress.order = state.order;
progress.command = state.command.slice();
const fps = state.progress.fps || 0;
@ -3367,6 +3373,10 @@ class Restreamer {
progress.frames = state.progress.frames || 0;
progress.drop = state.progress.drop || 0;
progress.dup = state.progress.dup || 0;
progress.cpu = state.cpu_usage || 0;
progress.memory = state.memory_bytes || 0;
progress.inputs = state.progress.inputs.slice();
progress.outputs = state.progress.outputs.slice();
}
return progress;

View File

@ -206,6 +206,7 @@ export default function Main(props) {
const open = !$processDetails.open;
let logdata = {
command: [],
prelude: [],
log: [],
};