перевод на jwt

jwt
Rinsvent 1 year ago
parent de7a4b6d52
commit 92fd933c60
  1. 17
      api/authentication.ts
  2. 39
      api/schema-client.ts
  3. 7
      api/sm/sm-client.ts
  4. 10
      components/elements/commands/index.tsx
  5. 23
      components/elements/processes/index.tsx
  6. 6
      context/token.ts
  7. 120
      functions/token.ts
  8. 30
      hooks/use-api.ts
  9. 48
      hooks/use-token.ts
  10. 9
      pages/_app.tsx
  11. 95
      pages/index.tsx

@ -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)
}); });

@ -17,11 +17,7 @@ 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";
let baseUrl = typeof location !== 'undefined' && location.origin.includes('.wallester.') ? location.origin : 'http://fmw.sipachev.sv'
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 {
@ -104,6 +100,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)
} }
@ -48,7 +48,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,

@ -25,11 +25,11 @@ 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";
enum Action { enum Action {
Run, Run,
@ -54,11 +54,12 @@ 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);
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 +81,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 +92,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
}) })
@ -136,7 +137,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,
@ -145,7 +146,7 @@ export default function Processes() {
} }
if (action === Action.Repeat) { if (action === Action.Repeat) {
await smClient.repeatProcess({ await api.repeatProcess({
id: selectedProcess.id, id: selectedProcess.id,
requestId: dialogId requestId: dialogId
}) })
@ -278,7 +279,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)
}} }}
@ -303,10 +304,10 @@ export default function Processes() {
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,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,120 @@
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('.')
let payload = 3 === parts.length && parts[1] ? JSON.parse(window.atob(parts[1])) : null
let headers = 3 === parts.length && parts[0] ? JSON.parse(window.atob(parts[0])) : null
if (!payload || !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: 'http://' + (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,48 @@
import {useState, useEffect} from 'react'
import {cleanToken, grabToken, 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): void
}
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)
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) => {
storeToken(token)
setToken(grabToken()?.payload)
}
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>
)
} }

@ -1,10 +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} from '@mui/material'; import {Tabs, Tab, MenuItem, Select, InputLabel, TextareaAutosize, Button} from '@mui/material';
import {useState} from "react"; import {useContext, useEffect, 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";
export enum TabEnum { export enum TabEnum {
Commands, Commands,
@ -12,8 +14,42 @@ export enum TabEnum {
} }
export default function Home() { export default function Home() {
let {token, tokens, addToken} = 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>
@ -23,21 +59,46 @@ 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}>
<Tabs {tokens.length > 0 && <>
value={tab} <InputLabel id="tokens">Tokens</InputLabel>
onChange={handleChange} <Select
variant="scrollable" value={token}
scrollButtons labelId="tokens"
allowScrollButtonsMobile id="demo-simple-select-autowidth"
aria-label="scrollable force tabs example" label="Tokens"
> onChange={(event: any) => {
<Tab value={TabEnum.Commands} label="Commands" /> // selectToken(event.target.value)
<Tab value={TabEnum.Processes} label="Processes" /> // const storedToken = grabToken()
</Tabs> // setToken(storedToken)
<TabProvider value={{tab, setTab}}> // if (storedToken) {
{tab === TabEnum.Commands && <Commands />} // smClient.baseUrl = storedToken.payload.host
{tab === TabEnum.Processes && <Processes />} // }
</TabProvider> }}
>
{tokens.map((token, index) => (
<MenuItem key={index} value={index}>{token.host}</MenuItem>
))}
</Select>
</>}
{token && <>
<Tabs
value={tab}
onChange={handleChange}
variant="scrollable"
scrollButtons
allowScrollButtonsMobile
aria-label="scrollable force tabs example"
>
<Tab value={TabEnum.Commands} label="Commands" />
<Tab value={TabEnum.Processes} label="Processes" />
</Tabs>
<TabProvider value={{tab, setTab}}>
{tab === TabEnum.Commands && <Commands />}
{tab === TabEnum.Processes && <Processes />}
</TabProvider>
</>}
</main> </main>
</> </>
) )

Loading…
Cancel
Save