зеркало из https://github.com/Azure/ipam.git
Added subnet planning feature
This commit is contained in:
Родитель
03d08cd68e
Коммит
11c0fec6de
|
@ -72,12 +72,16 @@ import Peering from "../../img/Peering";
|
|||
// import Conflict from "../../img/Conflict";
|
||||
import Person from "../../img/Person";
|
||||
import Rule from "../../img/Rule";
|
||||
import Tools from "../../img/Tools";
|
||||
import Planner from "../../img/Planner";
|
||||
|
||||
import UserSettings from "./userSettings";
|
||||
|
||||
import Welcome from "../welcome/Welcome";
|
||||
import DiscoverTabs from "../tabs/discoverTabs";
|
||||
import AnalyzeTabs from "../tabs/analyzeTabs";
|
||||
import ToolsTabs from "../tabs/toolsTabs";
|
||||
|
||||
import AdminTabs from "../tabs/adminTabs";
|
||||
import ConfigureIPAM from "../configure/configure";
|
||||
|
||||
|
@ -140,7 +144,7 @@ export default function NavDrawer() {
|
|||
icon: Home,
|
||||
link: "/",
|
||||
admin: false
|
||||
},
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
|
@ -199,6 +203,19 @@ export default function NavDrawer() {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Tools",
|
||||
icon: Tools,
|
||||
admin: false,
|
||||
children: [
|
||||
{
|
||||
title: "Planner",
|
||||
icon: Planner,
|
||||
link: "tools/planner",
|
||||
admin: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
|
@ -225,7 +242,7 @@ export default function NavDrawer() {
|
|||
admin: true
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -791,6 +808,7 @@ export default function NavDrawer() {
|
|||
<Route path="discover/endpoint" element={<DiscoverTabs />} />
|
||||
<Route path="analyze/visualize" element={<AnalyzeTabs />} />
|
||||
<Route path="analyze/peering" element={<AnalyzeTabs />} />
|
||||
<Route path="tools/planner" element={<ToolsTabs />} />
|
||||
<Route path="configure" element={<ConfigureIPAM />} />
|
||||
{/* <Route path="admin" element={<Administration />} /> */}
|
||||
<Route path="admin/admins" element={<AdminTabs />} />
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import * as React from 'react';
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import Planner from '../tools/planner';
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box sx={{ height: 'calc(100vh - 112px)' }}>
|
||||
{children}
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
index: PropTypes.number.isRequired,
|
||||
value: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
export default function ToolsTabs() {
|
||||
const allTabs = ['/tools/planner'];
|
||||
|
||||
let location = useLocation();
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', height: 'calc(100vh - 137px)'}}>
|
||||
<React.Fragment>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={allTabs.indexOf(location.pathname)}>
|
||||
<Tab label="Planner" component={Link} to={allTabs[0]} {...a11yProps(0)} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
<TabPanel value={allTabs.indexOf(location.pathname)} index={0}><Planner /></TabPanel>
|
||||
</React.Fragment>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,415 @@
|
|||
import * as React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { InteractionRequiredAuthError } from "@azure/msal-browser";
|
||||
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { find } from 'lodash';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Tooltip,
|
||||
Autocomplete,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Typography,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
|
||||
import Grid from '@mui/material/Unstable_Grid2';
|
||||
|
||||
import {
|
||||
FilterList as FilterListIcon,
|
||||
FilterListOff as FilterListOffIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import {
|
||||
selectVNets
|
||||
} from "../ipam/ipamSlice";
|
||||
|
||||
import {
|
||||
fetchSubscriptions
|
||||
} from "../ipam/ipamAPI";
|
||||
|
||||
import { apiRequest } from "../../msal/authConfig";
|
||||
|
||||
import { availableSubnets } from './utils/iputils';
|
||||
|
||||
const Item = styled(Paper)(({ theme }) => ({
|
||||
backgroundColor: "red",
|
||||
padding: theme.spacing(1),
|
||||
textAlign: "center",
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: "clamp(12px, 1vw, 20px)"
|
||||
}));
|
||||
|
||||
const cidrMasks = [
|
||||
{ name: '/8', value: 8},
|
||||
{ name: '/9', value: 9},
|
||||
{ name: '/10', value: 10},
|
||||
{ name: '/11', value: 11},
|
||||
{ name: '/12', value: 12},
|
||||
{ name: '/13', value: 13},
|
||||
{ name: '/14', value: 14},
|
||||
{ name: '/15', value: 15},
|
||||
{ name: '/16', value: 16},
|
||||
{ name: '/17', value: 17},
|
||||
{ name: '/18', value: 18},
|
||||
{ name: '/19', value: 19},
|
||||
{ name: '/20', value: 20},
|
||||
{ name: '/21', value: 21},
|
||||
{ name: '/22', value: 22},
|
||||
{ name: '/23', value: 23},
|
||||
{ name: '/24', value: 24},
|
||||
{ name: '/25', value: 25},
|
||||
{ name: '/26', value: 26},
|
||||
{ name: '/27', value: 27},
|
||||
{ name: '/28', value: 28},
|
||||
{ name: '/29', value: 29},
|
||||
{ name: '/30', value: 30},
|
||||
{ name: '/31', value: 31},
|
||||
{ name: '/32', value: 32}
|
||||
];
|
||||
|
||||
const Separator = (props) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "row", pt: 2, pb: 2 }}>
|
||||
<div style={{ flexGrow: 0.1 }}>
|
||||
<hr />
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', margin: '0px 8px' }}>
|
||||
<Typography>
|
||||
MASK: {props.name} | ({props.used}/{props.total} Used)
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{ flexGrow: 2 }}>
|
||||
<hr />
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Planner = () => {
|
||||
const { instance, accounts } = useMsal();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [subscriptions, setSubscriptions] = React.useState(null);
|
||||
const [newVNets, setNewVNets] = React.useState([]);
|
||||
|
||||
const [subnetData, setSubnetData] = React.useState(null);
|
||||
|
||||
const [vNetInput, setVNetInput] = React.useState('');
|
||||
const [maskInput, setMaskInput] = React.useState('');
|
||||
|
||||
const [selectedVNet, setSelectedVNet] = React.useState(null);
|
||||
const [selectedPrefix, setSelectedPrefix] = React.useState('');
|
||||
const [selectedMask, setSelectedMask] = React.useState(null);
|
||||
const [exclusions, setExclusions] = React.useState([]);
|
||||
|
||||
const [vNetOptions, setVNetOptions] = React.useState([]);
|
||||
const [prefixOptions, setPrefixOptions] = React.useState(null);
|
||||
const [maskOptions, setMaskOptions] = React.useState([]);
|
||||
|
||||
const [showAll, setShowAll] = React.useState(false);
|
||||
|
||||
const subsLoadingRef = React.useRef(false);
|
||||
|
||||
const vNets = useSelector(selectVNets);
|
||||
|
||||
const loading = !vNets || subsLoadingRef.current;
|
||||
|
||||
const refreshSubscriptions = React.useCallback(() => {
|
||||
const request = {
|
||||
scopes: apiRequest.scopes,
|
||||
account: accounts[0],
|
||||
};
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
subsLoadingRef.current = true;
|
||||
const response = await instance.acquireTokenSilent(request);
|
||||
const data = await fetchSubscriptions(response.accessToken);
|
||||
setSubscriptions(data);
|
||||
} catch (e) {
|
||||
if (e instanceof InteractionRequiredAuthError) {
|
||||
instance.acquireTokenRedirect(request);
|
||||
} else {
|
||||
console.log("ERROR");
|
||||
console.log("------------------");
|
||||
console.log(e);
|
||||
console.log("------------------");
|
||||
enqueueSnackbar("Error fetching subnets", { variant: "error" });
|
||||
}
|
||||
} finally {
|
||||
subsLoadingRef.current = false;
|
||||
}
|
||||
})();
|
||||
}, [accounts, enqueueSnackbar, instance]);
|
||||
|
||||
React.useEffect(() => {
|
||||
!subsLoadingRef.current && refreshSubscriptions();
|
||||
}, [vNets, refreshSubscriptions]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (vNets && subscriptions) {
|
||||
const subMap = subscriptions.reduce((prev, curr) => {
|
||||
return {
|
||||
...prev,
|
||||
[curr.subscription_id]: curr.name,
|
||||
};
|
||||
}, {});
|
||||
|
||||
const newNets = vNets.map((vnet) => {
|
||||
var subName = subMap[vnet.subscription_id] || vnet.subscription_id;
|
||||
|
||||
return {
|
||||
...vnet,
|
||||
subscription_name: subName,
|
||||
};
|
||||
});
|
||||
|
||||
setNewVNets(newNets);
|
||||
}
|
||||
}, [vNets, subscriptions]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if(!find(newVNets, selectedVNet)) {
|
||||
setSelectedVNet(null);
|
||||
setVNetInput('');
|
||||
}
|
||||
}, [newVNets, selectedVNet]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelectedVNet(null);
|
||||
setVNetInput('');
|
||||
}, [showAll]);
|
||||
|
||||
React.useEffect(() => {
|
||||
showAll
|
||||
? setVNetOptions(newVNets.sort((a, b) => (a.subscription_name > b.subscription_name) ? 1 : -1))
|
||||
: setVNetOptions(newVNets.filter(v => v.parentSpace !== null).sort((a, b) => (a.parentSpace > b.parentSpace) ? 1 : (a.parentSpace === b.parentSpace) ? ((a.parentBlock > b.parentBlock) ? 1 : -1) : -1 ));
|
||||
}, [showAll, newVNets]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selectedVNet) {
|
||||
let exclusions = selectedVNet.subnets.map((sub) => sub.prefix);
|
||||
let prefixParts = selectedVNet.prefixes[0].split("/");
|
||||
let currentMask = parseInt(prefixParts[1], 10);
|
||||
let availableMasks = cidrMasks.filter((opt) => opt.value > currentMask && opt.value <= currentMask + 10);
|
||||
|
||||
setExclusions(exclusions);
|
||||
setPrefixOptions(selectedVNet.prefixes);
|
||||
setSelectedPrefix(selectedVNet.prefixes[0]);
|
||||
setMaskOptions(availableMasks);
|
||||
setSelectedMask(availableMasks[0]);
|
||||
} else {
|
||||
setExclusions([]);
|
||||
setPrefixOptions(null);
|
||||
setSelectedPrefix("");
|
||||
setSelectedMask(null);
|
||||
setMaskInput("");
|
||||
setMaskOptions([]);
|
||||
}
|
||||
}, [selectedVNet]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selectedPrefix) {
|
||||
let prefixParts = selectedPrefix.split("/");
|
||||
let currentMask = parseInt(prefixParts[1], 10);
|
||||
let availableMasks = cidrMasks.filter((opt) => opt.value > currentMask && opt.value <= currentMask + 10);
|
||||
|
||||
setMaskOptions(availableMasks);
|
||||
setSelectedMask(availableMasks[0]);
|
||||
} else {
|
||||
setSelectedMask(null);
|
||||
setMaskInput("");
|
||||
setMaskOptions([]);
|
||||
}
|
||||
}, [selectedPrefix]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selectedVNet && selectedPrefix && selectedMask) {
|
||||
let prefixParts = selectedPrefix.split("/");
|
||||
let currentMask = parseInt(prefixParts[1], 10);
|
||||
|
||||
let query = {
|
||||
address: prefixParts[0],
|
||||
netmask: currentMask,
|
||||
netmaskRange: { max: selectedMask.value, min: currentMask || 16 },
|
||||
};
|
||||
|
||||
let subnetsObj = availableSubnets(query, exclusions);
|
||||
|
||||
setSubnetData(subnetsObj);
|
||||
} else {
|
||||
setSubnetData(null);
|
||||
}
|
||||
}, [selectedVNet, selectedPrefix, selectedMask, exclusions]);
|
||||
|
||||
const handleShowAll = (event, newFilter) => {
|
||||
if (newFilter !== null) {
|
||||
setShowAll(newFilter);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%'}}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: '8px', pt: 1, pb: 1, pr: 3, pl: 3, alignItems: 'center' }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: '8px', pt: 1, pb: 1 }}>
|
||||
<Autocomplete
|
||||
freeSolo
|
||||
id="grouped-demo"
|
||||
size="small"
|
||||
options={vNetOptions}
|
||||
groupBy={(option) => showAll ? option.subscription_name : `${option.parentSpace} ➜ ${option.parentBlock}`}
|
||||
getOptionLabel={(option) => option.name}
|
||||
inputValue={vNetInput}
|
||||
onInputChange={(event, newInputValue) => setVNetInput(newInputValue)}
|
||||
value={selectedVNet}
|
||||
onChange={(event, newValue) => setSelectedVNet(newValue)}
|
||||
sx={{ width: 300 }}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Virtual Network"
|
||||
placeholder={showAll ? "By Subscription" : "By Space ➜ Block"}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<React.Fragment>
|
||||
{loading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
renderGroup={(params) => (
|
||||
<li key={params.group}>
|
||||
<Box sx={{ top: '-8px', padding: '4px 10px' }}>{params.group}</Box>
|
||||
<ul style={{ padding: 0 }}>{params.children}</ul>
|
||||
</li>
|
||||
)}
|
||||
renderOption={(props, option) => {
|
||||
return (
|
||||
<li {...props} key={option.id}>
|
||||
{option.name}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormControl size="small">
|
||||
<InputLabel
|
||||
disabled={selectedVNet === null}
|
||||
id="prefix-select-label"
|
||||
>
|
||||
Address Space
|
||||
</InputLabel>
|
||||
<Select
|
||||
disabled={selectedVNet === null}
|
||||
labelId="prefix-select-label"
|
||||
id="vnet-select"
|
||||
value={selectedPrefix}
|
||||
label="Address Space"
|
||||
onChange={(event) => setSelectedPrefix(event.target.value)}
|
||||
sx={{ width: '22ch' }}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: 36 * 10,
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
{prefixOptions ?
|
||||
prefixOptions.map((opt) => (
|
||||
<MenuItem
|
||||
key={opt}
|
||||
value={opt}
|
||||
>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
)) : null
|
||||
}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Autocomplete
|
||||
freeSolo
|
||||
disabled={selectedPrefix === ''}
|
||||
id="cidr-mask-max"
|
||||
size="small"
|
||||
options={maskOptions}
|
||||
getOptionLabel={(option) => option.name}
|
||||
inputValue={maskInput}
|
||||
onInputChange={(event, newInputValue) => setMaskInput(newInputValue)}
|
||||
value={selectedMask}
|
||||
onChange={(event, newValue) => setSelectedMask(newValue)}
|
||||
sx={{ width: '5ch' }}
|
||||
renderInput={(params) => <TextField {...params} label="Mask" placeholder="Max" />}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: '8px', pt: 1, mb: 1, marginLeft: 'auto' }}>
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
color="primary"
|
||||
value={showAll}
|
||||
exclusive
|
||||
onChange={handleShowAll}
|
||||
>
|
||||
<ToggleButton value={false} aria-label="list">
|
||||
<Tooltip title="Filter Networks">
|
||||
<FilterListIcon />
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
<ToggleButton value={true} aria-label="module">
|
||||
<Tooltip title="All Networks">
|
||||
<FilterListOffIcon />
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, pb: 3, pr: 3, pl: 3, overflowY: 'scroll', overflowX: 'hidden' }}>
|
||||
{
|
||||
subnetData &&
|
||||
[...new Set(subnetData.subnets.map((x) => x.mask))].map((mask) => {
|
||||
return (
|
||||
<React.Fragment key={`fragment-${mask}`}>
|
||||
<Separator key={`sep-${mask}`} name={mask} total={subnetData.subnets.filter((x) => x.mask === mask).length} used={subnetData.subnets.filter((x) => x.mask === mask && x.overlap).length} />
|
||||
<Grid key={`grid-container-${mask}`} container spacing={2}>
|
||||
{
|
||||
subnetData?.subnets.filter((x) => x.mask === mask).map((item) => {
|
||||
return (
|
||||
<Grid key={`grid-item-${item.network}-${mask}`} xs={5} sm={3} md={2}>
|
||||
<Item
|
||||
style={{
|
||||
backgroundColor: item.overlap ? "orangered" : "lawngreen",
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{item.network}/{mask}
|
||||
</Item>
|
||||
</Grid>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Planner;
|
|
@ -0,0 +1,166 @@
|
|||
function allowedOctets() {
|
||||
return [128, 64, 32, 16, 8, 4, 2, 1, 128, 64, 32, 16];
|
||||
}
|
||||
|
||||
function ip2Integer(ip) {
|
||||
return ip.split('.').reduce(function(ipInt, octet) { return (ipInt << 8) + parseInt(octet, 10) }, 0) >>> 0;
|
||||
}
|
||||
|
||||
function probabalCombinations(arr, Addressbytes, position) {
|
||||
var res = [];
|
||||
|
||||
for (var i = 0; i < Math.pow(2, arr.length); i++) {
|
||||
var bin = (i).toString(2);
|
||||
var set = [];
|
||||
|
||||
bin = new Array((arr.length - bin.length) + 1).join("0") + bin;
|
||||
|
||||
for (var j = 0; j < bin.length; j++) {
|
||||
if (bin[j] === "1") {
|
||||
set.push(arr[j]);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var sum = set.reduce((a, b) => { return a + b; });
|
||||
res.push(sum);
|
||||
} catch(e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
res = res.filter(n => { return n >= Addressbytes[position] });
|
||||
|
||||
arr.indexOf(0) !== -1 ?? res.push(0);
|
||||
res = [...new Set(res)];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function getIpRangeForSubnet(subnetCIDR) {
|
||||
var address = subnetCIDR.split('/')[0].split('.');
|
||||
var netmask = parseInt(subnetCIDR.split('/')[1], 10);
|
||||
var allowed = allowedOctets();
|
||||
var pos = Math.ceil(netmask / 8) - 1;
|
||||
var endAddress = [...address];
|
||||
|
||||
endAddress[pos] = parseInt(endAddress[pos], 10) + ((!allowed[(netmask % 8) - 1]) ? 0 : allowed[(netmask % 8) - 1] - 1);
|
||||
|
||||
if(pos === 2 && endAddress[3] < 255) {
|
||||
endAddress[3] = 255;
|
||||
}
|
||||
|
||||
return {'start': address.join('.'), 'end': endAddress.join('.')};
|
||||
}
|
||||
|
||||
function isSubnetOverlap(subnetCIDR, existingSubnetCIDR) {
|
||||
var ipRangeforCurrent = getIpRangeForSubnet(subnetCIDR);
|
||||
|
||||
var isOverlap = existingSubnetCIDR.map(subnet => {
|
||||
var ipRange = getIpRangeForSubnet(subnet);
|
||||
|
||||
if((ip2Integer(ipRangeforCurrent.start) >= ip2Integer(ipRange.start) && ip2Integer(ipRangeforCurrent.start) <= ip2Integer(ipRange.end)) || (ip2Integer(ipRangeforCurrent.end) >= ip2Integer(ipRange.start) && ip2Integer(ipRangeforCurrent.end) <= ip2Integer(ipRange.end)) || (ip2Integer(ipRange.start) >= ip2Integer(ipRangeforCurrent.start) && ip2Integer(ipRange.start) <= ip2Integer(ipRangeforCurrent.end)) || (ip2Integer(ipRange.end) >= ip2Integer(ipRangeforCurrent.start) && ip2Integer(ipRange.end) <= ip2Integer(ipRangeforCurrent.end)) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}).some(item => item === true);
|
||||
|
||||
return isOverlap;
|
||||
}
|
||||
|
||||
function possibleSubnets(obj, index, existingSubnetCIDR) {
|
||||
var sliceTo = ((index % 8) === 0) ? 8 : (index % 8);
|
||||
var filteredOctets = [];
|
||||
var pos = Math.ceil(index / 8) - 1;
|
||||
var subnets = [];
|
||||
var subnetsExcluded = [];
|
||||
var allowed = allowedOctets();
|
||||
var addressBytes = obj.address.split('.', 4).map(num => parseInt(num, 10));
|
||||
|
||||
if((obj.netmask === 24 && index === 24) || (obj.netmask === 16 && index === 16)) {
|
||||
filteredOctets.push(addressBytes[2]);
|
||||
} else if((obj.netmask % 8) <= sliceTo && index <= 24) {
|
||||
filteredOctets = allowed.slice(obj.netmask%8, sliceTo);
|
||||
filteredOctets.push(addressBytes[2]);
|
||||
} else if(index >= 24 && addressBytes[3] === 0) {
|
||||
filteredOctets = allowed.slice(0, sliceTo);
|
||||
filteredOctets.push(addressBytes[3]);
|
||||
} else {
|
||||
filteredOctets = allowed.slice(0, sliceTo);
|
||||
}
|
||||
|
||||
var allowedCombinations = probabalCombinations(filteredOctets, addressBytes, pos);
|
||||
|
||||
allowedCombinations.forEach(function(octet) {
|
||||
let range = (index >= 25 & obj.netmask < 24) ? {
|
||||
'from': addressBytes[2],
|
||||
'to': addressBytes[2] + ((allowed[(obj.netmask % 8) - 1] === undefined) ? 256 : allowed[(obj.netmask % 8) - 1]) - 1
|
||||
} : {
|
||||
'from': addressBytes[2],
|
||||
'to': addressBytes[2]
|
||||
}
|
||||
|
||||
for (let i = range.from; i <= range.to; i++) {
|
||||
var subnetBytes = [...addressBytes];
|
||||
|
||||
subnetBytes[2] = i;
|
||||
subnetBytes[pos] = octet;
|
||||
|
||||
var subnetObject = {
|
||||
'network': subnetBytes.join('.'),
|
||||
'mask': index,
|
||||
'cidr': subnetBytes.join('.') + '/' + index,
|
||||
'ipRange': getIpRangeForSubnet(subnetBytes.join('.') + '/' + index)
|
||||
};
|
||||
|
||||
var doesOverlap = isSubnetOverlap(subnetObject.cidr, existingSubnetCIDR);
|
||||
|
||||
if(!doesOverlap) {
|
||||
subnetObject.overlap = false;
|
||||
subnets.push(subnetObject);
|
||||
} else {
|
||||
subnetObject.overlap = true;
|
||||
subnets.push(subnetObject);
|
||||
subnetsExcluded.push(subnetObject.cidr);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {'subnets': subnets, 'subnetsExcluded': subnetsExcluded};
|
||||
}
|
||||
|
||||
export function availableSubnets(obj, existingSubnetCIDR) {
|
||||
var subnetsObj = {
|
||||
subnets: [],
|
||||
subnetsExcluded: []
|
||||
};
|
||||
|
||||
var startIndex = obj.netmaskRange.min;
|
||||
|
||||
for(var i = startIndex; i <= obj.netmaskRange.max; i++) {
|
||||
var res = possibleSubnets(obj, i, existingSubnetCIDR);
|
||||
|
||||
subnetsObj.subnets = [...subnetsObj.subnets, ...res.subnets];
|
||||
subnetsObj.subnetsExcluded = [...subnetsObj.subnetsExcluded, ...res.subnetsExcluded];
|
||||
}
|
||||
|
||||
subnetsObj.subnets = subnetsObj.subnets.sort((a,b) => {
|
||||
let netA = ip2Integer(a.network);
|
||||
let netB = ip2Integer(b.network);
|
||||
|
||||
if (a.mask < b.mask)
|
||||
return -1
|
||||
else if (a.mask > b.mask)
|
||||
return 1
|
||||
else if (a.mask === b.mask)
|
||||
if (netA > netB)
|
||||
return 1
|
||||
else
|
||||
return -1
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return subnetsObj;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
|
||||
function Planner() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="none" d="M0 0H24V24H0z"></path>
|
||||
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"></path>
|
||||
<path d="M7 12H9V17H7z"></path>
|
||||
<path d="M15 7H17V17H15z"></path>
|
||||
<path d="M11 14H13V17H11z"></path>
|
||||
<path d="M11 10H13V12H11z"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default Planner;
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
|
||||
function Tools() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="none" d="M0 0H24V24H0z"></path>
|
||||
<path d="M20 8h-3V6c0-1.1-.9-2-2-2H9c-1.1 0-2 .9-2 2v2H4c-1.1 0-2 .9-2 2v10h20V10c0-1.1-.9-2-2-2zM9 6h6v2H9V6zm11 12H4v-3h2v1h2v-1h8v1h2v-1h2v3zm-2-5v-1h-2v1H8v-1H6v1H4v-3h16v3h-2z"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tools;
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><rect fill="none" height="24" width="24"/><g><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,19H5V5h14V19z"/><rect height="5" width="2" x="7" y="12"/><rect height="10" width="2" x="15" y="7"/><rect height="3" width="2" x="11" y="14"/><rect height="2" width="2" x="11" y="10"/></g></g></svg>
|
После Ширина: | Высота: | Размер: 459 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M20,8h-3V6c0-1.1-0.9-2-2-2H9C7.9,4,7,4.9,7,6v2H4c-1.1,0-2,0.9-2,2v10h20V10C22,8.9,21.1,8,20,8z M9,6h6v2H9V6z M20,18H4 v-3h2v1h2v-1h8v1h2v-1h2V18z M18,13v-1h-2v1H8v-1H6v1H4v-3h3h10h3v3H18z"/></g></g></svg>
|
После Ширина: | Высота: | Размер: 385 B |
Загрузка…
Ссылка в новой задаче