updated ui

This commit is contained in:
2024-05-21 23:53:23 +03:00
parent c7667ec5b5
commit 2ebcf0a8c4
11 changed files with 10609 additions and 10723 deletions

View File

@@ -1,7 +1,7 @@
import { Box, Paper, ThemeProvider, List, ListItem, ListItemButton, ListItemText, SwipeableDrawer, ListItemIcon, IconButton, AppBar, Toolbar, PaletteMode, createTheme, useMediaQuery, useTheme } from "@mui/material";
import React, { Dispatch, ReactNode, useState } from "react";
import React, { Dispatch, ReactNode } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { ApiWrapper, getDesignTokens, GlobalOpenApi, GlobalUserInfo } from "./common";
import { ApiWrapper, getDesignTokens, GlobalUserInfo } from "./common";
import { LoginPage } from "./login";
import ServersBoard from "./servers";
import MenuIcon from '@mui/icons-material/Menu';
@@ -121,7 +121,6 @@ export default function App() {
<ThemeProvider theme={theme}>
<BrowserRouter>
<ApiWrapper>
<GlobalOpenApi>
<GlobalUserInfo>
<Menu setMode={setMode}>
<Routes>
@@ -134,7 +133,6 @@ export default function App() {
</Routes>
</Menu>
</GlobalUserInfo>
</GlobalOpenApi>
</ApiWrapper>
</BrowserRouter>
</ThemeProvider>

View File

@@ -596,3 +596,27 @@ export const SERVER_ACTIONS: ActionInfo[] = [
response_action: "Ignore"
}
]
export const CREATE_SERVER_ACTION: ActionInfo = {
name: "Create Server",
args: {
"$ref": "#/definitions/CreateServer",
definitions: definitions,
},
requestType: "post",
endpoint: "/servers",
permissions: Permission.Create,
response_action: "Ignore"
}
export const INVITE_USER_ACTION: ActionInfo = {
name: "Create Server",
args: {
"$ref": "#/definitions/InviteUserRequests",
definitions: definitions,
},
requestType: "post",
endpoint: "/users",
permissions: Permission.Admin,
response_action: "Ignore"
}

View File

@@ -1,7 +1,7 @@
import { TableRow, TableCell, TableContainer, TableHead, Table, Button, Popover, Paper, TableBody, Chip, Link, ButtonGroup, Modal, Box } from "@mui/material"
import React, { useContext, Dispatch, useState, useEffect, createContext, Context } from "react"
import Form from "@rjsf/mui"
import { apiAuthenticatedContext, useApiDoc, api, ActionItem, formModalStyle, DataTable, useActions, ActionInfo, ActionGroup, actionIdentifierContext } from "./common"
import { apiAuthenticatedContext, api, DataTable, ActionInfo, ActionGroup, actionIdentifierContext } from "./common"
import validator from '@rjsf/validator-ajv8';
import { Browser, ServerInfo } from "./interfaces";
import { loadServers } from "./servers";
@@ -85,7 +85,7 @@ export function BrowsersPage({ }) {
)
}
return <DataTable headers={['Browser Owner', 'Server Owner', 'Server Game', 'Game Version', 'Actions']}>
<browserActionContext.Provider value={useActions(api, '/browsers/{browser_id}')}>
<browserActionContext.Provider value={[]}>
{browserComponents}
</browserActionContext.Provider>
</DataTable>

View File

@@ -79,8 +79,6 @@ export const api: AxiosInstance = axios.create({
baseURL: API_URL,
});
console.log(process.env)
export function ApiWrapper(p: { children: ReactNode}) {
const {children} = p
const token = localStorage.getItem('token')
@@ -99,34 +97,6 @@ export function ApiWrapper(p: { children: ReactNode}) {
</apiAuthenticatedContext.Provider>)
}
export function useApiDoc(api: AxiosInstance, path: string, method: 'get' | 'post' | 'delete') {
const [apiRecord, setApiRecord] = useState(null as OpenAPISchema|null)
api.get('/openapi.json').then(
(value) => {
setApiRecord(value.data)
}
)
if (apiRecord) {
const schema = apiRecord.paths[path][method]
if (!schema){
return
}
let formSchema: JSONSchema7 = { title: schema.summary, type: 'object', required: [], properties: {}, definitions: apiRecord.components, default: {} }
if (schema.requestBody) {
formSchema = schema.requestBody.content['application/json'].schema
formSchema.definitions = apiRecord.components
}
return JSON.parse(JSON.stringify(formSchema).replaceAll('#/components', '#/definitions'))
}
}
export interface ActionInfo {
name: string
requestType: 'post' | 'get' | 'delete'
@@ -137,87 +107,6 @@ export interface ActionInfo {
}
export function useActions(api: AxiosInstance, path_prefix: string): ActionInfo[] {
const openapi: OpenAPISchema|null = useContext(OpenApiContext)
let [actions, setActions]: [Record<string, ActionInfo[]>, Dispatch<Record<string, ActionInfo[]>>] = useState({})
console.log(actions)
if (actions[path_prefix] && actions[path_prefix].length > 0) {
return actions[path_prefix]
}
if (openapi === null) {
return []
}
let responseActions: ActionInfo[] = []
let paths = openapi.paths
for (let [path, request] of Object.entries(paths)) {
if (path.startsWith(path_prefix)) {
for (let [method, schema] of Object.entries(request)) {
let formSchema: JSONSchema7 = { title: schema.summary, type: 'object', required: [], properties: {}, definitions: openapi.components, default: {} }
if (schema.requestBody) {
formSchema = schema.requestBody.content['application/json'].schema
formSchema.definitions = openapi.components
}
responseActions.push(
{
name: schema.summary,
args: formSchema,
requestType: (method as 'get' | 'post' | 'delete'),
endpoint: path,
permissions: schema.permissions,
response_action: schema.api_response,
}
)
}
}
}
actions[path_prefix] = responseActions
setActions(actions)
if (actions[path_prefix]) {
return actions[path_prefix]
}
else {
return []
}
}
export function useAction(p: {path: string, method: 'post' | 'get' | 'delete'}): ActionInfo|undefined{
const apiDoc: OpenAPISchema|null = useContext(OpenApiContext)
const [action, setAction]: [ActionInfo|null, Dispatch<ActionInfo|null>] = useState(null as ActionInfo|null)
if (action != null){
return action
}
if (!apiDoc){
return
}
const schema = apiDoc.paths[p.path][p.method]
if (!schema){
return
}
let formSchema: JSONSchema7 = { title: schema.summary, type: 'object', required: [], properties: {}, definitions: apiDoc.components, default: {} }
if (schema.requestBody) {
formSchema = schema.requestBody.content['application/json'].schema
formSchema.definitions = apiDoc.components
}
const calculatedAction = {
name: schema.summary,
args: JSON.parse(JSON.stringify(formSchema).replaceAll('#/components', '#/definitions')),
requestType: p.method,
endpoint: p.path,
permissions: schema.permissions,
response_action: schema.api_response,
}
setAction(calculatedAction)
return calculatedAction
}
interface Options{
label: string
const: string
@@ -253,6 +142,7 @@ function FetcherField(props: WidgetProps){
function isUserAllowed(user: User|null, action: ActionInfo): boolean{
console.log({User: user, Action: action})
if (user === null){
return false
}
@@ -468,19 +358,3 @@ export function GlobalUserInfo(props: {children: any}){
{props.children}
</UserInfoContext.Provider>
}
export const OpenApiContext: Context<OpenAPISchema|null> = createContext(null as OpenAPISchema|null)
export function GlobalOpenApi(props: {children: any}){
const [openApi, setOpenApi]: [OpenAPISchema|null, Dispatch<OpenAPISchema|null>] = useState(null as OpenAPISchema|null)
if (openApi === null){
api.get('/openapi.json').then((event)=>{
setOpenApi(event.data)
}
)
}
return <OpenApiContext.Provider value={openApi}>
{props.children}
</OpenApiContext.Provider>
}

View File

@@ -6,14 +6,14 @@ import Cookies from 'js-cookie'
export const fetchToken = async (username: string, password: string, remember: boolean) => {
try {
const response = await api.post('/token', {
const response = await api.post('/auth/signin', {
username: username,
password: password,
remember: remember,
}, {
withCredentials: true
});
return response.data.access_token;
return response.data;
} catch (error) {
console.error('Error fetching token:', error);
throw error;
@@ -41,7 +41,7 @@ export function LoginPage(props: {}) {
fetchToken(username, password, Boolean(data.get('remember'))).then(
(token) => {
api.defaults.headers.common.Authorization = `Bearer ${token}`;
localStorage.setItem('token', token)
Cookies.set('auth', token)
setApiAuthenticated(true)
},
(error) => {

View File

@@ -1,10 +1,10 @@
import { AxiosInstance } from "axios"
import { ActionGroup, ActionInfo, DataTable, UserInfoContext, actionIdentifierContext, api, apiAuthenticatedContext, useAction, useActions } from "./common"
import { ActionGroup, ActionInfo, DataTable, UserInfoContext, actionIdentifierContext, api, apiAuthenticatedContext} from "./common"
import React, { Context, Dispatch, createContext, useContext, useEffect, useState } from "react"
import { TableRow, TableCell, Chip } from "@mui/material"
import { ImageInfo, ServerInfo } from "./interfaces"
import { JSONSchema7 } from "json-schema"
import { Permission, SERVER_ACTIONS } from "./actions"
import { CREATE_SERVER_ACTION, Permission, SERVER_ACTIONS } from "./actions"
@@ -24,7 +24,7 @@ function ServerItem(props: { server_info: ServerInfo }) {
const [serverPermissions, setServerPermissions] = useState(null as null|number)
const user = useContext(UserInfoContext)
let permissions = 0
console.log(user)
if (props.server_info.OwnerId === user?.Username){
permissions |= Permission.Admin
}else{
@@ -68,23 +68,6 @@ function ServerItem(props: { server_info: ServerInfo }) {
export default function ServersBoard() {
const [servers, setServers]: [ServerInfo[], Dispatch<ServerInfo[]>] = useState([] as ServerInfo[]);
const [apiAuthenticated, setApiAuthenticated] = useContext(apiAuthenticatedContext)
const [images, setImages] = useState([] as ImageInfo[])
let schema: JSONSchema7 = {
properties: {
image_id: {
type: "string",
oneOf: images.map((value, index, array) => { return { const: value.Id, title: `${value.Name} ${value.Version}` } }),
title: "Image Id"
}
},
type: "object",
required: [
"image_id"
],
title: "CreateServer"
}
function handleServers() {
if (!apiAuthenticated) {
@@ -123,23 +106,7 @@ export default function ServersBoard() {
return (
<DataTable headers={['Nickname', 'Owner', 'Server', 'Version', 'Domain', 'Ports', 'Actions']} actionInfo={useAction({path: '/servers', method: 'post'})} actionHook={() => {
api.get('/images').then((value) => {
setImages(value.data)
}).catch(
(error) => {
console.log('Failed to get images: ' + error);
if (error.response) {
if (error.response.status === 401) {
setApiAuthenticated(false)
}
else if (error.response.status === 403) {
setApiAuthenticated(false)
}
}
}
)
}}>
<DataTable headers={['Nickname', 'Owner', 'Server', 'Version', 'Domain', 'Ports', 'Actions']} actionInfo={CREATE_SERVER_ACTION}>
<serverActionsContext.Provider value={SERVER_ACTIONS}>
{
servers.sort((s1: ServerInfo, s2: ServerInfo) => { return s1.Id < s2.Id ? 0 : 1 }).map(

View File

@@ -1,12 +1,35 @@
import { TableRow, TableCell, TableContainer, TableHead, Table, Button, Paper, TableBody, Chip, Modal, Box } from "@mui/material"
import React, { useContext, Dispatch, useState, useEffect, Context, createContext } from "react"
import Form from "@rjsf/mui"
import { apiAuthenticatedContext, useApiDoc, api, ActionInfo, useActions, ActionGroup, actionIdentifierContext, ActionItem, DataTable, useAction } from "./common"
import validator from '@rjsf/validator-ajv8';
import { TableRow, TableCell, Chip } from "@mui/material"
import { useContext, Dispatch, useState, useEffect, Context, createContext } from "react"
import { apiAuthenticatedContext, api, ActionInfo, ActionGroup, actionIdentifierContext, DataTable } from "./common"
import { User } from './interfaces'
import { INVITE_USER_ACTION, Permission } from "./actions";
const UserActionsContext: Context<ActionInfo[]> = createContext([] as ActionInfo[])
function getPermissionStrings(permissions: number){
let strings = []
if (permissions & Permission.Admin){
strings.push("Admin")
}
if (permissions & Permission.Start){
strings.push("Start")
}
if (permissions & Permission.Stop){
strings.push("Stop")
}
if (permissions & Permission.Browse){
strings.push("Browse")
}
if (permissions & Permission.Create){
strings.push("Create")
}
if (permissions & Permission.Delete){
strings.push("Delete")
}
if (permissions & Permission.RunCommand){
strings.push("RunCommand")
}
return strings
}
function UserItem(p: { user: User }) {
const user = p.user;
@@ -16,7 +39,7 @@ function UserItem(p: { user: User }) {
<TableRow>
<TableCell>{user.Username}</TableCell>
<TableCell>{user.Email}</TableCell>
<TableCell>{user.Permissions.map((value, index, array) => { return <Chip label={value} /> })}</TableCell>
<TableCell>{getPermissionStrings(user.Permissions).map((value, index, array) => { return <Chip label={value} /> })}</TableCell>
<TableCell><ActionGroup actions={userActions} identifierSubstring="username" /></TableCell>
</TableRow>
)
@@ -26,7 +49,7 @@ export function UsersPage({ }) {
const [apiAuthenticated, setApiAuthenticated] = useContext(apiAuthenticatedContext)
const [users, setUsers]: [User[], Dispatch<User[]>] = useState([] as User[])
const action: ActionInfo|undefined = useAction({path: '/users', method: 'post'})
const action: ActionInfo|undefined = INVITE_USER_ACTION
useEffect(() => {
if (!apiAuthenticated) {
@@ -73,7 +96,7 @@ export function UsersPage({ }) {
}
return <DataTable headers={['Username', 'Email', 'Permissions', 'Actions']} actionInfo={action}>
<UserActionsContext.Provider value={useActions(api, '/users/{username}')}>
<UserActionsContext.Provider value={[]}>
{userComponents}
</UserActionsContext.Provider>
</DataTable>