diff --git a/engine/app/models.py b/engine/app/models.py
index 9fc0711..c740c62 100644
--- a/engine/app/models.py
+++ b/engine/app/models.py
@@ -347,11 +347,27 @@ class SpaceCIDRReq(BaseModel):
class BlockCIDRReq(BaseModel):
"""DOCSTRING"""
- size: int
+ size: Optional[int] = None
+ cidr: Optional[IPv4Network] = None
desc: Optional[str] = None
reverse_search: Optional[bool] = False
smallest_cidr: Optional[bool] = False
+ @model_validator(mode='before')
+ @classmethod
+ def validate_request(cls, data: Any) -> Any:
+ if isinstance(data, dict):
+ if 'cidr' in data and any(x in data for x in ['reverse_search', 'smallest_cidr']):
+ if data['cidr'] is not None:
+ raise AssertionError("the 'cidr' parameter can only be used in conjuction with 'desc'")
+ if 'cidr' in data and 'size' in data:
+ if data['cidr'] is not None:
+ raise AssertionError("the 'cidr' and 'size' parameters can only be used alternatively")
+ if 'cidr' not in data and 'size' not in data:
+ raise AssertionError("it is required to provide either the 'cidr' or 'size' parameter")
+
+ return data
+
class JSONPatch(BaseModel):
"""DOCSTRING"""
@@ -438,7 +454,7 @@ class Admin(BaseModel):
@model_validator(mode='before')
@classmethod
- def check_email(cls, data: Any) -> Any:
+ def validate_request(cls, data: Any) -> Any:
if isinstance(data, dict):
if 'type' in data and 'email' in data:
if data['type'] == "Principal" and data['email'] is not None:
diff --git a/engine/app/routers/space.py b/engine/app/routers/space.py
index 61c8fd3..9a180d0 100644
--- a/engine/app/routers/space.py
+++ b/engine/app/routers/space.py
@@ -1796,6 +1796,7 @@ async def create_block_reservation(
Create a CIDR Reservation for the target Block with the following information:
- **size**: Network mask bits
+ - **cidr**: Specific CIDR to reserve (optional)
- **desc**: Description (optional)
- **reverse_search**:
- **true**: New networks will be created as close to the end of the block as possible
@@ -1803,6 +1804,47 @@ async def create_block_reservation(
- **smallest_cidr**:
- **true**: New networks will be created using the smallest possible available block (e.g. it will not break up large CIDR blocks when possible)
- **false (default)**: New networks will be created using the first available block, regardless of size
+
+ ### Usage Examples
+
+ #### *Request a new /24:*
+
+ ```json
+ {
+ "size": 24
+ "desc": "New CIDR for Business Unit 1"
+ }
+ ```
+
+ #### *Request a new /24 at the end of the block:*
+
+ ```json
+ {
+ "size": 24,
+ "desc": "New CIDR for Business Unit 1",
+ "reverse_search": true
+ }
+ ```
+
+ #### *Request a new /24 at the end of the block, using the smallest available block:*
+
+ ```json
+ {
+ "size": 24,
+ "desc": "New CIDR for Business Unit 1",
+ "reverse_search": true,
+ "smallest_cidr": true
+ }
+ ```
+
+ #### *Request a specific /24:*
+
+ ```json
+ {
+ "cidr": "10.0.100.0/24",
+ "desc" "New CIDR for Business Unit 1"
+ }
+ ```
"""
user_assertion = authorization.split(' ')[1]
@@ -1839,20 +1881,28 @@ async def create_block_reservation(
reserved_set = IPSet(block_all_cidrs)
available_set = block_set ^ reserved_set
- available_slicer = slice(None, None, -1) if req.reverse_search else slice(None)
- next_selector = -1 if req.reverse_search else 0
+ next_cidr = None
+
+ if req.cidr is not None:
+ if IPNetwork(req.cidr) not in available_set:
+ raise HTTPException(status_code=409, detail="Requested CIDR overlaps existing network(s).")
- if req.smallest_cidr:
- cidr_list = list(filter(lambda x: x.prefixlen <= req.size, available_set.iter_cidrs()[available_slicer]))
- min_mask = max(map(lambda x: x.prefixlen, cidr_list))
- available_block = next((net for net in list(filter(lambda network: network.prefixlen == min_mask, cidr_list))), None)
+ next_cidr = IPNetwork(req.cidr)
else:
- available_block = next((net for net in list(available_set.iter_cidrs())[available_slicer] if net.prefixlen <= req.size), None)
+ available_slicer = slice(None, None, -1) if req.reverse_search else slice(None)
+ next_selector = -1 if req.reverse_search else 0
- if not available_block:
- raise HTTPException(status_code=500, detail="Network of requested size unavailable in target block.")
+ if req.smallest_cidr:
+ cidr_list = list(filter(lambda x: x.prefixlen <= req.size, available_set.iter_cidrs()[available_slicer]))
+ min_mask = max(map(lambda x: x.prefixlen, cidr_list))
+ available_block = next((net for net in list(filter(lambda network: network.prefixlen == min_mask, cidr_list))), None)
+ else:
+ available_block = next((net for net in list(available_set.iter_cidrs())[available_slicer] if net.prefixlen <= req.size), None)
- next_cidr = list(available_block.subnet(req.size))[next_selector]
+ if not available_block:
+ raise HTTPException(status_code=500, detail="Network of requested size unavailable in target block.")
+
+ next_cidr = list(available_block.subnet(req.size))[next_selector]
if "preferred_username" in decoded:
creator_id = decoded["preferred_username"]
diff --git a/lb/nginx.conf b/lb/nginx.conf
index cc777a9..00e40cf 100644
--- a/lb/nginx.conf
+++ b/lb/nginx.conf
@@ -21,6 +21,7 @@ http {
# Frontend
location / {
+ add_header X-Frame-Options "DENY";
proxy_pass http://ui;
proxy_intercept_errors on;
proxy_set_header Host $http_host;
diff --git a/ui/src/features/configure/block/Utils/editReservations.jsx b/ui/src/features/configure/block/Utils/editReservations.jsx
index 4d77461..4b94836 100644
--- a/ui/src/features/configure/block/Utils/editReservations.jsx
+++ b/ui/src/features/configure/block/Utils/editReservations.jsx
@@ -687,6 +687,7 @@ export default function EditReservations(props) {
function onClose() {
handleClose();
setFilterActive(true);
+ setSelectionModel([]);
}
function onSubmit() {