Compare commits

..

8 Commits

  1. 3
      api/sm/requests/approve-process.ts
  2. 1
      api/sm/responses/processes.ts
  3. 10
      api/sm/schemas/approve-process.ts
  4. 10
      api/sm/sm-client.ts
  5. 30
      components/elements/processes/index.tsx
  6. 115
      components/elements/tokens/index.tsx
  7. 0
      components/elements/tokens/styles.module.css
  8. 16
      functions/token.ts
  9. 2
      hooks/use-api.ts
  10. 13
      hooks/use-token.ts
  11. 63
      pages/index.tsx

@ -0,0 +1,3 @@
export interface ApproveProcessRequest {
id: string
}

@ -37,6 +37,7 @@ export interface ProcessInterface {
canRepeat: boolean canRepeat: boolean
canStop: boolean canStop: boolean
canKill: boolean canKill: boolean
canApprove: boolean
} }
export enum Status { export enum Status {

@ -0,0 +1,10 @@
import {Method, SchemaInterface} from "../../schema-client";
class ApproveProcessSchema implements SchemaInterface {
method = Method.POST
url = '/system-monitoring/processes/{id}/approve'
contentType = null
}
export let approveProcessSchema = new ApproveProcessSchema()
export default approveProcessSchema

@ -8,7 +8,9 @@ import {CommandInterface} from "./responses/comamnds";
import commandsSchema from "./schemas/commands"; import commandsSchema from "./schemas/commands";
import runCommandsSchema from "./schemas/run-commands"; import runCommandsSchema from "./schemas/run-commands";
import {RepeatProcessRequest} from "./requests/repeat-process"; import {RepeatProcessRequest} from "./requests/repeat-process";
import {ApproveProcessRequest} from "./requests/approve-process";
import repeatProcessSchema from "./schemas/repeat-process"; import repeatProcessSchema from "./schemas/repeat-process";
import approveProcessSchema from "./schemas/approve-process";
import processOutputSchema from "./schemas/process-output"; import processOutputSchema from "./schemas/process-output";
import {ProcessOutputRequest} from "./requests/process-ouput"; import {ProcessOutputRequest} from "./requests/process-ouput";
import playProcessSchema from "./schemas/play-process"; import playProcessSchema from "./schemas/play-process";
@ -52,6 +54,14 @@ export class SMClient extends SchemaClient {
} }
} }
async approveProcess(data: ApproveProcessRequest): Promise<ResponseInterface> {
let { responseData, headers } = await this.send(approveProcessSchema, data)
return {
data: responseData as ProcessInterface[],
headers: headers
}
}
async repeatProcess(data: RepeatProcessRequest): Promise<ResponseInterface> { async repeatProcess(data: RepeatProcessRequest): Promise<ResponseInterface> {
let { responseData, headers } = await this.send(repeatProcessSchema, data) let { responseData, headers } = await this.send(repeatProcessSchema, data)
return { return {

@ -1,5 +1,5 @@
import styles from './styles.module.css' import styles from './styles.module.css'
import {useEffect, useState} from "react" import {useContext, useEffect, useState} from "react"
import DeleteForeverOutlined from "@mui/icons-material/DeleteForeverOutlined" import DeleteForeverOutlined from "@mui/icons-material/DeleteForeverOutlined"
import StopCircleOutlined from "@mui/icons-material/StopCircleOutlined" import StopCircleOutlined from "@mui/icons-material/StopCircleOutlined"
import PauseCircleOutline from "@mui/icons-material/PauseCircleOutline" import PauseCircleOutline from "@mui/icons-material/PauseCircleOutline"
@ -10,6 +10,7 @@ import ErrorOutline from "@mui/icons-material/ErrorOutline"
import ReplayOutlined from "@mui/icons-material/ReplayOutlined" import ReplayOutlined from "@mui/icons-material/ReplayOutlined"
import RunCircleOutlined from "@mui/icons-material/RunCircleOutlined" import RunCircleOutlined from "@mui/icons-material/RunCircleOutlined"
import FactCheckOutlined from "@mui/icons-material/FactCheckOutlined" import FactCheckOutlined from "@mui/icons-material/FactCheckOutlined"
import ThumbUpAltOutlined from '@mui/icons-material/ThumbUpAltOutlined'
import { import {
Box, Box,
TextField, TextField,
@ -30,6 +31,7 @@ import Command from "../commands/elements/command";
import {CommandInterface} from "../../../api/sm/responses/comamnds"; import {CommandInterface} from "../../../api/sm/responses/comamnds";
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
import {useApi} from "../../../hooks/use-api"; import {useApi} from "../../../hooks/use-api";
import Context from "../../../context/token";
enum Action { enum Action {
Run, Run,
@ -38,9 +40,11 @@ enum Action {
Kill, Kill,
Play, Play,
Pause, Pause,
Approve,
} }
export default function Processes() { export default function Processes() {
const {token} = useContext(Context)
const [processes, setProcesses] = useState<ProcessInterface[]>([]); const [processes, setProcesses] = useState<ProcessInterface[]>([]);
const [commands, setCommands] = useState<CommandInterface[]>([]); const [commands, setCommands] = useState<CommandInterface[]>([]);
const [page, setPage] = useState<number>(0); const [page, setPage] = useState<number>(0);
@ -116,6 +120,8 @@ export default function Processes() {
}, [action]); }, [action]);
let isFinished = (process: ProcessInterface) => process.cancelledAt || process.completedAt let isFinished = (process: ProcessInterface) => process.cancelledAt || process.completedAt
let isCancelled = (process: ProcessInterface) => process.cancelledAt
let isRunning = (process: ProcessInterface) => process.startedAt && !isFinished(process)
const handleChangePage = (event: any, page: number) => { const handleChangePage = (event: any, page: number) => {
setPage(page); setPage(page);
@ -145,6 +151,12 @@ export default function Processes() {
}) })
} }
if (action === Action.Approve) {
await api.approveProcess({
id: selectedProcess.id,
})
}
if (action === Action.Repeat) { if (action === Action.Repeat) {
await api.repeatProcess({ await api.repeatProcess({
id: selectedProcess.id, id: selectedProcess.id,
@ -213,8 +225,10 @@ export default function Processes() {
<TableRow key={process.id}> <TableRow key={process.id}>
<TableCell>{process.name}</TableCell> <TableCell>{process.name}</TableCell>
<TableCell> <TableCell>
{!process.progress && !isFinished(process) && <LinearProgress/>} {!process.progress && !isFinished(process) && isRunning(process) && <LinearProgress/>}
{!process.progress && isFinished(process) && <LinearProgress variant="determinate" value={100}/>} {!process.progress && !isFinished(process) && !isRunning(process) && <LinearProgress variant="determinate" value={0}/>}
{!process.progress && isFinished(process) && !isCancelled(process) && <LinearProgress variant="determinate" value={100}/>}
{!process.progress && isFinished(process) && isCancelled(process) && <LinearProgress variant="determinate" value={0}/>}
{process.progress && <LinearProgress variant="determinate" value={process.progress.percent}/>} {process.progress && <LinearProgress variant="determinate" value={process.progress.percent}/>}
{process.progress && <span> {process.progress && <span>
{`${process.progress.progress}`} / {`${process.progress.total}`} - {process.progress.percent}% [{process.progress.memory}] / {process.progress.remaining} {`${process.progress.progress}`} / {`${process.progress.total}`} - {process.progress.percent}% [{process.progress.memory}] / {process.progress.remaining}
@ -247,6 +261,9 @@ export default function Processes() {
</IconButton>} </IconButton>}
</TableCell> </TableCell>
<TableCell> <TableCell>
{process.canApprove && token?.permissions.indexOf('approve_process') > -1 && <IconButton onClick={() => openDialog(process, Action.Approve)} title={`Approve`} aria-label="Approve">
<ThumbUpAltOutlined/>
</IconButton>}
{process.canRepeat && <IconButton onClick={() => openDialog(process, Action.Repeat)} title={`Repeat`} aria-label="Repeat"> {process.canRepeat && <IconButton onClick={() => openDialog(process, Action.Repeat)} title={`Repeat`} aria-label="Repeat">
<ReplayOutlined/> <ReplayOutlined/>
</IconButton>} </IconButton>}
@ -300,6 +317,13 @@ export default function Processes() {
argumentsParams={selectedProcess.arguments} argumentsParams={selectedProcess.arguments}
callback={callback}/>} callback={callback}/>}
</ConfirmDialog>} </ConfirmDialog>}
{selectedProcess && <ConfirmDialog
title={selectedProcess.name}
open={Action.Approve === action}
agreeCallback={agreeCallback}
closeCallback={() => {setAction(null)}}>
Approve?
</ConfirmDialog>}
{selectedProcess && <ConfirmDialog {selectedProcess && <ConfirmDialog
title={`Are you sure?`} title={`Are you sure?`}
open={ !!action && ([Action.Play, Action.Pause, Action.Stop, Action.Kill].indexOf(action) > -1) } open={ !!action && ([Action.Play, Action.Pause, Action.Stop, Action.Kill].indexOf(action) > -1) }

@ -0,0 +1,115 @@
import styles from './styles.module.css'
import {Autocomplete, IconButton, TextareaAutosize, TextField} from "@mui/material";
import {useContext, useEffect, useState} from "react";
import Context from "../../../context/token";
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
import DeleteForeverOutlined from '@mui/icons-material/DeleteForeverOutlined';
import Modal from '@mui/material/Modal';
import Fade from '@mui/material/Fade';
import Backdrop from '@mui/material/Backdrop';
import Box from '@mui/material/Box';
import Grid from "@mui/material/Grid";
import ConfirmDialog from "../confirm-dialog";
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '100%',
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function Tokens() {
const {token, tokens, addToken, deleteToken, switchToken} = useContext(Context)
const [showAddForm, setShowAddForm] = useState(false)
const [showConfirm, setShowConfirm] = useState(false)
let variants = tokens.map((token, index) => ({
...token,
label: token.host
}));
useEffect(() => {
setShowAddForm(!token)
}, [token])
return (
<>
<Grid container>
<Grid item xs={2}>
<Autocomplete
value={{
...token,
label: token?.host ?? ''
}}
onChange={(event: any, newValue: any) => {
console.log('newValue', newValue);
if (newValue?.id) {
switchToken(newValue?.id)
}
}}
disablePortal
options={variants}
renderInput={(params) => <TextField {...params} label="Tokens"/>}
/>
</Grid>
<Grid item xs={1}>
<IconButton onClick={() => setShowAddForm(true)} title={`Add new token`}
aria-label="Add new token">
<AddCircleOutlineOutlinedIcon/>
</IconButton>
<IconButton onClick={() => setShowConfirm(true)} title={`Delete current token`}
aria-label="Add new token">
<DeleteForeverOutlined/>
</IconButton>
</Grid>
</Grid>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
open={showAddForm}
onClose={() => setShowAddForm(false)}
closeAfterTransition
slots={{backdrop: Backdrop}}
slotProps={{
backdrop: {
timeout: 500,
},
}}
>
<Fade in={showAddForm}>
<Box sx={style}>
<TextareaAutosize
placeholder={`Pass JWT token`}
minRows={25}
style={{width: '100%'}}
onInput={(event: any) => {
if (addToken(event.target.value)) {
setShowAddForm(false)
}
}}
/>
</Box>
</Fade>
</Modal>
<ConfirmDialog
title={`Delete curent token`}
open={showConfirm}
agreeCallback={async () => {
let tokenId = token?.id || null
if (tokenId) {
deleteToken(tokenId)
}
setShowConfirm(false)
}}
closeCallback={() => {setShowConfirm(false)}}>
Confirm?
</ConfirmDialog>
</>
)
}

@ -62,12 +62,22 @@ export const grabTokenData = (token: string): TokenData|null => {
if (!isClient()) { if (!isClient()) {
return null return null
} }
const parts = token.split('.') const parts = token.split('.')
let payload = 3 === parts.length && parts[1] ? JSON.parse(window.atob(parts[1])) : null if (parts.length !== 3) {
let headers = 3 === parts.length && parts[0] ? JSON.parse(window.atob(parts[0])) : null return null
if (!payload || !headers){ }
let payload = JSON.parse(window.atob(parts[1]))
if (!payload){
return null return null
} }
let headers = JSON.parse(window.atob(parts[0]))
if (!headers){
return null
}
return { return {
payload, payload,
headers, headers,

@ -13,7 +13,7 @@ const grabClient = (token: Token | null, rawToken: string | null): SMClient => {
} }
} }
return new SMClient({ return new SMClient({
baseUrl: 'http://' + (token?.host || 'localhost'), baseUrl: token?.host || 'localhost',
...authentication ...authentication
}) })
} }

@ -1,5 +1,5 @@
import {useState, useEffect} from 'react' import {useState, useEffect} from 'react'
import {cleanToken, grabToken, grabTokens, selectToken, storeToken} from "../functions/token"; import {cleanToken, grabToken, grabTokenData, grabTokens, selectToken, storeToken} from "../functions/token";
export class Token { export class Token {
id!: string id!: string
@ -39,9 +39,18 @@ export function useToken(): UseTokenInterface {
setToken(grabToken()?.payload) setToken(grabToken()?.payload)
} }
const addToken = (token: string) => { const addToken = (token: string): boolean => {
if (!grabTokenData(token)) {
return false;
}
storeToken(token) storeToken(token)
setToken(grabToken()?.payload) setToken(grabToken()?.payload)
setTokens([
...tokens,
grabToken()?.payload
])
return true
} }
return {token, tokens, switchToken, deleteToken, addToken} return {token, tokens, switchToken, deleteToken, addToken}

@ -1,11 +1,12 @@
import Head from 'next/head' import Head from 'next/head'
import styles from '../styles/Home.module.css' import styles from '../styles/Home.module.css'
import Processes from "../components/elements/processes"; import Processes from "../components/elements/processes";
import {Tabs, Tab, MenuItem, Select, InputLabel, TextareaAutosize, Button} from '@mui/material'; import {Tabs, Tab} from '@mui/material';
import {useContext, useEffect, useState} from "react"; import {useContext, useState} from "react";
import Commands from "../components/elements/commands"; import Commands from "../components/elements/commands";
import {Provider as TabProvider} from '../context/tab' import {Provider as TabProvider} from '../context/tab'
import Context from "../context/token"; import Context from "../context/token";
import Tokens from "../components/elements/tokens";
export enum TabEnum { export enum TabEnum {
@ -14,42 +15,10 @@ export enum TabEnum {
} }
export default function Home() { export default function Home() {
let {token, tokens, addToken} = useContext(Context) let {token} = useContext(Context)
const [tab, setTab] = useState<TabEnum>(TabEnum.Processes); const [tab, setTab] = useState<TabEnum>(TabEnum.Processes);
const handleChange = (event: any, tab: number) => setTab(tab) const handleChange = (event: any, tab: number) => setTab(tab)
// useEffect(() => {
// const storedToken = grabToken()
// setToken(storedToken)
// const storedTokens = grabTokens()
// setTokens(storedTokens)
// }, [])
let onTokenInput = (event: any) => {
}
if (!token) {
return (
<>
<Head>
<title>System monitoring</title>
<meta name="description" content="System monitoring service"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" href="/favicon.ico"/>
</Head>
<main className={styles.main}>
<h2>Для дальнейшей работы добавьте JWT токен</h2>
<TextareaAutosize
minRows={20}
style={{ width: '100%' }}
onInput={(event: any) => addToken(event.target.value)}
/>
</main>
</>
)
}
return ( return (
<> <>
<Head> <Head>
@ -59,29 +28,7 @@ export default function Home() {
<link rel="icon" href="/favicon.ico"/> <link rel="icon" href="/favicon.ico"/>
</Head> </Head>
<main className={styles.main}> <main className={styles.main}>
{tokens.length > 0 && <> <Tokens />
<InputLabel id="tokens">Tokens</InputLabel>
<Select
value={token}
labelId="tokens"
id="demo-simple-select-autowidth"
label="Tokens"
onChange={(event: any) => {
// selectToken(event.target.value)
// const storedToken = grabToken()
// setToken(storedToken)
// if (storedToken) {
// smClient.baseUrl = storedToken.payload.host
// }
}}
>
{tokens.map((token, index) => (
<MenuItem key={index} value={index}>{token.host}</MenuItem>
))}
</Select>
</>}
{token && <> {token && <>
<Tabs <Tabs
value={tab} value={tab}

Loading…
Cancel
Save