Compare commits

...

17 Commits

Author SHA1 Message Date
Sipachev Igor 38a3d4b564 Сгенерировал новую версию плагина 1 year ago
Sipachev Igor 906043d52d Добавил несколько фильтров 1 year ago
Rinsvent 26b0881a46 Собрал новую сборку 1 year ago
Rinsvent 931042e716 Поправил типы 1 year ago
Rinsvent 5f59bbe18f Добавил доп информацию 1 year ago
Rinsvent 4cb539d58a Починил настроил лоадер 1 year ago
Rinsvent c852ef14dd Починил ошибки в консоли 1 year ago
Rinsvent 3e8f3943dc Добавил иконку отображающую заблокирован ли процесс 1 year ago
Rinsvent 4dadbec6e8 Доработал смену токена 1 year ago
Rinsvent fbf3c06efd Поправил логику вывода прогресса 1 year ago
Rinsvent 473a9a1993 Файл стилей для компонент 1 year ago
Rinsvent 30c711add4 Вынес токены в компонент 1 year ago
Rinsvent b7fef53ad7 Оптимизировал добавление плюс настроил реактивность для списка 1 year ago
Rinsvent 3c76c7dc09 Оптимизировал проверки 1 year ago
Rinsvent 4f02f56f85 удалил лишний перенос 1 year ago
Rinsvent 0bc9aad4cf Поправил формирвоание адреса 1 year ago
Rinsvent 92fd933c60 перевод на jwt 1 year ago
  1. 17
      api/authentication.ts
  2. 39
      api/schema-client.ts
  3. 3
      api/sm/requests/approve-process.ts
  4. 3
      api/sm/requests/unlock-process.ts
  5. 2
      api/sm/responses/processes.ts
  6. 10
      api/sm/schemas/approve-process.ts
  7. 10
      api/sm/schemas/unlock-process.ts
  8. 27
      api/sm/sm-client.ts
  9. 11
      components/elements/commands/index.tsx
  10. 100
      components/elements/processes/index.tsx
  11. 116
      components/elements/tokens/index.tsx
  12. 0
      components/elements/tokens/styles.module.css
  13. 6
      context/token.ts
  14. 130
      functions/token.ts
  15. 30
      hooks/use-api.ts
  16. 57
      hooks/use-token.ts
  17. 9
      pages/_app.tsx
  18. 10
      pages/index.tsx
  19. 4
      plugin/popup/404.html
  20. 1
      plugin/popup/_next/static/InkGKW3c-wb1BcW3qzzDZ/_buildManifest.js
  21. 0
      plugin/popup/_next/static/InkGKW3c-wb1BcW3qzzDZ/_ssgManifest.js
  22. 1
      plugin/popup/_next/static/J4k37v5qNO58v9nWL4ohq/_buildManifest.js
  23. 166
      plugin/popup/_next/static/chunks/317-7a9f5b1fcf3cfbe5.js
  24. 166
      plugin/popup/_next/static/chunks/556-56e85e4880bbe04b.js
  25. 1
      plugin/popup/_next/static/chunks/pages/_app-526701f4ebc801df.js
  26. 1
      plugin/popup/_next/static/chunks/pages/_app-891652dd44e1e4e1.js
  27. 1
      plugin/popup/_next/static/chunks/pages/index-84dfafb8f3c7c7ce.js
  28. 1
      plugin/popup/_next/static/chunks/pages/index-e05dda18f04fa62a.js
  29. 2
      plugin/popup/index.html

@ -0,0 +1,17 @@
export interface AuthenticationInterface {
key: string
token(): string
}
export class BearerAuthentication implements AuthenticationInterface {
key = 'Authorization'
_token: string
constructor(token: string) {
this._token = token
}
token(): string {
return `Bearer ${this._token}`;
}
}

@ -1,3 +1,5 @@
import {AuthenticationInterface} from "./authentication";
export enum Method { export enum Method {
GET = 'GET', GET = 'GET',
POST = 'POST', POST = 'POST',
@ -9,10 +11,6 @@ export let hasQuery = (method: Method) => {
return Method.GET === method return Method.GET === method
} }
export interface ClientOptions {
baseUrl: string
}
export interface SchemaInterface { export interface SchemaInterface {
url: string, url: string,
method: Method method: Method
@ -25,12 +23,23 @@ export class Context {
memoryKey?: string memoryKey?: string
} }
interface SchemaClientOptionsInterface {
baseUrl?: string
authentication?: AuthenticationInterface
}
export class SchemaClient { export class SchemaClient {
baseUrl: string | null = null baseUrl: string | null = null
authentication: AuthenticationInterface | null = null
context: Context | null = null context: Context | null = null
memory: Record<string, any> = {} memory: Record<string, any> = {}
constructor(options: SchemaClientOptionsInterface) {
this.baseUrl = options.baseUrl || null
this.authentication = options.authentication || null
}
debouncing(time: number) debouncing(time: number)
{ {
let context = this.grabContext() let context = this.grabContext()
@ -47,6 +56,10 @@ export class SchemaClient {
} }
async send(schema: SchemaInterface, data: any) { async send(schema: SchemaInterface, data: any) {
if (this.baseUrl === null) {
throw new Error('Base url not defined!');
}
let context = this.context let context = this.context
this.context = null this.context = null
@ -64,12 +77,22 @@ export class SchemaClient {
preparedUrl += '?' + new URLSearchParams(preparedData) preparedUrl += '?' + new URLSearchParams(preparedData)
} }
let token = ''
if (typeof localStorage !== 'undefined') {
let tokenId = localStorage.getItem('token')
token = localStorage.getItem(tokenId || '') || ''
}
let headers: Record<string, any> = {
...(schema.contentType ? {'Content-Type': schema.contentType} : {}),
'X-Plugin-Token': 'passw0rd',
}
if (this.authentication) {
headers[this.authentication.key] = this.authentication.token();
}
let response = await fetch(preparedUrl, { let response = await fetch(preparedUrl, {
method: schema.method.toString(), method: schema.method.toString(),
headers: { headers,
...(schema.contentType ? {'Content-Type': schema.contentType} : {}),
'X-Plugin-Token': 'passw0rd'
},
body: hasQuery(schema.method) ? null : JSON.stringify(preparedData) body: hasQuery(schema.method) ? null : JSON.stringify(preparedData)
}); });

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

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

@ -37,6 +37,8 @@ export interface ProcessInterface {
canRepeat: boolean canRepeat: boolean
canStop: boolean canStop: boolean
canKill: boolean canKill: boolean
canApprove: boolean
canUnlock: 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

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

@ -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";
@ -16,12 +18,10 @@ import pauseProcessSchema from "./schemas/pause-process";
import stopProcessSchema from "./schemas/stop-process"; import stopProcessSchema from "./schemas/stop-process";
import killProcessSchema from "./schemas/kill-process"; import killProcessSchema from "./schemas/kill-process";
import commandSchema from "./schemas/command"; import commandSchema from "./schemas/command";
import {UnlockProcessRequest} from "./requests/unlock-process";
let baseUrl = typeof location !== 'undefined' && location.origin.includes('.wallester.') ? location.origin : 'http://fmw.sipachev.sv' import unlockProcessSchema from "./schemas/unlock-process";
export class SMClient extends SchemaClient { export class SMClient extends SchemaClient {
baseUrl = baseUrl
async getCommands(): Promise<ResponseInterface<CommandInterface[]>> { async getCommands(): Promise<ResponseInterface<CommandInterface[]>> {
let { responseData, headers } = await this.send(commandsSchema, {}) let { responseData, headers } = await this.send(commandsSchema, {})
return { return {
@ -56,6 +56,22 @@ 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 unlockProcess(data: UnlockProcessRequest): Promise<ResponseInterface> {
let { responseData, headers } = await this.send(unlockProcessSchema, 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 {
@ -104,6 +120,3 @@ export class SMClient extends SchemaClient {
} }
} }
} }
export let smClient = new SMClient
export default smClient

@ -1,14 +1,13 @@
import styles from './styles.module.css' import styles from './styles.module.css'
import {Table, TableBody, TableCell, TableHead, TableRow, IconButton, Autocomplete, TextField} from "@mui/material"; import {IconButton, Autocomplete, TextField} from "@mui/material";
import Command from "./elements/command"; import Command from "./elements/command";
import {useContext, useEffect, useState} from "react"; import {useContext, useEffect, useState} from "react";
import PlayCircleOutline from '@mui/icons-material/PlayCircleOutline';
import ConfirmDialog from "../confirm-dialog"; import ConfirmDialog from "../confirm-dialog";
import TabContext from "../../../context/tab"; import TabContext from "../../../context/tab";
import {TabEnum} from "../../../pages"; import {TabEnum} from "../../../pages";
import smClient from "../../../api/sm/sm-client";
import Send from "@mui/icons-material/Send" import Send from "@mui/icons-material/Send"
import {CommandInterface} from "../../../api/sm/responses/comamnds"; import {CommandInterface} from "../../../api/sm/responses/comamnds";
import {useApi} from "../../../hooks/use-api";
export default function Commands() { export default function Commands() {
const {setTab} = useContext(TabContext) const {setTab} = useContext(TabContext)
@ -18,9 +17,10 @@ export default function Commands() {
const [argumentList, setArgumentList] = useState<Record<string, any>>({}); const [argumentList, setArgumentList] = useState<Record<string, any>>({});
const [value, setValue] = useState<string | null>(''); const [value, setValue] = useState<string | null>('');
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const api = useApi()
let refreshCommands = async () => { let refreshCommands = async () => {
const { data: commands } = await smClient.useMemory().getCommands() const { data: commands } = await api.useMemory().getCommands()
setCommands(commands) setCommands(commands)
} }
@ -34,6 +34,7 @@ export default function Commands() {
}, [value]) }, [value])
let variants = commands.map((command: CommandInterface, index: number) => command.name); let variants = commands.map((command: CommandInterface, index: number) => command.name);
variants.push('')
let callback = (name: string, optionList: Record<string, any>, argumentList: Record<string, any>) => { let callback = (name: string, optionList: Record<string, any>, argumentList: Record<string, any>) => {
setOptionList(optionList) setOptionList(optionList)
@ -48,7 +49,7 @@ export default function Commands() {
return return
} }
lock = true lock = true
await smClient.runCommand({ await api.runCommand({
commandName: selectedCommand.name, commandName: selectedCommand.name,
options: optionList, options: optionList,
arguments: argumentList, arguments: argumentList,

@ -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,10 @@ 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 LockOutlined from '@mui/icons-material/LockOutlined';
import LockOpenOutlined from '@mui/icons-material/LockOpenOutlined';
import { import {
Box, Box,
TextField, TextField,
@ -25,11 +29,12 @@ import {
TablePagination, Autocomplete TablePagination, Autocomplete
} from "@mui/material" } from "@mui/material"
import ConfirmDialog from "../confirm-dialog"; import ConfirmDialog from "../confirm-dialog";
import smClient from "../../../api/sm/sm-client";
import {ProcessInterface, Status} from "../../../api/sm/responses/processes"; import {ProcessInterface, Status} from "../../../api/sm/responses/processes";
import Command from "../commands/elements/command"; 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 Context from "../../../context/token";
enum Action { enum Action {
Run, Run,
@ -38,15 +43,18 @@ enum Action {
Kill, Kill,
Play, Play,
Pause, Pause,
Approve,
Unlock,
} }
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);
const [count, setCount] = useState<number>(0); const [count, setCount] = useState<number>(0);
const [type, setType] = useState<string | null>(''); const [type, setType] = useState<string>('');
const [name, setName] = useState<string | null>(''); const [name, setName] = useState<string>('');
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [modalLoading, setModalLoading] = useState<boolean>(true); const [modalLoading, setModalLoading] = useState<boolean>(true);
const [action, setAction] = useState<Action | null>(null); const [action, setAction] = useState<Action | null>(null);
@ -54,11 +62,13 @@ export default function Processes() {
const [selectedProcess, setSelectedProcess] = useState<ProcessInterface | null>(null); const [selectedProcess, setSelectedProcess] = useState<ProcessInterface | null>(null);
const [optionList, setOptionList] = useState<Record<string, any>>({}); const [optionList, setOptionList] = useState<Record<string, any>>({});
const [argumentList, setArgumentList] = useState<Record<string, any>>({}); const [argumentList, setArgumentList] = useState<Record<string, any>>({});
const api = useApi()
let variants = commands.map((command: CommandInterface, index: number) => command.name); let variants = commands.map((command: CommandInterface, index: number) => command.name);
variants.push('')
let refreshCommands = async () => { let refreshCommands = async () => {
const { data: commands } = await smClient.useMemory().getCommands() const { data: commands } = await api.useMemory().getCommands()
setCommands(commands) setCommands(commands)
} }
@ -80,7 +90,7 @@ export default function Processes() {
if (!!name) { if (!!name) {
data['name'] = name data['name'] = name
} }
const { data: processes, headers } = await smClient.getProcesses({ const { data: processes, headers } = await api.getProcesses({
...data, ...data,
page: page + 1, page: page + 1,
limit: 20, limit: 20,
@ -91,7 +101,7 @@ export default function Processes() {
refreshLock = false refreshLock = false
} }
let output = async (process: ProcessInterface) => { let output = async (process: ProcessInterface) => {
const { data: output } = await smClient.getProcessOutput({ const { data: output } = await api.getProcessOutput({
id: process.id id: process.id
}) })
@ -115,6 +125,12 @@ 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 processName = (process: ProcessInterface) => {
return process.name + ' ' + JSON.stringify(process.options)+ ' ' + JSON.stringify(process.arguments)
}
const handleChangePage = (event: any, page: number) => { const handleChangePage = (event: any, page: number) => {
setPage(page); setPage(page);
@ -136,7 +152,7 @@ export default function Processes() {
lock = true lock = true
if (action === Action.Run) { if (action === Action.Run) {
await smClient.runCommand({ await api.runCommand({
commandName: selectedProcess.name, commandName: selectedProcess.name,
options: optionList, options: optionList,
arguments: argumentList, arguments: argumentList,
@ -144,8 +160,20 @@ export default function Processes() {
}) })
} }
if (action === Action.Approve) {
await api.approveProcess({
id: selectedProcess.id,
})
}
if (action === Action.Unlock) {
await api.unlockProcess({
id: selectedProcess.id,
})
}
if (action === Action.Repeat) { if (action === Action.Repeat) {
await smClient.repeatProcess({ await api.repeatProcess({
id: selectedProcess.id, id: selectedProcess.id,
requestId: dialogId requestId: dialogId
}) })
@ -176,10 +204,11 @@ export default function Processes() {
<Autocomplete <Autocomplete
value={type} value={type}
onChange={(event: any, newValue: string | null) => { onChange={(event: any, newValue: string | null) => {
setType(newValue); setType(newValue || '');
setLoading(true)
}} }}
disablePortal disablePortal
options={['None', "Running", "History"]} options={['None', "Running", "History", "Locked", 'Need_Approve', '']}
renderInput={(params) => <TextField {...params} label="Type"/>} renderInput={(params) => <TextField {...params} label="Type"/>}
/> />
</Grid> </Grid>
@ -189,6 +218,7 @@ export default function Processes() {
value={name} value={name}
onChange={(event: any, newValue: string | null) => { onChange={(event: any, newValue: string | null) => {
setName(newValue || ''); setName(newValue || '');
setLoading(true)
}} }}
disablePortal disablePortal
options={variants} options={variants}
@ -210,10 +240,12 @@ export default function Processes() {
<TableBody> <TableBody>
{processes.map((process: ProcessInterface, index: number) => ( {processes.map((process: ProcessInterface, index: number) => (
<TableRow key={process.id}> <TableRow key={process.id}>
<TableCell>{process.name}</TableCell> <TableCell title={processName(process)}>{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}
@ -244,12 +276,21 @@ export default function Processes() {
{Status.Wait === process.status && <IconButton title={`Wait`} aria-label="Wait"> {Status.Wait === process.status && <IconButton title={`Wait`} aria-label="Wait">
<HourglassEmptyOutlined/> <HourglassEmptyOutlined/>
</IconButton>} </IconButton>}
{process.lock && <IconButton title={`Locked`} aria-label="Locked">
<LockOutlined/>
</IconButton>}
</TableCell> </TableCell>
<TableCell> <TableCell>
{process.canRepeat && <IconButton onClick={() => openDialog(process, Action.Repeat)} title={`Repeat`} aria-label="Repeat"> {process.canUnlock && token && token?.permissions.indexOf('unlock_process') > -1 && <IconButton onClick={() => openDialog(process, Action.Unlock)} title={`Unlock`} aria-label="Unlock">
<LockOpenOutlined/>
</IconButton>}
{process.canApprove && token && token?.permissions.indexOf('approve_process') > -1 && <IconButton onClick={() => openDialog(process, Action.Approve)} title={`Approve`} aria-label="Approve">
<ThumbUpAltOutlined/>
</IconButton>}
{process.canRepeat && token && token?.permissions.indexOf('run_command') > -1 && <IconButton onClick={() => openDialog(process, Action.Repeat)} title={`Repeat`} aria-label="Repeat">
<ReplayOutlined/> <ReplayOutlined/>
</IconButton>} </IconButton>}
{process.canKill && <IconButton onClick={() => openDialog(process, Action.Kill)} title={`Kill`} aria-label="Kill"> {process.canKill && token && token?.permissions.indexOf('kill_process') > -1 && <IconButton onClick={() => openDialog(process, Action.Kill)} title={`Kill`} aria-label="Kill">
<DeleteForeverOutlined/> <DeleteForeverOutlined/>
</IconButton>} </IconButton>}
{process?.outputId && <IconButton title={`Output`} onClick={() => output(process)} aria-label="Output"> {process?.outputId && <IconButton title={`Output`} onClick={() => output(process)} aria-label="Output">
@ -257,7 +298,7 @@ export default function Processes() {
</IconButton>} </IconButton>}
</TableCell> </TableCell>
<TableCell title={new Date(process.createdAt).toLocaleString()}> <TableCell title={new Date(process.createdAt).toLocaleString()}>
{new Date(process.createdAt).toLocaleDateString()} {new Date(process.createdAt).toLocaleString()}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@ -268,6 +309,7 @@ export default function Processes() {
component="div" component="div"
count={count} count={count}
rowsPerPage={20} rowsPerPage={20}
rowsPerPageOptions={[10, 20, 50, 100]}
page={page} page={page}
onPageChange={handleChangePage} onPageChange={handleChangePage}
/> />
@ -278,7 +320,7 @@ export default function Processes() {
modifyCallback={async () => { modifyCallback={async () => {
setAction(Action.Run) setAction(Action.Run)
setModalLoading(true) setModalLoading(true)
let {data: command} = await smClient.getCommand(selectedProcess.name) let {data: command} = await api.getCommand(selectedProcess.name)
setCommand(command) setCommand(command)
setModalLoading(false) setModalLoading(false)
}} }}
@ -299,14 +341,28 @@ 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
title={selectedProcess.name}
open={Action.Unlock === action}
agreeCallback={agreeCallback}
closeCallback={() => {setAction(null)}}>
Unlock?
</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) }
agreeCallback={async () => { agreeCallback={async () => {
Action.Play === action && await smClient.playProcess(selectedProcess.id) Action.Play === action && await api.playProcess(selectedProcess.id)
Action.Pause === action && await smClient.pauseProcess(selectedProcess.id) Action.Pause === action && await api.pauseProcess(selectedProcess.id)
Action.Stop === action && await smClient.stopProcess(selectedProcess.id) Action.Stop === action && await api.stopProcess(selectedProcess.id)
Action.Kill === action && await smClient.killProcess(selectedProcess.id) Action.Kill === action && await api.killProcess(selectedProcess.id)
setAction(null) setAction(null)
}} }}
closeCallback={() => {setAction(null)}}> closeCallback={() => {setAction(null)}}>

@ -0,0 +1,116 @@
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} [${token.id}]`
}));
useEffect(() => {
setShowAddForm(!token)
}, [token])
return (
<>
<Grid container>
<Grid item xs={2}>
<Autocomplete
freeSolo
value={{
...token,
label: token?.host ? `${token.host} [${token.id}]` : ''
}}
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>
</>
)
}

@ -0,0 +1,6 @@
import React from 'react'
import {UseTokenInterface} from "../hooks/use-token"
const Context = React.createContext({} as UseTokenInterface)
export const Provider = Context.Provider
export default Context

@ -0,0 +1,130 @@
export interface TokenData<T=any> {
payload: T
headers: Record<string, any>
}
const isClient = () => {
return typeof window !== 'undefined'
}
export const storeToken = (token: string) => {
if (!isClient()) {
return;
}
let tokenData = grabTokenData(token)
let tokenId = tokenData?.payload?.id;
if (!tokenId) {
return;
}
let tokens: string | null = window.localStorage.getItem('tokens')
let tokensData: string[] = JSON.parse(tokens || '[]');
if (!tokensData) {
return
}
window.localStorage.setItem('token', tokenId)
window.localStorage.setItem(tokenId, token)
tokensData.push(tokenId)
window.localStorage.setItem('tokens', JSON.stringify(tokensData))
}
export const cleanToken = (id: string) => {
if (!isClient()) {
return;
}
let tokens: string | null = window.localStorage.getItem('tokens')
let tokensData: string[] = JSON.parse(tokens || '[]');
if (!tokensData) {
return
}
tokensData = tokensData.filter((storedTokenId: string) => storedTokenId !== id)
window.localStorage.setItem('tokens', JSON.stringify(tokensData))
window.localStorage.removeItem(id)
// Чистим текущий токен
let activeTokenId: string | null = window.localStorage.getItem('token')
if (id === activeTokenId) {
window.localStorage.removeItem('token')
}
}
export const selectToken = (id: string) => {
if (!isClient()) {
return;
}
window.localStorage.setItem('token', id)
}
export const grabTokenData = (token: string): TokenData|null => {
if (!isClient()) {
return null
}
const parts = token.split('.')
if (parts.length !== 3) {
return null
}
let payload = JSON.parse(window.atob(parts[1]))
if (!payload){
return null
}
let headers = JSON.parse(window.atob(parts[0]))
if (!headers){
return null
}
return {
payload,
headers,
};
}
export const grabRawToken = (): string | null => {
if (!isClient()) {
return null
}
const tokenId = window.localStorage.getItem('token')
if (!tokenId) {
return null
}
return window.localStorage.getItem(tokenId)
}
export const grabToken = (): TokenData | null => {
const token = grabRawToken()
if (!token) {
return null
}
return grabTokenData(token);
}
export const grabTokens = (): TokenData[] => {
if (!isClient()) {
return []
}
const storedTokens = window.localStorage.getItem('tokens')
let tokens: string[] = JSON.parse(storedTokens || '[]');
if (!tokens) {
return []
}
let result: TokenData[] = []
tokens.forEach((tokenId) => {
let token = window.localStorage.getItem(tokenId)
if (!token) {
return true
}
let tokenData = grabTokenData(token)
if (tokenData) {
result.push(tokenData)
}
})
return result
}

@ -0,0 +1,30 @@
import {useContext, useEffect, useState} from 'react'
import {SMClient} from "../api/sm/sm-client";
import Context from "../context/token";
import {Token} from "./use-token";
import {BearerAuthentication} from "../api/authentication";
import {grabRawToken} from "../functions/token";
const grabClient = (token: Token | null, rawToken: string | null): SMClient => {
let authentication = {}
if (rawToken) {
authentication = {
authentication: new BearerAuthentication(rawToken)
}
}
return new SMClient({
baseUrl: token?.host || 'localhost',
...authentication
})
}
export function useApi(): SMClient {
let {token} = useContext(Context)
let [client, setClient] = useState<SMClient>(grabClient(token, grabRawToken()))
useEffect(() => {
setClient(grabClient(token, grabRawToken()))
}, [token])
return client
}

@ -0,0 +1,57 @@
import {useState, useEffect} from 'react'
import {cleanToken, grabToken, grabTokenData, grabTokens, selectToken, storeToken} from "../functions/token";
export class Token {
id!: string
host!: string
permissions!: string[]
}
export interface UseTokenInterface {
token: Token|null
tokens: Token[]
switchToken(tokenId: string): void
deleteToken(tokenId: string): void
addToken(tokenId: string): boolean
}
export function useToken(): UseTokenInterface {
const [token, setToken] = useState<Token|null>(null);
const [tokens, setTokens] = useState<Token[]>([]);
useEffect(() => {
setToken(grabToken()?.payload)
let tokens = grabTokens().map((tokenData) => tokenData.payload as Token)
setTokens(tokens)
return () => {
}
}, [])
const switchToken = (tokenId: string) => {
selectToken(tokenId)
setToken(grabToken()?.payload)
}
const deleteToken = (tokenId: string) => {
cleanToken(tokenId)
setToken(grabToken()?.payload)
}
const addToken = (token: string): boolean => {
if (!grabTokenData(token)) {
return false;
}
storeToken(token)
setToken(grabToken()?.payload)
setTokens([
...tokens,
grabToken()?.payload
])
return true
}
return {token, tokens, switchToken, deleteToken, addToken}
}

@ -1,6 +1,13 @@
import '../styles/globals.css' import '../styles/globals.css'
import type { AppProps } from 'next/app' import type { AppProps } from 'next/app'
import {Provider} from "../context/token";
import {useToken} from "../hooks/use-token";
export default function App({ Component, pageProps }: AppProps) { export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} /> let {token, tokens, switchToken, deleteToken, addToken} = useToken()
return (
<Provider value={{token, tokens, switchToken, deleteToken, addToken}}>
<Component {...pageProps} />
</Provider>
)
} }

@ -2,9 +2,12 @@ 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} from '@mui/material'; import {Tabs, Tab} from '@mui/material';
import {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 Tokens from "../components/elements/tokens";
export enum TabEnum { export enum TabEnum {
Commands, Commands,
@ -12,8 +15,10 @@ export enum TabEnum {
} }
export default function Home() { export default function Home() {
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)
return ( return (
<> <>
<Head> <Head>
@ -23,6 +28,8 @@ 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 />
{token && <>
<Tabs <Tabs
value={tab} value={tab}
onChange={handleChange} onChange={handleChange}
@ -38,6 +45,7 @@ export default function Home() {
{tab === TabEnum.Commands && <Commands />} {tab === TabEnum.Commands && <Commands />}
{tab === TabEnum.Processes && <Processes />} {tab === TabEnum.Processes && <Processes />}
</TabProvider> </TabProvider>
</>}
</main> </main>
</> </>
) )

@ -1,4 +1,4 @@
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/_next/static/css/961153d3a9f88130.css" as="style"/><link rel="stylesheet" href="/_next/static/css/961153d3a9f88130.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/_next/static/chunks/webpack-0b5d8249fb15f5f3.js" defer=""></script><script src="/_next/static/chunks/framework-114634acb84f8baa.js" defer=""></script><script src="/_next/static/chunks/main-66ca454f7bdf962f.js" defer=""></script><script src="/_next/static/chunks/pages/_app-891652dd44e1e4e1.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8353112a01355ec2.js" defer=""></script><script src="/_next/static/J4k37v5qNO58v9nWL4ohq/_buildManifest.js" defer=""></script><script src="/_next/static/J4k37v5qNO58v9nWL4ohq/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:-apple-system, BlinkMacSystemFont, Roboto, &quot;Segoe UI&quot;, &quot;Fira Sans&quot;, Avenir, &quot;Helvetica Neue&quot;, &quot;Lucida Grande&quot;, sans-serif;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style> <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/_next/static/css/961153d3a9f88130.css" as="style"/><link rel="stylesheet" href="/_next/static/css/961153d3a9f88130.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/_next/static/chunks/webpack-0b5d8249fb15f5f3.js" defer=""></script><script src="/_next/static/chunks/framework-114634acb84f8baa.js" defer=""></script><script src="/_next/static/chunks/main-66ca454f7bdf962f.js" defer=""></script><script src="/_next/static/chunks/pages/_app-526701f4ebc801df.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8353112a01355ec2.js" defer=""></script><script src="/_next/static/InkGKW3c-wb1BcW3qzzDZ/_buildManifest.js" defer=""></script><script src="/_next/static/InkGKW3c-wb1BcW3qzzDZ/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:-apple-system, BlinkMacSystemFont, Roboto, &quot;Segoe UI&quot;, &quot;Fira Sans&quot;, Avenir, &quot;Helvetica Neue&quot;, &quot;Lucida Grande&quot;, sans-serif;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>
body { margin: 0; color: #000; background: #fff; } body { margin: 0; color: #000; background: #fff; }
.next-error-h1 { .next-error-h1 {
border-right: 1px solid rgba(0, 0, 0, .3); border-right: 1px solid rgba(0, 0, 0, .3);
@ -9,4 +9,4 @@
.next-error-h1 { .next-error-h1 {
border-right: 1px solid rgba(255, 255, 255, .3); border-right: 1px solid rgba(255, 255, 255, .3);
} }
}</style><h1 class="next-error-h1" style="display:inline-block;margin:0;margin-right:20px;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block;text-align:left;line-height:49px;height:49px;vertical-align:middle"><h2 style="font-size:14px;font-weight:normal;line-height:49px;margin:0;padding:0">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"J4k37v5qNO58v9nWL4ohq","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html> }</style><h1 class="next-error-h1" style="display:inline-block;margin:0;margin-right:20px;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block;text-align:left;line-height:49px;height:49px;vertical-align:middle"><h2 style="font-size:14px;font-weight:normal;line-height:49px;margin:0;padding:0">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"InkGKW3c-wb1BcW3qzzDZ","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>

@ -0,0 +1 @@
self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/chunks/317-7a9f5b1fcf3cfbe5.js","static/css/758d1a1ad3b56ec4.css","static/chunks/pages/index-e05dda18f04fa62a.js"],"/_error":["static/chunks/pages/_error-8353112a01355ec2.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

@ -1 +0,0 @@
self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/chunks/556-56e85e4880bbe04b.js","static/css/758d1a1ad3b56ec4.css","static/chunks/pages/index-84dfafb8f3c7c7ce.js"],"/_error":["static/chunks/pages/_error-8353112a01355ec2.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{6840:function(t,e,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return n(2595)}])},7889:function(t,e,n){"use strict";n.d(e,{z:function(){return l}});var r=n(7294);let o=r.createContext({}),l=o.Provider;e.Z=o},4085:function(t,e,n){"use strict";n.d(e,{DR:function(){return c},F8:function(){return d},Y0:function(){return l},gn:function(){return a},mB:function(){return o},rK:function(){return u},se:function(){return i}});let r=()=>!0,o=t=>{var e;if(!r())return;let n=i(t),o=null==n?void 0:null===(e=n.payload)||void 0===e?void 0:e.id;if(!o)return;let l=JSON.parse(window.localStorage.getItem("tokens")||"[]");l&&(window.localStorage.setItem("token",o),window.localStorage.setItem(o,t),l.push(o),window.localStorage.setItem("tokens",JSON.stringify(l)))},l=t=>{if(!r())return;let e=JSON.parse(window.localStorage.getItem("tokens")||"[]");e&&(e=e.filter(e=>e!==t),window.localStorage.setItem("tokens",JSON.stringify(e)),window.localStorage.removeItem(t),t===window.localStorage.getItem("token")&&window.localStorage.removeItem("token"))},u=t=>{r()&&window.localStorage.setItem("token",t)},i=t=>{if(!r())return null;let e=t.split(".");if(3!==e.length)return null;let n=JSON.parse(window.atob(e[1]));if(!n)return null;let o=JSON.parse(window.atob(e[0]));return o?{payload:n,headers:o}:null},a=()=>{if(!r())return null;let t=window.localStorage.getItem("token");return t?window.localStorage.getItem(t):null},d=()=>{let t=a();return t?i(t):null},c=()=>{if(!r())return[];let t=window.localStorage.getItem("tokens"),e=JSON.parse(t||"[]");if(!e)return[];let n=[];return e.forEach(t=>{let e=window.localStorage.getItem(t);if(!e)return!0;let r=i(e);r&&n.push(r)}),n}},2595:function(t,e,n){"use strict";n.r(e),n.d(e,{default:function(){return i}});var r=n(5893);n(4744);var o=n(7889),l=n(7294),u=n(4085);function i(t){let{Component:e,pageProps:n}=t,{token:i,tokens:a,switchToken:d,deleteToken:c,addToken:f}=function(){let[t,e]=(0,l.useState)(null),[n,r]=(0,l.useState)([]);(0,l.useEffect)(()=>{var t;return e(null===(t=(0,u.F8)())||void 0===t?void 0:t.payload),r((0,u.DR)().map(t=>t.payload)),()=>{}},[]);let o=t=>{var n;(0,u.rK)(t),e(null===(n=(0,u.F8)())||void 0===n?void 0:n.payload)},i=t=>{var n;(0,u.Y0)(t),e(null===(n=(0,u.F8)())||void 0===n?void 0:n.payload)},a=t=>{var o,l;return!!(0,u.se)(t)&&((0,u.mB)(t),e(null===(o=(0,u.F8)())||void 0===o?void 0:o.payload),r([...n,null===(l=(0,u.F8)())||void 0===l?void 0:l.payload]),!0)};return{token:t,tokens:n,switchToken:o,deleteToken:i,addToken:a}}();return(0,r.jsx)(o.z,{value:{token:i,tokens:a,switchToken:d,deleteToken:c,addToken:f},children:(0,r.jsx)(e,{...n})})}},4744:function(){}},function(t){var e=function(e){return t(t.s=e)};t.O(0,[774,179],function(){return e(6840),e(880)}),_N_E=t.O()}]);

@ -1 +0,0 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{6840:function(n,u,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return t(3847)}])},3847:function(n,u,t){"use strict";t.r(u),t.d(u,{default:function(){return _}});var r=t(5893);function _(n){let{Component:u,pageProps:t}=n;return(0,r.jsx)(u,{...t})}t(4744)},4744:function(){}},function(n){var u=function(u){return n(n.s=u)};n.O(0,[774,179],function(){return u(6840),u(880)}),_N_E=n.O()}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save