fixed mantine forms and menu + improvements on dev mode with vite

This commit is contained in:
Domingo Dirutigliano
2023-06-14 21:49:15 +02:00
parent 2e65d803a2
commit 3f2a1db324
18 changed files with 82 additions and 58 deletions

View File

@@ -16,7 +16,7 @@ async def frontend_debug_proxy(path):
httpc = httpx.AsyncClient() httpc = httpx.AsyncClient()
req = httpc.build_request("GET",f"http://127.0.0.1:{os.getenv('F_PORT','5173')}/"+path) req = httpc.build_request("GET",f"http://127.0.0.1:{os.getenv('F_PORT','5173')}/"+path)
resp = await httpc.send(req, stream=True) resp = await httpc.send(req, stream=True)
return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code) return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code, headers=resp.headers)
async def react_deploy(path): async def react_deploy(path):
file_request = os.path.join(REACT_BUILD_DIR, path) file_request = os.path.join(REACT_BUILD_DIR, path)
@@ -35,10 +35,10 @@ def frontend_deploy(app):
while True: while True:
data = await ws_b.recv() data = await ws_b.recv()
await ws_a.send_text(data) await ws_a.send_text(data)
@app.websocket("/ws") @app.websocket("/")
async def websocket_debug_proxy(ws: WebSocket): async def websocket_debug_proxy(ws: WebSocket):
await ws.accept() await ws.accept()
async with websockets.connect(f"ws://127.0.0.1:{os.getenv('F_PORT','5173')}/ws") as ws_b_client: async with websockets.connect(f"ws://127.0.0.1:{os.getenv('F_PORT','5173')}/") as ws_b_client:
fwd_task = asyncio.create_task(forward_websocket(ws, ws_b_client)) fwd_task = asyncio.create_task(forward_websocket(ws, ws_b_client))
rev_task = asyncio.create_task(reverse_websocket(ws, ws_b_client)) rev_task = asyncio.create_task(reverse_websocket(ws, ws_b_client))
await asyncio.gather(fwd_task, rev_task) await asyncio.gather(fwd_task, rev_task)

View File

@@ -60,7 +60,7 @@ function App() {
password:"", password:"",
}, },
validate:{ validate:{
password: (value) => value !== "" password: (value) => value !== "" ? null : "Password is required",
} }
}) })

View File

@@ -25,9 +25,9 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
deactive:false deactive:false
}, },
validate:{ validate:{
regex: (value) => value !== "", regex: (value) => value !== "" ? null : "Regex is required",
type: (value) => ["blacklist","whitelist"].includes(value), type: (value) => ["blacklist","whitelist"].includes(value) ? null : "Invalid type",
mode: (value) => ['C -> S', 'S -> C', 'C <-> S'].includes(value) mode: (value) => ['C -> S', 'S -> C', 'C <-> S'].includes(value) ? null : "Invalid mode",
} }
}) })

View File

@@ -12,7 +12,7 @@ function ResetPasswordModal({ opened, onClose }:{ opened: boolean, onClose: () =
expire:true expire:true
}, },
validate:{ validate:{
password: (value) => value !== "" password: (value) => value !== ""? null : "Password is required"
} }
}) })
const [loadingBtn, setLoadingBtn] = useState(false) const [loadingBtn, setLoadingBtn] = useState(false)

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { ActionIcon, Divider, Image, Menu, Tooltip, MediaQuery, Burger, Space, Header } from '@mantine/core'; import { ActionIcon, Divider, Image, Menu, Tooltip, MediaQuery, Burger, Space, Header, Button, ThemeIcon } from '@mantine/core';
import style from "./index.module.scss"; import style from "./index.module.scss";
import { errorNotify, getmainpath, logout } from '../../js/utils'; import { errorNotify, getmainpath, logout } from '../../js/utils';
import { AiFillHome } from "react-icons/ai" import { AiFillHome } from "react-icons/ai"
@@ -9,6 +9,8 @@ import { MdOutlineSettingsBackupRestore } from 'react-icons/md';
import { ImExit } from 'react-icons/im'; import { ImExit } from 'react-icons/im';
import ResetPasswordModal from './ResetPasswordModal'; import ResetPasswordModal from './ResetPasswordModal';
import ResetModal from './ResetModal'; import ResetModal from './ResetModal';
import { RiMenu5Fill } from 'react-icons/ri';
import { MenuDropDownWithButton } from '../MainLayout';
function HeaderPage({navOpen, setNav, ...other}: { navOpen: boolean, setNav:React.Dispatch<React.SetStateAction<boolean>>}) { function HeaderPage({navOpen, setNav, ...other}: { navOpen: boolean, setNav:React.Dispatch<React.SetStateAction<boolean>>}) {
@@ -51,15 +53,13 @@ function HeaderPage({navOpen, setNav, ...other}: { navOpen: boolean, setNav:Reac
<div className="flex-spacer" /> <div className="flex-spacer" />
<MenuDropDownWithButton>
<Menu>
<Menu.Label>Firewall Access</Menu.Label> <Menu.Label>Firewall Access</Menu.Label>
<Menu.Item icon={<FaLock size={14} />} onClick={() => setChangePasswordModal(true)}>Change Password</Menu.Item> <Menu.Item icon={<FaLock size={14} />} onClick={() => setChangePasswordModal(true)}>Change Password</Menu.Item>
<Divider /> <Divider />
<Menu.Label>Actions</Menu.Label> <Menu.Label>Actions</Menu.Label>
<Menu.Item color="red" icon={<MdOutlineSettingsBackupRestore size={18} />} onClick={() => setResetFiregexModal(true)}>Reset Firegex</Menu.Item> <Menu.Item color="red" icon={<MdOutlineSettingsBackupRestore size={18} />} onClick={() => setResetFiregexModal(true)}>Reset Firegex</Menu.Item>
</MenuDropDownWithButton>
</Menu>
<Space w="md" /> <Space w="md" />
<Tooltip label="Home" position='bottom' color="teal" opened={tooltipHomeOpened}> <Tooltip label="Home" position='bottom' color="teal" opened={tooltipHomeOpened}>
<ActionIcon color="teal" style={{marginRight:"10px"}} <ActionIcon color="teal" style={{marginRight:"10px"}}

View File

@@ -1,11 +1,12 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Container, Space } from '@mantine/core'; import { ActionIcon, Container, Menu, Space, ThemeIcon } from '@mantine/core';
import { AppShell } from '@mantine/core'; import { AppShell } from '@mantine/core';
import NavBar from './NavBar'; import NavBar from './NavBar';
import FooterPage from './Footer'; import FooterPage from './Footer';
import HeaderPage from './Header'; import HeaderPage from './Header';
import { getmainpath } from '../js/utils'; import { getmainpath } from '../js/utils';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { RiMenu5Fill } from 'react-icons/ri';
@@ -41,3 +42,14 @@ function MainLayout({ children }:{ children:any }) {
} }
export default MainLayout; export default MainLayout;
export const MenuDropDownWithButton = ({children}:{children:any}) => <Menu withArrow>
<Menu.Target>
<ActionIcon variant='transparent'>
<RiMenu5Fill size={24} />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
{children}
</Menu.Dropdown>
</Menu>

View File

@@ -25,10 +25,10 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
autostart: true autostart: true
}, },
validate:{ validate:{
name: (value) => value !== ""?true:false, name: (value) => value !== "" ? null : "Service name is required",
port: (value) => value>0 && value<65536, port: (value) => (value>0 && value<65536) ? null : "Invalid port",
proto: (value) => ["tcp","udp"].includes(value), proto: (value) => ["tcp","udp"].includes(value) ? null : "Invalid protocol",
ip_int: (value) => value.match(regex_ipv6)?true:false || value.match(regex_ipv4)?true:false ip_int: (value) => (value.match(regex_ipv6) || value.match(regex_ipv4)) ? null : "Invalid IP address",
} }
}) })

View File

@@ -9,7 +9,7 @@ function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>v
const form = useForm({ const form = useForm({
initialValues: { name:service.name }, initialValues: { name:service.name },
validate:{ name: (value) => value !== "" } validate:{ name: (value) => value !== ""? null : "Service name is required" }
}) })
const close = () =>{ const close = () =>{

View File

@@ -9,6 +9,7 @@ import { errorNotify, okNotify, regex_ipv4 } from '../../../js/utils';
import { BsTrashFill } from 'react-icons/bs'; import { BsTrashFill } from 'react-icons/bs';
import { BiRename } from 'react-icons/bi' import { BiRename } from 'react-icons/bi'
import RenameForm from './RenameForm'; import RenameForm from './RenameForm';
import { MenuDropDownWithButton } from '../../MainLayout';
function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) { function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
@@ -108,13 +109,13 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
<><Space w="xl" /><Space w="xl" /></> <><Space w="xl" /><Space w="xl" /></>
</MediaQuery> </MediaQuery>
<div className="center-flex"> <div className="center-flex">
<Menu> <MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label> <Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item> <Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Divider /> <Divider />
<Menu.Label><b>Danger zone</b></Menu.Label> <Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item> <Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</Menu> </MenuDropDownWithButton>
<Space w="md"/> <Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}> <Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}>
<ActionIcon color="red" loading={buttonLoading} <ActionIcon color="red" loading={buttonLoading}

View File

@@ -1,16 +1,16 @@
import { Autocomplete, AutocompleteItem, Space, Title } from "@mantine/core" import { Autocomplete, AutocompleteItem, Select, Space, Title } from "@mantine/core"
import React, { useEffect, useState } from "react" import React, { useEffect, useState } from "react"
import { getipinterfaces } from "../js/utils"; import { getipinterfaces } from "../js/utils";
import PortInput from "./PortInput"; import PortInput from "./PortInput";
import { UseFormReturnType } from "@mantine/form/lib/types"; import { UseFormReturnType } from "@mantine/form/lib/types";
interface ItemProps extends AutocompleteItem { interface ItemProps extends AutocompleteItem {
label: string; netint: string;
} }
const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>( const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
({ label, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}> ({ netint, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}>
( <b>{label}</b> ) -{">"} <b>{value}</b> ( <b>{netint}</b> ) -{">"} <b>{value}</b>
</div> </div>
); );
@@ -21,7 +21,7 @@ export default function PortAndInterface({ form, int_name, port_name, label }:{
useEffect(()=>{ useEffect(()=>{
getipinterfaces().then(data => { getipinterfaces().then(data => {
setIpInterfaces(data.map(item => ({label:item.name, value:item.addr}))); setIpInterfaces(data.map(item => ({netint:item.name, value:item.addr, label:item.addr})));
}) })
},[]) },[])
@@ -29,17 +29,26 @@ export default function PortAndInterface({ form, int_name, port_name, label }:{
{label?<> {label?<>
<Title order={6}>{label}</Title> <Title order={6}>{label}</Title>
<Space h="xs" /></> :null} <Space h="xs" /></> :null}
<div className='center-flex' style={{width:"100%"}}>
<div className='center-flex' style={{width:"100%"}}> <Select
<Autocomplete placeholder="10.1.1.1"
placeholder="10.1.1.1" itemComponent={AutoCompleteItem}
itemComponent={AutoCompleteItem} data={ipInterfaces}
data={ipInterfaces} searchable
{...form.getInputProps(int_name)} dropdownPosition="bottom"
style={{width:"100%"}} maxDropdownHeight={100}
/> creatable
<Space w="sm" /><span style={{marginTop:"-3px", fontSize:"1.5em"}}>:</span><Space w="sm" /> getCreateLabel={(query) => `+ Use this: ${query}`}
<PortInput {...form.getInputProps(port_name)} /> onCreate={(query) => {
</div> const item = { value: query, netint: "CUSTOM", label: query };
setIpInterfaces((current) => [...current, item]);
return item;
}}
{...form.getInputProps(int_name)}
style={{width:"100%"}}
/>
<Space w="sm" /><span style={{marginTop:"-3px", fontSize:"1.5em"}}>:</span><Space w="sm" />
<PortInput {...form.getInputProps(port_name)} />
</div>
</> </>
} }

View File

@@ -29,12 +29,12 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
autostart: false, autostart: false,
}, },
validate:{ validate:{
name: (value) => value !== ""?true:false, name: (value) => value !== ""? null : "Service name is required",
public_port: (value) => value>0 && value<65536, public_port: (value) => (value>0 && value<65536) ? null : "Invalid public port",
proxy_port: (value) => value>0 && value<65536, proxy_port: (value) => (value>0 && value<65536) ? null : "Invalid proxy port",
proto: (value) => ["tcp","udp"].includes(value), proto: (value) => ["tcp","udp"].includes(value) ? null : "Invalid protocol",
ip_src: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false, ip_src: (value) => (value.match(regex_ipv6_no_cidr) || value.match(regex_ipv4_no_cidr)) ? null : "Invalid source IP address",
ip_dst: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false ip_dst: (value) => (value.match(regex_ipv6_no_cidr) || value.match(regex_ipv4_no_cidr)) ? null : "Invalid destination IP address",
} }
}) })

View File

@@ -14,8 +14,8 @@ function ChangeDestination({ opened, onClose, service }:{ opened:boolean, onClos
proxy_port:service.proxy_port proxy_port:service.proxy_port
}, },
validate:{ validate:{
proxy_port: (value) => value>0 && value<65536, proxy_port: (value) => (value>0 && value<65536) ? null : "Invalid proxy port",
ip_dst: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false ip_dst: (value) => (value.match(regex_ipv6_no_cidr) || value.match(regex_ipv4_no_cidr))? null : "Invalid destination IP address",
} }
}) })

View File

@@ -9,7 +9,7 @@ function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>v
const form = useForm({ const form = useForm({
initialValues: { name:service.name }, initialValues: { name:service.name },
validate:{ name: (value) => value !== "" } validate:{ name: (value) => value !== ""? null : "Service name is required" }
}) })
const close = () =>{ const close = () =>{

View File

@@ -11,6 +11,7 @@ import RenameForm from './RenameForm';
import ChangeDestination from './ChangeDestination'; import ChangeDestination from './ChangeDestination';
import PortInput from '../../PortInput'; import PortInput from '../../PortInput';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { MenuDropDownWithButton } from '../../MainLayout';
function ServiceRow({ service }:{ service:Service }) { function ServiceRow({ service }:{ service:Service }) {
@@ -25,7 +26,7 @@ function ServiceRow({ service }:{ service:Service }) {
const form = useForm({ const form = useForm({
initialValues: { proxy_port:service.proxy_port }, initialValues: { proxy_port:service.proxy_port },
validate:{ proxy_port: (value) => value > 0 && value < 65536 } validate:{ proxy_port: (value) => (value > 0 && value < 65536)? null : "Invalid proxy port" }
}) })
const onChangeProxyPort = ({proxy_port}:{proxy_port:number}) => { const onChangeProxyPort = ({proxy_port}:{proxy_port:number}) => {
@@ -132,7 +133,7 @@ function ServiceRow({ service }:{ service:Service }) {
<Space w="xl" /><Space w="xl" /> <Space w="xl" /><Space w="xl" />
<div className="center-flex"> <div className="center-flex">
<Menu> <MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label> <Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item> <Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Menu.Label><b>Change destination</b></Menu.Label> <Menu.Label><b>Change destination</b></Menu.Label>
@@ -140,7 +141,7 @@ function ServiceRow({ service }:{ service:Service }) {
<Divider /> <Divider />
<Menu.Label><b>Danger zone</b></Menu.Label> <Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item> <Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</Menu> </MenuDropDownWithButton>
<Space w="md"/> <Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}> <Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}>
<ActionIcon color="red" loading={buttonLoading} <ActionIcon color="red" loading={buttonLoading}

View File

@@ -25,9 +25,9 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
autostart: true autostart: true
}, },
validate:{ validate:{
name: (value) => value !== ""?true:false, name: (value) => value !== ""? null : "Service name is required",
port: (value) => value>0 && value<65536, port: (value) => (value>0 && value<65536) ? null : "Invalid port",
internalPort: (value) => value>0 && value<65536, internalPort: (value) => (value>0 && value<65536) ? null : "Invalid internal port",
} }
}) })

View File

@@ -20,8 +20,8 @@ function ChangePortModal({ service, opened, onClose }:{ service:Service, opened:
port: service.public_port port: service.public_port
}, },
validate:{ validate:{
internalPort: (value) => value>0 && value<65536, internalPort: (value) => (value>0 && value<65536) ? null : "Invalid internal port",
port: (value) => value>0 && value<65536 port: (value) => (value>0 && value<65536) ? null : "Invalid public port",
} }
}) })

View File

@@ -9,7 +9,7 @@ function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>v
const form = useForm({ const form = useForm({
initialValues: { name:service.name }, initialValues: { name:service.name },
validate:{ name: (value) => value !== "" } validate:{ name: (value) => value !== ""? null : "Service name is required" }
}) })
const close = () =>{ const close = () =>{

View File

@@ -11,6 +11,7 @@ import { BiRename } from 'react-icons/bi'
import ChangePortModal from './ChangePortModal'; import ChangePortModal from './ChangePortModal';
import RenameForm from './RenameForm'; import RenameForm from './RenameForm';
import { regexproxy, Service } from '../utils'; import { regexproxy, Service } from '../utils';
import { MenuDropDownWithButton } from '../../MainLayout';
//"status":"stop"/"wait"/"active"/"pause", //"status":"stop"/"wait"/"active"/"pause",
function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) { function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
@@ -139,7 +140,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
<><Space w="xl" /><Space w="xl" /></> <><Space w="xl" /><Space w="xl" /></>
</MediaQuery> </MediaQuery>
<div className="center-flex"> <div className="center-flex">
<Menu> <MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label> <Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item> <Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Divider /> <Divider />
@@ -149,7 +150,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
<Divider /> <Divider />
<Menu.Label><b>Danger zone</b></Menu.Label> <Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item> <Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</Menu> </MenuDropDownWithButton>
<Space w="md"/> <Space w="md"/>
{["pause","wait"].includes(service.status)? {["pause","wait"].includes(service.status)?