Finilize VPC View support

This commit is contained in:
Borys_Levytskyi
2021-04-24 13:10:41 +03:00
parent 4e1b35a55b
commit 18272c712e
12 changed files with 157 additions and 99 deletions

View File

@@ -25,8 +25,8 @@ code { font-size: 1.2em; font-weight: bold; }
.result .content { padding-left: 10px} .result .content { padding-left: 10px}
.result .cur { margin-right: 5px; } .result .cur { margin-right: 5px; }
.hashLink { text-decoration: none; margin-left: 5px; visibility: hidden } .hashLink { text-decoration: none; margin-left: 5px; visibility: hidden; margin-right: 0; padding: 0; text-decoration: none; }
.hashLink:hover { text-decoration: underline; margin-left: 5px; } .hashLink:hover { text-decoration: underline; margin-left: 5px; background: none; }
.result:hover .hashLink { visibility: visible } .result:hover .hashLink { visibility: visible }
.expression .label { font-weight: bold; padding-right: 5px; text-align: right; } .expression .label { font-weight: bold; padding-right: 5px; text-align: right; }
@@ -48,7 +48,7 @@ code { font-size: 1.2em; font-weight: bold; }
.help ul { list-style-type: none; margin: 0; padding: 0; } .help ul { list-style-type: none; margin: 0; padding: 0; }
.help p { margin-top: 0 } .help p { margin-top: 0 }
.indicator { padding: 2px 5px; font-family: monospace; font-size: 1.3em; background: transparent; border: none; cursor: pointer } .indicator { padding: 0px 5px; background: transparent; border: none; cursor: pointer; vertical-align: middle; }
.error { color: maroon; } .error { color: maroon; }
.soft { opacity: 0.7 } .soft { opacity: 0.7 }
@@ -68,13 +68,13 @@ code { font-size: 1.2em; font-weight: bold; }
.light .on { color: #121212; } .light .on { color: #121212; }
.light .prefix { color: #888} .light .prefix { color: #888}
.light .other { color: #bbb } .light .other { color: #bbb }
.light .hashLink, .light .hashLink:visited { color: #ddd } .light .hashLink, .light .hashLink:visited { color: #ddd; }
.light .hashLink:hover { color: #888 } .light .hashLink:hover { color: #888 }
.light ul.top-links li:hover { background: #ddd } .light ul.top-links li:hover { background: #ddd }
.light .error { color: #da586d } .light .error { color: #da586d }
.light button { color: black} .light button.btn { color: black}
.light button:hover { background: #ddd} .light button.btn:hover { background: #ddd}
.light button:disabled { color: #888; background-color: inherit; } .light button.btn:disabled { color: #888; background-color: inherit; }
/* Dark */ /* Dark */
.dark { background: #121212; color: white;} .dark { background: #121212; color: white;}
@@ -90,9 +90,9 @@ code { font-size: 1.2em; font-weight: bold; }
.dark .hashLink:hover { color: #999 } .dark .hashLink:hover { color: #999 }
.dark ul.top-links li:hover { background: #333 } .dark ul.top-links li:hover { background: #333 }
.dark .error { color: #da586d} .dark .error { color: #da586d}
.dark button { color: white} .dark button.btn { color: white}
.dark button:hover { background: #333} .dark button.btn:hover { background: #333}
.dark button:disabled { color: #999; background-color: inherit; } .dark button.btn:disabled { color: #999; background-color: inherit; }
/* /*
Midnight Theme Midnight Theme
@@ -113,9 +113,9 @@ code { font-size: 1.2em; font-weight: bold; }
.midnight ul.top-links li:hover { background: #132537 } .midnight ul.top-links li:hover { background: #132537 }
.midnight .error { color:#da586d} .midnight .error { color:#da586d}
.midnight .changelog .item-new .date { font-weight: bold } .midnight .changelog .item-new .date { font-weight: bold }
.midnight button { color: white} .midnight button.btn { color: white}
.midnight button:hover { background: #132537} .midnight button.btn:hover { background: #132537}
.midnight button:disabled { color: #85a0ad; background-color: inherit; } .midnight button.btn:disabled { color: #85a0ad; background-color: inherit; }
button { button {
border: none; border: none;

View File

@@ -41,9 +41,9 @@ function SubnetView(props : {subnet : SubnetCommand}) {
</td> </td>
<td data-test-name="decimal"> <td data-test-name="decimal">
<button onClick={decrementMask} disabled={subnet.cidr.maskBits === 0} title="Decrease mask size">-</button> <button className="btn" onClick={decrementMask} disabled={subnet.cidr.maskBits === 0} title="Decrease mask size">-</button>
<span>{subnet.cidr.maskBits}</span> <span>{subnet.cidr.maskBits}</span>
<button onClick={incrementMask} disabled={subnet.cidr.maskBits === 32} title="Increase mask size">+</button> <button className="btn"onClick={incrementMask} disabled={subnet.cidr.maskBits === 32} title="Increase mask size">+</button>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -32,7 +32,7 @@
} }
.vpc-view .address-space { .vpc-view .address-space {
font-size: 1.5em; font-size: 1.2em;
vertical-align: middle; vertical-align: middle;
} }

View File

@@ -1,16 +1,18 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import BinaryStringView from '../../core/components/BinaryString'; import BinaryStringView from '../../core/components/BinaryString';
import './VpcView.css'; import './VpcView.css';
import { getNetworkAddress, getBroadCastAddress, createSubnetMaskIp, getAddressSpaceSize } from '../subnet-utils'; import { getNetworkAddress, getAddressSpaceSize } from '../subnet-utils';
import { chunkifyString } from '../../core/utils';
import IpAddressBinaryString from './IpAddressBinaryString'; import IpAddressBinaryString from './IpAddressBinaryString';
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand, VpcCommand } from '../models'; import { IpAddress, IpAddressWithSubnetMask, VpcCommand } from '../models';
import formatter, { padLeft } from '../../core/formatter'; import formatter from '../../core/formatter';
import Toggle from '../../shell/components/Toggle';
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const MAX_NON_HOSTS_BITS = 30; // leave two bits for hosts min const MAX_NON_HOSTS_BITS = 30; // leave two bits for hosts min
function SubnetView(props : {vpc : VpcCommand}) { function SubnetView(props: { vpc: VpcCommand }) {
const [vpc, setVpc] = useState(VpcModel.create(props.vpc)); const [vpc, setVpc] = useState(VpcModel.create(props.vpc));
@@ -19,93 +21,107 @@ function SubnetView(props : {vpc : VpcCommand}) {
const hostsPerSubnet = getAddressSpaceSize(subnetMaskSize); const hostsPerSubnet = getAddressSpaceSize(subnetMaskSize);
const networkAddress = getNetworkAddress(vpc.cidr); const networkAddress = getNetworkAddress(vpc.cidr);
const decrSubnet = () => setVpc(vpc.changeSubnetBits(vpc.subnetBits-1)); const decrSubnet = () => setVpc(vpc.changeSubnetBits(vpc.subnetBits - 1));
const incrSubnet = () => 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 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))); const decrVpc = () => setVpc(vpc.changeVpcCidr(new IpAddressWithSubnetMask(vpc.cidr.ipAddress, vpc.cidr.maskBits - 1)));
const split = formatter.splitByMasks(networkAddress.toBinaryString(), vpc.cidr.maskBits, subnetMaskSize); const split = formatter.splitByMasks(networkAddress.toBinaryString(), vpc.cidr.maskBits, subnetMaskSize);
return <React.Fragment> return <React.Fragment>
<div className="expression vpc-view"> <div className="expression vpc-view">
<div className="address-container"> <div className="address-container">
<div> <div>
<span>VPC Network Address</span> <span>VPC Network Address</span>
</div> </div>
<BinaryStringView binaryString={split.vpc} disableHighlight={true} className="address-space soft" /> <div>
<BinaryStringView binaryString={split.subnet} disableHighlight={true} className="address-space subnet-part"/> <BinaryStringView binaryString={split.vpc} disableHighlight={true} className="address-space soft" />
<BinaryStringView binaryString={split.hosts} disableHighlight={true} className="address-space host-part" /> <BinaryStringView binaryString={split.subnet} disableHighlight={true} className="address-space subnet-part" />
<span className="address-space decimal-part">{networkAddress.toString()}</span> <BinaryStringView binaryString={split.hosts} disableHighlight={true} className="address-space host-part" />
<span className="address-space decimal-part">{networkAddress.toString()}</span>
<Toggle text="[i]" isOn={vpc.showLegend} onClick={() => setVpc(vpc.toggleLegend())} title="Show/Hide Color Legend">
<FontAwesomeIcon className="icon" icon={faQuestionCircle} size="sm" />
</Toggle>
</div>
<div style={{"display" : vpc.showLegend ? '' : 'none'}}>
<p>
Color Legend
</p>
<span className="address-space soft">000</span> - VPC address bits <br/>
<span className="address-space subnet-part">000</span> - Bits dedicated for subnets address<br/>
<span className="address-space host-part">000</span> - Bits dedicated to host addresses inside each subnet
</div>
</div> </div>
<table className="vpc-details"> <table className="vpc-details">
<tr> <tbody>
<td className="soft"> <tr>
VPC CIDR Mask: <td className="soft">
VPC CIDR Mask:
</td> </td>
<td> <td>
<button onClick={decrVpc} disabled={vpc.cidr.maskBits <= 1} title="Decrease vpc address bits">-</button> <button className="btn" onClick={decrVpc} disabled={vpc.cidr.maskBits <= 1} title="Decrease vpc address bits">-</button>
/{vpc.cidr.maskBits} /{vpc.cidr.maskBits}
<button onClick={incrVpc} disabled={subnetMaskSize >= MAX_NON_HOSTS_BITS} title="Increse vpc address bits">+</button> <button className="btn" onClick={incrVpc} disabled={subnetMaskSize >= MAX_NON_HOSTS_BITS} title="Increse vpc address bits">+</button>
</td>
</tr>
<tr>
<td className="soft">
Subnet CIDR Mask:
</td> </td>
</tr> <td>
<tr> <button className="btn" onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Increase subnet bits">-</button>
<td className="soft">
Subnet CIDR Mask:
</td>
<td>
<button onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Increase subnet bits">-</button>
/{subnetMaskSize} /{subnetMaskSize}
<button onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button> <button className="btn" onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button>
</td>
</tr>
<tr>
<td className="soft">
Max Subnets in VPC:
</td> </td>
</tr> <td>
<tr> <button className="btn" onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Decrease subnet bits">-</button>
<td className="soft"> {maxSubnets}
Max Subnets in VPC: <button className="btn" onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button>
</td>
</tr>
<tr>
<td className="soft">
Max Hosts in VPC:
</td> </td>
<td> <td>
<button onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Decrease subnet bits">-</button> {maxSubnets * hostsPerSubnet}
{maxSubnets} </td>
<button onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button> </tr>
<tr>
<td className="soft">
Hosts Per Subnet:
</td> </td>
</tr> <td>
<tr> {hostsPerSubnet}
<td className="soft"> </td>
Max Hosts in VPC: </tr>
</td> </tbody>
<td> </table>
{maxSubnets*hostsPerSubnet}
</td>
</tr>
<tr>
<td className="soft">
Hosts Per Subnet:
</td>
<td>
{hostsPerSubnet}
</td>
</tr>
</table>
</div> </div>
</React.Fragment>; </React.Fragment>;
} }
function Indicator2(props: { ip: IpAddress, descr: string}) { function Indicator2(props: { ip: IpAddress, descr: string }) {
const {ip, descr} = props; const { ip, descr } = props;
return <tr> return <tr>
<td className="soft" data-test-name="label">{descr}</td> <td className="soft" data-test-name="label">{descr}</td>
<td data-test-name="decimal" className="ip-address-col"> <td data-test-name="decimal" className="ip-address-col">
{ip.toString()} {ip.toString()}
</td> </td>
<td data-test-name="bin"> <td data-test-name="bin">
<IpAddressBinaryString ip={ip} /> <IpAddressBinaryString ip={ip} />
</td> </td>
</tr>; </tr>;
} }
export default SubnetView; export default SubnetView;
@@ -114,17 +130,23 @@ class VpcModel {
cidr: IpAddressWithSubnetMask; cidr: IpAddressWithSubnetMask;
subnetBits: number; subnetBits: number;
subnetNum: number; subnetNum: number;
showLegend: boolean;
constructor(cidr: IpAddressWithSubnetMask, subnetBits: number) { constructor(cidr: IpAddressWithSubnetMask, subnetBits: number) {
this.cidr = cidr; this.cidr = cidr;
this.subnetBits = subnetBits; this.subnetBits = subnetBits;
this.subnetNum = 0; this.subnetNum = 0;
this.showLegend = false;
} }
static create(vpc: VpcCommand) { static create(vpc: VpcCommand) {
return new VpcModel(vpc.cidr, vpc.subnetBits); return new VpcModel(vpc.cidr, vpc.subnetBits);
} }
clone() : VpcModel {
return Object.assign(new VpcModel(this.cidr, this.subnetBits), this);
}
changeSubnetBits(n: number) { changeSubnetBits(n: number) {
return new VpcModel(this.cidr, n); return new VpcModel(this.cidr, n);
} }
@@ -132,4 +154,10 @@ class VpcModel {
changeVpcCidr(newCidr: IpAddressWithSubnetMask) { changeVpcCidr(newCidr: IpAddressWithSubnetMask) {
return new VpcModel(newCidr, this.subnetBits); return new VpcModel(newCidr, this.subnetBits);
} }
toggleLegend() {
var n = new VpcModel(this.cidr, this.subnetBits);
n.showLegend = !this.showLegend;
return n;
}
} }

View File

@@ -14,8 +14,7 @@ export class IpAddressWithSubnetMask {
} }
getAdressSpaceSize(): number { getAdressSpaceSize(): number {
const spaceLengthInBits = 32 - this.maskBits; return getAddressSpaceSize(this.maskBits);
return getAddressSpaceSize(spaceLengthInBits);
} }
toString() { toString() {

View File

@@ -1,6 +1,6 @@
import log from 'loglevel'; import log from 'loglevel';
const APP_VERSION = 6; const APP_VERSION = 7;
export type PersistedAppData = { export type PersistedAppData = {
emphasizeBytes: boolean; emphasizeBytes: boolean;
@@ -59,6 +59,14 @@ export default class AppState {
this.triggerChanged(); this.triggerChanged();
} }
removeResult(index: number) {
if(index < 0 || index >= this.commandResults.length)
return;
this.commandResults.splice(index, 1);
this.triggerChanged();
}
toggleEmphasizeBytes() { toggleEmphasizeBytes() {
this.emphasizeBytes = !this.emphasizeBytes; this.emphasizeBytes = !this.emphasizeBytes;
this.triggerChanged(); this.triggerChanged();

View File

@@ -38,7 +38,7 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
getResultViews() : JSX.Element[] { getResultViews() : JSX.Element[] {
var results = this.state.commandResults.map((r, i) => var results = this.state.commandResults.map((r, i) =>
<DisplayResultView key={r.key} input={r.input} inputHash={hash.encodeHash(r.input)} appState={this.props.appState}> <DisplayResultView resultIndex={i} resultKey={r.key} key={r.key} input={r.input} inputHash={hash.encodeHash(r.input)} appState={this.props.appState}>
{r.view} {r.view}
</DisplayResultView>); </DisplayResultView>);
return results; return results;

View File

@@ -23,7 +23,7 @@ function DebugIndicators(props: {appState: AppState}) {
return null; return null;
return <div className="debug-indicators"> return <div className="debug-indicators">
{list.map(i => <span title={i}>[{i.substring(0,1)}]&nbsp;</span>)} {list.map(i => <span title={i} key={i}>[{i.substring(0,1)}]&nbsp;</span>)}
</div> </div>
} }

View File

@@ -1,3 +1,5 @@
import { faTrashAlt, faHashtag } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React from 'react'; import React from 'react';
import AppState from '../AppState'; import AppState from '../AppState';
@@ -6,23 +8,32 @@ type DisplayResultProps = {
appState: AppState, appState: AppState,
inputHash: string, inputHash: string,
input: string, input: string,
key: number, resultKey: number,
resultIndex: number,
onRemove?: (i: number) => void; onRemove?: (i: number) => void;
} }
export default class DisplayResultView extends React.Component<DisplayResultProps> { const DisplayResultView: React.FunctionComponent<DisplayResultProps> = (props) => {
render() {
const resultKey : number = props.resultKey;
const appState = props.appState;
return <div className="result"> return <div className="result">
<div className="input mono"> <div className="input mono">
<span className="cur"> <span className="cur">
&gt;</span>{this.props.input} &gt;</span>{props.input}
<a className="hashLink" title="Link for this expression" href={window.location.pathname + '#' + this.props.inputHash}>#</a> <a className="hashLink" title="Link for this expression" href={window.location.pathname + '#' + props.inputHash}>
<FontAwesomeIcon className="icon" icon={faHashtag} size="xs" />
</a>
<button className="hashLink" title="Remove this result" onClick={() => appState.removeResult(props.resultIndex)}>
<FontAwesomeIcon className="icon" icon={faTrashAlt} size="xs" />
</button>
</div> </div>
<div className="content"> <div className="content">
{this.props.children} {props.children}
</div> </div>
</div>; </div>;
}
} }
export default DisplayResultView;

View File

@@ -20,6 +20,7 @@ function HelpResultView() {
<li><code><CommandLink text="127.0.0.1" /></code> enter single or multiple ip addresses (separated by space) to see their binary represenation</li> <li><code><CommandLink text="127.0.0.1" /></code> enter single or multiple ip addresses (separated by space) to see their binary represenation</li>
<li><code><CommandLink text="192.168.0.1/8" /></code> subnet mask notiations are support as well</li> <li><code><CommandLink text="192.168.0.1/8" /></code> subnet mask notiations are support as well</li>
<li><code><CommandLink text="subnet 192.168.24.1/14" /></code> display information about subnet (network address, broadcast address, etc.)</li> <li><code><CommandLink text="subnet 192.168.24.1/14" /></code> display information about subnet (network address, broadcast address, etc.)</li>
<li><code><CommandLink text="vpc 192.168.24.1/24" /></code> see how VPC network address bits are divided between VPC address, Subnets and Hosts</li>
</ul> </ul>
</div> </div>
<div className="section"> <div className="section">

View File

@@ -2,18 +2,21 @@
import React from "react"; import React from "react";
export type ToggleProps = { export type ToggleProps = {
text: string, text?: string,
isOn: boolean, isOn: boolean,
title: string, title: string,
elementId?: string elementId?: string
onClick: () => void onClick: () => void
}; };
function Toggle(props: ToggleProps) { const Toggle: React.FunctionComponent<ToggleProps> = (props) => {
return <span id={props.elementId} return <span id={props.elementId}
className={"indicator " + getIndicator(props.isOn)} className={"indicator " + getIndicator(props.isOn)}
title={props.title} title={props.title}
onClick={() => props.onClick()}>{props.text}</span> onClick={() => props.onClick()}>
{ !props.children ? props.text : props.children }
</span>
} }
function getIndicator(value : boolean) { function getIndicator(value : boolean) {

View File

@@ -7,6 +7,14 @@ function WhatsnewResultView() {
return <div className="changelog"> return <div className="changelog">
<h3>Changelog</h3> <h3>Changelog</h3>
<div className="item item-new"> <div className="item item-new">
<p><span className="soft date">Jul 24th, 2021</span> <br/>
<ul>
<li>Added support of <code>vpc</code> command to see hpw VPC network address is divided bettwen VPC, Subnets and Hosts. Try it out: <CommandLink text="vpc 192.168.24.1/24" /></li>
<li>Added ability to remove individual results</li>
</ul>
</p>
</div>
<div className="item">
<p><span className="soft date">Jun 16th, 2021</span> <br/> <p><span className="soft date">Jun 16th, 2021</span> <br/>
Added support of <code>subnet</code> command to display information about subnet ip adress such. Try it out: <CommandLink text="subnet 192.168.24.1/14" /> Added support of <code>subnet</code> command to display information about subnet ip adress such. Try it out: <CommandLink text="subnet 192.168.24.1/14" />
</p> </p>