|
|
|
import styles from './styles.module.css'
|
|
|
|
import {useEffect, useState} from "react"
|
|
|
|
import DeleteForeverOutlined from "@mui/icons-material/DeleteForeverOutlined"
|
|
|
|
import StopCircleOutlined from "@mui/icons-material/StopCircleOutlined"
|
|
|
|
import PauseCircleOutline from "@mui/icons-material/PauseCircleOutline"
|
|
|
|
import PlayCircleOutline from "@mui/icons-material/PlayCircleOutline"
|
|
|
|
import HourglassEmptyOutlined from "@mui/icons-material/HourglassEmptyOutlined"
|
|
|
|
import CheckCircleOutline from "@mui/icons-material/CheckCircleOutline"
|
|
|
|
import ErrorOutline from "@mui/icons-material/ErrorOutline"
|
|
|
|
import ReplayOutlined from "@mui/icons-material/ReplayOutlined"
|
|
|
|
import RunCircleOutlined from "@mui/icons-material/RunCircleOutlined"
|
|
|
|
import FactCheckOutlined from "@mui/icons-material/FactCheckOutlined"
|
|
|
|
import {IconButton, LinearProgress, TableContainer, Table, TableBody, TableCell, TableHead, TableRow, TablePagination} from "@mui/material"
|
|
|
|
import ConfirmDialog from "../confirm-dialog";
|
|
|
|
|
|
|
|
export interface ProcessExceptionInterface {
|
|
|
|
message: string,
|
|
|
|
file: string,
|
|
|
|
line: number,
|
|
|
|
code: number,
|
|
|
|
trace: any[],
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ProcessProgressInterface {
|
|
|
|
total: number,
|
|
|
|
progress: number,
|
|
|
|
memory: any[],
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ProcessInterface {
|
|
|
|
id: string,
|
|
|
|
lock: string | null,
|
|
|
|
containerUuid: string | null,
|
|
|
|
pid: bigint | null,
|
|
|
|
name: string,
|
|
|
|
options: Record<string, any>,
|
|
|
|
arguments: Record<string, any>,
|
|
|
|
exception: ProcessExceptionInterface | null,
|
|
|
|
progress: ProcessProgressInterface | null,
|
|
|
|
outputId: string | null,
|
|
|
|
createdAt: string,
|
|
|
|
updatedAt: string | null,
|
|
|
|
startedAt: string | null,
|
|
|
|
pausedAt: string | null,
|
|
|
|
cancelledAt: string | null
|
|
|
|
completedAt: string | null
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Status {
|
|
|
|
Wait,
|
|
|
|
Running,
|
|
|
|
Cancelled,
|
|
|
|
Error,
|
|
|
|
Success
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function Processes() {
|
|
|
|
const [processes, setProcesses] = useState<ProcessInterface[]>([]);
|
|
|
|
const [page, setPage] = useState<number>(0);
|
|
|
|
const [count, setCount] = useState<number>(0);
|
|
|
|
const [open, setOpen] = useState<boolean>(false);
|
|
|
|
const [selectedProcess, setSelectedProcess] = useState<ProcessInterface | null>(null);
|
|
|
|
|
|
|
|
let refreshLock = false
|
|
|
|
let refreshProcesses = async () => {
|
|
|
|
if (refreshLock) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
refreshLock = true
|
|
|
|
let response = await fetch('http://fmw.sipachev.sv/system-monitoring/processes?' + new URLSearchParams({
|
|
|
|
page: page + 1,
|
|
|
|
limit: 20,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
method: 'GET',
|
|
|
|
headers: {
|
|
|
|
'X-Plugin-Token': 'passw0rd',
|
|
|
|
'X-Pagination-Count': '0',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const processes: ProcessInterface[] = await response.json()
|
|
|
|
setProcesses(processes)
|
|
|
|
setCount(Number(response.headers.get('X-Pagination-Count')))
|
|
|
|
refreshLock = false
|
|
|
|
}
|
|
|
|
let output = async (process: ProcessInterface) => {
|
|
|
|
let response = await fetch(`http://fmw.sipachev.sv/system-monitoring/processes/${process.id}/output`,
|
|
|
|
{
|
|
|
|
method: 'GET',
|
|
|
|
headers: {
|
|
|
|
'X-Plugin-Token': 'passw0rd',
|
|
|
|
'X-Pagination-Count': '0',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const output: string = await response.text()
|
|
|
|
let a = document.createElement("a");
|
|
|
|
let file = new Blob([output], {type: 'plain/text'});
|
|
|
|
a.href = URL.createObjectURL(file);
|
|
|
|
a.download = `${process.id}.txt`;
|
|
|
|
a.click();
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const timer = setInterval(() => refreshProcesses(), 1000)
|
|
|
|
return () => clearInterval(timer);
|
|
|
|
}, [page]);
|
|
|
|
|
|
|
|
let isFinished = (process: ProcessInterface) => process.cancelledAt || process.completedAt
|
|
|
|
let canPlay = (process: ProcessInterface) => !isFinished(process) && process.progress && process.pausedAt
|
|
|
|
let canPause = (process: ProcessInterface) => !isFinished(process) && process.progress && !process.pausedAt
|
|
|
|
let canRepeat = (process: ProcessInterface) => isFinished(process)
|
|
|
|
let canStop = (process: ProcessInterface) => !isFinished(process) && process.progress
|
|
|
|
let canKill = (process: ProcessInterface) => !isFinished(process) && process.startedAt
|
|
|
|
let progress = (progress: ProcessProgressInterface) => progress.progress / progress.total * 100
|
|
|
|
|
|
|
|
let status = (process: ProcessInterface): Status => {
|
|
|
|
if (process.cancelledAt && process.completedAt) {
|
|
|
|
return Status.Cancelled
|
|
|
|
}
|
|
|
|
if (process.exception && process.completedAt) {
|
|
|
|
return Status.Error
|
|
|
|
}
|
|
|
|
if (!process.exception && process.completedAt) {
|
|
|
|
return Status.Success
|
|
|
|
}
|
|
|
|
if (process.startedAt && !process.completedAt) {
|
|
|
|
return Status.Running
|
|
|
|
}
|
|
|
|
return Status.Wait
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleChangePage = (event: any, page: number) => {
|
|
|
|
setPage(page);
|
|
|
|
}
|
|
|
|
|
|
|
|
const openRepeatDialog = (process: ProcessInterface) => {
|
|
|
|
setSelectedProcess(process)
|
|
|
|
setOpen(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
let lock = false
|
|
|
|
const repeat = async (dialogId: string) => {
|
|
|
|
if (lock) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (!selectedProcess) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
lock = true
|
|
|
|
let url = `http://fmw.sipachev.sv/system-monitoring/processes/${selectedProcess.id}/repeat`
|
|
|
|
let data = {
|
|
|
|
requestId: dialogId
|
|
|
|
}
|
|
|
|
let response = await fetch(url, {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json;charset=utf-8',
|
|
|
|
'X-Plugin-Token': 'passw0rd'
|
|
|
|
},
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
});
|
|
|
|
lock = false
|
|
|
|
setSelectedProcess(null)
|
|
|
|
setOpen(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<TableContainer>
|
|
|
|
<Table>
|
|
|
|
<TableHead>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell>Name</TableCell>
|
|
|
|
<TableCell>Progress</TableCell>
|
|
|
|
<TableCell>Status</TableCell>
|
|
|
|
<TableCell>Action</TableCell>
|
|
|
|
<TableCell>Created</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
</TableHead>
|
|
|
|
<TableBody>
|
|
|
|
{processes.map((process: ProcessInterface, index: number) => (
|
|
|
|
<TableRow key={process.id}>
|
|
|
|
<TableCell>{process.name}</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{!process.progress && !isFinished(process) && <LinearProgress/>}
|
|
|
|
{!process.progress && isFinished(process) && <LinearProgress variant="determinate" value={100}/>}
|
|
|
|
{process.progress && <LinearProgress value={progress(process.progress)}/>}
|
|
|
|
{process.progress && <span>
|
|
|
|
{`${process.progress.progress}`} / {`${process.progress.total}`} - 50% [53Mb] / 20 sec
|
|
|
|
</span>}
|
|
|
|
{canPlay(process) && <IconButton title={`Play`} aria-label="Play">
|
|
|
|
<PlayCircleOutline/>
|
|
|
|
</IconButton>}
|
|
|
|
{canPause(process) && <IconButton title={`Pause`} aria-label="Pause">
|
|
|
|
<PauseCircleOutline/>
|
|
|
|
</IconButton>}
|
|
|
|
{canStop(process) && <IconButton title={`Stop`} aria-label="Stop">
|
|
|
|
<StopCircleOutlined/>
|
|
|
|
</IconButton>}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{Status.Error === status(process) && <IconButton title={`Error`} aria-label="Error">
|
|
|
|
<ErrorOutline/>
|
|
|
|
</IconButton>}
|
|
|
|
{Status.Success === status(process) && <IconButton title={`Success`} aria-label="Success">
|
|
|
|
<CheckCircleOutline/>
|
|
|
|
</IconButton>}
|
|
|
|
{Status.Running === status(process) && <IconButton title={`Running`} aria-label="Running">
|
|
|
|
<RunCircleOutlined/>
|
|
|
|
</IconButton>}
|
|
|
|
{Status.Cancelled === status(process) && <IconButton title={`Cancelled`} aria-label="Cancelled">
|
|
|
|
<StopCircleOutlined/>
|
|
|
|
</IconButton>}
|
|
|
|
{Status.Wait === status(process) && <IconButton title={`Wait`} aria-label="Wait">
|
|
|
|
<HourglassEmptyOutlined/>
|
|
|
|
</IconButton>}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{canRepeat(process) && <IconButton onClick={() => openRepeatDialog(process)} title={`Repeat`} aria-label="Repeat">
|
|
|
|
<ReplayOutlined/>
|
|
|
|
</IconButton>}
|
|
|
|
{canKill(process) && <IconButton title={`Kill`} aria-label="Kill">
|
|
|
|
<DeleteForeverOutlined/>
|
|
|
|
</IconButton>}
|
|
|
|
{process?.outputId && <IconButton title={`Output`} onClick={() => output(process)} aria-label="Output">
|
|
|
|
<FactCheckOutlined/>
|
|
|
|
</IconButton>}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell title={new Date(process.createdAt).toLocaleString()}>
|
|
|
|
{new Date(process.createdAt).toLocaleDateString()}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
))}
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</TableContainer>
|
|
|
|
<TablePagination
|
|
|
|
component="div"
|
|
|
|
count={count}
|
|
|
|
rowsPerPage={20}
|
|
|
|
page={page}
|
|
|
|
onPageChange={handleChangePage}
|
|
|
|
/>
|
|
|
|
<ConfirmDialog open={open} text={'!!!'} agreeCallback={repeat} closeCallback={() => {
|
|
|
|
setSelectedProcess(null)
|
|
|
|
setOpen(false)
|
|
|
|
}}/>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|