Frontend re-styling

This commit is contained in:
Domingo Dirutigliano
2025-02-12 22:24:59 +01:00
parent 2fb77a348f
commit ec3bd84aaf
14 changed files with 330 additions and 170 deletions

View File

@@ -55,7 +55,7 @@ This means that firegex is projected to avoid any possibility to have the servic
Initiially the project was based only on regex filters, and also now the main function uses regexes, but firegex have and will have also other filtering tools. Initiially the project was based only on regex filters, and also now the main function uses regexes, but firegex have and will have also other filtering tools.
# Credits # Credits
- Copyright (c) 2022 Pwnzer0tt1 - Copyright (c) 2022-2025 Pwnzer0tt1
## Star History ## Star History

View File

@@ -141,17 +141,17 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@mantine/core": ["@mantine/core@7.16.2", "", { "dependencies": { "@floating-ui/react": "^0.26.28", "clsx": "^2.1.1", "react-number-format": "^5.4.3", "react-remove-scroll": "^2.6.2", "react-textarea-autosize": "8.5.6", "type-fest": "^4.27.0" }, "peerDependencies": { "@mantine/hooks": "7.16.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-6dwFz+8HrOqFan7GezgpoWyZSCxedh10S8iILGVsc3GXiD4gzo+3VZndZKccktkYZ3GVC9E3cCS3SxbiyKSAVw=="], "@mantine/core": ["@mantine/core@7.16.3", "", { "dependencies": { "@floating-ui/react": "^0.26.28", "clsx": "^2.1.1", "react-number-format": "^5.4.3", "react-remove-scroll": "^2.6.2", "react-textarea-autosize": "8.5.6", "type-fest": "^4.27.0" }, "peerDependencies": { "@mantine/hooks": "7.16.3", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-cxhIpfd2i0Zmk9TKdejYAoIvWouMGhzK3OOX+VRViZ5HEjnTQCGl2h3db56ThqB6NfVPCno6BPbt5lwekTtmuQ=="],
"@mantine/form": ["@mantine/form@7.16.2", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "klona": "^2.0.6" }, "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-JZkLbZ7xWAZndPrxObkf10gjHj57x8yvI/vobjDhfWN3zFPTSWmSSF6yBE1FpITseOs3oR03hlkqG6EclK6g+g=="], "@mantine/form": ["@mantine/form@7.16.3", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "klona": "^2.0.6" }, "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-GqomUG2Ri5adxYsTU1S5IhKRPcqTG5JkPvMERns8PQAcUz/lvzsnk3wY1v4K5CEbCAdpimle4bSsZTM9g697vg=="],
"@mantine/hooks": ["@mantine/hooks@7.16.2", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-ZFHQhDi9T+r6VR5NEeE47gigPPIAHVIKDOCWsCsbCqHc3yz5l8kiO2RdfUmsTKV2KD/AiXnAw4b6pjQEP58GOg=="], "@mantine/hooks": ["@mantine/hooks@7.16.3", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-B94FBWk5Sc81tAjV+B3dGh/gKzfqzpzVC/KHyBRWOOyJRqeeRbI/FAaJo4zwppyQo1POSl5ArdyjtDRrRIj2SQ=="],
"@mantine/modals": ["@mantine/modals@7.16.2", "", { "peerDependencies": { "@mantine/core": "7.16.2", "@mantine/hooks": "7.16.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-REwAV53Fcz021EE3zLyYdkdFlfG+b24y279Y+eA1jCCH9VMLivXL+gacrox4BcpzREsic9nGVInSNv3VJwPlAQ=="], "@mantine/modals": ["@mantine/modals@7.16.3", "", { "peerDependencies": { "@mantine/core": "7.16.3", "@mantine/hooks": "7.16.3", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-BJuDzRugK6xLbuFTTo8NLJumVvVmSYsNVcEtmlXOWTE3NkDGktBXGKo8V1B0XfJ9/d/rZw7HCE0p4i76MtA+bQ=="],
"@mantine/notifications": ["@mantine/notifications@7.16.2", "", { "dependencies": { "@mantine/store": "7.16.2", "react-transition-group": "4.4.5" }, "peerDependencies": { "@mantine/core": "7.16.2", "@mantine/hooks": "7.16.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-U342XWiiRI1NvOlLsI6PH/pSNe0rxNClJ2w5orvjOMXvaAfDe52mhnzRmtzRxYENp06++3b/G7MjPH+466rF9Q=="], "@mantine/notifications": ["@mantine/notifications@7.16.3", "", { "dependencies": { "@mantine/store": "7.16.3", "react-transition-group": "4.4.5" }, "peerDependencies": { "@mantine/core": "7.16.3", "@mantine/hooks": "7.16.3", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-wtEME9kSYfXWYmAmQUZ8c+rwNmhdWRBaW1mlPdQsPkzMqkv4q6yy0IpgwcnuHStSG9EHaQBXazmVxMZJdEAWBQ=="],
"@mantine/store": ["@mantine/store@7.16.2", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-9dEGLosrYSePlAwhfx3CxTLcWu2M98TtuYnelAiHEdNEkyafirvZxNt4paMoFXLKR1XPm5wdjDK7bdTaE0t7Og=="], "@mantine/store": ["@mantine/store@7.16.3", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-6M2M5+0BrRtnVv+PUmr04tY1RjPqyapaHplo90uK1NMhP/1EIqrwTL9KoEtCNCJ5pog1AQtu0bj0QPbqUvxwLg=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
@@ -205,7 +205,7 @@
"@types/jest": ["@types/jest@27.5.2", "", { "dependencies": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" } }, "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA=="], "@types/jest": ["@types/jest@27.5.2", "", { "dependencies": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" } }, "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA=="],
"@types/node": ["@types/node@20.17.16", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw=="], "@types/node": ["@types/node@20.17.17", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-/WndGO4kIfMicEQLTi/mDANUu/iVUhT7KboZPdEqqHQ4aTS+3qT3U5gIqWDFV+XouorjfgGqvKILJeHhuQgFYg=="],
"@types/prop-types": ["@types/prop-types@15.7.14", "", {}, "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="], "@types/prop-types": ["@types/prop-types@15.7.14", "", {}, "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="],

View File

@@ -5,14 +5,14 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@hello-pangea/dnd": "^16.6.0", "@hello-pangea/dnd": "^16.6.0",
"@mantine/core": "^7.16.2", "@mantine/core": "^7.16.3",
"@mantine/form": "^7.16.2", "@mantine/form": "^7.16.3",
"@mantine/hooks": "^7.16.2", "@mantine/hooks": "^7.16.3",
"@mantine/modals": "^7.16.2", "@mantine/modals": "^7.16.3",
"@mantine/notifications": "^7.16.2", "@mantine/notifications": "^7.16.3",
"@tanstack/react-query": "^4.36.1", "@tanstack/react-query": "^4.36.1",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/node": "^20.17.16", "@types/node": "^20.17.17",
"@types/react": "^18.3.18", "@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5", "@types/react-dom": "^18.3.5",
"buffer": "^6.0.3", "buffer": "^6.0.3",

View File

@@ -86,16 +86,16 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
/> />
</Box> </Box>
<Group align="right" mt="md"> <Group justify='flex-end' mt="md" mb="sm">
<Button loading={submitLoading} type="submit">Add Service</Button> <Button loading={submitLoading} type="submit">Add Service</Button>
</Group> </Group>
<Space h="md" />
{error?<> {error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}> <Space h="md" />
Error: {error} <Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
</Notification><Space h="md" /></>:null} Error: {error}
</Notification><Space h="md" />
</>:null}
</form> </form>
</Modal> </Modal>

View File

@@ -49,16 +49,16 @@ function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>v
placeholder="Awesome Service Name!" placeholder="Awesome Service Name!"
{...form.getInputProps('name')} {...form.getInputProps('name')}
/> />
<Group align="right" mt="md"> <Group mt="md" justify="flex-end" mb="sm">
<Button loading={submitLoading} type="submit">Rename</Button> <Button loading={submitLoading} type="submit">Rename</Button>
</Group> </Group>
<Space h="md" />
{error?<> {error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}> <Space h="md" />
Error: {error} <Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
</Notification><Space h="md" /></>:null} Error: {error}
</Notification><Space h="md" />
</>:null}
</form> </form>
</Modal> </Modal>

View File

@@ -2,7 +2,7 @@ import { ActionIcon, Badge, Box, Divider, Grid, Menu, Space, Title, Tooltip } fr
import { useState } from 'react'; import { useState } from 'react';
import { FaPlay, FaStop } from 'react-icons/fa'; import { FaPlay, FaStop } from 'react-icons/fa';
import { nfregex, Service, serviceQueryKey } from '../utils'; import { nfregex, Service, serviceQueryKey } from '../utils';
import { MdOutlineArrowForwardIos } from "react-icons/md" import { MdDoubleArrow, MdOutlineArrowForwardIos } from "react-icons/md"
import YesNoModal from '../../YesNoModal'; import YesNoModal from '../../YesNoModal';
import { errorNotify, isMediumScreen, okNotify, regex_ipv4 } from '../../../js/utils'; import { errorNotify, isMediumScreen, okNotify, regex_ipv4 } from '../../../js/utils';
import { BsTrashFill } from 'react-icons/bs'; import { BsTrashFill } from 'react-icons/bs';
@@ -10,8 +10,10 @@ import { BiRename } from 'react-icons/bi'
import RenameForm from './RenameForm'; import RenameForm from './RenameForm';
import { MenuDropDownWithButton } from '../../MainLayout'; import { MenuDropDownWithButton } from '../../MainLayout';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { FaFilter } from "react-icons/fa";
import { VscRegex } from "react-icons/vsc";
function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) { export default function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
let status_color = "gray"; let status_color = "gray";
switch(service.status){ switch(service.status){
@@ -72,36 +74,34 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
return <> return <>
<Box className='firegex__nfregex__rowbox'> <Box className='firegex__nfregex__rowbox'>
<Grid className="firegex__nfregex__row" justify="flex-end" style={{width:"100%"}}> <Box className="firegex__nfregex__row" style={{width:"100%", flexDirection: isMedium?"row":"column"}}>
<Grid.Col span={{ md:4, xs: 12 }}> <Box>
<Box className="center-flex" style={{ justifyContent: "flex-start" }}>
<Box className={"center-flex-row"}> <MdDoubleArrow size={30} style={{color: "white"}}/>
<Title className="firegex__nfregex__name"> <Title className="firegex__nfregex__name" ml="xs">
{service.name} {service.name}
</Title> </Title>
<Box className="center-flex" style={{ gap: 6 }}>
<Badge color={status_color} radius="md" size="lg" variant="filled">Status: <u>{service.status}</u></Badge>
<Badge size="lg" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient" radius="md">
:{service.port}
</Badge>
</Box>
{isMedium?null:<Space w="xl" />}
</Box> </Box>
</Grid.Col> <Box className="center-flex" style={{ gap: 8, marginTop: 15, justifyContent: "flex-start" }}>
<Badge color={status_color} radius="md" size="lg" variant="filled">{service.status}</Badge>
<Badge size="lg" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient" radius="md" style={{ fontSize: "110%" }}>
:{service.port}
</Badge>
</Box>
{isMedium?null:<Space w="xl" />}
</Box>
<Grid.Col className={isMedium?"center-flex":"center-flex-row"} span={{ md:8, xs: 12 }}> <Box className={isMedium?"center-flex":"center-flex-row"}>
<Box visibleFrom='md' className='flex-spacer' />
<Space hiddenFrom='md' h="md" />
<Space hiddenFrom='md' w="xl" />
<Space hiddenFrom='md' w="md" />
<Box className="center-flex-row"> <Box className="center-flex-row">
<Badge color="yellow" radius="sm" size="md" variant="filled">Connections Blocked: {service.n_packets}</Badge>
<Space h="xs" />
<Badge color="violet" radius="sm" size="md" variant="filled">Regex: {service.n_regex}</Badge>
<Space h="xs" />
<Badge color={service.ip_int.match(regex_ipv4)?"cyan":"pink"} radius="sm" size="md" variant="filled">{service.ip_int} on {service.proto}</Badge> <Badge color={service.ip_int.match(regex_ipv4)?"cyan":"pink"} radius="sm" size="md" variant="filled">{service.ip_int} on {service.proto}</Badge>
<Space h="xs" />
<Box className='center-flex'>
<Badge color="yellow" radius="sm" size="md" variant="filled"><FaFilter style={{ marginBottom: -2}} /> {service.n_packets}</Badge>
<Space w="xs" />
<Badge color="violet" radius="sm" size="md" variant="filled"><VscRegex style={{ marginBottom: -2}} size={13} /> {service.n_regex}</Badge>
</Box>
</Box> </Box>
{isMedium?<Box className='flex-spacer' />:<Space h="xl" />} {isMedium?<Space w="xl" />:<Space h="lg" />}
<Box className="center-flex"> <Box className="center-flex">
<MenuDropDownWithButton> <MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label> <Menu.Label><b>Rename service</b></Menu.Label>
@@ -129,14 +129,12 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
{isMedium?<Space w="xl" />:<Space w="md" />} {isMedium?<Space w="xl" />:<Space w="md" />}
{onClick?<Box style={{ backgroundColor: "var(--secondary_color)", borderRadius: "38%", width:"35px", height:"35px", display:"flex", justifyContent: "center", alignItems: "center", border:"#AAA 2px solid"}}> {onClick?<Box className='firegex__service_forward_btn'>
<MdOutlineArrowForwardIos onClick={onClick} style={{cursor:"pointer"}} size={25} /> <MdOutlineArrowForwardIos onClick={onClick} style={{cursor:"pointer"}} size={25} />
</Box>:null} </Box>:null}
{isMedium?<Space w="xl" />:null}
</Box> </Box>
</Box>
</Grid.Col> </Box>
</Grid>
</Box> </Box>
<YesNoModal <YesNoModal
title='Are you sure to delete this service?' title='Are you sure to delete this service?'
@@ -152,5 +150,3 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
/> />
</> </>
} }
export default ServiceRow;

View File

@@ -94,16 +94,16 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
/> />
</Box> </Box>
<Group align="right" mt="md"> <Group justify='flex-end' mt="md" mb="sm">
<Button loading={submitLoading} type="submit">Add Service</Button> <Button loading={submitLoading} type="submit">Add Service</Button>
</Group> </Group>
<Space h="md" />
{error?<> {error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}> <Space h="md" />
Error: {error} <Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
</Notification><Space h="md" /></>:null} Error: {error}
</Notification><Space h="md" />
</>:null}
</form> </form>
</Modal> </Modal>

View File

@@ -53,15 +53,16 @@ function ChangeDestination({ opened, onClose, service }:{ opened:boolean, onClos
<form onSubmit={form.onSubmit(submitRequest)}> <form onSubmit={form.onSubmit(submitRequest)}>
<PortAndInterface form={form} int_name="ip_dst" port_name="proxy_port" /> <PortAndInterface form={form} int_name="ip_dst" port_name="proxy_port" />
<Group align="right" mt="md"> <Group justify='flex-end' mt="xl" mb="sm">
<Button loading={submitLoading} type="submit">Change</Button> <Button loading={submitLoading} type="submit">Change</Button>
</Group> </Group>
<Space h="md" />
{error?<> {error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}> <Space h="md" />
Error: {error} <Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
</Notification><Space h="md" /></>:null} Error: {error}
</Notification><Space h="md" />
</>:null}
</form> </form>
</Modal> </Modal>

View File

@@ -49,16 +49,16 @@ function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>v
placeholder="Awesome Service Name!" placeholder="Awesome Service Name!"
{...form.getInputProps('name')} {...form.getInputProps('name')}
/> />
<Group align="right" mt="md"> <Group mt="md" justify="flex-end" mb="sm">
<Button loading={submitLoading} type="submit">Rename</Button> <Button loading={submitLoading} type="submit">Rename</Button>
</Group> </Group>
<Space h="md" />
{error?<> {error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}> <Space h="md" />
Error: {error} <Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
</Notification><Space h="md" /></>:null} Error: {error}
</Notification><Space h="md" />
</>:null}
</form> </form>
</Modal> </Modal>

View File

@@ -8,11 +8,11 @@ import { BsArrowRepeat, 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 ChangeDestination from './ChangeDestination'; import ChangeDestination from './ChangeDestination';
import PortInput from '../../PortInput';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { MenuDropDownWithButton } from '../../MainLayout'; import { MenuDropDownWithButton } from '../../MainLayout';
import { MdDoubleArrow } from "react-icons/md";
function ServiceRow({ service }:{ service:Service }) { export default function ServiceRow({ service }:{ service:Service }) {
let status_color = service.active ? "teal": "red" let status_color = service.active ? "teal": "red"
@@ -72,40 +72,36 @@ function ServiceRow({ service }:{ service:Service }) {
return <> return <>
<Box className='firegex__nfregex__rowbox'> <Box className='firegex__nfregex__rowbox'>
<Grid className="firegex__nfregex__row" justify="flex-end" style={{width:"100%"}}> <Box className="firegex__nfregex__row" style={{width:"100%", flexDirection: isMedium?"row":"column"}}>
<Grid.Col span={{ md:4, xs: 12 }}> <Box>
<Box className={"center-flex-row"}> <Box className="center-flex" style={{ justifyContent: "flex-start" }}>
<Title className="firegex__nfregex__name"> <MdDoubleArrow size={30} style={{color: "white"}}/>
<Title className="firegex__nfregex__name" ml="xs">
{service.name} {service.name}
</Title> </Title>
<Box className="center-flex" style={{ gap: 6 }}>
<Badge color={status_color} radius="md" size="lg" variant="filled">Status: <u>{service.active?"ENABLED":"DISABLED"}</u></Badge>
<Badge color={service.proto === "tcp"?"cyan":"orange"} radius="md" size="lg" variant="filled">
{service.proto}
</Badge>
</Box>
{isMedium?null:<Space w="xl" />}
</Box> </Box>
</Grid.Col> <Box className="center-flex" style={{ gap: 8, marginTop: 15, justifyContent: "flex-start" }}>
<Badge color={status_color} radius="md" size="md" variant="filled">{service.active?"ENABLED":"DISABLED"}</Badge>
<Badge color={service.proto === "tcp"?"cyan":"orange"} radius="md" size="md" variant="filled">
{service.proto}
</Badge>
</Box>
{isMedium?null:<Space w="xl" />}
</Box>
<Grid.Col className={isMedium?"center-flex":"center-flex-row"} span={{ md:8, xs: 12 }}> <Box className={isMedium?"center-flex":"center-flex-row"}>
<Box visibleFrom='md' className='flex-spacer' />
<Space hiddenFrom='md' h="md" />
<Space hiddenFrom='md' w="xl" />
<Space hiddenFrom='md' w="md" />
<Box className="center-flex-row"> <Box className="center-flex-row">
<Badge color="lime" radius="sm" size="md" variant="filled"> <Badge color="lime" radius="sm" size="lg" variant="filled">
FROM {service.ip_src} : {service.public_port} FROM {service.ip_src} :{service.public_port}
</Badge> </Badge>
<Space h="sm" /> <Space h="sm" />
<Badge color="blue" radius="sm" size="md" variant="filled"> <Badge color="blue" radius="sm" size="lg" variant="filled">
<Box className="center-flex"> <Box className="center-flex">
TO {service.ip_dst} : service.proxy_port TO {service.ip_dst} :{service.proxy_port}
</Box> </Box>
</Badge> </Badge>
</Box> </Box>
{isMedium?<Box className='flex-spacer' />:<Space h="xl" />} {isMedium?<Space w="xl" />:<Space h="lg" />}
<Box className="center-flex"> <Box className="center-flex">
<MenuDropDownWithButton> <MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label> <Menu.Label><b>Rename service</b></Menu.Label>
@@ -134,14 +130,10 @@ function ServiceRow({ service }:{ service:Service }) {
<FaPlay size="20px" /> <FaPlay size="20px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
{isMedium?<Space w="xl" />:null} </Box>
</Box>
</Grid.Col>
</Grid>
</Box> </Box>
<YesNoModal <YesNoModal
title='Are you sure to delete this service?' title='Are you sure to delete this service?'
@@ -162,5 +154,3 @@ function ServiceRow({ service }:{ service:Service }) {
/> />
</> </>
} }
export default ServiceRow;

View File

@@ -1,18 +1,19 @@
import { Grid, Text, Title, Badge, Space, ActionIcon, Tooltip, Box } from '@mantine/core'; import { Text, Title, Badge, Space, ActionIcon, Tooltip, Box } from '@mantine/core';
import { useState } from 'react'; import { useState } from 'react';
import { RegexFilter } from '../../js/models'; import { RegexFilter } from '../../js/models';
import { b64decode, errorNotify, getapiobject, okNotify } from '../../js/utils'; import { b64decode, errorNotify, getapiobject, isMediumScreen, okNotify } from '../../js/utils';
import { BsTrashFill } from "react-icons/bs" import { BsTrashFill } from "react-icons/bs"
import YesNoModal from '../YesNoModal'; import YesNoModal from '../YesNoModal';
import { FaPause, FaPlay } from 'react-icons/fa'; import { FaPause, FaPlay } from 'react-icons/fa';
import { useClipboard } from '@mantine/hooks'; import { useClipboard } from '@mantine/hooks';
import { FaFilter } from "react-icons/fa";
import { VscRegex } from "react-icons/vsc";
function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) { function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
const mode_string = regexInfo.mode === "C"? "C -> S": const mode_string = regexInfo.mode === "C"? "C -> S":
regexInfo.mode === "S"? "S -> C": regexInfo.mode === "S"? "S -> C":
regexInfo.mode === "B"? "S <-> C": "🤔" regexInfo.mode === "B"? "C <-> S": "🤔"
let regex_expr = b64decode(regexInfo.regex); let regex_expr = b64decode(regexInfo.regex);
@@ -20,6 +21,7 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
const [deleteTooltipOpened, setDeleteTooltipOpened] = useState(false); const [deleteTooltipOpened, setDeleteTooltipOpened] = useState(false);
const [statusTooltipOpened, setStatusTooltipOpened] = useState(false); const [statusTooltipOpened, setStatusTooltipOpened] = useState(false);
const clipboard = useClipboard({ timeout: 500 }); const clipboard = useClipboard({ timeout: 500 });
const isMedium = isMediumScreen();
const deleteRegex = () => { const deleteRegex = () => {
getapiobject().regexdelete(regexInfo.id).then(res => { getapiobject().regexdelete(regexInfo.id).then(res => {
@@ -42,57 +44,39 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
} }
return <Box className="firegex__regexview__box"> return <Box className="firegex__regexview__box">
<Grid> <Box>
<Grid.Col span={2} className="center-flex"> <Box className='center-flex' style={{width: "100%"}}>
<Title order={4}>Regex:</Title>
</Grid.Col>
<Grid.Col span={8}>
<Box className="firegex__regexview__outer_regex_text"> <Box className="firegex__regexview__outer_regex_text">
<Text className="firegex__regexview__regex_text" onClick={()=>{ <Text className="firegex__regexview__regex_text" onClick={()=>{
clipboard.copy(regex_expr) clipboard.copy(regex_expr)
okNotify("Regex copied to clipboard!",`The regex '${regex_expr}' has been copied to the clipboard!`) okNotify("Regex copied to clipboard!",`The regex '${regex_expr}' has been copied to the clipboard!`)
}}>{regex_expr}</Text> }}>{regex_expr}</Text>
</Box> </Box>
</Grid.Col>
<Grid.Col span={2} className='center-flex'>
<Space w="xs" /> <Space w="xs" />
<Tooltip label={regexInfo.active?"Deactivate":"Activate"} zIndex={0} color={regexInfo.active?"orange":"teal"} opened={statusTooltipOpened}> <Tooltip label={regexInfo.active?"Deactivate":"Activate"} zIndex={0} color={regexInfo.active?"orange":"teal"} opened={statusTooltipOpened}>
<ActionIcon color={regexInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="xl" radius="md" variant="filled" <ActionIcon color={regexInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="xl" radius="md" variant="filled"
onFocus={() => setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)} onFocus={() => setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)}
onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)} onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)}
>{regexInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon> >{regexInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon>
</Tooltip> </Tooltip>
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Delete regex" zIndex={0} color="red" opened={deleteTooltipOpened} > <Tooltip label="Delete regex" zIndex={0} color="red" opened={deleteTooltipOpened} >
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled" <ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"
onFocus={() => setDeleteTooltipOpened(false)} onBlur={() => setDeleteTooltipOpened(false)} onFocus={() => setDeleteTooltipOpened(false)} onBlur={() => setDeleteTooltipOpened(false)}
onMouseEnter={() => setDeleteTooltipOpened(true)} onMouseLeave={() => setDeleteTooltipOpened(false)} onMouseEnter={() => setDeleteTooltipOpened(true)} onMouseLeave={() => setDeleteTooltipOpened(false)}
><BsTrashFill size={22} /></ActionIcon> ><BsTrashFill size={22} /></ActionIcon>
</Tooltip> </Tooltip>
</Box>
</Grid.Col> <Box display="flex" mt="sm" ml="xs">
<Grid.Col className='center-flex' span={12}> <Badge size="md" color="yellow" variant="filled"><FaFilter style={{ marginBottom: -2}} /> {regexInfo.n_packets}</Badge>
<Box className='center-flex-row'> <Space w="xs" />
<Space h="md" /> <Badge size="md" color={regexInfo.active?"lime":"red"} variant="filled">{regexInfo.active?"ACTIVE":"DISABLED"}</Badge>
<Box className='center-flex'> <Space w="xs" />
<Badge size="md" color="cyan" variant="filled">Service: {regexInfo.service_id}</Badge> <Badge size="md" color={regexInfo.is_case_sensitive?"grape":"pink"} variant="filled">{regexInfo.is_case_sensitive?"Strict":"Loose"}</Badge>
<Space w="xs" /> <Space w="xs" />
<Badge size="md" color={regexInfo.active?"lime":"red"} variant="filled">{regexInfo.active?"ACTIVE":"DISABLED"}</Badge> <Badge size="md" color="blue" variant="filled">{mode_string}</Badge>
<Space w="xs" /> </Box>
<Badge size="md" color="gray" variant="filled">ID: {regexInfo.id}</Badge> </Box>
</Box>
</Box>
<Box className='flex-spacer' />
<Box className='center-flex-row'>
<Badge size="md" color={regexInfo.is_case_sensitive?"grape":"pink"} variant="filled">Case: {regexInfo.is_case_sensitive?"SENSIIVE":"INSENSITIVE"}</Badge>
<Space h="xs" />
<Badge size="md" color="yellow" variant="filled">Packets filtered: {regexInfo.n_packets}</Badge>
<Space h="xs" />
<Badge size="md" color="blue" variant="filled">Mode: {mode_string}</Badge>
</Box>
</Grid.Col>
</Grid>
<YesNoModal <YesNoModal
title='Are you sure to delete this regex?' title='Are you sure to delete this regex?'
description={`You are going to delete the regex '${regex_expr}'.`} description={`You are going to delete the regex '${regex_expr}'.`}

View File

@@ -5,7 +5,7 @@ function YesNoModal( { title, description, action, onClose, opened}:{ title:stri
return <Modal size="xl" title={title} opened={opened} onClose={onClose} centered> return <Modal size="xl" title={title} opened={opened} onClose={onClose} centered>
{description} {description}
<Group align="right" mt="md"> <Group justify='flex-end' mt="md">
<Button onClick={()=>{ <Button onClick={()=>{
onClose() onClose()
action() action()

View File

@@ -32,14 +32,15 @@ body {
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 4px;
margin:3px; height: 4px;
margin:2px;
background: #333; background: #333;
cursor: pointer;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #757575; width: 2px;
border-radius: 8px; background: #777;
border-radius: 5px;
} }
.mantine-Modal-content { .mantine-Modal-content {
@@ -72,7 +73,7 @@ body {
} }
.firegex__regexview__box{ .firegex__regexview__box{
padding:30px; padding:10px;
margin:5px; margin:5px;
} }
@@ -80,6 +81,7 @@ body {
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
margin: 6px; margin: 6px;
width: 100%;
} }
.firegex__regexview__regex_text{ .firegex__regexview__regex_text{
@@ -131,25 +133,25 @@ body {
text-decoration: underline; text-decoration: underline;
} }
.firegex__nfregex__row{ .firegex__nfregex__row{
width: 95%; width: 95%;
padding: 20px; padding: 10px;
border-radius: 20px; padding-left: 35px;
padding-right: 35px;
display: flex; display: flex;
justify-content: center; justify-content: space-between;
align-items: center; align-items: center;
} }
.firegex__nfregex__name{ .firegex__nfregex__name{
font-size: 1.8em; font-size: 1.6em;
font-weight: bolder; font-weight: bolder;
margin-right: 10px; text-transform: capitalize;
margin-bottom: 13px;
color:#FFF; color:#FFF;
max-width: 300px; max-width: 300px;
overflow: hidden; overflow: hidden;
text-align: center; margin-left: -20px;
margin-right: 20px;
} }
.firegex__nfregex__name:hover{ .firegex__nfregex__name:hover{
@@ -207,7 +209,47 @@ body {
.firegex__nfregex__rowbox{ .firegex__nfregex__rowbox{
width: 100%; width: 100%;
height: 150px;
align-items: center;
justify-content: center;
display: flex;
background-color: var(--fourth_color); background-color: var(--fourth_color);
border-radius: 20px; border-radius: 10px;
border: #444 3px solid; border: #444 3px solid;
}
@media (max-width: 599px) {
.firegex__nfregex__row{
margin: 0;
padding: 0;
}
.firegex__nfregex__row > *{
padding: 10px 0;
}
.firegex__nfregex__rowbox{
height: 100%;
width: 90%;
padding: 20px 20px;
margin: 0;
}
.firegex__nfregex__name{
padding: 0;
margin: 0;
}
}
.firegex__service_forward_btn{
background-color: var(--primary_color);
border-radius: 38%;
width: 35px;
height: 35px;
display: flex;
justify-content: center;
align-items: center;
border: 2px solid #DDD;
transition: 0.3s;
}
.firegex__service_forward_btn:hover{
opacity: 0.8;
transition: 0.3s;
} }

View File

@@ -1,13 +1,25 @@
import { ActionIcon, Box, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core'; import { ActionIcon, Box, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import { useState } from 'react'; import { Navigate, useNavigate, useParams } from 'react-router-dom';
import { Navigate, useParams } from 'react-router-dom';
import RegexView from '../../components/RegexView'; import RegexView from '../../components/RegexView';
import ServiceRow from '../../components/NFRegex/ServiceRow';
import AddNewRegex from '../../components/AddNewRegex'; import AddNewRegex from '../../components/AddNewRegex';
import { BsPlusLg } from "react-icons/bs"; import { BsPlusLg } from "react-icons/bs";
import { nfregexServiceQuery, nfregexServiceRegexesQuery } from '../../components/NFRegex/utils'; import { nfregexServiceQuery, nfregexServiceRegexesQuery } from '../../components/NFRegex/utils';
import { Badge, Divider, Menu } from '@mantine/core';
import { useState } from 'react';
import { FaFilter, FaPlay, FaStop } from 'react-icons/fa';
import { nfregex, serviceQueryKey } from '../../components/NFRegex/utils';
import { MdDoubleArrow } from "react-icons/md"
import YesNoModal from '../../components/YesNoModal';
import { errorNotify, isMediumScreen, okNotify, regex_ipv4 } from '../../js/utils';
import { BsTrashFill } from 'react-icons/bs';
import { BiRename } from 'react-icons/bi'
import RenameForm from '../../components/NFRegex/ServiceRow/RenameForm';
import { MenuDropDownWithButton } from '../../components/MainLayout';
import { useQueryClient } from '@tanstack/react-query';
import { FaArrowLeft } from "react-icons/fa";
import { VscRegex } from 'react-icons/vsc';
function ServiceDetailsNFRegex() { export default function ServiceDetailsNFRegex() {
const {srv} = useParams() const {srv} = useParams()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@@ -15,13 +27,138 @@ function ServiceDetailsNFRegex() {
const serviceInfo = services.data?.find(s => s.service_id == srv) const serviceInfo = services.data?.find(s => s.service_id == srv)
const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false) const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false)
const regexesList = nfregexServiceRegexesQuery(srv??"") const regexesList = nfregexServiceRegexesQuery(srv??"")
const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false)
const [buttonLoading, setButtonLoading] = useState(false)
const queryClient = useQueryClient()
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [tooltipBackOpened, setTooltipBackOpened] = useState(false);
const navigate = useNavigate()
const isMedium = isMediumScreen()
if (services.isLoading) return <LoadingOverlay visible={true} /> if (services.isLoading) return <LoadingOverlay visible={true} />
if (!srv || !serviceInfo || regexesList.isError) return <Navigate to="/" replace /> if (!srv || !serviceInfo || regexesList.isError) return <Navigate to="/" replace />
let status_color = "gray";
switch(serviceInfo.status){
case "stop": status_color = "red"; break;
case "active": status_color = "teal"; break;
}
const startService = async () => {
setButtonLoading(true)
await nfregex.servicestart(serviceInfo.service_id).then(res => {
if(!res){
okNotify(`Service ${serviceInfo.name} started successfully!`,`The service on ${serviceInfo.port} has been started!`)
queryClient.invalidateQueries(serviceQueryKey)
}else{
errorNotify(`An error as occurred during the starting of the service ${serviceInfo.port}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the starting of the service ${serviceInfo.port}`,`Error: ${err}`)
})
setButtonLoading(false)
}
const deleteService = () => {
nfregex.servicedelete(serviceInfo.service_id).then(res => {
if (!res){
okNotify("Service delete complete!",`The service ${serviceInfo.name} has been deleted!`)
queryClient.invalidateQueries(serviceQueryKey)
}else
errorNotify("An error occurred while deleting a service",`Error: ${res}`)
}).catch(err => {
errorNotify("An error occurred while deleting a service",`Error: ${err}`)
})
}
const stopService = async () => {
setButtonLoading(true)
await nfregex.servicestop(serviceInfo.service_id).then(res => {
if(!res){
okNotify(`Service ${serviceInfo.name} stopped successfully!`,`The service on ${serviceInfo.port} has been stopped!`)
queryClient.invalidateQueries(serviceQueryKey)
}else{
errorNotify(`An error as occurred during the stopping of the service ${serviceInfo.port}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the stopping of the service ${serviceInfo.port}`,`Error: ${err}`)
})
setButtonLoading(false);
}
return <> return <>
<LoadingOverlay visible={regexesList.isLoading} /> <LoadingOverlay visible={regexesList.isLoading} />
<ServiceRow service={serviceInfo} /> <Box className={isMedium?'center-flex':'center-flex-row'} style={{ justifyContent: "space-between"}} px="md" mt="lg">
<Box>
<Title order={1}>
<Box className="center-flex">
<MdDoubleArrow /><Space w="sm" />{serviceInfo.name}
</Box>
</Title>
</Box>
{isMedium?null:<Space h="md" />}
<Box className='center-flex'>
<Badge color={status_color} radius="md" size="xl" variant="filled" mr="sm">
{serviceInfo.status}
</Badge>
<Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient" radius="md" mr="sm">
:{serviceInfo.port}
</Badge>
<MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item leftSection={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Divider />
<Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</MenuDropDownWithButton>
</Box>
</Box>
{isMedium?null:<Space h="md" />}
<Box className={isMedium?'center-flex':'center-flex-row'} style={{ justifyContent: "space-between"}} px="md" mt="lg">
<Box className={isMedium?'center-flex':'center-flex-row'}>
<Box className='center-flex'>
<Badge color="yellow" radius="sm" size="md" variant="filled"><FaFilter style={{ marginBottom: -2}} /> {serviceInfo.n_packets}</Badge>
<Space w="xs" />
<Badge color="violet" radius="sm" size="md" variant="filled"><VscRegex style={{ marginBottom: -2}} size={13} /> {serviceInfo.n_regex}</Badge>
</Box>
{isMedium?<Space w="xs" />:<Space h="xs" />}
<Badge color={serviceInfo.ip_int.match(regex_ipv4)?"cyan":"pink"} radius="sm" size="md" variant="filled" mr="xs">{serviceInfo.ip_int} on {serviceInfo.proto}</Badge>
</Box>
{isMedium?null:<Space h="xl" />}
<Box className='center-flex'>
<Tooltip label="Go back" zIndex={0} color="cyan" opened={tooltipBackOpened}>
<ActionIcon color="cyan"
onClick={() => navigate("/")} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-back-id"
onFocus={() => setTooltipBackOpened(false)} onBlur={() => setTooltipBackOpened(false)}
onMouseEnter={() => setTooltipBackOpened(true)} onMouseLeave={() => setTooltipBackOpened(false)}>
<FaArrowLeft size="25px" />
</ActionIcon>
</Tooltip>
<Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}>
<ActionIcon color="red" loading={buttonLoading}
onClick={stopService} size="xl" radius="md" variant="filled"
disabled={serviceInfo.status === "stop"}
aria-describedby="tooltip-stop-id"
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
<FaStop size="20px" />
</ActionIcon>
</Tooltip>
<Space w="md"/>
<Tooltip label="Start service" zIndex={0} color="teal">
<ActionIcon color="teal" size="xl" radius="md" onClick={startService} loading={buttonLoading}
variant="filled" disabled={!["stop","pause"].includes(serviceInfo.status)?true:false}>
<FaPlay size="20px" />
</ActionIcon>
</Tooltip>
</Box>
</Box>
<Divider my="xl" />
{(!regexesList.data || regexesList.data.length == 0)?<> {(!regexesList.data || regexesList.data.length == 0)?<>
<Space h="xl" /> <Space h="xl" />
<Title className='center-flex' style={{textAlign:"center"}} order={3}>No regex found for this service! Add one by clicking the "+" buttons</Title> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No regex found for this service! Add one by clicking the "+" buttons</Title>
@@ -41,7 +178,17 @@ function ServiceDetailsNFRegex() {
} }
{srv?<AddNewRegex opened={open} onClose={() => {setOpen(false);}} service={srv} />:null} {srv?<AddNewRegex opened={open} onClose={() => {setOpen(false);}} service={srv} />:null}
<YesNoModal
title='Are you sure to delete this service?'
description={`You are going to delete the service '${serviceInfo.port}', causing the stopping of the firewall and deleting all the regex associated. This will cause the shutdown of your service! ⚠️`}
onClose={()=>setDeleteModal(false) }
action={deleteService}
opened={deleteModal}
/>
<RenameForm
onClose={()=>setRenameModal(false)}
opened={renameModal}
service={serviceInfo}
/>
</> </>
} }
export default ServiceDetailsNFRegex;