mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2025-12-10 15:02:07 +01:00
Subnet command (#17)
* Started working on subnets * Basic version of the subnet command * Improved subnet command * almost done with subnets * improve positioning
This commit is contained in:
18
src/core/byte.test.ts
Normal file
18
src/core/byte.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {flipBitsToZero, flipBitsToOne} from './byte';
|
||||
|
||||
describe('byte', () => {
|
||||
it('can zero out bits', () => {
|
||||
expect(flipBitsToZero(255, 1)).toBe(254);
|
||||
expect(flipBitsToZero(212, 6)).toBe(192);
|
||||
expect(flipBitsToZero(123, 8)).toBe(0);
|
||||
expect(flipBitsToZero(23, 0)).toBe(23);
|
||||
});
|
||||
|
||||
it('can flip bits to one', () => {
|
||||
expect(flipBitsToOne(122,4)).toBe(127);
|
||||
expect(flipBitsToOne(0,8)).toBe(255);
|
||||
expect(flipBitsToOne(0,3)).toBe(7);
|
||||
expect(flipBitsToOne(0,2)).toBe(3);
|
||||
expect(flipBitsToOne(0,1)).toBe(1);
|
||||
});
|
||||
});
|
||||
26
src/core/byte.ts
Normal file
26
src/core/byte.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
function flipBitsToZero(byte: number, numberOfBits : number) : number {
|
||||
if(numberOfBits == 0)
|
||||
return byte;
|
||||
|
||||
const zerouOutMask = Math.pow(2, 8-numberOfBits)-1<<numberOfBits; // E.g. 11111000 for flipping first three bits
|
||||
const result = byte & zerouOutMask;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: continue here to implement getting broadcast address
|
||||
|
||||
function flipBitsToOne(byte : number, numberOfBits : number) : number {
|
||||
if(numberOfBits == 0) return byte;
|
||||
|
||||
const zerouOutMask = Math.pow(2, numberOfBits)-1; // E.g. 00000111 for flipping first three bits
|
||||
const result = byte | zerouOutMask;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function createSubnetMaskByte(numberOfBits: number) {
|
||||
return 255<<(8-numberOfBits)&255;;
|
||||
}
|
||||
|
||||
export {flipBitsToZero, createSubnetMaskByte, flipBitsToOne};
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
export type BinaryStringViewProps = {
|
||||
allowFlipBits: boolean;
|
||||
allowFlipBits?: boolean;
|
||||
binaryString: string;
|
||||
onFlipBit?: (input: FlipBitEventArg) => void;
|
||||
emphasizeBytes: boolean;
|
||||
emphasizeBytes?: boolean;
|
||||
className?:string
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
|
||||
const css = allowFlipBits ? ' flipable' : ''
|
||||
|
||||
return bitChars.map((c, i) => {
|
||||
var className = c == '0' ? `zero${css}` : `one${css}`;
|
||||
var className = c == '1' ? `one${css}` : `zero${css}`;
|
||||
return <span className={className} key={i} onClick={e => this.onBitClick(i, e)}>{c}</span>
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ export default {
|
||||
}
|
||||
|
||||
return sb.join('');
|
||||
},
|
||||
bin(number: number) {
|
||||
return this.formatString(number, 'bin');
|
||||
},
|
||||
emBin(number: number) {
|
||||
return this.padLeft(this.bin(number), 8, '0');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
9
src/core/utils.test.tsx
Normal file
9
src/core/utils.test.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { chunkifyString } from "./utils";
|
||||
|
||||
describe('utils', () => {
|
||||
it('chunkifyString', () => {
|
||||
expect(chunkifyString('aabbc', 2)).toMatchObject(["aa", "bb", "c"]);
|
||||
expect(chunkifyString('aabbc', 3)).toMatchObject(["aab", "bc"]);
|
||||
expect(chunkifyString('aabbc', 10)).toMatchObject(["aabbc"]);
|
||||
})
|
||||
})
|
||||
12
src/core/utils.tsx
Normal file
12
src/core/utils.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
function chunkifyString(input: string, chunkSize: number) : string[] {
|
||||
|
||||
const result : string[] = [];
|
||||
for(var i=0;i<input.length;i+=chunkSize) {
|
||||
const size = Math.min(chunkSize, input.length-i);
|
||||
result.push(input.substr(i, size));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export {chunkifyString};
|
||||
@@ -41,6 +41,7 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
.error { color: maroon; }
|
||||
|
||||
.soft { opacity: 0.7 }
|
||||
.small-text { font-size: 0.8em;}
|
||||
|
||||
#view { padding: 10px}
|
||||
|
||||
|
||||
19
src/networking/components/IpAddressBinaryString.tsx
Normal file
19
src/networking/components/IpAddressBinaryString.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import BinaryStringView from '../../core/components/BinaryString';
|
||||
import formatter from '../../core/formatter';
|
||||
import { IpAddress } from '../models';
|
||||
|
||||
function IpAddressBinaryString({ip}: {ip:IpAddress}) {
|
||||
|
||||
return <React.Fragment>
|
||||
<BinaryStringView binaryString={formatter.emBin(ip.firstByte)} />
|
||||
<span className="soft">.</span>
|
||||
<BinaryStringView binaryString={formatter.emBin(ip.secondByte)} />
|
||||
<span className="soft">.</span>
|
||||
<BinaryStringView binaryString={formatter.emBin(ip.thirdByte)} />
|
||||
<span className="soft">.</span>
|
||||
<BinaryStringView binaryString={formatter.emBin(ip.fourthByte)} />
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
export default IpAddressBinaryString;
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { IpAddress, OctetNumber, getNetworkClass } from '../ip';
|
||||
import formatter from '../../core/formatter'
|
||||
import BinaryStringView from '../../core/components/BinaryString';
|
||||
import './IpAddressView.css';
|
||||
import { IpAddress, OctetNumber } from '../models';
|
||||
|
||||
type IpAddressViewProps = {
|
||||
ipAddresses: IpAddress[]
|
||||
};
|
||||
@@ -11,13 +12,6 @@ export class IpAddressView extends React.Component<IpAddressViewProps>
|
||||
{
|
||||
|
||||
render() {
|
||||
if(this.props.ipAddresses.length === 1)
|
||||
return this.renderSingleIp(this.props.ipAddresses[0]);
|
||||
|
||||
return this.renderMultipleIps();
|
||||
}
|
||||
|
||||
renderMultipleIps() {
|
||||
return <table className="expression">
|
||||
<tbody>
|
||||
{this.props.ipAddresses.map((ip, i) => <tr key={i}>
|
||||
@@ -30,33 +24,7 @@ export class IpAddressView extends React.Component<IpAddressViewProps>
|
||||
</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
renderSingleIp(ip: IpAddress) {
|
||||
return <table className="expression">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='first-decimal'>{ip.firstByte}</th>
|
||||
<th className='second-decimal'>{ip.secondByte}</th>
|
||||
<th className='third-decimal'>{ip.thirdByte}</th>
|
||||
<th className='fourth-decimal'>{ip.fourthByte}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className='first-bin'>{this.bin(ip.firstByte, 1, ip)}</td>
|
||||
<td className='second-bin'>{this.bin(ip.secondByte, 2, ip)}</td>
|
||||
<td className='third-bin'>{this.bin(ip.thirdByte, 3, ip)}</td>
|
||||
<td className='fourth-bin'>{this.bin(ip.fourthByte, 4, ip)}</td>
|
||||
</tr>
|
||||
{/* <tr>
|
||||
<td colSpan={2} className="ip-address-info">
|
||||
<a href="https://www.wikiwand.com/en/Classful_network" target="_blank">Network Class: {getNetworkClass(ip).toUpperCase()}</a>
|
||||
</td>
|
||||
</tr> */}
|
||||
</tbody>
|
||||
</table>;
|
||||
</table>;
|
||||
}
|
||||
|
||||
bin(value: number, octetNumber: OctetNumber, ip: IpAddress) {
|
||||
|
||||
16
src/networking/components/SubnetView.css
Normal file
16
src/networking/components/SubnetView.css
Normal file
@@ -0,0 +1,16 @@
|
||||
.subnet-view .description {
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.subnet-view td {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.subnet-view {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.subnet-view .part {
|
||||
border-bottom: solid 1px;
|
||||
}
|
||||
60
src/networking/components/SubnetView.tsx
Normal file
60
src/networking/components/SubnetView.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import BinaryStringView from '../../core/components/BinaryString';
|
||||
import './SubnetView.css';
|
||||
import { getNetworkAddress, getBroadCastAddress, createSubnetMaskIp } from '../subnet-utils';
|
||||
import { chunkifyString } from '../../core/utils';
|
||||
import IpAddressBinaryString from './IpAddressBinaryString';
|
||||
import { IpAddress, SubnetCommand } from '../models';
|
||||
|
||||
function SubnetView({subnet} : {subnet : SubnetCommand}) {
|
||||
|
||||
return <React.Fragment>
|
||||
<table className="expression subnet-view">
|
||||
<tbody>
|
||||
<SubnetRow ip={subnet.input.ipAddress} descr="Address"/>
|
||||
<SubnetRow ip={getNetworkAddress(subnet.input)} descr="Network"/>
|
||||
<SubnetRow ip={createSubnetMaskIp(subnet.input)} descr="Net Mask"/>
|
||||
<SubnetRow ip={getBroadCastAddress(subnet.input)} descr="Broadcast"/>
|
||||
<tr>
|
||||
<td className="description soft">
|
||||
<span>Mask Length</span>
|
||||
</td>
|
||||
<td>
|
||||
{subnet.input.maskBits}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="description soft">
|
||||
<span>Network Size</span>
|
||||
</td>
|
||||
<td>
|
||||
{subnet.getAdressSpaceSize()}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
function SubnetRow(props: { ip: IpAddress, descr: string}) {
|
||||
|
||||
const {ip, descr} = props;
|
||||
|
||||
return <tr>
|
||||
<td className="description soft">{descr}</td>
|
||||
<td className="ip">
|
||||
{ip.toString()}
|
||||
</td>
|
||||
<td className="class-part">
|
||||
<IpAddressBinaryString ip={ip} />
|
||||
</td>
|
||||
</tr>;
|
||||
|
||||
function addDots(bin: string) {
|
||||
return chunkifyString(bin, 8).map((s, i) => <BinaryStringView binaryString={s} key={i} />)
|
||||
}
|
||||
}
|
||||
|
||||
export default SubnetView;
|
||||
65
src/networking/ip-parser.test.ts
Normal file
65
src/networking/ip-parser.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import ipAddressParser, { ParsedIpObject, ParsingError } from './ip-parser';
|
||||
import { IpAddressWithSubnetMask, IpAddress, SubnetCommand } from "./models";
|
||||
|
||||
|
||||
describe('parser tests', () => {
|
||||
it('can parse correct ip address', () => {
|
||||
const actual = ipAddressParser.parse('127.1.2.3');
|
||||
expect(actual).not.toBe(null);
|
||||
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||
|
||||
const obj = (actual as ParsedIpObject[])[0];
|
||||
const expected = new IpAddress(127, 1, 2, 3);
|
||||
expect(obj).not.toBe(null);
|
||||
expect(obj.toString()).toBe(expected.toString());
|
||||
});
|
||||
|
||||
it('cannot parse incorrect ip address', () => {
|
||||
expect(ipAddressParser.parse('abc')).toBe(null);
|
||||
expect(ipAddressParser.parse('')).toBe(null);
|
||||
});
|
||||
|
||||
it('should parse invalid ip address', () => {
|
||||
expect(ipAddressParser.parse('256.0.0.0')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.256.0.0')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.256.0')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.0.256')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.0.255 asd')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.0.255/99')).toBeInstanceOf(ParsingError);
|
||||
});
|
||||
|
||||
it('parses correct ip and subnet mask', () => {
|
||||
const actual = ipAddressParser.parse('127.0.0.1/24');
|
||||
|
||||
expect(actual).not.toBe(null);
|
||||
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||
|
||||
const obj = (actual as ParsedIpObject[])[0];
|
||||
expect(obj).toBeInstanceOf(IpAddressWithSubnetMask);
|
||||
expect(obj!.toString()).toBe('127.0.0.1/24');
|
||||
expect((obj as IpAddressWithSubnetMask).maskBits).toBe(24);
|
||||
});
|
||||
|
||||
it('parses list of ip addresses', () => {
|
||||
const actual = ipAddressParser.parse('127.0.0.1/24 255.255.1.1');
|
||||
|
||||
expect(actual).not.toBe(null);
|
||||
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||
|
||||
const first = (actual as ParsedIpObject[])[0];
|
||||
expect(first).toBeInstanceOf(IpAddressWithSubnetMask);
|
||||
expect(first!.toString()).toBe('127.0.0.1/24');
|
||||
expect((first as IpAddressWithSubnetMask).maskBits).toBe(24);
|
||||
|
||||
const second = (actual as ParsedIpObject[])[1];
|
||||
expect(second).toBeInstanceOf(IpAddress);
|
||||
expect(second!.toString()).toBe('255.255.1.1');
|
||||
});
|
||||
|
||||
it('parses subnet command', () => {
|
||||
const actual = ipAddressParser.parse('subnet 192.168.1.1/23');
|
||||
expect(actual).toBeInstanceOf(SubnetCommand);
|
||||
const subnet = actual as SubnetCommand;
|
||||
expect(subnet.toString()).toBe('192.168.1.1/23');
|
||||
});
|
||||
});
|
||||
115
src/networking/ip-parser.ts
Normal file
115
src/networking/ip-parser.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from './models';
|
||||
|
||||
export type ParsedIpObject = IpAddress | IpAddressWithSubnetMask;
|
||||
|
||||
|
||||
const ipAddressParser = {
|
||||
parse: function(input: string) : ParsedIpObject[] | SubnetCommand | ParsingError | null {
|
||||
|
||||
const result = this.parseCommand(input);
|
||||
|
||||
const matches = this.getMaches(result.nextInput);
|
||||
const correctInputs = matches.filter(m => m.matches != null);
|
||||
const incorrectInputs = matches.filter(m => m.matches == null);
|
||||
|
||||
if(correctInputs.length == 0)
|
||||
return null;
|
||||
|
||||
if(incorrectInputs.length > 0) {
|
||||
return new ParsingError(`Value(s) ${incorrectInputs.map(v => v.input).join(',')} was not recognized as valid ip address or ip address with a subnet mask`);
|
||||
}
|
||||
|
||||
const parsedObjects = matches.map(m => this.parseSingle(m.matches!, m.input));
|
||||
const parsingErrors = parsedObjects.filter(p => p instanceof ParsingError);
|
||||
|
||||
if(parsingErrors.length > 0) {
|
||||
return parsingErrors[0] as ParsingError;
|
||||
}
|
||||
|
||||
if(result.command != null) {
|
||||
const result = this.createSubnetDefinition(parsedObjects as ParsedIpObject[]);
|
||||
|
||||
if(result instanceof ParsingError)
|
||||
return result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return parsedObjects as ParsedIpObject[];
|
||||
},
|
||||
|
||||
parseCommand(input : string) : { command: null | string, nextInput: string } {
|
||||
|
||||
const command = 'subnet';
|
||||
if(input.startsWith(command))
|
||||
return { command, nextInput: input.substring(command.length)}
|
||||
|
||||
return { command: null, nextInput: input };
|
||||
},
|
||||
|
||||
getMaches(input : string) : { matches: RegExpExecArray | null, input: string }[] {
|
||||
|
||||
return input.
|
||||
replace(/[\t\s]+/g, ' ')
|
||||
.split(' ')
|
||||
.filter(s => s.length>0)
|
||||
.map(s => {
|
||||
const ipV4Regex = /^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})(\/\d+)?$/;
|
||||
const matches = ipV4Regex.exec(s);
|
||||
|
||||
if(matches == null || matches.length === 0)
|
||||
return {matches: null, input: s};
|
||||
|
||||
return {matches, input: s};
|
||||
});
|
||||
},
|
||||
|
||||
parseSingle(matches : RegExpExecArray, input: string) : ParsedIpObject | ParsingError {
|
||||
const invalid = (n: number) => n < 0 || n > 255;
|
||||
|
||||
const first = parseInt(matches[1]);
|
||||
const second = parseInt(matches[2]);
|
||||
const third = parseInt(matches[3]);
|
||||
const fourth = parseInt(matches[4]);
|
||||
|
||||
if(invalid(first) || invalid(second) || invalid(third) || invalid(fourth))
|
||||
return new ParsingError(`${input} value doesn't fall within the valid range of the IP address space`);
|
||||
|
||||
const ipAddress = new IpAddress(first, second, third, fourth);
|
||||
|
||||
if(matches[5]) {
|
||||
const maskPart = matches[5].substr(1);
|
||||
const maskBits = parseInt(maskPart);
|
||||
|
||||
if(maskBits > 32) {
|
||||
return new ParsingError(`Subnet mask value in ${input} is out of range`);
|
||||
}
|
||||
|
||||
return new IpAddressWithSubnetMask(ipAddress, maskBits);
|
||||
}
|
||||
|
||||
return ipAddress;
|
||||
},
|
||||
|
||||
createSubnetDefinition(items: ParsedIpObject[]) : SubnetCommand | ParsingError {
|
||||
if(items.length != 1)
|
||||
return new ParsingError("Incorrect network definition");
|
||||
|
||||
const first = items[0];
|
||||
if(first instanceof IpAddressWithSubnetMask) {
|
||||
return new SubnetCommand(first);
|
||||
}
|
||||
|
||||
return new ParsingError("Network definition requires subnet mask");
|
||||
}
|
||||
}
|
||||
|
||||
export class ParsingError {
|
||||
errorMessage: string;
|
||||
constructor(message: string) {
|
||||
this.errorMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default ipAddressParser;
|
||||
@@ -1,104 +0,0 @@
|
||||
import {IpAddress, ipAddressParser, getNetworkClass, ParsingError, IpAddressWithSubnetMask, ParsedIpObject} from './ip';
|
||||
|
||||
|
||||
describe('parser tests', () => {
|
||||
it('can parse correct ip address', () => {
|
||||
const actual = ipAddressParser.parse('127.1.2.3');
|
||||
expect(actual).not.toBe(null);
|
||||
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||
|
||||
const obj = (actual as ParsedIpObject[])[0];
|
||||
const expected = new IpAddress(127, 1, 2, 3);
|
||||
expect(obj).not.toBe(null);
|
||||
expect(obj.toString()).toBe(expected.toString());
|
||||
});
|
||||
|
||||
it('cannot parse incorrect ip address', () => {
|
||||
expect(ipAddressParser.parse('abc')).toBe(null);
|
||||
expect(ipAddressParser.parse('')).toBe(null);
|
||||
});
|
||||
|
||||
it('should parse invalid ip address', () => {
|
||||
expect(ipAddressParser.parse('256.0.0.0')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.256.0.0')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.256.0')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.0.256')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.0.255 asd')).toBeInstanceOf(ParsingError);
|
||||
expect(ipAddressParser.parse('0.0.0.255/99')).toBeInstanceOf(ParsingError);
|
||||
});
|
||||
|
||||
it('parses correct ip and subnet mask', () => {
|
||||
const actual = ipAddressParser.parse('127.0.0.1/24');
|
||||
|
||||
expect(actual).not.toBe(null);
|
||||
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||
|
||||
const obj = (actual as ParsedIpObject[])[0];
|
||||
expect(obj).toBeInstanceOf(IpAddressWithSubnetMask);
|
||||
expect(obj!.toString()).toBe('127.0.0.1/24');
|
||||
expect((obj as IpAddressWithSubnetMask).maskBits).toBe(24);
|
||||
});
|
||||
|
||||
it('parses list of ip addresses', () => {
|
||||
const actual = ipAddressParser.parse('127.0.0.1/24 255.255.1.1');
|
||||
|
||||
expect(actual).not.toBe(null);
|
||||
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||
|
||||
const first = (actual as ParsedIpObject[])[0];
|
||||
expect(first).toBeInstanceOf(IpAddressWithSubnetMask);
|
||||
expect(first!.toString()).toBe('127.0.0.1/24');
|
||||
expect((first as IpAddressWithSubnetMask).maskBits).toBe(24);
|
||||
|
||||
const second = (actual as ParsedIpObject[])[1];
|
||||
expect(second).toBeInstanceOf(IpAddress);
|
||||
expect(second!.toString()).toBe('255.255.1.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNetworkClass tests', () => {
|
||||
it('detects class a', () => {
|
||||
expect(getNetworkClass(new IpAddress(1, 0, 0, 0))).toBe('a');
|
||||
expect(getNetworkClass(new IpAddress(55, 0, 0, 0))).toBe('a');
|
||||
expect(getNetworkClass(new IpAddress(97, 0, 0, 0))).toBe('a');
|
||||
expect(getNetworkClass(new IpAddress(127, 0, 0, 0))).toBe('a');
|
||||
});
|
||||
|
||||
it('detects class b', () => {
|
||||
expect(getNetworkClass(new IpAddress(128, 0, 0, 0))).toBe('b');
|
||||
expect(getNetworkClass(new IpAddress(134, 0, 0, 0))).toBe('b');
|
||||
expect(getNetworkClass(new IpAddress(180, 0, 0, 0))).toBe('b');
|
||||
expect(getNetworkClass(new IpAddress(191, 0, 0, 0))).toBe('b');
|
||||
});
|
||||
|
||||
it('detects class c', () => {
|
||||
expect(getNetworkClass(new IpAddress(192, 0, 0, 0))).toBe('c');
|
||||
expect(getNetworkClass(new IpAddress(218, 0, 0, 0))).toBe('c');
|
||||
expect(getNetworkClass(new IpAddress(223, 0, 0, 0))).toBe('c');
|
||||
});
|
||||
|
||||
it('detects class d', () => {
|
||||
expect(getNetworkClass(new IpAddress(224, 0, 0, 0))).toBe('d');
|
||||
expect(getNetworkClass(new IpAddress(234, 0, 0, 0))).toBe('d');
|
||||
expect(getNetworkClass(new IpAddress(239, 0, 0, 0))).toBe('d');
|
||||
});
|
||||
|
||||
it('detects class e', () => {
|
||||
expect(getNetworkClass(new IpAddress(240, 0, 0, 0))).toBe('e');
|
||||
expect(getNetworkClass(new IpAddress(241, 0, 0, 0))).toBe('e');
|
||||
expect(getNetworkClass(new IpAddress(255, 0, 0, 0))).toBe('e');
|
||||
});
|
||||
});
|
||||
|
||||
describe('IpAddressWithSubnetMask tests', () => {
|
||||
|
||||
it('creates subnetmask ip', () => {
|
||||
const ip = new IpAddress(127, 0, 0, 1);
|
||||
expect(new IpAddressWithSubnetMask(ip, 1).createSubnetMaskIp().toString()).toBe('128.0.0.0');
|
||||
expect(new IpAddressWithSubnetMask(ip, 8).createSubnetMaskIp().toString()).toBe('255.0.0.0');
|
||||
expect(new IpAddressWithSubnetMask(ip, 10).createSubnetMaskIp().toString()).toBe('255.192.0.0');
|
||||
expect(new IpAddressWithSubnetMask(ip, 20).createSubnetMaskIp().toString()).toBe('255.255.240.0');
|
||||
expect(new IpAddressWithSubnetMask(ip, 30).createSubnetMaskIp().toString()).toBe('255.255.255.252');
|
||||
expect(new IpAddressWithSubnetMask(ip, 32).createSubnetMaskIp().toString()).toBe('255.255.255.255');
|
||||
});
|
||||
});
|
||||
@@ -1,183 +0,0 @@
|
||||
import formatter from '../core/formatter';
|
||||
|
||||
|
||||
export type OctetNumber = 1 | 2 | 3 | 4;
|
||||
export type NetworkClass = 'a' | 'b' | 'c' | 'd' | 'e';
|
||||
export type ParsedIpObject = IpAddress | IpAddressWithSubnetMask;
|
||||
|
||||
const ipAddressParser = {
|
||||
parse: function(input: string) : ParsedIpObject[] | ParsingError | null {
|
||||
|
||||
const matches = this.getMaches(input);
|
||||
const correctInputs = matches.filter(m => m.matches != null);
|
||||
const incorrectInputs = matches.filter(m => m.matches == null);
|
||||
|
||||
if(correctInputs.length == 0)
|
||||
return null;
|
||||
|
||||
if(incorrectInputs.length > 0) {
|
||||
return new ParsingError(`Value(s) ${incorrectInputs.map(v => v.input).join(',')} was not recognized as valid ip address or ip address with a subnet mask`);
|
||||
}
|
||||
|
||||
const parsedObjects = matches.map(m => this.parseSingle(m.matches!, m.input));
|
||||
const parsingErrors = parsedObjects.filter(p => p instanceof ParsingError);
|
||||
|
||||
if(parsingErrors.length > 0) {
|
||||
return parsingErrors[0] as ParsingError;
|
||||
}
|
||||
|
||||
return parsedObjects as ParsedIpObject[];
|
||||
|
||||
},
|
||||
|
||||
getMaches(input : string) : { matches: RegExpExecArray | null, input: string }[] {
|
||||
|
||||
return input.
|
||||
replace(/[\t\s]+/g, ' ')
|
||||
.split(' ')
|
||||
.filter(s => s.length>0)
|
||||
.map(s => {
|
||||
const ipV4Regex = /^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})(\/\d+)?$/;
|
||||
const matches = ipV4Regex.exec(s);
|
||||
|
||||
if(matches == null || matches.length === 0)
|
||||
return {matches: null, input: s};
|
||||
|
||||
return {matches, input: s};
|
||||
});
|
||||
},
|
||||
|
||||
parseSingle(matches : RegExpExecArray, input: string) : ParsedIpObject | ParsingError {
|
||||
const invalid = (n: number) => n < 0 || n > 255;
|
||||
|
||||
const first = parseInt(matches[1]);
|
||||
const second = parseInt(matches[2]);
|
||||
const third = parseInt(matches[3]);
|
||||
const fourth = parseInt(matches[4]);
|
||||
|
||||
if(invalid(first) || invalid(second) || invalid(third) || invalid(fourth))
|
||||
return new ParsingError(`${input} value doesn't fall within the valid range of the IP address space`);
|
||||
|
||||
const ipAddress = new IpAddress(first, second, third, fourth);
|
||||
|
||||
if(matches[5]) {
|
||||
const maskPart = matches[5].substr(1);
|
||||
const maskBits = parseInt(maskPart);
|
||||
|
||||
if(maskBits > 32) {
|
||||
return new ParsingError(`Subnet mask value in ${input} is out of range`);
|
||||
}
|
||||
|
||||
return new IpAddressWithSubnetMask(ipAddress, maskBits);
|
||||
}
|
||||
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
|
||||
export class ParsingError {
|
||||
errorMessage: string;
|
||||
constructor(message: string) {
|
||||
this.errorMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
export class IpAddressWithSubnetMask {
|
||||
maskBits: number;
|
||||
ipAddress: IpAddress;
|
||||
|
||||
constructor(ipAddress : IpAddress, maskBits : number) {
|
||||
this.ipAddress = ipAddress;
|
||||
this.maskBits = maskBits;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this.ipAddress.toString()}/${this.maskBits}`;
|
||||
}
|
||||
|
||||
createSubnetMaskIp() : IpAddress {
|
||||
|
||||
const mask = (bits: number) => 255<<(8-bits)&255;
|
||||
|
||||
if(this.maskBits <= 8) {
|
||||
return new IpAddress(mask(this.maskBits), 0, 0, 0);
|
||||
}
|
||||
else if(this.maskBits <= 16) {
|
||||
return new IpAddress(255, mask(this.maskBits-8), 0, 0);
|
||||
}
|
||||
else if(this.maskBits <= 24) {
|
||||
return new IpAddress(255, 255, mask(this.maskBits-16), 0);
|
||||
}
|
||||
else {
|
||||
return new IpAddress(255, 255, 255, mask(this.maskBits-24));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class IpAddress {
|
||||
|
||||
firstByte : number;
|
||||
secondByte: number;
|
||||
thirdByte : number;
|
||||
fourthByte: number
|
||||
|
||||
constructor(firstByte : number, secondByte: number, thirdByte : number, fourthByte: number) {
|
||||
this.firstByte = firstByte;
|
||||
this.secondByte = secondByte;
|
||||
this.thirdByte = thirdByte;
|
||||
this.fourthByte = fourthByte;
|
||||
}
|
||||
|
||||
toString() : string {
|
||||
return `${this.firstByte}.${this.secondByte}.${this.thirdByte}.${this.fourthByte}`;
|
||||
}
|
||||
|
||||
setOctet(octet: OctetNumber, value : number) {
|
||||
switch(octet) {
|
||||
case 1:
|
||||
this.firstByte = value;
|
||||
break;
|
||||
case 2:
|
||||
this.secondByte = value;
|
||||
break;
|
||||
case 3:
|
||||
this.thirdByte = value;
|
||||
break;
|
||||
case 4:
|
||||
this.fourthByte = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const getNetworkClass = function (ipAddress: IpAddress) : NetworkClass {
|
||||
const byte = ipAddress.firstByte;
|
||||
const bineryRep = formatter.formatString(ipAddress.firstByte, 'bin');
|
||||
|
||||
const firstBitOne = (byte & 128) === 128;
|
||||
const firstBitZero = (byte & 128) === 0;
|
||||
const secondBitOne = (byte & 64) === 64;
|
||||
|
||||
const thirdBitOne = (byte & 32) === 32;
|
||||
const thirdBitZero = (byte & 32) === 0;
|
||||
|
||||
const forthBitZero = (byte & 16) === 0;
|
||||
const forthBitOne = (byte & 16) === 16;
|
||||
|
||||
// e: 1111
|
||||
|
||||
if(firstBitOne && secondBitOne && thirdBitOne && forthBitOne)
|
||||
return 'e';
|
||||
|
||||
if(firstBitOne && secondBitOne && thirdBitOne && forthBitZero) // Start bits: 1110;
|
||||
return 'd';
|
||||
|
||||
if(firstBitOne && secondBitOne && thirdBitZero) // Start bits: 110;
|
||||
return 'c';
|
||||
|
||||
return firstBitOne ? 'b' : 'a';
|
||||
};
|
||||
|
||||
export {ipAddressParser, getNetworkClass};
|
||||
73
src/networking/models.ts
Normal file
73
src/networking/models.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
export type OctetNumber = 1 | 2 | 3 | 4;
|
||||
export type NetworkClass = 'a' | 'b' | 'c' | 'd' | 'e';
|
||||
|
||||
export class IpAddressWithSubnetMask {
|
||||
maskBits: number;
|
||||
ipAddress: IpAddress;
|
||||
|
||||
constructor(ipAddress: IpAddress, maskBits: number) {
|
||||
this.ipAddress = ipAddress;
|
||||
this.maskBits = maskBits;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this.ipAddress.toString()}/${this.maskBits}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class IpAddress {
|
||||
|
||||
firstByte: number;
|
||||
secondByte: number;
|
||||
thirdByte: number;
|
||||
fourthByte: number;
|
||||
|
||||
constructor(firstByte: number, secondByte: number, thirdByte: number, fourthByte: number) {
|
||||
this.firstByte = firstByte;
|
||||
this.secondByte = secondByte;
|
||||
this.thirdByte = thirdByte;
|
||||
this.fourthByte = fourthByte;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `${this.firstByte}.${this.secondByte}.${this.thirdByte}.${this.fourthByte}`;
|
||||
}
|
||||
|
||||
clone(): IpAddress {
|
||||
return new IpAddress(this.firstByte, this.secondByte, this.thirdByte, this.fourthByte);
|
||||
}
|
||||
|
||||
setOctet(octet: OctetNumber, value: number) {
|
||||
switch (octet) {
|
||||
case 1:
|
||||
this.firstByte = value;
|
||||
break;
|
||||
case 2:
|
||||
this.secondByte = value;
|
||||
break;
|
||||
case 3:
|
||||
this.thirdByte = value;
|
||||
break;
|
||||
case 4:
|
||||
this.fourthByte = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SubnetCommand {
|
||||
input: IpAddressWithSubnetMask;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ import AppState from '../shell/AppState';
|
||||
import { CmdShell, CommandInput } from '../shell/cmd';
|
||||
import ErrorResultView from '../shell/components/ErrorResultView';
|
||||
import IpAddressView from './components/IpAddressView';
|
||||
import { ipAddressParser, ParsingError, IpAddress, ParsedIpObject, IpAddressWithSubnetMask } from './ip';
|
||||
import ipAddressParser, {ParsingError, ParsedIpObject} from './ip-parser';
|
||||
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from "./models";
|
||||
import log from 'loglevel';
|
||||
import SubnetView from './components/SubnetView';
|
||||
import { createSubnetMaskIp } from './subnet-utils';
|
||||
|
||||
const networkingAppModule = {
|
||||
setup: function(appState: AppState, cmd: CmdShell) {
|
||||
@@ -23,13 +26,18 @@ const networkingAppModule = {
|
||||
return;
|
||||
}
|
||||
|
||||
if(result instanceof SubnetCommand) {
|
||||
appState.addCommandResult(c.input, <SubnetView subnet={result} />);
|
||||
return;
|
||||
}
|
||||
|
||||
const ipAddresses : IpAddress[] = [];
|
||||
|
||||
(result as ParsedIpObject[]).forEach(r => {
|
||||
if(r instanceof IpAddressWithSubnetMask)
|
||||
{
|
||||
ipAddresses.push(r.ipAddress);
|
||||
ipAddresses.push(r.createSubnetMaskIp());
|
||||
ipAddresses.push(createSubnetMaskIp(r));
|
||||
}
|
||||
else if(r instanceof IpAddress) {
|
||||
ipAddresses.push(r);
|
||||
|
||||
90
src/networking/subnet-utils.test.tsx
Normal file
90
src/networking/subnet-utils.test.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { IpAddress, IpAddressWithSubnetMask } from "./models";
|
||||
import {createSubnetMaskIp,getBroadCastAddress,getNetworkAddress, getNetworkClass} from './subnet-utils';
|
||||
|
||||
describe('utils', () => {
|
||||
it('createSubnetMaskIp', () => {
|
||||
const ip = new IpAddress(127, 0, 0, 1);
|
||||
const mask = (n: number) => new IpAddressWithSubnetMask(ip, n);
|
||||
|
||||
expect(createSubnetMaskIp(mask(1)).toString()).toBe('128.0.0.0');
|
||||
expect(createSubnetMaskIp(mask(8)).toString()).toBe('255.0.0.0');
|
||||
expect(createSubnetMaskIp(mask(10)).toString()).toBe('255.192.0.0');
|
||||
expect(createSubnetMaskIp(mask(20)).toString()).toBe('255.255.240.0');
|
||||
expect(createSubnetMaskIp(mask(30)).toString()).toBe('255.255.255.252');
|
||||
expect(createSubnetMaskIp(mask(32)).toString()).toBe('255.255.255.255');
|
||||
});
|
||||
|
||||
it('getNetworkAddress', () => {
|
||||
const ipm = (f:number, s:number, t:number, fr:number, m:number) =>
|
||||
new IpAddressWithSubnetMask(new IpAddress(f,s,t,fr), m);
|
||||
|
||||
expect(getNetworkAddress(ipm(192,188,107,11, 12)).toString())
|
||||
.toBe('192.176.0.0');
|
||||
|
||||
expect(getNetworkAddress(ipm(192,168,123,1, 20)).toString())
|
||||
.toBe('192.168.112.0');
|
||||
|
||||
expect(getNetworkAddress(ipm(192,168,123,1, 23)).toString())
|
||||
.toBe('192.168.122.0');
|
||||
|
||||
expect(getNetworkAddress(ipm(192,168,5,125, 28)).toString())
|
||||
.toBe('192.168.5.112');
|
||||
|
||||
expect(getNetworkAddress(ipm(255,255,255,253, 30)).toString())
|
||||
.toBe('255.255.255.252');
|
||||
});
|
||||
|
||||
it('getBroadcastAddress', () => {
|
||||
const ipm = (f:number, s:number, t:number, fr:number, m:number) =>
|
||||
new IpAddressWithSubnetMask(new IpAddress(f,s,t,fr), m);
|
||||
|
||||
expect(getBroadCastAddress(ipm(192,188,107,11, 12)).toString())
|
||||
.toBe('192.191.255.255');
|
||||
|
||||
expect(getBroadCastAddress(ipm(192,168,123,1, 20)).toString())
|
||||
.toBe('192.168.127.255');
|
||||
|
||||
expect(getBroadCastAddress(ipm(192,168,123,1, 23)).toString())
|
||||
.toBe('192.168.123.255');
|
||||
|
||||
expect(getBroadCastAddress(ipm(192,168,5,125, 28)).toString())
|
||||
.toBe('192.168.5.127');
|
||||
|
||||
expect(getBroadCastAddress(ipm(255,255,255,253, 30)).toString())
|
||||
.toBe('255.255.255.255');
|
||||
});
|
||||
|
||||
describe('getNetworkClass tests', () => {
|
||||
it('detects class a', () => {
|
||||
expect(getNetworkClass(new IpAddress(1, 0, 0, 0))).toBe('a');
|
||||
expect(getNetworkClass(new IpAddress(55, 0, 0, 0))).toBe('a');
|
||||
expect(getNetworkClass(new IpAddress(97, 0, 0, 0))).toBe('a');
|
||||
expect(getNetworkClass(new IpAddress(127, 0, 0, 0))).toBe('a');
|
||||
});
|
||||
|
||||
it('detects class b', () => {
|
||||
expect(getNetworkClass(new IpAddress(128, 0, 0, 0))).toBe('b');
|
||||
expect(getNetworkClass(new IpAddress(134, 0, 0, 0))).toBe('b');
|
||||
expect(getNetworkClass(new IpAddress(180, 0, 0, 0))).toBe('b');
|
||||
expect(getNetworkClass(new IpAddress(191, 0, 0, 0))).toBe('b');
|
||||
});
|
||||
|
||||
it('detects class c', () => {
|
||||
expect(getNetworkClass(new IpAddress(192, 0, 0, 0))).toBe('c');
|
||||
expect(getNetworkClass(new IpAddress(218, 0, 0, 0))).toBe('c');
|
||||
expect(getNetworkClass(new IpAddress(223, 0, 0, 0))).toBe('c');
|
||||
});
|
||||
|
||||
it('detects class d', () => {
|
||||
expect(getNetworkClass(new IpAddress(224, 0, 0, 0))).toBe('d');
|
||||
expect(getNetworkClass(new IpAddress(234, 0, 0, 0))).toBe('d');
|
||||
expect(getNetworkClass(new IpAddress(239, 0, 0, 0))).toBe('d');
|
||||
});
|
||||
|
||||
it('detects class e', () => {
|
||||
expect(getNetworkClass(new IpAddress(240, 0, 0, 0))).toBe('e');
|
||||
expect(getNetworkClass(new IpAddress(241, 0, 0, 0))).toBe('e');
|
||||
expect(getNetworkClass(new IpAddress(255, 0, 0, 0))).toBe('e');
|
||||
});
|
||||
});
|
||||
});
|
||||
83
src/networking/subnet-utils.tsx
Normal file
83
src/networking/subnet-utils.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { createSubnetMaskByte } from "../core/byte";
|
||||
import { flipBitsToOne, flipBitsToZero } from '../core/byte';
|
||||
import { IpAddress, IpAddressWithSubnetMask, NetworkClass } from "./models";
|
||||
|
||||
function createSubnetMaskIp(ipm: IpAddressWithSubnetMask) : IpAddress {
|
||||
|
||||
const mask = createSubnetMaskByte;
|
||||
const maskBits = ipm.maskBits;
|
||||
|
||||
if (maskBits <= 8) {
|
||||
return new IpAddress(mask(maskBits), 0, 0, 0);
|
||||
}
|
||||
else if (maskBits <= 16) {
|
||||
return new IpAddress(255, mask(maskBits - 8), 0, 0);
|
||||
}
|
||||
else if (maskBits <= 24) {
|
||||
return new IpAddress(255, 255, mask(maskBits - 16), 0);
|
||||
}
|
||||
else {
|
||||
return new IpAddress(255, 255, 255, mask(maskBits - 24));
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkAddress(ipm: IpAddressWithSubnetMask) : IpAddress {
|
||||
return flipSubnetMaskBits(ipm, flipBitsToZero, 0);
|
||||
}
|
||||
|
||||
function getBroadCastAddress(ipm: IpAddressWithSubnetMask) : IpAddress {
|
||||
return flipSubnetMaskBits(ipm, flipBitsToOne, 255);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const ip = ipm.ipAddress;
|
||||
const maskBits = ipm.maskBits;
|
||||
|
||||
if (maskBits <= 8) {
|
||||
return new IpAddress(flip(maskBits, ip.firstByte), fullByte, fullByte, fullByte);
|
||||
}
|
||||
else if (maskBits <= 16) {
|
||||
return new IpAddress(ip.firstByte, flip(maskBits - 8, ip.secondByte), fullByte, fullByte);
|
||||
}
|
||||
else if (maskBits <= 24) {
|
||||
return new IpAddress(ip.firstByte, ip.secondByte, flip(maskBits - 16, ip.thirdByte), fullByte);
|
||||
}
|
||||
|
||||
else
|
||||
return new IpAddress(ip.firstByte, ip.secondByte, ip.thirdByte, flip(maskBits - 24, ip.fourthByte));
|
||||
}
|
||||
|
||||
function getNetworkClass (ipAddress: IpAddress) : NetworkClass {
|
||||
const byte = ipAddress.firstByte;
|
||||
|
||||
const firstBitOne = (byte & 128) === 128;
|
||||
const firstBitZero = (byte & 128) === 0;
|
||||
const secondBitOne = (byte & 64) === 64;
|
||||
|
||||
const thirdBitOne = (byte & 32) === 32;
|
||||
const thirdBitZero = (byte & 32) === 0;
|
||||
|
||||
const forthBitZero = (byte & 16) === 0;
|
||||
const forthBitOne = (byte & 16) === 16;
|
||||
|
||||
// e: 1111
|
||||
|
||||
if(firstBitOne && secondBitOne && thirdBitOne && forthBitOne)
|
||||
return 'e';
|
||||
|
||||
if(firstBitOne && secondBitOne && thirdBitOne && forthBitZero) // Start bits: 1110;
|
||||
return 'd';
|
||||
|
||||
if(firstBitOne && secondBitOne && thirdBitZero) // Start bits: 110;
|
||||
return 'c';
|
||||
|
||||
return firstBitOne ? 'b' : 'a';
|
||||
};
|
||||
|
||||
|
||||
type FlipFunction = (byte: number, numberOfBits: number) => number;
|
||||
|
||||
export {createSubnetMaskIp, getBroadCastAddress, getNetworkAddress, getNetworkClass};
|
||||
1
src/shell/components/DebugIndicators.css
Normal file
1
src/shell/components/DebugIndicators.css
Normal file
@@ -0,0 +1 @@
|
||||
.debug-indicators { position: absolute; top: 1em; left: 1em}
|
||||
@@ -1,5 +1,6 @@
|
||||
import AppState from "../AppState";
|
||||
import React from "react";
|
||||
import './DebugIndicators.css';
|
||||
|
||||
function DebugIndicators(props: {appState: AppState}) {
|
||||
|
||||
@@ -21,8 +22,8 @@ function DebugIndicators(props: {appState: AppState}) {
|
||||
if(list.length == 0)
|
||||
return null;
|
||||
|
||||
return <div>
|
||||
{list.map(i => <span>{i} </span>)}
|
||||
return <div className="debug-indicators">
|
||||
{list.map(i => <span title={i}>[{i.substring(0,1)}] </span>)}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
.top-links { position: absolute; right: 10px; top: 10px; list-style-type: none; margin: 0 }
|
||||
.top-links { position: absolute; right: 1em; top: 1em; list-style-type: none; margin: 0 }
|
||||
.top-links li { float: left; }
|
||||
.top-links a { display: inline-block; padding: 10px 15px}
|
||||
.top-links .icon { margin-right: 5px; vertical-align: middle; }
|
||||
@@ -7,6 +7,7 @@ import ErrorResultView from './components/ErrorResultView';
|
||||
import HelpResultView from './components/HelpResultView';
|
||||
import TextResultView from './components/TextResultView';
|
||||
import WhatsnewResultView from './components/WhatsNewResultView';
|
||||
import {STARTUP_COMMAND_KEY} from './startup';
|
||||
|
||||
const shellModule = {
|
||||
setup: function(appState: AppState, cmd: CmdShell) {
|
||||
@@ -29,6 +30,35 @@ const shellModule = {
|
||||
appState.addCommandResult(c.input, <TextResultView text={`Debug Mode: ${appState.debugMode}`}/>);
|
||||
});
|
||||
|
||||
if(appState.env !== 'prod') {
|
||||
|
||||
// Default command for development purposes
|
||||
cmd.command({
|
||||
canHandle: (s: string) => s.indexOf('default') === 0,
|
||||
handle: (s: CommandInput) => {
|
||||
|
||||
const executeCommand = (c: string) => {
|
||||
console.log(c);
|
||||
|
||||
if(c.length === 0) {
|
||||
return "Default comand: " + localStorage.getItem(STARTUP_COMMAND_KEY);
|
||||
}
|
||||
else if(c === 'clear') {
|
||||
localStorage.removeItem(STARTUP_COMMAND_KEY);
|
||||
return "Default startup command cleared";
|
||||
}
|
||||
|
||||
localStorage.setItem(STARTUP_COMMAND_KEY, c);
|
||||
return `Default startup command saved: ${c}`;
|
||||
};
|
||||
|
||||
const command = s.input.substring(7).trim();
|
||||
const result = executeCommand(command);
|
||||
appState.addCommandResult(s.input, <TextResultView text={result} />);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cmd.onError((input: string, err: Error) => appState.addCommandResult(input, <ErrorResultView errorMessage={err.toString()} />));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@ import hash from '../core/hash';
|
||||
import AppState from './AppState';
|
||||
import { Env } from './interfaces';
|
||||
import appStateStore from './appStateStore';
|
||||
import CommandLink from '../core/components/CommandLink';
|
||||
|
||||
export type StartupAppData = {
|
||||
appState: AppState,
|
||||
startupCommands: string[]
|
||||
}
|
||||
|
||||
const STARTUP_COMMAND_KEY = 'StartupCommand';
|
||||
const DEFAULT_COMMANDS = ['help', '127.0.0.1 192.168.0.0/8', '1|2&6','4 0b1000000 0x80'];
|
||||
|
||||
function bootstrapAppData() : StartupAppData {
|
||||
const env = window.location.host === "bitwisecmd.com" ? 'prod' : 'stage';
|
||||
|
||||
@@ -35,7 +39,10 @@ function createAppState(env:string) {
|
||||
function getStartupCommands(appState : AppState) : string[] {
|
||||
var hashArgs = hash.getArgs(window.location.hash);
|
||||
|
||||
var startupCommands = ['help', '127.0.0.1 192.168.0.0/8', '1|2&6','4 0b1000000 0x80'];
|
||||
var startupCommands = loadStoredCommands();
|
||||
|
||||
if(startupCommands.length == 0)
|
||||
startupCommands = DEFAULT_COMMANDS;
|
||||
|
||||
if(appState.wasOldVersion) {
|
||||
startupCommands = ["whatsnew"];
|
||||
@@ -50,6 +57,11 @@ function getStartupCommands(appState : AppState) : string[] {
|
||||
return startupCommands;
|
||||
}
|
||||
|
||||
function loadStoredCommands() : string[] {
|
||||
const json = localStorage.getItem(STARTUP_COMMAND_KEY);
|
||||
return json != null ? [json] : [];
|
||||
}
|
||||
|
||||
function setupLogger(env: Env) {
|
||||
if(env != 'prod'){
|
||||
log.setLevel("debug");
|
||||
@@ -59,4 +71,5 @@ function setupLogger(env: Env) {
|
||||
}
|
||||
}
|
||||
|
||||
export {STARTUP_COMMAND_KEY};
|
||||
export default bootstrapAppData;
|
||||
Reference in New Issue
Block a user