optional nfqueue fail-open option
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
139
frontend/src/components/NFRegex/AddEditService.tsx
Normal file
139
frontend/src/components/NFRegex/AddEditService.tsx
Normal 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;
|
||||||
@@ -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;
|
|
||||||
@@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user