feat(new tool): random MAC address generator (#657)
* #521 Random MAC address generator * refactor(mac-address-generator): improved ux * refactor(mac-address-generator): improved ux --------- Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
This commit is contained in:
		
							parent
							
								
									681f7bf644
								
							
						
					
					
						commit
						cc3425dc77
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -116,6 +116,7 @@ declare module '@vue/runtime-core' { | |||||||
|     KeycodeInfo: typeof import('./src/tools/keycode-info/keycode-info.vue')['default'] |     KeycodeInfo: typeof import('./src/tools/keycode-info/keycode-info.vue')['default'] | ||||||
|     ListConverter: typeof import('./src/tools/list-converter/list-converter.vue')['default'] |     ListConverter: typeof import('./src/tools/list-converter/list-converter.vue')['default'] | ||||||
|     LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default'] |     LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default'] | ||||||
|  |     MacAddressGenerator: typeof import('./src/tools/mac-address-generator/mac-address-generator.vue')['default'] | ||||||
|     MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default'] |     MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default'] | ||||||
|     MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default'] |     MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default'] | ||||||
|     MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default'] |     MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default'] | ||||||
|  | |||||||
| @ -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 macAddressGenerator } from './mac-address-generator'; | ||||||
| import { tool as textToBinary } from './text-to-binary'; | import { tool as textToBinary } from './text-to-binary'; | ||||||
| import { tool as ulidGenerator } from './ulid-generator'; | import { tool as ulidGenerator } from './ulid-generator'; | ||||||
| import { tool as ibanValidatorAndParser } from './iban-validator-and-parser'; | import { tool as ibanValidatorAndParser } from './iban-validator-and-parser'; | ||||||
| @ -140,7 +141,7 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Network', |     name: 'Network', | ||||||
|     components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, ipv6UlaGenerator], |     components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Math', |     name: 'Math', | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								src/tools/mac-address-generator/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/mac-address-generator/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | import { Devices } from '@vicons/tabler'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'MAC address generator', | ||||||
|  |   path: '/mac-address-generator', | ||||||
|  |   description: 'Enter the quantity and prefix. MAC addresses will be generated in your chosen case (uppercase or lowercase)', | ||||||
|  |   keywords: ['mac', 'address', 'generator', 'random', 'prefix'], | ||||||
|  |   component: () => import('./mac-address-generator.vue'), | ||||||
|  |   icon: Devices, | ||||||
|  |   createdAt: new Date('2023-11-31'), | ||||||
|  | }); | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | import { expect, test } from '@playwright/test'; | ||||||
|  | 
 | ||||||
|  | test.describe('Tool - MAC address generator', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('/mac-address-generator'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Has correct title', async ({ page }) => { | ||||||
|  |     await expect(page).toHaveTitle('MAC address generator - IT Tools'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										103
									
								
								src/tools/mac-address-generator/mac-address-generator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/tools/mac-address-generator/mac-address-generator.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | |||||||
|  | <script setup lang="ts"> | ||||||
|  | import _ from 'lodash'; | ||||||
|  | import { generateRandomMacAddress } from './mac-adress-generator.models'; | ||||||
|  | import { computedRefreshable } from '@/composable/computedRefreshable'; | ||||||
|  | import { useCopy } from '@/composable/copy'; | ||||||
|  | import { usePartialMacAddressValidation } from '@/utils/macAddress'; | ||||||
|  | 
 | ||||||
|  | const amount = useStorage('mac-address-generator-amount', 1); | ||||||
|  | const macAddressPrefix = useStorage('mac-address-generator-prefix', '64:16:7F'); | ||||||
|  | 
 | ||||||
|  | const prefixValidation = usePartialMacAddressValidation(macAddressPrefix); | ||||||
|  | 
 | ||||||
|  | const casesTransformers = [ | ||||||
|  |   { label: 'Uppercase', value: (value: string) => value.toUpperCase() }, | ||||||
|  |   { label: 'Lowercase', value: (value: string) => value.toLowerCase() }, | ||||||
|  | ]; | ||||||
|  | const caseTransformer = ref(casesTransformers[0].value); | ||||||
|  | 
 | ||||||
|  | const separators = [ | ||||||
|  |   { | ||||||
|  |     label: ':', | ||||||
|  |     value: ':', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: '-', | ||||||
|  |     value: '-', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: '.', | ||||||
|  |     value: '.', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: 'None', | ||||||
|  |     value: '', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | const separator = useStorage('mac-address-generator-separator', separators[0].value); | ||||||
|  | 
 | ||||||
|  | const [macAddresses, refreshMacAddresses] = computedRefreshable(() => { | ||||||
|  |   if (!prefixValidation.isValid) { | ||||||
|  |     return ''; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const ids = _.times(amount.value, () => caseTransformer.value(generateRandomMacAddress({ | ||||||
|  |     prefix: macAddressPrefix.value, | ||||||
|  |     separator: separator.value, | ||||||
|  |   }))); | ||||||
|  |   return ids.join('\n'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const { copy } = useCopy({ source: macAddresses, text: 'MAC addresses copied to the clipboard' }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div flex flex-col justify-center gap-2> | ||||||
|  |     <div flex items-center> | ||||||
|  |       <label w-150px pr-12px text-right> Quantity:</label> | ||||||
|  |       <n-input-number v-model:value="amount" min="1" max="100" flex-1 /> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <c-input-text | ||||||
|  |       v-model:value="macAddressPrefix" | ||||||
|  |       label="MAC address prefix:" | ||||||
|  |       placeholder="Set a prefix, e.g. 64:16:7F" | ||||||
|  |       clearable | ||||||
|  |       label-position="left" | ||||||
|  |       spellcheck="false" | ||||||
|  |       :validation="prefixValidation" | ||||||
|  |       raw-text | ||||||
|  |       label-width="150px" | ||||||
|  |       label-align="right" | ||||||
|  |     /> | ||||||
|  | 
 | ||||||
|  |     <c-buttons-select | ||||||
|  |       v-model:value="caseTransformer" | ||||||
|  |       :options="casesTransformers" | ||||||
|  |       label="Case:" | ||||||
|  |       label-width="150px" | ||||||
|  |       label-align="right" | ||||||
|  |     /> | ||||||
|  | 
 | ||||||
|  |     <c-buttons-select | ||||||
|  |       v-model:value="separator" | ||||||
|  |       :options="separators" | ||||||
|  |       label="Separator:" | ||||||
|  |       label-width="150px" | ||||||
|  |       label-align="right" | ||||||
|  |     /> | ||||||
|  | 
 | ||||||
|  |     <c-card mt-5 flex data-test-id="ulids"> | ||||||
|  |       <pre m-0 m-x-auto>{{ macAddresses }}</pre> | ||||||
|  |     </c-card> | ||||||
|  | 
 | ||||||
|  |     <div flex justify-center gap-2> | ||||||
|  |       <c-button data-test-id="refresh" @click="refreshMacAddresses()"> | ||||||
|  |         Refresh | ||||||
|  |       </c-button> | ||||||
|  |       <c-button @click="copy()"> | ||||||
|  |         Copy | ||||||
|  |       </c-button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
| @ -0,0 +1,43 @@ | |||||||
|  | import { describe, expect, it } from 'vitest'; | ||||||
|  | import { generateRandomMacAddress, splitPrefix } from './mac-adress-generator.models'; | ||||||
|  | 
 | ||||||
|  | describe('mac-adress-generator models', () => { | ||||||
|  |   describe('splitPrefix', () => { | ||||||
|  |     it('a mac address prefix is splitted around non hex characters', () => { | ||||||
|  |       expect(splitPrefix('')).toEqual([]); | ||||||
|  |       expect(splitPrefix('01')).toEqual(['01']); | ||||||
|  |       expect(splitPrefix('01:')).toEqual(['01']); | ||||||
|  |       expect(splitPrefix('01:23')).toEqual(['01', '23']); | ||||||
|  |       expect(splitPrefix('01-23')).toEqual(['01', '23']); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('when a prefix contains only hex characters, they are grouped by 2', () => { | ||||||
|  |       expect(splitPrefix('0123')).toEqual(['01', '23']); | ||||||
|  |       expect(splitPrefix('012345')).toEqual(['01', '23', '45']); | ||||||
|  |       expect(splitPrefix('0123456')).toEqual(['01', '23', '45', '06']); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('generateRandomMacAddress', () => { | ||||||
|  |     const createRandomByteGenerator = () => { | ||||||
|  |       let i = 0; | ||||||
|  |       return () => (i++).toString(16).padStart(2, '0'); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     it('generates a random mac address', () => { | ||||||
|  |       expect(generateRandomMacAddress({ getRandomByte: createRandomByteGenerator() })).toBe('00:01:02:03:04:05'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('generates a random mac address with a prefix', () => { | ||||||
|  |       expect(generateRandomMacAddress({ prefix: 'ff:ee:aa', getRandomByte: createRandomByteGenerator() })).toBe('ff:ee:aa:00:01:02'); | ||||||
|  |       expect(generateRandomMacAddress({ prefix: 'ff:ee:a', getRandomByte: createRandomByteGenerator() })).toBe('ff:ee:0a:00:01:02'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('generates a random mac address with a prefix and a different separator', () => { | ||||||
|  |       expect(generateRandomMacAddress({ prefix: 'ff-ee-aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02'); | ||||||
|  |       expect(generateRandomMacAddress({ prefix: 'ff:ee:aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02'); | ||||||
|  |       expect(generateRandomMacAddress({ prefix: 'ff-ee:aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02'); | ||||||
|  |       expect(generateRandomMacAddress({ prefix: 'ff ee:aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02'); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @ -0,0 +1,18 @@ | |||||||
|  | import _ from 'lodash'; | ||||||
|  | 
 | ||||||
|  | export { splitPrefix, generateRandomMacAddress }; | ||||||
|  | 
 | ||||||
|  | function splitPrefix(prefix: string): string[] { | ||||||
|  |   const base = prefix.match(/[^0-9a-f]/i) === null ? prefix.match(/.{1,2}/g) ?? [] : prefix.split(/[^0-9a-f]/i); | ||||||
|  | 
 | ||||||
|  |   return base.filter(Boolean).map(byte => byte.padStart(2, '0')); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function generateRandomMacAddress({ prefix: rawPrefix = '', separator = ':', getRandomByte = () => _.random(0, 255).toString(16).padStart(2, '0') }: { prefix?: string; separator?: string; getRandomByte?: () => string } = {}) { | ||||||
|  |   const prefix = splitPrefix(rawPrefix); | ||||||
|  | 
 | ||||||
|  |   const randomBytes = _.times(6 - prefix.length, getRandomByte); | ||||||
|  |   const bytes = [...prefix, ...randomBytes]; | ||||||
|  | 
 | ||||||
|  |   return bytes.join(separator); | ||||||
|  | } | ||||||
| @ -15,4 +15,18 @@ function macAddressValidation(value: Ref) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export { macAddressValidation, macAddressValidationRules }; | const partialMacAddressValidationRules = [ | ||||||
|  |   { | ||||||
|  |     message: 'Invalid partial MAC address', | ||||||
|  |     validator: (value: string) => value.trim().match(/^([0-9a-f]{2}[:\-. ]){0,5}([0-9a-f]{0,2})$/i), | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | function usePartialMacAddressValidation(value: Ref) { | ||||||
|  |   return useValidation({ | ||||||
|  |     source: value, | ||||||
|  |     rules: partialMacAddressValidationRules, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { macAddressValidation, macAddressValidationRules, usePartialMacAddressValidation, partialMacAddressValidationRules }; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user