mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2026-01-25 13:14:21 +01:00
Add initial implementation of the VPC command
This commit is contained in:
@@ -12,7 +12,7 @@ export type FlipBitEventArg = {
|
||||
index: number;
|
||||
binaryString: string;
|
||||
$event: any;
|
||||
newBinaryString: string,
|
||||
newBinaryString: string
|
||||
};
|
||||
|
||||
export default class BinaryStringView extends React.Component<BinaryStringViewProps> {
|
||||
|
||||
@@ -33,6 +33,8 @@ function getBase(kind:string) : number {
|
||||
throw new Error("Unsupported kind: " + kind);
|
||||
}
|
||||
|
||||
export default formatter;
|
||||
const emBin = formatter.emBin;
|
||||
export {emBin};
|
||||
const emBin = formatter.emBin.bind(formatter);
|
||||
const padLeft = formatter.padLeft.bind(formatter);
|
||||
|
||||
export {emBin, padLeft}
|
||||
export default formatter;
|
||||
@@ -107,6 +107,7 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
.midnight .zero { color: #85a0ad;}
|
||||
.midnight .prefix { color: #85a0ad}
|
||||
.midnight .other { color: #9FBAC7;}
|
||||
.midnight .accent-background { background-color: #3b5268;}
|
||||
.midnight .hashLink, .dark .hashLink:visited { color: #85a0ad }
|
||||
.midnight .hashLink:hover { color: #9FBAC7 }
|
||||
.midnight ul.top-links li:hover { background: #132537 }
|
||||
|
||||
@@ -32,7 +32,7 @@ function SubnetView(props : {subnet : SubnetCommand}) {
|
||||
<span>Network Size</span>
|
||||
</td>
|
||||
<td data-test-name="decimal">
|
||||
{subnet.getAdressSpaceSize()}
|
||||
{subnet.input.getAdressSpaceSize()}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
23
src/networking/components/VpcView.css
Normal file
23
src/networking/components/VpcView.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.vpc-view .description {
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vpc-view td {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.vpc-view {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.vpc-view .part {
|
||||
border-bottom: solid 1px;
|
||||
}
|
||||
|
||||
|
||||
.vpc-view button {
|
||||
margin:0 3px;
|
||||
}
|
||||
|
||||
.vpc-view .ip-address-col { min-width: 8.5em;}
|
||||
115
src/networking/components/VpcView.tsx
Normal file
115
src/networking/components/VpcView.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useState } from 'react';
|
||||
import BinaryStringView from '../../core/components/BinaryString';
|
||||
import './SubnetView.css';
|
||||
import { getNetworkAddress, getBroadCastAddress, createSubnetMaskIp, getAddressSpaceSize } from '../subnet-utils';
|
||||
import { chunkifyString } from '../../core/utils';
|
||||
import IpAddressBinaryString from './IpAddressBinaryString';
|
||||
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand, VpcCommand } from '../models';
|
||||
import { padLeft } from '../../core/formatter';
|
||||
|
||||
function SubnetView(props : {vpc : VpcCommand}) {
|
||||
|
||||
const [vpc, setVpc] = useState(VpcModel.create(props.vpc));
|
||||
|
||||
const subnetMask = vpc.cidr.maskBits + vpc.subnetBits;
|
||||
const vpcPart = getNetworkAddress(vpc.cidr).toBinaryString(true).substring(0, vpc.cidr.maskBits);
|
||||
const subnetsPart = padLeft("0", vpc.subnetBits, "0");
|
||||
const lastPart = padLeft("0", 32 - (vpc.cidr.maskBits + vpc.subnetBits), "0");
|
||||
|
||||
const decrSubnet = () => setVpc(vpc.changeSubnetBits(vpc.subnetBits-1));
|
||||
const incrSubnet = () => setVpc(vpc.changeSubnetBits(vpc.subnetBits+1));
|
||||
const incrVpc = () => setVpc(vpc.changeVpcCidr(new IpAddressWithSubnetMask(vpc.cidr.ipAddress, vpc.cidr.maskBits+1)));
|
||||
const decrVpc = () => setVpc(vpc.changeVpcCidr(new IpAddressWithSubnetMask(vpc.cidr.ipAddress, vpc.cidr.maskBits-1)));
|
||||
|
||||
return <React.Fragment>
|
||||
<div className="expression vpc-view">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
VPC Address Space
|
||||
</td>
|
||||
<td>
|
||||
<BinaryStringView binaryString={vpcPart} /><BinaryStringView binaryString={subnetsPart} className="accent-background" /><BinaryStringView binaryString={lastPart} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
VPC Mask:
|
||||
</td>
|
||||
<td>
|
||||
<button onClick={decrVpc} disabled={vpc.cidr.maskBits <= 1} title="Increase vpc space">-</button>
|
||||
/{vpc.cidr.maskBits}
|
||||
<button onClick={incrVpc} disabled={subnetMask >= 31} title="Decrease vpc space">+</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Subnet Mask:
|
||||
</td>
|
||||
<td>
|
||||
<button onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Increase subnet space">-</button>
|
||||
/{subnetMask}
|
||||
<button onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= 31} title="Decrease subnet space">+</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Max Subnets:
|
||||
</td>
|
||||
<td>
|
||||
{Math.pow(2, vpc.subnetBits)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Subnet Network Size:
|
||||
</td>
|
||||
<td>
|
||||
{getAddressSpaceSize(subnetMask)}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
function VpcRow(props: { ip: IpAddress, descr: string}) {
|
||||
|
||||
const {ip, descr} = props;
|
||||
|
||||
return <tr>
|
||||
<td className="soft" data-test-name="label">{descr}</td>
|
||||
<td data-test-name="decimal" className="ip-address-col">
|
||||
{ip.toString()}
|
||||
</td>
|
||||
<td data-test-name="bin">
|
||||
<IpAddressBinaryString ip={ip} />
|
||||
</td>
|
||||
</tr>;
|
||||
}
|
||||
|
||||
export default SubnetView;
|
||||
|
||||
class VpcModel {
|
||||
cidr: IpAddressWithSubnetMask;
|
||||
subnetBits: number;
|
||||
subnetNum: number;
|
||||
|
||||
constructor(cidr: IpAddressWithSubnetMask, subnetBits: number) {
|
||||
this.cidr = cidr;
|
||||
this.subnetBits = subnetBits;
|
||||
this.subnetNum = 0;
|
||||
}
|
||||
|
||||
static create(vpc: VpcCommand) {
|
||||
return new VpcModel(vpc.cidr, vpc.subnetBits);
|
||||
}
|
||||
|
||||
changeSubnetBits(n: number) {
|
||||
return new VpcModel(this.cidr, n);
|
||||
}
|
||||
|
||||
changeVpcCidr(newCidr: IpAddressWithSubnetMask) {
|
||||
return new VpcModel(newCidr, this.subnetBits);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import ipAddressParser, { ParsedIpObject, ParsingError } from './ip-parser';
|
||||
import { IpAddressWithSubnetMask, IpAddress, SubnetCommand } from "./models";
|
||||
import { IpAddressWithSubnetMask, IpAddress, SubnetCommand, VpcCommand } from "./models";
|
||||
|
||||
|
||||
describe('parser tests', () => {
|
||||
@@ -62,4 +62,11 @@ describe('parser tests', () => {
|
||||
const subnet = actual as SubnetCommand;
|
||||
expect(subnet.toString()).toBe('192.168.1.1/23');
|
||||
});
|
||||
|
||||
it('parses vpc command', () => {
|
||||
const actual = ipAddressParser.parse('vpc 192.168.1.1/23');
|
||||
expect(actual).toBeInstanceOf(VpcCommand);
|
||||
const vpc = actual as VpcCommand;
|
||||
expect(vpc.toString()).toBe('192.168.1.1/23');
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,18 @@
|
||||
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from './models';
|
||||
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand, VpcCommand } from './models';
|
||||
|
||||
export type ParsedIpObject = IpAddress | IpAddressWithSubnetMask;
|
||||
|
||||
export type ParsingResult = ParsedIpObject[] |
|
||||
SubnetCommand |
|
||||
ParsingError |
|
||||
VpcCommand |
|
||||
null;
|
||||
|
||||
const CMD_SUBNET = 'subnet';
|
||||
const CMD_VPC = 'vpc';
|
||||
|
||||
const ipAddressParser = {
|
||||
parse: function(input: string) : ParsedIpObject[] | SubnetCommand | ParsingError | null {
|
||||
parse: function(input: string) : ParsingResult {
|
||||
|
||||
const result = this.parseCommand(input);
|
||||
|
||||
@@ -27,12 +35,12 @@ const ipAddressParser = {
|
||||
}
|
||||
|
||||
if(result.command != null) {
|
||||
const result = this.createSubnetDefinition(parsedObjects as ParsedIpObject[]);
|
||||
const cmd =
|
||||
result.command == CMD_SUBNET
|
||||
? this.createSubnetDefinition(parsedObjects as ParsedIpObject[])
|
||||
: this.createVpcDefinition(parsedObjects as ParsedIpObject[]);
|
||||
|
||||
if(result instanceof ParsingError)
|
||||
return result;
|
||||
|
||||
return result;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
return parsedObjects as ParsedIpObject[];
|
||||
@@ -40,9 +48,14 @@ const ipAddressParser = {
|
||||
|
||||
parseCommand(input : string) : { command: null | string, nextInput: string } {
|
||||
|
||||
const command = 'subnet';
|
||||
if(input.startsWith(command))
|
||||
return { command, nextInput: input.substring(command.length)}
|
||||
|
||||
|
||||
if(input.startsWith(CMD_SUBNET))
|
||||
return { command: CMD_SUBNET, nextInput: input.substring(CMD_SUBNET.length)};
|
||||
|
||||
if(input.startsWith(CMD_VPC)) {
|
||||
return {command: CMD_VPC, nextInput: input.substring(CMD_VPC.length)};
|
||||
}
|
||||
|
||||
return { command: null, nextInput: input };
|
||||
},
|
||||
@@ -101,6 +114,19 @@ const ipAddressParser = {
|
||||
}
|
||||
|
||||
return new ParsingError("Network definition requires subnet mask");
|
||||
},
|
||||
|
||||
createVpcDefinition(items: ParsedIpObject[]) : VpcCommand | ParsingError {
|
||||
|
||||
if(items.length != 1)
|
||||
return new ParsingError("Incorrect VPC definition");
|
||||
|
||||
const first = items[0];
|
||||
if(first instanceof IpAddressWithSubnetMask) {
|
||||
return new VpcCommand(first);
|
||||
}
|
||||
|
||||
return new ParsingError("VPC definition requires subnet mask");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,5 +137,4 @@ export class ParsingError {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default ipAddressParser;
|
||||
@@ -1,4 +1,6 @@
|
||||
import {emBin} from "../core/formatter";
|
||||
import { numberParser } from "../expression/numberParser";
|
||||
import { getAddressSpaceSize } from "./subnet-utils";
|
||||
|
||||
export type OctetNumber = 1 | 2 | 3 | 4;
|
||||
export type NetworkClass = 'a' | 'b' | 'c' | 'd' | 'e';
|
||||
@@ -12,6 +14,11 @@ export class IpAddressWithSubnetMask {
|
||||
this.maskBits = maskBits;
|
||||
}
|
||||
|
||||
getAdressSpaceSize(): number {
|
||||
const spaceLengthInBits = 32 - this.maskBits;
|
||||
return getAddressSpaceSize(spaceLengthInBits);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this.ipAddress.toString()}/${this.maskBits}`;
|
||||
}
|
||||
@@ -35,9 +42,12 @@ export class IpAddress {
|
||||
return `${this.firstByte}.${this.secondByte}.${this.thirdByte}.${this.fourthByte}`;
|
||||
}
|
||||
|
||||
toBinaryString() {
|
||||
toBinaryString(skipDots?: boolean) {
|
||||
|
||||
return `${emBin(this.firstByte)}).${emBin(this.secondByte)}.${emBin(this.thirdByte)}.${emBin(this.fourthByte)}`;
|
||||
if(!skipDots)
|
||||
return `${emBin(this.firstByte)}.${emBin(this.secondByte)}.${emBin(this.thirdByte)}.${emBin(this.fourthByte)}`;
|
||||
else
|
||||
return `${emBin(this.firstByte)}${emBin(this.secondByte)}${emBin(this.thirdByte)}${emBin(this.fourthByte)}`;
|
||||
}
|
||||
|
||||
clone(): IpAddress {
|
||||
@@ -63,18 +73,25 @@ export class IpAddress {
|
||||
}
|
||||
|
||||
export class SubnetCommand {
|
||||
input: IpAddressWithSubnetMask;
|
||||
input: IpAddressWithSubnetMask; // TODO: rename to cidr
|
||||
constructor(definition: IpAddressWithSubnetMask) {
|
||||
this.input = definition;
|
||||
}
|
||||
|
||||
getAdressSpaceSize(): number {
|
||||
const spaceLengthInBits = 32 - this.input.maskBits;
|
||||
return Math.pow(2, spaceLengthInBits) - 2; // 0 - network address, 1 - multicast address
|
||||
}
|
||||
|
||||
|
||||
toString() {
|
||||
return this.input.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export class VpcCommand {
|
||||
cidr: IpAddressWithSubnetMask;
|
||||
subnetBits: number;
|
||||
constructor(cidr: IpAddressWithSubnetMask) {
|
||||
this.cidr = cidr;
|
||||
this.subnetBits = 3;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.cidr.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import { CmdShell, CommandInput, CommandOptions } from '../shell/cmd';
|
||||
import ErrorResultView from '../shell/components/ErrorResultView';
|
||||
import IpAddressView from './components/IpAddressView';
|
||||
import ipAddressParser, {ParsingError, ParsedIpObject} from './ip-parser';
|
||||
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from "./models";
|
||||
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand, VpcCommand } from "./models";
|
||||
import log from 'loglevel';
|
||||
import SubnetView from './components/SubnetView';
|
||||
import { createSubnetMaskIp } from './subnet-utils';
|
||||
import {sendAnalyticsEvent} from '../shell/analytics';
|
||||
import TextResultView from '../shell/components/TextResultView';
|
||||
import VpcView from './components/VpcView';
|
||||
|
||||
const networkingAppModule = {
|
||||
setup: function(appState: AppState, cmd: CmdShell) {
|
||||
@@ -33,6 +35,12 @@ const networkingAppModule = {
|
||||
return;
|
||||
}
|
||||
|
||||
if(result instanceof VpcCommand) {
|
||||
appState.addCommandResult(c.input, <VpcView vpc={result} />);
|
||||
trackCommand('VpcCommand', c.options);
|
||||
return;
|
||||
}
|
||||
|
||||
const ipAddresses : IpAddress[] = [];
|
||||
|
||||
(result as ParsedIpObject[]).forEach(r => {
|
||||
@@ -43,7 +51,7 @@ const networkingAppModule = {
|
||||
}
|
||||
else if(r instanceof IpAddress) {
|
||||
ipAddresses.push(r);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
trackCommand("IpAddressesInput", c.options);
|
||||
|
||||
@@ -29,6 +29,11 @@ function getBroadCastAddress(ipm: IpAddressWithSubnetMask) : IpAddress {
|
||||
return flipSubnetMaskBits(ipm, flipBitsToOne, 255);
|
||||
}
|
||||
|
||||
function getAddressSpaceSize(maskSize: number) {
|
||||
const spaceLengthInBits = 32 - maskSize;
|
||||
return Math.pow(2, spaceLengthInBits) - 2; // 0 - network address, 1 - multicast address
|
||||
}
|
||||
|
||||
function flipSubnetMaskBits(ipm: IpAddressWithSubnetMask, flipper : FlipFunction, fullByte: number) {
|
||||
// Cannot treat ip address as a single number operation because 244 << 24 results in a negative number in JS
|
||||
const flip = (maskBits: number, byte: number) => flipper(byte, 8 - maskBits);
|
||||
@@ -80,4 +85,4 @@ function getNetworkClass (ipAddress: IpAddress) : NetworkClass {
|
||||
|
||||
type FlipFunction = (byte: number, numberOfBits: number) => number;
|
||||
|
||||
export {createSubnetMaskIp, getBroadCastAddress, getNetworkAddress, getNetworkClass};
|
||||
export {createSubnetMaskIp, getBroadCastAddress, getNetworkAddress, getNetworkClass, getAddressSpaceSize};
|
||||
Reference in New Issue
Block a user