optional nfqueue fail-open option

This commit is contained in:
Domingo Dirutigliano
2025-02-18 17:36:15 +01:00
parent ece058d533
commit 59652fc697
11 changed files with 247 additions and 133 deletions

View File

@@ -237,17 +237,15 @@ class NfQueue {
nlh = nfq_nlmsg_put(queue_msg_buffer, NFQNL_MSG_CONFIG, queue_num); nlh = nfq_nlmsg_put(queue_msg_buffer, NFQNL_MSG_CONFIG, queue_num);
nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff); nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
#ifdef NFQUEUE_FAIL_OPEN char * enable_fail_open = getenv("FIREGEX_NFQUEUE_FAIL_OPEN");
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO|NFQA_CFG_F_FAIL_OPEN)); if (strcmp(enable_fail_open, "1") == 0){
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO|NFQA_CFG_F_FAIL_OPEN)); mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO|NFQA_CFG_F_FAIL_OPEN));
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO|NFQA_CFG_F_FAIL_OPEN));
#else }else{
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO));
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO)); mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO));
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO)); }
#endif
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
_clear(); _clear();

View File

@@ -9,10 +9,7 @@ using namespace Firegex::Regex;
using Firegex::NfQueue::MultiThreadQueue; using Firegex::NfQueue::MultiThreadQueue;
/* /*
Compile options: Compile options:
NFQUEUE_FAIL_OPEN - enable fail-open option of nfqueueß
---
USE_PIPES_FOR_BLOKING_QUEUE - use pipes instead of conditional variable, queue and mutex for blocking queue USE_PIPES_FOR_BLOKING_QUEUE - use pipes instead of conditional variable, queue and mutex for blocking queue
*/ */
@@ -64,13 +61,13 @@ int main(int argc, char *argv[]){
stream_mode = false; stream_mode = false;
} }
bool fail_open = strcmp(getenv("FIREGEX_NFQUEUE_FAIL_OPEN"), "1") == 0;
regex_config.reset(new RegexRules(stream_mode)); regex_config.reset(new RegexRules(stream_mode));
MultiThreadQueue<RegexNfQueue> queue_manager(n_of_threads); MultiThreadQueue<RegexNfQueue> queue_manager(n_of_threads);
osyncstream(cout) << "QUEUE " << queue_manager.queue_num() << endl; osyncstream(cout) << "QUEUE " << queue_manager.queue_num() << endl;
cerr << "[info] [main] Queue: " << queue_manager.queue_num() << " threads assigned: " << n_of_threads << " stream mode: " << stream_mode << endl; cerr << "[info] [main] Queue: " << queue_manager.queue_num() << " threads assigned: " << n_of_threads << " stream mode: " << stream_mode << " fail open: " << fail_open << endl;
thread qthr([&](){ thread qthr([&](){
queue_manager.start(); queue_manager.start();

View File

@@ -88,7 +88,11 @@ class FiregexInterceptor:
self.process = await asyncio.create_subprocess_exec( self.process = await asyncio.create_subprocess_exec(
proxy_binary_path, proxy_binary_path,
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE,
env={"MATCH_MODE": "stream" if self.srv.proto == "tcp" else "block", "NTHREADS": os.getenv("NTHREADS","1")}, env={
"MATCH_MODE": "stream" if self.srv.proto == "tcp" else "block",
"NTHREADS": os.getenv("NTHREADS","1"),
"FIREGEX_NFQUEUE_FAIL_OPEN": "1" if self.srv.fail_open else "0",
},
) )
line_fut = self.process.stdout.readuntil() line_fut = self.process.stdout.readuntil()
try: try:

View File

@@ -1,13 +1,14 @@
import base64 import base64
class Service: class Service:
def __init__(self, service_id: str, status: str, port: int, name: str, proto: str, ip_int: str, **other): def __init__(self, service_id: str, status: str, port: int, name: str, proto: str, ip_int: str, fail_open: bool, **other):
self.id = service_id self.id = service_id
self.status = status self.status = status
self.port = port self.port = port
self.name = name self.name = name
self.proto = proto self.proto = proto
self.ip_int = ip_int self.ip_int = ip_int
self.fail_open = fail_open
@classmethod @classmethod
def from_dict(cls, var: dict): def from_dict(cls, var: dict):

View File

@@ -19,10 +19,17 @@ class ServiceModel(BaseModel):
ip_int: str ip_int: str
n_regex: int n_regex: int
n_packets: int n_packets: int
fail_open: bool
class RenameForm(BaseModel): class RenameForm(BaseModel):
name:str name:str
class SettingsForm(BaseModel):
port: PortType|None = None
proto: str|None = None
ip_int: str|None = None
fail_open: bool|None = None
class RegexModel(BaseModel): class RegexModel(BaseModel):
regex:str regex:str
mode:str mode:str
@@ -44,6 +51,7 @@ class ServiceAddForm(BaseModel):
port: PortType port: PortType
proto: str proto: str
ip_int: str ip_int: str
fail_open: bool = False
class ServiceAddResponse(BaseModel): class ServiceAddResponse(BaseModel):
status:str status:str
@@ -59,6 +67,7 @@ db = SQLite('db/nft-regex.db', {
'name': 'VARCHAR(100) NOT NULL UNIQUE', 'name': 'VARCHAR(100) NOT NULL UNIQUE',
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))', 'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
'ip_int': 'VARCHAR(100) NOT NULL', 'ip_int': 'VARCHAR(100) NOT NULL',
'fail_open': 'BOOLEAN NOT NULL CHECK (fail_open IN (0, 1)) DEFAULT 1'
}, },
'regexes': { 'regexes': {
'regex': 'TEXT NOT NULL', 'regex': 'TEXT NOT NULL',
@@ -128,6 +137,7 @@ async def get_service_list():
s.name name, s.name name,
s.proto proto, s.proto proto,
s.ip_int ip_int, s.ip_int ip_int,
s.fail_open fail_open,
COUNT(r.regex_id) n_regex, COUNT(r.regex_id) n_regex,
COALESCE(SUM(r.blocked_packets),0) n_packets COALESCE(SUM(r.blocked_packets),0) n_packets
FROM services s LEFT JOIN regexes r ON s.service_id = r.service_id FROM services s LEFT JOIN regexes r ON s.service_id = r.service_id
@@ -145,6 +155,7 @@ async def get_service_by_id(service_id: str):
s.name name, s.name name,
s.proto proto, s.proto proto,
s.ip_int ip_int, s.ip_int ip_int,
s.fail_open fail_open,
COUNT(r.regex_id) n_regex, COUNT(r.regex_id) n_regex,
COALESCE(SUM(r.blocked_packets),0) n_packets COALESCE(SUM(r.blocked_packets),0) n_packets
FROM services s LEFT JOIN regexes r ON s.service_id = r.service_id FROM services s LEFT JOIN regexes r ON s.service_id = r.service_id
@@ -190,6 +201,45 @@ async def service_rename(service_id: str, form: RenameForm):
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
@app.put('/services/{service_id}/settings', response_model=StatusMessageModel)
async def service_settings(service_id: str, form: SettingsForm):
"""Request to change the settings of a specific service (will cause a restart)"""
if form.proto is not None and form.proto not in ["tcp", "udp"]:
raise HTTPException(status_code=400, detail="Invalid protocol")
if form.port is not None and (form.port < 1 or form.port > 65535):
raise HTTPException(status_code=400, detail="Invalid port")
if form.ip_int is not None:
try:
form.ip_int = ip_parse(form.ip_int)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid address")
keys = []
values = []
for key, value in form.model_dump(exclude_none=True).items():
keys.append(key)
values.append(value)
if len(keys) == 0:
raise HTTPException(status_code=400, detail="No settings to change provided")
try:
db.query(f'UPDATE services SET {", ".join([f"{key}=?" for key in keys])} WHERE service_id = ?;', *values, service_id)
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="A service with these settings already exists")
old_status = firewall.get(service_id).status
await firewall.remove(service_id)
await firewall.reload()
await firewall.get(service_id).next(old_status)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/services/{service_id}/regexes', response_model=list[RegexModel]) @app.get('/services/{service_id}/regexes', response_model=list[RegexModel])
async def get_service_regexe_list(service_id: str): async def get_service_regexe_list(service_id: str):
"""Get the list of the regexes of a service""" """Get the list of the regexes of a service"""
@@ -275,8 +325,8 @@ async def add_new_service(form: ServiceAddForm):
srv_id = None srv_id = None
try: try:
srv_id = gen_service_id() srv_id = gen_service_id()
db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?)", db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int, fail_open) VALUES (?, ?, ?, ?, ?, ?, ?)",
srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int) srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int, form.fail_open)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="This type of service already exists") raise HTTPException(status_code=400, detail="This type of service already exists")
await firewall.reload() await firewall.reload()

View File

@@ -0,0 +1,139 @@
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Box, Tooltip } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useEffect, useState } from 'react';
import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { nfregex, Service } from './utils';
import PortAndInterface from '../PortAndInterface';
import { IoMdInformationCircleOutline } from "react-icons/io";
import { ServiceAddForm as ServiceAddFormOriginal } from './utils';
type ServiceAddForm = ServiceAddFormOriginal & {autostart: boolean}
function AddEditService({ opened, onClose, edit }:{ opened:boolean, onClose:()=>void, edit?:Service }) {
const initialValues = {
name: "",
port:edit?.port??8080,
ip_int:edit?.ip_int??"",
proto:edit?.proto??"tcp",
fail_open: edit?.fail_open??false,
autostart: true
}
const form = useForm({
initialValues: initialValues,
validate:{
name: (value) => edit? null : value !== "" ? null : "Service name is required",
port: (value) => (value>0 && value<65536) ? null : "Invalid port",
proto: (value) => ["tcp","udp"].includes(value) ? null : "Invalid protocol",
ip_int: (value) => (value.match(regex_ipv6) || value.match(regex_ipv4)) ? null : "Invalid IP address",
}
})
useEffect(() => {
if (opened){
form.setInitialValues(initialValues)
form.reset()
}
}, [opened])
const close = () =>{
onClose()
form.reset()
setError(null)
}
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = ({ name, port, autostart, proto, ip_int, fail_open }:ServiceAddForm) =>{
setSubmitLoading(true)
if (edit){
nfregex.settings(edit.service_id, { port, proto, ip_int, fail_open }).then( res => {
if (!res){
setSubmitLoading(false)
close();
okNotify(`Service ${name} settings updated`, `Successfully updated settings for service ${name}`)
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}else{
nfregex.servicesadd({ name, port, proto, ip_int, fail_open }).then( res => {
if (res.status === "ok" && res.service_id){
setSubmitLoading(false)
close();
if (autostart) nfregex.servicestart(res.service_id)
okNotify(`Service ${name} has been added`, `Successfully added service with port ${port}`)
}else{
setSubmitLoading(false)
setError("Invalid request! [ "+res.status+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
}
return <Modal size="xl" title={edit?`Editing ${edit.name} service`:"Add a new service"} opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
{!edit?<TextInput
label="Service name"
placeholder="Challenge 01"
{...form.getInputProps('name')}
/>:null}
<Space h="md" />
<PortAndInterface form={form} int_name="ip_int" port_name="port" label={"Public IP Interface and port (ipv4/ipv6 + CIDR allowed)"} />
<Space h="md" />
<Box className='center-flex'>
<Box>
{!edit?<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })}
/>:null}
<Space h="sm" />
<Switch
label={<Box className='center-flex'>
Enable fail-open nfqueue
<Space w="xs" />
<Tooltip label={<>
Firegex use internally nfqueue to handle packets<br />enabling this option will allow packets to pass through the firewall <br /> in case the filtering is too slow or too many traffic is coming<br />
</>}>
<IoMdInformationCircleOutline size={15} />
</Tooltip>
</Box>}
{...form.getInputProps('fail_open', { type: 'checkbox' })}
/>
</Box>
<Box className="flex-spacer"></Box>
<SegmentedControl
data={[
{ label: 'TCP', value: 'tcp' },
{ label: 'UDP', value: 'udp' },
]}
{...form.getInputProps('proto')}
/>
</Box>
<Group justify='flex-end' mt="md" mb="sm">
<Button loading={submitLoading} type="submit" disabled={edit?!form.isDirty():false}>{edit?"Edit Service":"Add Service"}</Button>
</Group>
{error?<>
<Space h="md" />
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" />
</>:null}
</form>
</Modal>
}
export default AddEditService;

View File

@@ -1,105 +0,0 @@
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Box } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useState } from 'react';
import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { nfregex } from './utils';
import PortAndInterface from '../PortAndInterface';
type ServiceAddForm = {
name:string,
port:number,
proto:string,
ip_int:string,
autostart: boolean,
}
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
const form = useForm({
initialValues: {
name:"",
port:8080,
ip_int:"",
proto:"tcp",
autostart: true
},
validate:{
name: (value) => value !== "" ? null : "Service name is required",
port: (value) => (value>0 && value<65536) ? null : "Invalid port",
proto: (value) => ["tcp","udp"].includes(value) ? null : "Invalid protocol",
ip_int: (value) => (value.match(regex_ipv6) || value.match(regex_ipv4)) ? null : "Invalid IP address",
}
})
const close = () =>{
onClose()
form.reset()
setError(null)
}
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = ({ name, port, autostart, proto, ip_int }:ServiceAddForm) =>{
setSubmitLoading(true)
nfregex.servicesadd({name, port, proto, ip_int }).then( res => {
if (res.status === "ok" && res.service_id){
setSubmitLoading(false)
close();
if (autostart) nfregex.servicestart(res.service_id)
okNotify(`Service ${name} has been added`, `Successfully added service with port ${port}`)
}else{
setSubmitLoading(false)
setError("Invalid request! [ "+res.status+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
return <Modal size="xl" title="Add a new service" opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
<TextInput
label="Service name"
placeholder="Challenge 01"
{...form.getInputProps('name')}
/>
<Space h="md" />
<PortAndInterface form={form} int_name="ip_int" port_name="port" label={"Public IP Interface and port (ipv4/ipv6 + CIDR allowed)"} />
<Space h="md" />
<Box className='center-flex'>
<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })}
/>
<Box className="flex-spacer"></Box>
<SegmentedControl
data={[
{ label: 'TCP', value: 'tcp' },
{ label: 'UDP', value: 'udp' },
]}
{...form.getInputProps('proto')}
/>
</Box>
<Group justify='flex-end' mt="md" mb="sm">
<Button loading={submitLoading} type="submit">Add Service</Button>
</Group>
{error?<>
<Space h="md" />
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" />
</>:null}
</form>
</Modal>
}
export default AddNewService;

View File

@@ -12,6 +12,8 @@ import { MenuDropDownWithButton } from '../../MainLayout';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { FaFilter } from "react-icons/fa"; import { FaFilter } from "react-icons/fa";
import { VscRegex } from "react-icons/vsc"; import { VscRegex } from "react-icons/vsc";
import { IoSettingsSharp } from 'react-icons/io5';
import AddEditService from '../AddEditService';
export default function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) { export default function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
@@ -26,6 +28,7 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
const [tooltipStopOpened, setTooltipStopOpened] = useState(false); const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [deleteModal, setDeleteModal] = useState(false) const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false) const [renameModal, setRenameModal] = useState(false)
const [editModal, setEditModal] = useState(false)
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
const stopService = async () => { const stopService = async () => {
@@ -104,7 +107,8 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
{isMedium?<Space w="xl" />:<Space h="lg" />} {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.Item><b>Edit service</b></Menu.Item>
<Menu.Item leftSection={<IoSettingsSharp size={18} />} onClick={()=>setEditModal(true)}>Service Settings</Menu.Item>
<Menu.Item leftSection={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item> <Menu.Item leftSection={<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>
@@ -148,5 +152,10 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
opened={renameModal} opened={renameModal}
service={service} service={service}
/> />
<AddEditService
opened={editModal}
onClose={()=>setEditModal(false)}
edit={service}
/>
</> </>
} }

View File

@@ -12,6 +12,7 @@ export type Service = {
ip_int: string, ip_int: string,
n_packets:number, n_packets:number,
n_regex:number, n_regex:number,
fail_open:boolean,
} }
export type ServiceAddForm = { export type ServiceAddForm = {
@@ -19,6 +20,14 @@ export type ServiceAddForm = {
port:number, port:number,
proto:string, proto:string,
ip_int:string, ip_int:string,
fail_open: boolean,
}
export type ServiceSettings = {
port?:number,
proto?:string,
ip_int?:string,
fail_open?: boolean,
} }
export type ServiceAddResponse = { export type ServiceAddResponse = {
@@ -79,5 +88,9 @@ export const nfregex = {
}, },
serviceregexes: async (service_id:string) => { serviceregexes: async (service_id:string) => {
return await getapi(`nfregex/services/${service_id}/regexes`) as RegexFilter[]; return await getapi(`nfregex/services/${service_id}/regexes`) as RegexFilter[];
} },
settings: async (service_id:string, data:ServiceSettings) => {
const { status } = await putapi(`nfregex/services/${service_id}/settings`,data) as ServerResponse;
return status === "ok"?undefined:status
},
} }

View File

@@ -3,7 +3,7 @@ import { Navigate, useNavigate, useParams } from 'react-router-dom';
import RegexView from '../../components/RegexView'; import RegexView from '../../components/RegexView';
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, Service } from '../../components/NFRegex/utils';
import { Badge, Divider, Menu } from '@mantine/core'; import { Badge, Divider, Menu } from '@mantine/core';
import { useState } from 'react'; import { useState } from 'react';
import { FaFilter, FaPlay, FaStop } from 'react-icons/fa'; import { FaFilter, FaPlay, FaStop } from 'react-icons/fa';
@@ -18,6 +18,8 @@ import { MenuDropDownWithButton } from '../../components/MainLayout';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { FaArrowLeft } from "react-icons/fa"; import { FaArrowLeft } from "react-icons/fa";
import { VscRegex } from 'react-icons/vsc'; import { VscRegex } from 'react-icons/vsc';
import { IoSettingsSharp } from 'react-icons/io5';
import AddEditService from '../../components/NFRegex/AddEditService';
export default function ServiceDetailsNFRegex() { export default function ServiceDetailsNFRegex() {
@@ -29,6 +31,7 @@ export default function ServiceDetailsNFRegex() {
const regexesList = nfregexServiceRegexesQuery(srv??"") const regexesList = nfregexServiceRegexesQuery(srv??"")
const [deleteModal, setDeleteModal] = useState(false) const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false) const [renameModal, setRenameModal] = useState(false)
const [editModal, setEditModal] = useState(false)
const [buttonLoading, setButtonLoading] = useState(false) const [buttonLoading, setButtonLoading] = useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [tooltipStopOpened, setTooltipStopOpened] = useState(false); const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
@@ -108,7 +111,8 @@ export default function ServiceDetailsNFRegex() {
</Badge> </Badge>
<MenuDropDownWithButton> <MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label> <Menu.Item><b>Edit service</b></Menu.Item>
<Menu.Item leftSection={<IoSettingsSharp size={18} />} onClick={()=>setEditModal(true)}>Service Settings</Menu.Item>
<Menu.Item leftSection={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item> <Menu.Item leftSection={<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>
@@ -190,5 +194,10 @@ export default function ServiceDetailsNFRegex() {
opened={renameModal} opened={renameModal}
service={serviceInfo} service={serviceInfo}
/> />
<AddEditService
opened={editModal}
onClose={()=>setEditModal(false)}
edit={serviceInfo}
/>
</> </>
} }

View File

@@ -5,7 +5,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import ServiceRow from '../../components/NFRegex/ServiceRow'; import ServiceRow from '../../components/NFRegex/ServiceRow';
import { nfregexServiceQuery } from '../../components/NFRegex/utils'; import { nfregexServiceQuery } from '../../components/NFRegex/utils';
import { errorNotify, getErrorMessage, isMediumScreen } from '../../js/utils'; import { errorNotify, getErrorMessage, isMediumScreen } from '../../js/utils';
import AddNewService from '../../components/NFRegex/AddNewService'; import AddEditService from '../../components/NFRegex/AddEditService';
import AddNewRegex from '../../components/AddNewRegex'; import AddNewRegex from '../../components/AddNewRegex';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { TbReload } from 'react-icons/tb'; import { TbReload } from 'react-icons/tb';
@@ -81,13 +81,12 @@ function NFRegex({ children }: { children: any }) {
</Tooltip> </Tooltip>
</Box> </Box>
</>} </>}
<AddNewService opened={open} onClose={closeModal} />
</>} </>}
</Box> </Box>
{srv?children:null} {srv?children:null}
{srv? {srv?
<AddNewRegex opened={open} onClose={closeModal} service={srv} />: <AddNewRegex opened={open} onClose={closeModal} service={srv} />:
<AddNewService opened={open} onClose={closeModal} /> <AddEditService opened={open} onClose={closeModal} />
} }
</> </>
} }