feat(new-tool): IPv4 subnet calculator
This commit is contained in:
		
							parent
							
								
									47948dd343
								
							
						
					
					
						commit
						c339ab3551
					
				
							
								
								
									
										18
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								package.json
									
									
									
									
									
								
							| @ -47,7 +47,7 @@ | |||||||
|     "colord": "^2.9.3", |     "colord": "^2.9.3", | ||||||
|     "composerize": "^1.2.0", |     "composerize": "^1.2.0", | ||||||
|     "cron-validator": "^1.3.1", |     "cron-validator": "^1.3.1", | ||||||
|     "cronstrue": "^2.23.0", |     "cronstrue": "^2.24.0", | ||||||
|     "crypto-js": "^4.1.1", |     "crypto-js": "^4.1.1", | ||||||
|     "date-fns": "^2.29.3", |     "date-fns": "^2.29.3", | ||||||
|     "figue": "^1.2.0", |     "figue": "^1.2.0", | ||||||
| @ -59,13 +59,14 @@ | |||||||
|     "mathjs": "^10.6.4", |     "mathjs": "^10.6.4", | ||||||
|     "mime-types": "^2.1.35", |     "mime-types": "^2.1.35", | ||||||
|     "naive-ui": "^2.34.3", |     "naive-ui": "^2.34.3", | ||||||
|  |     "netmask": "^2.0.2", | ||||||
|     "node-forge": "^1.3.1", |     "node-forge": "^1.3.1", | ||||||
|     "pinia": "^2.0.33", |     "pinia": "^2.0.33", | ||||||
|     "plausible-tracker": "^0.3.8", |     "plausible-tracker": "^0.3.8", | ||||||
|     "qrcode": "^1.5.1", |     "qrcode": "^1.5.1", | ||||||
|     "randombytes": "^2.1.0", |     "randombytes": "^2.1.0", | ||||||
|     "sql-formatter": "^8.2.0", |     "sql-formatter": "^8.2.0", | ||||||
|     "ts-pattern": "^4.2.1", |     "ts-pattern": "^4.2.2", | ||||||
|     "uuid": "^8.3.2", |     "uuid": "^8.3.2", | ||||||
|     "vue": "^3.2.47", |     "vue": "^3.2.47", | ||||||
|     "vue-router": "^4.1.6" |     "vue-router": "^4.1.6" | ||||||
| @ -75,30 +76,31 @@ | |||||||
|     "@types/bcryptjs": "^2.4.2", |     "@types/bcryptjs": "^2.4.2", | ||||||
|     "@types/crypto-js": "^4.1.1", |     "@types/crypto-js": "^4.1.1", | ||||||
|     "@types/jsdom": "^16.2.15", |     "@types/jsdom": "^16.2.15", | ||||||
|     "@types/lodash": "^4.14.191", |     "@types/lodash": "^4.14.192", | ||||||
|     "@types/mime-types": "^2.1.1", |     "@types/mime-types": "^2.1.1", | ||||||
|     "@types/node": "^16.18.16", |     "@types/netmask": "^2.0.0", | ||||||
|  |     "@types/node": "^16.18.22", | ||||||
|     "@types/node-forge": "^1.3.1", |     "@types/node-forge": "^1.3.1", | ||||||
|     "@types/prettier": "^2.7.2", |     "@types/prettier": "^2.7.2", | ||||||
|     "@types/qrcode": "^1.5.0", |     "@types/qrcode": "^1.5.0", | ||||||
|     "@types/randombytes": "^2.0.0", |     "@types/randombytes": "^2.0.0", | ||||||
|     "@types/uuid": "^8.3.4", |     "@types/uuid": "^8.3.4", | ||||||
|     "@typescript-eslint/parser": "^5.55.0", |     "@typescript-eslint/parser": "^5.57.0", | ||||||
|     "@vitejs/plugin-vue": "^2.3.4", |     "@vitejs/plugin-vue": "^2.3.4", | ||||||
|     "@vitejs/plugin-vue-jsx": "^1.3.10", |     "@vitejs/plugin-vue-jsx": "^1.3.10", | ||||||
|     "@vue/eslint-config-prettier": "^7.1.0", |     "@vue/eslint-config-prettier": "^7.1.0", | ||||||
|     "@vue/eslint-config-typescript": "^10.0.0", |     "@vue/eslint-config-typescript": "^10.0.0", | ||||||
|     "@vue/test-utils": "^2.3.1", |     "@vue/test-utils": "^2.3.2", | ||||||
|     "@vue/tsconfig": "^0.1.3", |     "@vue/tsconfig": "^0.1.3", | ||||||
|     "c8": "^7.13.0", |     "c8": "^7.13.0", | ||||||
|     "eslint": "^8.36.0", |     "eslint": "^8.36.0", | ||||||
|     "eslint-config-prettier": "^8.7.0", |     "eslint-config-prettier": "^8.8.0", | ||||||
|     "eslint-import-resolver-typescript": "^3.5.3", |     "eslint-import-resolver-typescript": "^3.5.3", | ||||||
|     "eslint-plugin-import": "^2.27.5", |     "eslint-plugin-import": "^2.27.5", | ||||||
|     "eslint-plugin-vue": "^8.7.1", |     "eslint-plugin-vue": "^8.7.1", | ||||||
|     "jsdom": "^19.0.0", |     "jsdom": "^19.0.0", | ||||||
|     "less": "^4.1.3", |     "less": "^4.1.3", | ||||||
|     "prettier": "^2.8.4", |     "prettier": "^2.8.7", | ||||||
|     "standard-version": "^9.5.0", |     "standard-version": "^9.5.0", | ||||||
|     "start-server-and-test": "^1.15.4", |     "start-server-and-test": "^1.15.4", | ||||||
|     "typescript": "~4.5.5", |     "typescript": "~4.5.5", | ||||||
|  | |||||||
							
								
								
									
										3219
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3219
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,6 +1,7 @@ | |||||||
| import { tool as base64FileConverter } from './base64-file-converter'; | import { tool as base64FileConverter } from './base64-file-converter'; | ||||||
| import { tool as base64StringConverter } from './base64-string-converter'; | import { tool as base64StringConverter } from './base64-string-converter'; | ||||||
| import { tool as basicAuthGenerator } from './basic-auth-generator'; | import { tool as basicAuthGenerator } from './basic-auth-generator'; | ||||||
|  | import { tool as ipv4SubnetCalculator } from './ipv4-subnet-calculator'; | ||||||
| import { tool as dockerRunToDockerComposeConverter } from './docker-run-to-docker-compose-converter'; | import { tool as dockerRunToDockerComposeConverter } from './docker-run-to-docker-compose-converter'; | ||||||
| import { tool as htmlWysiwygEditor } from './html-wysiwyg-editor'; | import { tool as htmlWysiwygEditor } from './html-wysiwyg-editor'; | ||||||
| import { tool as rsaKeyPairGenerator } from './rsa-key-pair-generator'; | import { tool as rsaKeyPairGenerator } from './rsa-key-pair-generator'; | ||||||
| @ -96,6 +97,10 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|       dockerRunToDockerComposeConverter, |       dockerRunToDockerComposeConverter, | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     name: 'Network', | ||||||
|  |     components: [ipv4SubnetCalculator], | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     name: 'Math', |     name: 'Math', | ||||||
|     components: [mathEvaluator, etaCalculator], |     components: [mathEvaluator, etaCalculator], | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								src/tools/ipv4-subnet-calculator/copyable-ip-like.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/tools/ipv4-subnet-calculator/copyable-ip-like.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | <template> | ||||||
|  |   <n-tooltip trigger="hover"> | ||||||
|  |     <template #trigger> | ||||||
|  |       <span class="ip" @click="handleClick">{{ ip }}</span> | ||||||
|  |     </template> | ||||||
|  |     {{ tooltipText }} | ||||||
|  |   </n-tooltip> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { useClipboard } from '@vueuse/core'; | ||||||
|  | import { ref, toRefs } from 'vue'; | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<{ ip?: string }>(), { ip: '' }); | ||||||
|  | const { ip } = toRefs(props); | ||||||
|  | 
 | ||||||
|  | const initialText = 'Copy to clipboard'; | ||||||
|  | const tooltipText = ref(initialText); | ||||||
|  | 
 | ||||||
|  | const { copy } = useClipboard({ source: ip }); | ||||||
|  | 
 | ||||||
|  | function handleClick() { | ||||||
|  |   copy(); | ||||||
|  |   tooltipText.value = 'Copied!'; | ||||||
|  | 
 | ||||||
|  |   setTimeout(() => (tooltipText.value = initialText), 1000); | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped lang="less"> | ||||||
|  | .ip { | ||||||
|  |   font-family: monospace; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										11
									
								
								src/tools/ipv4-subnet-calculator/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/tools/ipv4-subnet-calculator/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | import { RouterOutlined } from '@vicons/material'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'IPv4 subnet calculator', | ||||||
|  |   path: '/ipv4-subnet-calculator', | ||||||
|  |   description: 'Parse your IPv4 CIDR blocks and get all the info you need about your sub network.', | ||||||
|  |   keywords: ['ipv4', 'subnet', 'calculator', 'mask', 'network', 'cidr', 'netmask', 'bitmask', 'broadcast', 'address'], | ||||||
|  |   component: () => import('./ipv4-subnet-calculator.vue'), | ||||||
|  |   icon: RouterOutlined, | ||||||
|  | }); | ||||||
| @ -0,0 +1,23 @@ | |||||||
|  | export { getIPClass }; | ||||||
|  | 
 | ||||||
|  | function getIPClass({ ip }: { ip: string }) { | ||||||
|  |   const [firstOctet] = ip.split('.').map(Number); | ||||||
|  | 
 | ||||||
|  |   if (firstOctet < 128) { | ||||||
|  |     return 'A'; | ||||||
|  |   } | ||||||
|  |   if (firstOctet > 127 && firstOctet < 192) { | ||||||
|  |     return 'B'; | ||||||
|  |   } | ||||||
|  |   if (firstOctet > 191 && firstOctet < 224) { | ||||||
|  |     return 'C'; | ||||||
|  |   } | ||||||
|  |   if (firstOctet > 223 && firstOctet < 240) { | ||||||
|  |     return 'D'; | ||||||
|  |   } | ||||||
|  |   if (firstOctet > 239 && firstOctet < 256) { | ||||||
|  |     return 'E'; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return undefined; | ||||||
|  | } | ||||||
							
								
								
									
										125
									
								
								src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | |||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <n-form-item label="An IPv4 address with or without mask" v-bind="validationAttrs"> | ||||||
|  |       <n-input v-model:value="ip" /> | ||||||
|  |     </n-form-item> | ||||||
|  | 
 | ||||||
|  |     <div v-if="networkInfo"> | ||||||
|  |       <n-table> | ||||||
|  |         <tbody> | ||||||
|  |           <tr v-for="{ getValue, label, undefinedFallback } in sections" :key="label"> | ||||||
|  |             <td> | ||||||
|  |               <n-text strong>{{ label }}</n-text> | ||||||
|  |             </td> | ||||||
|  |             <td> | ||||||
|  |               <copyable-ip-like v-if="getValue(networkInfo)" :ip="getValue(networkInfo)"></copyable-ip-like> | ||||||
|  |               <n-text v-else depth="3">{{ undefinedFallback }}</n-text> | ||||||
|  |             </td> | ||||||
|  |           </tr> | ||||||
|  |         </tbody> | ||||||
|  |       </n-table> | ||||||
|  | 
 | ||||||
|  |       <n-space style="margin-top: 14px" justify="space-between"> | ||||||
|  |         <n-button tertiary @click="switchToBlock({ count: -1 })"> | ||||||
|  |           <n-icon :component="ArrowLeft" /> | ||||||
|  |           Previous block | ||||||
|  |         </n-button> | ||||||
|  |         <n-button tertiary @click="switchToBlock({ count: 1 })"> | ||||||
|  |           Next block | ||||||
|  |           <n-icon :component="ArrowRight" /> | ||||||
|  |         </n-button> | ||||||
|  |       </n-space> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { computed } from 'vue'; | ||||||
|  | import { Netmask } from 'netmask'; | ||||||
|  | import { withDefaultOnError } from '@/utils/defaults'; | ||||||
|  | import { useValidation } from '@/composable/validation'; | ||||||
|  | import { isNotThrowing } from '@/utils/boolean'; | ||||||
|  | import { useStorage } from '@vueuse/core'; | ||||||
|  | import { ArrowLeft, ArrowRight } from '@vicons/tabler'; | ||||||
|  | import { getIPClass } from './ipv4-subnet-calculator.models'; | ||||||
|  | import CopyableIpLike from './copyable-ip-like.vue'; | ||||||
|  | 
 | ||||||
|  | const ip = useStorage('ipv4-subnet-calculator:ip', '192.168.0.1/24'); | ||||||
|  | 
 | ||||||
|  | const getNetworkInfo = (address: string) => new Netmask(address.trim()); | ||||||
|  | 
 | ||||||
|  | const networkInfo = computed(() => withDefaultOnError(() => getNetworkInfo(ip.value), undefined)); | ||||||
|  | 
 | ||||||
|  | const { attrs: validationAttrs } = useValidation({ | ||||||
|  |   source: ip, | ||||||
|  |   rules: [ | ||||||
|  |     { | ||||||
|  |       message: 'We cannot parse this address, check the format', | ||||||
|  |       validator: (value) => isNotThrowing(() => getNetworkInfo(value.trim())), | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const sections: { | ||||||
|  |   label: string; | ||||||
|  |   getValue: (blocks: Netmask) => string | undefined; | ||||||
|  |   undefinedFallback?: string; | ||||||
|  | }[] = [ | ||||||
|  |   { | ||||||
|  |     label: 'Netmask', | ||||||
|  |     getValue: (block) => block.toString(), | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'Network address', | ||||||
|  |     getValue: ({ base }) => base, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'Network mask', | ||||||
|  |     getValue: ({ mask }) => mask, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'Network mask in binary', | ||||||
|  |     getValue: ({ bitmask }) => ('1'.repeat(bitmask) + '0'.repeat(32 - bitmask)).match(/.{8}/g)?.join('.') ?? '', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'CIDR notation', | ||||||
|  |     getValue: ({ bitmask }) => `/${bitmask}`, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'Wildcard mask', | ||||||
|  |     getValue: ({ hostmask }) => hostmask, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'Network size', | ||||||
|  |     getValue: ({ size }) => String(size), | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'First address', | ||||||
|  |     getValue: ({ first }) => first, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'Last address', | ||||||
|  |     getValue: ({ last }) => last, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'Broadcast address', | ||||||
|  |     getValue: ({ broadcast }) => broadcast, | ||||||
|  |     undefinedFallback: 'No broadcast address with this mask', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'IP class', | ||||||
|  |     getValue: ({ base: ip }) => getIPClass({ ip }), | ||||||
|  |     undefinedFallback: 'Unknown class type', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | function switchToBlock({ count = 1 }: { count?: number }) { | ||||||
|  |   const next = networkInfo.value?.next(count); | ||||||
|  | 
 | ||||||
|  |   if (next) { | ||||||
|  |     ip.value = next.toString(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="less" scoped></style> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user