diff --git a/api/client.ts b/api/client.ts deleted file mode 100644 index 87affac..0000000 --- a/api/client.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export class Client { - -} \ No newline at end of file diff --git a/api/schema-client.ts b/api/schema-client.ts new file mode 100644 index 0000000..aaef855 --- /dev/null +++ b/api/schema-client.ts @@ -0,0 +1,63 @@ +export enum Method { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + PATCH = 'PATCH', +} + +export let hasQuery = (method: Method) => { + return Method.GET === method +} + +export interface ClientOptions { + baseUrl: string +} + +export interface SchemaInterface { + url: string, + method: Method + contentType: string | null +} + +export class SchemaClient { + baseUrl: string + + constructor({baseUrl}: ClientOptions) { + this.baseUrl = baseUrl + } + + async send(schema: SchemaInterface, data: any) { + let url = `${this.baseUrl}${schema.url}` + let {url: preparedUrl, data: preparedData} = this.processAttributes(url, data) + + if (hasQuery(schema.method)) { + preparedUrl += '?' + new URLSearchParams(preparedData) + } + + let response = await fetch(preparedUrl, { + method: schema.method.toString(), + headers: { + ...(schema.contentType ? {'Content-Type': schema.contentType} : {}), + 'X-Plugin-Token': 'passw0rd' + }, + body: hasQuery(schema.method) ? null : JSON.stringify(preparedData) + }); + + let responseData = response.headers.get('Content-Type')?.toString().includes('application/json') + ? await response.json() + : await response.text() + return {responseData: responseData, headers: response.headers} + } + + private processAttributes(url: string, data: any) + { + const preparedData = data + for (const key in data) { + if (url.indexOf('{' + key + '}') > -1) { + url = url.replace('{' + key + '}', preparedData[key]) + delete preparedData[key] + } + } + return {url, data: preparedData } + } +} \ No newline at end of file diff --git a/api/sm/requests/get-processes.ts b/api/sm/requests/get-processes.ts new file mode 100644 index 0000000..2555b48 --- /dev/null +++ b/api/sm/requests/get-processes.ts @@ -0,0 +1,9 @@ +export enum ProcessesType +{ + RUNNING = 'running', + HISTORY = 'history', +} + +export class GetProcessesRequest { + type?: ProcessesType +} \ No newline at end of file diff --git a/api/sm/requests/pagination.ts b/api/sm/requests/pagination.ts new file mode 100644 index 0000000..566c7aa --- /dev/null +++ b/api/sm/requests/pagination.ts @@ -0,0 +1,4 @@ +export class PaginationRequest { + page?: number + limit?: number +} \ No newline at end of file diff --git a/api/sm/responses/comamnds.ts b/api/sm/responses/comamnds.ts new file mode 100644 index 0000000..578c6c3 --- /dev/null +++ b/api/sm/responses/comamnds.ts @@ -0,0 +1,28 @@ +export interface OptionInterface { + name: string, + description: string | null, + default: any, + value: any, + shortcut: string | null, + isArray: boolean, + isNegatable: boolean, + isValueOptional: boolean, + isValueRequired: boolean + acceptValue: boolean +} + +export interface ArgumentInterface { + name: string, + description: string|null, + default: any, + isArray: boolean, + isRequired: boolean, +} + +export interface CommandResponseInterface { + class: string, + name: string, + description: string, + options: OptionInterface[], + arguments: ArgumentInterface[], +} \ No newline at end of file diff --git a/api/sm/responses/processes.ts b/api/sm/responses/processes.ts new file mode 100644 index 0000000..a69a976 --- /dev/null +++ b/api/sm/responses/processes.ts @@ -0,0 +1,32 @@ +export interface ProcessExceptionInterface { + message: string, + file: string, + line: number, + code: number, + trace: any[], +} + +export interface ProcessProgressInterface { + total: number, + progress: number, + memory: any[], +} + +export interface ProcessesResponseInterface { + id: string + lock: string | null + containerUuid: string | null + pid: bigint | null + name: string + options: Record + arguments: Record + 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 +} \ No newline at end of file diff --git a/api/sm/responses/response.ts b/api/sm/responses/response.ts new file mode 100644 index 0000000..dad140b --- /dev/null +++ b/api/sm/responses/response.ts @@ -0,0 +1,4 @@ +export interface ResponseInterface { + data: any + headers: Headers +} \ No newline at end of file diff --git a/api/sm/schemas/commands.ts b/api/sm/schemas/commands.ts new file mode 100644 index 0000000..3aafe05 --- /dev/null +++ b/api/sm/schemas/commands.ts @@ -0,0 +1,10 @@ +import {Method, SchemaInterface} from "../../schema-client"; + +class CommandsSchema implements SchemaInterface { + method = Method.GET + url = '/system-monitoring/commands' + contentType = null; +} + +export let commandsSchema = new CommandsSchema() +export default commandsSchema \ No newline at end of file diff --git a/api/sm/schemas/processes.ts b/api/sm/schemas/processes.ts new file mode 100644 index 0000000..e072da7 --- /dev/null +++ b/api/sm/schemas/processes.ts @@ -0,0 +1,10 @@ +import {Method, SchemaInterface} from "../../schema-client"; + +class ProcessesSchema implements SchemaInterface { + method = Method.GET + url = '/system-monitoring/processes' + contentType = null; +} + +export let processesSchema = new ProcessesSchema() +export default processesSchema \ No newline at end of file diff --git a/api/sm/schemas/run-commands.ts b/api/sm/schemas/run-commands.ts new file mode 100644 index 0000000..6de8713 --- /dev/null +++ b/api/sm/schemas/run-commands.ts @@ -0,0 +1,10 @@ +import {Method, SchemaInterface} from "../../schema-client"; + +class RunCommandsSchema implements SchemaInterface { + method = Method.POST + url = '/system-monitoring/commands/{commandName}/run' + contentType = 'application/json;charset=utf-8'; +} + +export let runCommandsSchema = new RunCommandsSchema() +export default runCommandsSchema \ No newline at end of file diff --git a/api/sm/sm-client.ts b/api/sm/sm-client.ts new file mode 100644 index 0000000..59977af --- /dev/null +++ b/api/sm/sm-client.ts @@ -0,0 +1,46 @@ +import {processesSchema} from "./schemas/processes"; +import {ProcessesResponseInterface} from "./responses/processes"; +import {SchemaClient} from "../schema-client"; +import {GetProcessesRequest} from "./requests/get-processes"; +import {PaginationRequest} from "./requests/pagination"; +import {ResponseInterface} from "./responses/response"; +import {CommandResponseInterface} from "./responses/comamnds"; +import commandsSchema from "./schemas/commands"; +import runCommandsSchema from "./schemas/run-commands"; + +export class SMClient { + schemaClient: SchemaClient + + constructor() { + this.schemaClient = new SchemaClient({ + baseUrl: 'http://fmw.sipachev.sv' + }) + } + + async getCommands(): Promise { + let { responseData, headers } = await this.schemaClient.send(commandsSchema, {}) + return { + data: responseData as CommandResponseInterface[], + headers: headers + } + } + + async runCommand(data: any): Promise { + let { headers } = await this.schemaClient.send(runCommandsSchema, data) + return { + data: null, + headers: headers + } + } + + async getProcesses(data: GetProcessesRequest | PaginationRequest): Promise { + let { responseData, headers } = await this.schemaClient.send(processesSchema, data) + return { + data: responseData as ProcessesResponseInterface[], + headers: headers + } + } +} + +export let smClient = new SMClient +export default smClient \ No newline at end of file diff --git a/components/elements/commands/index.tsx b/components/elements/commands/index.tsx index 8b621a0..3877b29 100644 --- a/components/elements/commands/index.tsx +++ b/components/elements/commands/index.tsx @@ -6,6 +6,7 @@ import PlayCircleOutline from '@mui/icons-material/PlayCircleOutline'; import ConfirmDialog from "../confirm-dialog"; import TabContext from "../../../context/tab"; import {TabEnum} from "../../../pages"; +import smClient from "../../../api/sm/sm-client"; export default function Commands() { const {setTab} = useContext(TabContext) @@ -16,14 +17,7 @@ export default function Commands() { const [open, setOpen] = useState(false); let refreshCommands = async () => { - let response = await fetch('http://fmw.sipachev.sv/system-monitoring/commands', { - method: 'GET', - headers: { - 'Content-Type': 'application/json;charset=utf-8', - 'X-Plugin-Token': 'passw0rd' - }, - }); - const commands: CommandInterface[] = await response.json() + const { data: commands } = await smClient.getCommands() setCommands(commands) } @@ -57,17 +51,12 @@ export default function Commands() { return } lock = true - let url = `http://fmw.sipachev.sv/system-monitoring/commands/${selectedCommand.name}/run` let data = command2data[selectedCommand.name] || {} 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) - }); + await smClient.runCommand({ + commandName: selectedCommand.name, + ...data + }) lock = false setTab(TabEnum.Processes) } diff --git a/components/elements/processes/index.tsx b/components/elements/processes/index.tsx index e80f4e0..140186c 100644 --- a/components/elements/processes/index.tsx +++ b/components/elements/processes/index.tsx @@ -12,6 +12,7 @@ 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"; +import smClient from "../../../api/sm/sm-client"; export interface ProcessExceptionInterface { message: string, @@ -67,20 +68,12 @@ export default function Processes() { 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() + const { data: processes, headers } = await smClient.getProcesses({ + page: page + 1, + limit: 20, + }) setProcesses(processes) - setCount(Number(response.headers.get('X-Pagination-Count'))) + setCount(Number(headers.get('X-Pagination-Count'))) refreshLock = false } let output = async (process: ProcessInterface) => { @@ -164,6 +157,55 @@ export default function Processes() { setOpen(false) } + let formatSize = (length: any) => { + var i = 0, type = ['b','Kb','Mb','Gb','Tb','Pb']; + while((length / 1000 | 0) && i < type.length - 1) { + length /= 1024; + i++; + } + return length.toFixed(2) + ' ' + type[i]; + } + + let remaining = (process: ProcessInterface) => + { + if (!process?.progress?.progress || !process.startedAt) { + return null + } + return Math.round((new Date().getTime() - new Date(process.startedAt).getTime()) / process?.progress.progress * (process?.progress.total - process?.progress.progress)); + } + + let formatTime = ($secs: number | null) => { + if ($secs === null) { + return '' + } + let $timeFormats = [ + [0, '< 1 sec'], + [1, '1 sec'], + [2, 'secs', 1], + [60, '1 min'], + [120, 'mins', 60], + [3600, '1 hr'], + [7200, 'hrs', 3600], + [86400, '1 day'], + [172800, 'days', 86400], + ]; + + for (let $index in $timeFormats) { + let $format = $timeFormats[$index] + if ($secs >= $format[0]) { + if ((typeof $timeFormats[$index + 1] !== 'undefined' && $secs < $timeFormats[$index + 1][0]) + || $index == $timeFormats.length - 1 + ) { + if (2 == $format.length) { + return $format[1]; + } + + return Math.floor($secs / $format[2]) + ' ' + $format[1]; + } + } + } + } + return ( <> @@ -184,9 +226,9 @@ export default function Processes() { {!process.progress && !isFinished(process) && } {!process.progress && isFinished(process) && } - {process.progress && } + {process.progress && } {process.progress && - {`${process.progress.progress}`} / {`${process.progress.total}`} - 50% [53Mb] / 20 sec + {`${process.progress.progress}`} / {`${process.progress.total}`} - {progress(process.progress)}% [{formatSize(process.progress.memory)}] / {formatTime(remaining(process))} } {canPlay(process) &&