зеркало из https://github.com/Azure/ipam.git
Updated network assignment to blocks to consider reservations and added additional descriptors API documentation
This commit is contained in:
Родитель
5ce7f0f81a
Коммит
944121d3b2
|
@ -1,4 +1,4 @@
|
|||
from fastapi import APIRouter, Depends, Request, Response, HTTPException, Header, status
|
||||
from fastapi import APIRouter, Depends, Request, Response, HTTPException, Header, Path, status
|
||||
from fastapi.responses import JSONResponse, PlainTextResponse
|
||||
from fastapi.exceptions import HTTPException as StarletteHTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
@ -55,6 +55,7 @@ async def new_admin_db(admin_list, exclusion_list, tenant_id):
|
|||
status_code = 200
|
||||
)
|
||||
async def get_admins(
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -85,6 +86,7 @@ async def get_admins(
|
|||
)
|
||||
async def create_admin(
|
||||
admin: Admin,
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -128,13 +130,14 @@ async def create_admin(
|
|||
)
|
||||
async def update_admins(
|
||||
admin_list: List[Admin],
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
"""
|
||||
Replace the list of IPAM Administrators with the following details:
|
||||
|
||||
- Array **[]** of:
|
||||
- Array **[ ]** of:
|
||||
- **name**: Full name of the Administrator
|
||||
- **email**: Email address for the Administrator
|
||||
- **id**: Azure AD ObjectID for the Administrator user
|
||||
|
@ -172,7 +175,8 @@ async def update_admins(
|
|||
error_msg = "Error removing admin, please try again."
|
||||
)
|
||||
async def delete_admin(
|
||||
objectId: UUID,
|
||||
objectId: UUID = Path(..., description="Azure AD ObjectID for the target user"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -208,6 +212,7 @@ async def delete_admin(
|
|||
status_code = 200
|
||||
)
|
||||
async def get_exclusions(
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -238,11 +243,12 @@ async def get_exclusions(
|
|||
)
|
||||
async def add_exclusions(
|
||||
exclusions: List[Subscription],
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
"""
|
||||
Add a list of excluded subscriptions:
|
||||
Add a list of excluded Subscriptions:
|
||||
|
||||
- **[<UUID>]**: Array of Subscription ID's
|
||||
"""
|
||||
|
@ -280,11 +286,12 @@ async def add_exclusions(
|
|||
)
|
||||
async def update_exclusions(
|
||||
exclusions: List[Subscription],
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
"""
|
||||
Replace the list of excluded subscriptions:
|
||||
Replace the list of excluded Subscriptions:
|
||||
|
||||
- **[<UUID>]**: Array of Subscription ID's
|
||||
"""
|
||||
|
@ -321,12 +328,13 @@ async def update_exclusions(
|
|||
error_msg = "Error removing exclusion, please try again."
|
||||
)
|
||||
async def remove_exclusion(
|
||||
subscriptionId: Subscription,
|
||||
subscriptionId: Subscription = Path(..., description="Azure Subscription ID"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
"""
|
||||
Remove an excluded subscription id
|
||||
Remove an excluded Subscription ID.
|
||||
"""
|
||||
|
||||
if not is_admin:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from fastapi import APIRouter, Depends, Request, Response, HTTPException, Header, status
|
||||
from fastapi import APIRouter, Depends, Request, Response, HTTPException, Header, Query, Path, status
|
||||
from fastapi.responses import JSONResponse, PlainTextResponse
|
||||
from fastapi.exceptions import HTTPException as StarletteHTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
@ -94,9 +94,9 @@ async def scrub_space_patch(patch):
|
|||
status_code = 200
|
||||
)
|
||||
async def get_spaces(
|
||||
expand: bool = False,
|
||||
utilization: bool = False,
|
||||
authorization: str = Header(None),
|
||||
expand: bool = Query(False, description="Expand network references to full network objects"),
|
||||
utilization: bool = Query(False, description="Append utilization information for each network"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -180,7 +180,7 @@ async def get_spaces(
|
|||
)
|
||||
async def create_space(
|
||||
space: SpaceReq,
|
||||
authorization: str = Header(None),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -227,10 +227,10 @@ async def create_space(
|
|||
status_code = 200
|
||||
)
|
||||
async def get_space(
|
||||
space: str,
|
||||
expand: bool = False,
|
||||
utilization: bool = False,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
expand: bool = Query(False, description="Expand network references to full network objects"),
|
||||
utilization: bool = Query(False, description="Append utilization information for each network"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -317,8 +317,9 @@ async def get_space(
|
|||
error_msg = "Error updating space, please try again."
|
||||
)
|
||||
async def update_space(
|
||||
space: str,
|
||||
updates: SpaceUpdate,
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -327,8 +328,6 @@ async def update_space(
|
|||
|
||||
- **[<JSON Patch>]**: Array of JSON Patches
|
||||
|
||||
|
||||
|
||||
Allowed operations:
|
||||
- **replace**
|
||||
|
||||
|
@ -369,8 +368,9 @@ async def update_space(
|
|||
error_msg = "Error deleting space, please try again."
|
||||
)
|
||||
async def delete_space(
|
||||
space: str,
|
||||
force: Optional[bool] = False,
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
force: Optional[bool] = Query(False, description="Forcefully delete a Space with existing Blocks"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -410,10 +410,10 @@ async def delete_space(
|
|||
status_code = 200
|
||||
)
|
||||
async def get_blocks(
|
||||
space: str,
|
||||
expand: bool = False,
|
||||
utilization: bool = False,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
expand: bool = Query(False, description="Expand network references to full network objects"),
|
||||
utilization: bool = Query(False, description="Append utilization information for each network"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -496,8 +496,9 @@ async def get_blocks(
|
|||
error_msg = "Error creating block, please try again."
|
||||
)
|
||||
async def create_block(
|
||||
space: str,
|
||||
block: BlockReq,
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -548,9 +549,9 @@ async def create_block(
|
|||
error_msg = "Error creating cidr reservation, please try again."
|
||||
)
|
||||
async def create_multi_block_reservation(
|
||||
space: str,
|
||||
req: SpaceCIDRReq,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id)
|
||||
):
|
||||
"""
|
||||
|
@ -659,11 +660,11 @@ async def create_multi_block_reservation(
|
|||
status_code = 200
|
||||
)
|
||||
async def get_block(
|
||||
space: str,
|
||||
block: str,
|
||||
expand: bool = False,
|
||||
utilization: bool = False,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
expand: bool = Query(False, description="Expand network references to full network objects"),
|
||||
utilization: bool = Query(False, description="Append utilization information for each network"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -747,9 +748,10 @@ async def get_block(
|
|||
error_msg = "Error deleting block, please try again."
|
||||
)
|
||||
async def delete_block(
|
||||
space: str,
|
||||
block: str,
|
||||
force: Optional[bool] = False,
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
force: Optional[bool] = Query(False, description="Forcefully delete a Block with existing networks and/or reservations"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -793,10 +795,10 @@ async def delete_block(
|
|||
status_code = 200
|
||||
)
|
||||
async def available_block_nets(
|
||||
space: str,
|
||||
block: str,
|
||||
expand: bool = False,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
expand: bool = Query(False, description="Expand network references to full network objects"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -860,10 +862,10 @@ async def available_block_nets(
|
|||
status_code = 200
|
||||
)
|
||||
async def available_block_nets(
|
||||
space: str,
|
||||
block: str,
|
||||
expand: bool = False,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
expand: bool = Query(False, description="Expand network references to full network objects"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -911,10 +913,10 @@ async def available_block_nets(
|
|||
error_msg = "Error adding network to block, please try again."
|
||||
)
|
||||
async def create_block_net(
|
||||
space: str,
|
||||
block: str,
|
||||
vnet: VNet,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -955,6 +957,8 @@ async def create_block_net(
|
|||
raise HTTPException(status_code=400, detail="Network CIDR not within block CIDR.")
|
||||
|
||||
block_net_cidrs = []
|
||||
resv_cidrs = IPSet(x['cidr'] for x in target_block['resv'])
|
||||
block_net_cidrs += resv_cidrs
|
||||
|
||||
for v in target_block['vnets']:
|
||||
target = next((x for x in net_list if x['id'].lower() == v['id'].lower()), None)
|
||||
|
@ -966,7 +970,7 @@ async def create_block_net(
|
|||
cidr_overlap = IPSet(block_net_cidrs) & IPSet([target_cidr])
|
||||
|
||||
if cidr_overlap:
|
||||
raise HTTPException(status_code=400, detail="Block already contains network(s) within the CIDR range of target network.")
|
||||
raise HTTPException(status_code=400, detail="Block already contains network(s) and/or reservation(s) within the CIDR range of target network.")
|
||||
|
||||
vnet.active = True
|
||||
target_block['vnets'].append(jsonable_encoder(vnet))
|
||||
|
@ -987,18 +991,17 @@ async def create_block_net(
|
|||
error_msg = "Error updating block networks, please try again."
|
||||
)
|
||||
async def update_block_vnets(
|
||||
space: str,
|
||||
block: str,
|
||||
vnets: VNetsUpdate,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
"""
|
||||
Replace the list of networks currently associated to the target Block with the following information:
|
||||
|
||||
- Array **[]** of:
|
||||
- **<str>**: Azure Resource ID
|
||||
- **[<str>]**: Array of Azure Resource ID's
|
||||
"""
|
||||
|
||||
if not is_admin:
|
||||
|
@ -1027,6 +1030,7 @@ async def update_block_vnets(
|
|||
outside_block_cidr = []
|
||||
net_ipset = IPSet([])
|
||||
net_overlap = False
|
||||
resv_ipset = IPSet(x['cidr'] for x in target_block['resv'])
|
||||
|
||||
for v in vnets:
|
||||
target_net = next((x for x in net_list if x['id'].lower() == v.lower()), None)
|
||||
|
@ -1047,6 +1051,9 @@ async def update_block_vnets(
|
|||
if net_overlap:
|
||||
raise HTTPException(status_code=400, detail="Network list contains overlapping CIDRs.")
|
||||
|
||||
if (net_ipset & resv_ipset):
|
||||
raise HTTPException(status_code=400, detail="Network list contains CIDR(s) that overlap outstanding reservations.")
|
||||
|
||||
if len(outside_block_cidr) > 0:
|
||||
raise HTTPException(status_code=400, detail="Network CIDR(s) not within Block CIDR: {}".format(outside_block_cidr))
|
||||
|
||||
|
@ -1080,9 +1087,10 @@ async def update_block_vnets(
|
|||
error_msg = "Error removing block network(s), please try again."
|
||||
)
|
||||
async def delete_block_nets(
|
||||
space: str,
|
||||
block: str,
|
||||
req: VNetsUpdate,
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -1134,9 +1142,9 @@ async def delete_block_nets(
|
|||
status_code = 200
|
||||
)
|
||||
async def get_block_reservations(
|
||||
space: str,
|
||||
block: str,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -1175,10 +1183,10 @@ async def get_block_reservations(
|
|||
error_msg = "Error creating cidr reservation, please try again."
|
||||
)
|
||||
async def create_block_reservation(
|
||||
space: str,
|
||||
block: str,
|
||||
req: BlockCIDRReq,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id)
|
||||
):
|
||||
"""
|
||||
|
@ -1271,10 +1279,10 @@ async def create_block_reservation(
|
|||
error_msg = "Error removing block reservation(s), please try again."
|
||||
)
|
||||
async def delete_block_reservations(
|
||||
space: str,
|
||||
block: str,
|
||||
req: DeleteResvReq,
|
||||
authorization: str = Header(None),
|
||||
space: str = Path(..., description="Name of the target Space"),
|
||||
block: str = Path(..., description="Name of the target Block"),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
|
|
@ -34,7 +34,7 @@ router = APIRouter(
|
|||
)
|
||||
async def next_available_subnet(
|
||||
req: SubnetCIDRReq,
|
||||
authorization: str = Header(None)
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
):
|
||||
"""
|
||||
Get the next available Subnet CIDR in a Virtual Network with the following information:
|
||||
|
|
|
@ -93,6 +93,7 @@ async def scrub_patch(patch):
|
|||
status_code = 200
|
||||
)
|
||||
async def get_users(
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
is_admin: str = Depends(get_admin)
|
||||
):
|
||||
|
@ -133,7 +134,7 @@ async def get_users(
|
|||
error_msg = "Error fetching user, please try again."
|
||||
)
|
||||
async def get_user(
|
||||
authorization: str = Header(None),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id)
|
||||
):
|
||||
"""
|
||||
|
@ -178,7 +179,7 @@ async def get_user(
|
|||
)
|
||||
async def update_user(
|
||||
updates: UserUpdate,
|
||||
authorization: str = Header(None),
|
||||
authorization: str = Header(None, description="Azure Bearer token"),
|
||||
tenant_id: str = Depends(get_tenant_id)
|
||||
):
|
||||
"""
|
||||
|
@ -186,8 +187,6 @@ async def update_user(
|
|||
|
||||
- **[<JSON Patch>]**: Array of JSON Patches
|
||||
|
||||
|
||||
|
||||
Allowed operations:
|
||||
- **replace**
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
fetchVHubs,
|
||||
fetchSubnets,
|
||||
fetchEndpoints,
|
||||
fetchNetworks,
|
||||
refreshAll,
|
||||
getMe
|
||||
} from './ipamAPI';
|
||||
|
@ -84,6 +85,15 @@ export const fetchEndpointsAsync = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
|
||||
export const fetchNetworksAsync = createAsyncThunk(
|
||||
'ipam/fetchNetworks',
|
||||
async (token) => {
|
||||
const response = await fetchNetworks(token);
|
||||
// The value we return becomes the `fulfilled` action payload
|
||||
return response;
|
||||
}
|
||||
);
|
||||
|
||||
export const refreshAllAsync = createAsyncThunk(
|
||||
'ipam/refreshAll',
|
||||
async (token) => {
|
||||
|
@ -142,7 +152,7 @@ export const ipamSlice = createSlice({
|
|||
const vnets = action.payload.map((vnet) => {
|
||||
vnet.available = (vnet.size - vnet.used);
|
||||
vnet.utilization = Math.round((vnet.used / vnet.size) * 100);
|
||||
vnet.prefixes = vnet.prefixes.join(", ");
|
||||
// vnet.prefixes = vnet.prefixes.join(", ");
|
||||
|
||||
return vnet;
|
||||
});
|
||||
|
@ -163,6 +173,53 @@ export const ipamSlice = createSlice({
|
|||
.addCase(fetchEndpointsAsync.fulfilled, (state, action) => {
|
||||
state.endpoints = action.payload;
|
||||
})
|
||||
.addCase(fetchNetworksAsync.fulfilled, (state, action) => {
|
||||
const vNetProvider = "Microsoft.Network/virtualNetworks";
|
||||
const vHubProvider = "Microsoft.Network/virtualHubs";
|
||||
|
||||
const vNetData = action.payload.filter((x) => x.id.toLowerCase().includes(vNetProvider.toLowerCase()));
|
||||
const vHubData = action.payload.filter((x) => x.id.toLowerCase().includes(vHubProvider.toLowerCase()));
|
||||
|
||||
const vnets = vNetData.map((vnet) => {
|
||||
vnet.available = (vnet.size - vnet.used);
|
||||
vnet.utilization = Math.round((vnet.used / vnet.size) * 100);
|
||||
// vnet.prefixes = vnet.prefixes.join(", ");
|
||||
|
||||
return vnet;
|
||||
});
|
||||
|
||||
state.vNets = vnets;
|
||||
|
||||
const subnets = vNetData.map((vnet) => {
|
||||
var subnetArray = [];
|
||||
|
||||
vnet.subnets.forEach((subnet) => {
|
||||
const subnetDetails = {
|
||||
name: subnet.name,
|
||||
id: `${vnet.id}/subnets/${subnet.name}`,
|
||||
prefix: subnet.prefix,
|
||||
resource_group: vnet.resource_group,
|
||||
subscription_id: vnet.subscription_id,
|
||||
tenant_id: vnet.tenant_id,
|
||||
vnet_name: vnet.name,
|
||||
vnet_id: vnet.id,
|
||||
used: subnet.used,
|
||||
size: subnet.size,
|
||||
available: (subnet.size - subnet.used),
|
||||
utilization: Math.round((subnet.used / subnet.size) * 100),
|
||||
type: subnetMap[subnet.type]
|
||||
};
|
||||
|
||||
subnetArray.push(subnetDetails);
|
||||
});
|
||||
|
||||
return subnetArray;
|
||||
}).flat();
|
||||
|
||||
state.subnets = subnets;
|
||||
|
||||
state.vHubs = vHubData;
|
||||
})
|
||||
.addCase(refreshAllAsync.fulfilled, (state, action) => {
|
||||
const vNetProvider = "Microsoft.Network/virtualNetworks";
|
||||
const vHubProvider = "Microsoft.Network/virtualHubs";
|
||||
|
|
Загрузка…
Ссылка в новой задаче