This commit is contained in:
sharevb 2025-04-13 04:12:52 +02:00 committed by GitHub
commit 36a470b4e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 6725 additions and 8343 deletions

5
components.d.ts vendored
View file

@ -37,6 +37,7 @@ declare module '@vue/runtime-core' {
'CFileUpload.demo': typeof import('./src/ui/c-file-upload/c-file-upload.demo.vue')['default'] 'CFileUpload.demo': typeof import('./src/ui/c-file-upload/c-file-upload.demo.vue')['default']
ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default'] ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default']
Chronometer: typeof import('./src/tools/chronometer/chronometer.vue')['default'] Chronometer: typeof import('./src/tools/chronometer/chronometer.vue')['default']
CidrInCidr: typeof import('./src/tools/cidr-in-cidr/cidr-in-cidr.vue')['default']
CInputText: typeof import('./src/ui/c-input-text/c-input-text.vue')['default'] CInputText: typeof import('./src/ui/c-input-text/c-input-text.vue')['default']
'CInputText.demo': typeof import('./src/ui/c-input-text/c-input-text.demo.vue')['default'] 'CInputText.demo': typeof import('./src/ui/c-input-text/c-input-text.demo.vue')['default']
CKeyValueList: typeof import('./src/ui/c-key-value-list/c-key-value-list.vue')['default'] CKeyValueList: typeof import('./src/ui/c-key-value-list/c-key-value-list.vue')['default']
@ -103,6 +104,7 @@ declare module '@vue/runtime-core' {
IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
IpInRange: typeof import('./src/tools/cidr-in-cidr/cidr-in-cidr.vue')['default']
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default']
Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default'] Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default']
Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default'] Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default']
@ -130,7 +132,6 @@ declare module '@vue/runtime-core' {
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
@ -141,7 +142,6 @@ declare module '@vue/runtime-core' {
NLayout: typeof import('naive-ui')['NLayout'] NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NSpace: typeof import('naive-ui')['NSpace']
NTable: typeof import('naive-ui')['NTable'] NTable: typeof import('naive-ui')['NTable']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
@ -161,6 +161,7 @@ declare module '@vue/runtime-core' {
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

View file

@ -70,6 +70,7 @@
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"iarna-toml-esm": "^3.0.5", "iarna-toml-esm": "^3.0.5",
"ibantools": "^4.3.3", "ibantools": "^4.3.3",
"ip-matching": "^2.1.2",
"js-base64": "^3.7.6", "js-base64": "^3.7.6",
"json5": "^2.2.3", "json5": "^2.2.3",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",

14254
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest';
import { cidrInCidr } from './cidr-in-cidr.service';
describe('cidr-in-cidr', () => {
describe('cidrInCidr', () => {
it('should return correct inclusion', () => {
expect(cidrInCidr({ baseRange: '192.168.0.0/16', ipOrRangeToTest: '192.168.0.1' }).isIncluded).toBe(true); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.1.0/24', ipOrRangeToTest: '192.168.0.10' }).isIncluded).toBe(false); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.1.*', ipOrRangeToTest: '192.168.0.10' }).isIncluded).toBe(false); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.0.0/24', ipOrRangeToTest: '192.168.0.0/28' }).isIncluded).toBe(true); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.0/24', ipOrRangeToTest: '192.168.1.0/28' }).isIncluded).toBe(false); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.*', ipOrRangeToTest: '192.168.1.0/28' }).isIncluded).toBe(false); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.0.*', ipOrRangeToTest: '192.168.0.0/28' }).isIncluded).toBe(true); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.1-192.168.20.15', ipOrRangeToTest: '192.168.20.12' }).isIncluded).toBe(true); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.1-192.168.20.15', ipOrRangeToTest: '192.168.20.20' }).isIncluded).toBe(false); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.1-192.168.20.15', ipOrRangeToTest: '192.168.20.9-192.168.20.12' }).isIncluded).toBe(true); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.1-192.168.20.15', ipOrRangeToTest: '192.168.20.9-192.168.20.20' }).isIncluded).toBe(false); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.0-192.168.20.255', ipOrRangeToTest: '192.168.20.0/28' }).isIncluded).toBe(true); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.20.0-192.168.20.255', ipOrRangeToTest: '192.168.1.0/28' }).isIncluded).toBe(false); // NOSONAR
expect(cidrInCidr({ baseRange: '192.168.0.0-192.168.1.255', ipOrRangeToTest: '192.168.1.0/28' }).isIncluded).toBe(true); // NOSONAR
});
it('should return correct subnet', () => {
expect(cidrInCidr({ baseRange: '192.168.0.0/16', ipOrRangeToTest: '192.168.0.1' }).baseSubnets).to.deep.eq([ // NOSONAR
{
cidr: '192.168.0.0/16', // NOSONAR
end: '192.168.255.255', // NOSONAR
start: '192.168.0.0', // NOSONAR
},
]);
expect(cidrInCidr({ baseRange: '192.168.20.1-192.168.20.3', ipOrRangeToTest: '192.168.1.0/28' }).baseSubnets).to.deep.eq([ // NOSONAR
{
cidr: '192.168.20.1/32', // NOSONAR
end: '192.168.20.1', // NOSONAR
start: '192.168.20.1', // NOSONAR
},
{
cidr: '192.168.20.2/31', // NOSONAR
end: '192.168.20.3', // NOSONAR
start: '192.168.20.2', // NOSONAR
},
]);
});
});
});

View file

@ -0,0 +1,28 @@
import { getMatch } from 'ip-matching';
export function cidrInCidr(
{ baseRange, ipOrRangeToTest }:
{
baseRange: string
ipOrRangeToTest: string
}) {
const baseMatchMasks = getMatch(baseRange)?.convertToMasks() || [];
const ipOrRangeToTestMatchMasks = getMatch(ipOrRangeToTest)?.convertToMasks() || [];
const baseSubnets = baseMatchMasks.map((mask) => {
const subnet = mask.convertToSubnet();
if (!subnet) {
return { cidr: '', start: '', end: '' };
}
return {
cidr: subnet.toString(),
start: subnet.getFirst().toString(),
end: subnet.getLast().toString(),
};
}).filter(subnet => subnet.cidr !== '');
return {
baseSubnets,
isIncluded: baseMatchMasks.some(baseMask => ipOrRangeToTestMatchMasks.every(ipOrRangeMask => ipOrRangeMask.isSubsetOf(baseMask))),
};
}

View file

@ -0,0 +1,80 @@
<script setup lang="ts">
import { useStorage } from '@vueuse/core';
import { Check as CheckIcon, LetterX as CrossIcon } from '@vicons/tabler';
import { getMatch } from 'ip-matching';
import { cidrInCidr } from './cidr-in-cidr.service';
import { withDefaultOnError } from '@/utils/defaults';
import { isNotThrowing } from '@/utils/boolean';
import SpanCopyable from '@/components/SpanCopyable.vue';
const baseRange = useStorage('cidr-in-cidr:range', '192.168.0.1/24'); // NOSONAR
const ipOrRangeToTest = useStorage('cidr-in-cidr:ip', '192.168.0.1'); // NOSONAR
const matchResult = computed(() => withDefaultOnError(
() => cidrInCidr({ baseRange: baseRange.value, ipOrRangeToTest: ipOrRangeToTest.value }),
{ baseSubnets: [], isIncluded: false }));
const rangeValidationRules = [
{
message: 'We cannot parse this CIDR/IP Range/Mask/Wildcard, check the format',
validator: (value: string) => isNotThrowing(() => getMatch(value)) && getMatch(value),
},
];
</script>
<template>
<div>
<c-input-text
v-model:value="baseRange"
label="An IPv4/6 CIDR/Range/Mask/Wildcard (base network)"
placeholder="The ipv4/6 CIDR..."
:validation-rules="rangeValidationRules"
mb-4
/>
<c-input-text
v-model:value="ipOrRangeToTest"
label="An IPv4/6 CIDR/Range/Mask/Wildcard (to test for inclusion)"
placeholder="The An IPv4/6 CIDR/Range/Mask/Wildcard..."
:validation-rules="rangeValidationRules"
mb-4
/>
<n-divider />
<div flex justify-center>
<span v-if="matchResult.isIncluded">
<n-icon color="green">
<CheckIcon />
</n-icon>
Included
</span>
<span v-else>
<n-icon color="red">
<CrossIcon />
</n-icon>
Not included
</span>
</div>
<n-divider />
<c-card title="Subnets">
<n-table>
<tbody>
<tr v-for="{ cidr, start, end } in matchResult.baseSubnets" :key="cidr">
<td font-bold>
<SpanCopyable :value="cidr" />
</td>
<td>
<SpanCopyable :value="start" />
</td>
<td>
<SpanCopyable :value="end" />
</td>
</tr>
</tbody>
</n-table>
</c-card>
</div>
</template>

View file

@ -0,0 +1,13 @@
import { UnfoldMoreOutlined } from '@vicons/material';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'IPv4-6/IPRange/CIDR in IPRange/CIDR/IPMask',
path: '/cidr-in-cidr',
description: 'Given a CIDR/IP Range/Wildcard IP/IP Mask, tell if a given IPv4-6/Range/CIDR/Wildcard IP/IP Mask is in subnet range',
keywords: ['ip', 'cidr', 'range', 'mask', 'wildcard', 'ipv4', 'ipv6', 'subnet', 'include', 'inclusion'],
component: () => import('./cidr-in-cidr.vue'),
redirectFrom: ['/ip-in-range'],
icon: UnfoldMoreOutlined,
createdAt: new Date('2025-01-12'),
});

View file

@ -86,6 +86,7 @@ import { tool as urlParser } from './url-parser';
import { tool as uuidGenerator } from './uuid-generator'; import { tool as uuidGenerator } from './uuid-generator';
import { tool as macAddressLookup } from './mac-address-lookup'; import { tool as macAddressLookup } from './mac-address-lookup';
import { tool as xmlFormatter } from './xml-formatter'; import { tool as xmlFormatter } from './xml-formatter';
import { tool as cidrInCidr } from './cidr-in-cidr';
import { tool as yamlViewer } from './yaml-viewer'; import { tool as yamlViewer } from './yaml-viewer';
export const toolsByCategory: ToolCategory[] = [ export const toolsByCategory: ToolCategory[] = [
@ -164,7 +165,15 @@ export const toolsByCategory: ToolCategory[] = [
}, },
{ {
name: 'Network', name: 'Network',
components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator], components: [
ipv4SubnetCalculator,
ipv4AddressConverter,
cidrInCidr,
ipv4RangeExpander,
macAddressLookup,
macAddressGenerator,
ipv6UlaGenerator,
],
}, },
{ {
name: 'Math', name: 'Math',