mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-25 08:59:31 +02:00
Revert "feat: add random number and port generators with customizable options and validations"
This reverts commit b8d6924abc
.
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import { tool as numberRandomPortGenerator } from './random-port-generator/meta';
|
||||
import { tool as numberRandomNumberGenerator } from './random-number-generator/meta';
|
||||
import { tool as numberSum } from './sum/meta';
|
||||
import { tool as numberGenerate } from './generate/meta';
|
||||
import { tool as numberArithmeticSequence } from './arithmetic-sequence/meta';
|
||||
|
@@ -1,235 +0,0 @@
|
||||
import { Box, Alert, Chip } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import { generateRandomNumbers, validateInput, formatNumbers } from './service';
|
||||
import { InitialValuesType, RandomNumberResult } from './types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
count: 10,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
export default function RandomNumberGenerator({
|
||||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [result, setResult] = useState<RandomNumberResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [formattedResult, setFormattedResult] = useState<string>('');
|
||||
|
||||
const compute = (values: InitialValuesType) => {
|
||||
try {
|
||||
setError(null);
|
||||
setResult(null);
|
||||
setFormattedResult('');
|
||||
|
||||
// Validate input
|
||||
const validationError = validateInput(values);
|
||||
if (validationError) {
|
||||
setError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate random numbers
|
||||
const randomResult = generateRandomNumbers(values);
|
||||
setResult(randomResult);
|
||||
|
||||
// Format for display
|
||||
const formatted = formatNumbers(
|
||||
randomResult.numbers,
|
||||
values.separator,
|
||||
values.allowDecimals
|
||||
);
|
||||
setFormattedResult(formatted);
|
||||
} catch (err) {
|
||||
console.error('Random number generation failed:', err);
|
||||
setError(t('number:randomNumberGenerator.error.generationFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const getGroups: GetGroupsType<InitialValuesType> | null = ({
|
||||
values,
|
||||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: t('number:randomNumberGenerator.options.range.title'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.minValue.toString()}
|
||||
onOwnChange={(value) =>
|
||||
updateField('minValue', parseInt(value) || 1)
|
||||
}
|
||||
description={t(
|
||||
'number:randomNumberGenerator.options.range.minDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
'data-testid': 'min-value-input'
|
||||
}}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.maxValue.toString()}
|
||||
onOwnChange={(value) =>
|
||||
updateField('maxValue', parseInt(value) || 100)
|
||||
}
|
||||
description={t(
|
||||
'number:randomNumberGenerator.options.range.maxDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
'data-testid': 'max-value-input'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('number:randomNumberGenerator.options.generation.title'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.count.toString()}
|
||||
onOwnChange={(value) => updateField('count', parseInt(value) || 10)}
|
||||
description={t(
|
||||
'number:randomNumberGenerator.options.generation.countDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 10000,
|
||||
'data-testid': 'count-input'
|
||||
}}
|
||||
/>
|
||||
|
||||
<CheckboxWithDesc
|
||||
title={t(
|
||||
'number:randomNumberGenerator.options.generation.allowDecimals.title'
|
||||
)}
|
||||
checked={values.allowDecimals}
|
||||
onChange={(value) => updateField('allowDecimals', value)}
|
||||
description={t(
|
||||
'number:randomNumberGenerator.options.generation.allowDecimals.description'
|
||||
)}
|
||||
/>
|
||||
|
||||
<CheckboxWithDesc
|
||||
title={t(
|
||||
'number:randomNumberGenerator.options.generation.allowDuplicates.title'
|
||||
)}
|
||||
checked={values.allowDuplicates}
|
||||
onChange={(value) => updateField('allowDuplicates', value)}
|
||||
description={t(
|
||||
'number:randomNumberGenerator.options.generation.allowDuplicates.description'
|
||||
)}
|
||||
/>
|
||||
|
||||
<CheckboxWithDesc
|
||||
title={t(
|
||||
'number:randomNumberGenerator.options.generation.sortResults.title'
|
||||
)}
|
||||
checked={values.sortResults}
|
||||
onChange={(value) => updateField('sortResults', value)}
|
||||
description={t(
|
||||
'number:randomNumberGenerator.options.generation.sortResults.description'
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('number:randomNumberGenerator.options.output.title'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.separator}
|
||||
onOwnChange={(value) => updateField('separator', value)}
|
||||
description={t(
|
||||
'number:randomNumberGenerator.options.output.separatorDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
'data-testid': 'separator-input'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
initialValues={initialValues}
|
||||
compute={compute}
|
||||
getGroups={getGroups}
|
||||
resultComponent={
|
||||
<Box>
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{result && (
|
||||
<Box>
|
||||
<ToolTextResult
|
||||
title={t('number:randomNumberGenerator.result.title')}
|
||||
value={formattedResult}
|
||||
/>
|
||||
|
||||
<Box sx={{ mt: 2, display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||
<Chip
|
||||
label={`${t('number:randomNumberGenerator.result.range')}: ${
|
||||
result.min
|
||||
} - ${result.max}`}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
<Chip
|
||||
label={`${t('number:randomNumberGenerator.result.count')}: ${
|
||||
result.count
|
||||
}`}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
/>
|
||||
{result.hasDuplicates && (
|
||||
<Chip
|
||||
label={t(
|
||||
'number:randomNumberGenerator.result.hasDuplicates'
|
||||
)}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
/>
|
||||
)}
|
||||
{result.isSorted && (
|
||||
<Chip
|
||||
label={t('number:randomNumberGenerator.result.isSorted')}
|
||||
variant="outlined"
|
||||
color="success"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
toolInfo={{
|
||||
title: t('number:randomNumberGenerator.info.title'),
|
||||
description:
|
||||
longDescription || t('number:randomNumberGenerator.info.description')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('number', {
|
||||
i18n: {
|
||||
name: 'number:randomNumberGenerator.title',
|
||||
description: 'number:randomNumberGenerator.description',
|
||||
shortDescription: 'number:randomNumberGenerator.shortDescription',
|
||||
longDescription: 'number:randomNumberGenerator.longDescription'
|
||||
},
|
||||
path: 'random-number-generator',
|
||||
icon: 'mdi:dice-multiple',
|
||||
keywords: [
|
||||
'random',
|
||||
'number',
|
||||
'generator',
|
||||
'range',
|
||||
'min',
|
||||
'max',
|
||||
'integer',
|
||||
'decimal',
|
||||
'float'
|
||||
],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
@@ -1,248 +0,0 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { generateRandomNumbers, validateInput, formatNumbers } from './service';
|
||||
import { InitialValuesType } from './types';
|
||||
|
||||
describe('Random Number Generator Service', () => {
|
||||
describe('generateRandomNumbers', () => {
|
||||
it('should generate random numbers within the specified range', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
count: 5,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = generateRandomNumbers(options);
|
||||
|
||||
expect(result.numbers).toHaveLength(5);
|
||||
expect(result.min).toBe(1);
|
||||
expect(result.max).toBe(10);
|
||||
expect(result.count).toBe(5);
|
||||
|
||||
// Check that all numbers are within range
|
||||
result.numbers.forEach((num) => {
|
||||
expect(num).toBeGreaterThanOrEqual(1);
|
||||
expect(num).toBeLessThanOrEqual(10);
|
||||
expect(Number.isInteger(num)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate decimal numbers when allowDecimals is true', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 0,
|
||||
maxValue: 1,
|
||||
count: 3,
|
||||
allowDecimals: true,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = generateRandomNumbers(options);
|
||||
|
||||
expect(result.numbers).toHaveLength(3);
|
||||
|
||||
// Check that numbers are within range and can be decimals
|
||||
result.numbers.forEach((num) => {
|
||||
expect(num).toBeGreaterThanOrEqual(0);
|
||||
expect(num).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate unique numbers when allowDuplicates is false', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
count: 3,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: false,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = generateRandomNumbers(options);
|
||||
|
||||
expect(result.numbers).toHaveLength(3);
|
||||
|
||||
// Check for uniqueness
|
||||
const uniqueNumbers = new Set(result.numbers);
|
||||
expect(uniqueNumbers.size).toBe(3);
|
||||
});
|
||||
|
||||
it('should sort results when sortResults is true', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
count: 5,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: true,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = generateRandomNumbers(options);
|
||||
|
||||
expect(result.numbers).toHaveLength(5);
|
||||
expect(result.isSorted).toBe(true);
|
||||
|
||||
// Check that numbers are sorted
|
||||
for (let i = 1; i < result.numbers.length; i++) {
|
||||
expect(result.numbers[i]).toBeGreaterThanOrEqual(result.numbers[i - 1]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw error when minValue >= maxValue', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 10,
|
||||
maxValue: 5,
|
||||
count: 5,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
expect(() => generateRandomNumbers(options)).toThrow(
|
||||
'Minimum value must be less than maximum value'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when count <= 0', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
count: 0,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
expect(() => generateRandomNumbers(options)).toThrow(
|
||||
'Count must be greater than 0'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when unique count exceeds available range', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
count: 10,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: false,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
expect(() => generateRandomNumbers(options)).toThrow(
|
||||
'Cannot generate unique numbers: count exceeds available range'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateInput', () => {
|
||||
it('should return null for valid input', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
count: 5,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return error when minValue >= maxValue', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 10,
|
||||
maxValue: 5,
|
||||
count: 5,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Minimum value must be less than maximum value');
|
||||
});
|
||||
|
||||
it('should return error when count <= 0', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
count: 0,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Count must be greater than 0');
|
||||
});
|
||||
|
||||
it('should return error when count > 10000', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
count: 10001,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Count cannot exceed 10,000');
|
||||
});
|
||||
|
||||
it('should return error when range > 1000000', () => {
|
||||
const options: InitialValuesType = {
|
||||
minValue: 1,
|
||||
maxValue: 1000002,
|
||||
count: 5,
|
||||
allowDecimals: false,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Range cannot exceed 1,000,000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatNumbers', () => {
|
||||
it('should format integers correctly', () => {
|
||||
const numbers = [1, 2, 3, 4, 5];
|
||||
const result = formatNumbers(numbers, ', ', false);
|
||||
expect(result).toBe('1, 2, 3, 4, 5');
|
||||
});
|
||||
|
||||
it('should format decimals correctly', () => {
|
||||
const numbers = [1.5, 2.7, 3.2];
|
||||
const result = formatNumbers(numbers, ' | ', true);
|
||||
expect(result).toBe('1.50 | 2.70 | 3.20');
|
||||
});
|
||||
|
||||
it('should handle custom separators', () => {
|
||||
const numbers = [1, 2, 3];
|
||||
const result = formatNumbers(numbers, ' -> ', false);
|
||||
expect(result).toBe('1 -> 2 -> 3');
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
const numbers: number[] = [];
|
||||
const result = formatNumbers(numbers, ', ', false);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,157 +0,0 @@
|
||||
import { InitialValuesType, RandomNumberResult } from './types';
|
||||
|
||||
/**
|
||||
* Generate random numbers within a specified range
|
||||
*/
|
||||
export function generateRandomNumbers(
|
||||
options: InitialValuesType
|
||||
): RandomNumberResult {
|
||||
const {
|
||||
minValue,
|
||||
maxValue,
|
||||
count,
|
||||
allowDecimals,
|
||||
allowDuplicates,
|
||||
sortResults
|
||||
} = options;
|
||||
|
||||
if (minValue >= maxValue) {
|
||||
throw new Error('Minimum value must be less than maximum value');
|
||||
}
|
||||
|
||||
if (count <= 0) {
|
||||
throw new Error('Count must be greater than 0');
|
||||
}
|
||||
|
||||
if (!allowDuplicates && count > maxValue - minValue + 1) {
|
||||
throw new Error(
|
||||
'Cannot generate unique numbers: count exceeds available range'
|
||||
);
|
||||
}
|
||||
|
||||
const numbers: number[] = [];
|
||||
|
||||
if (allowDuplicates) {
|
||||
// Generate random numbers with possible duplicates
|
||||
for (let i = 0; i < count; i++) {
|
||||
const randomNumber = generateRandomNumber(
|
||||
minValue,
|
||||
maxValue,
|
||||
allowDecimals
|
||||
);
|
||||
numbers.push(randomNumber);
|
||||
}
|
||||
} else {
|
||||
// Generate unique random numbers
|
||||
const availableNumbers = new Set<number>();
|
||||
|
||||
// Create a pool of available numbers
|
||||
for (let i = minValue; i <= maxValue; i++) {
|
||||
if (allowDecimals) {
|
||||
// For decimals, we need to generate more granular values
|
||||
for (let j = 0; j < 100; j++) {
|
||||
availableNumbers.add(i + j / 100);
|
||||
}
|
||||
} else {
|
||||
availableNumbers.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
const availableArray = Array.from(availableNumbers);
|
||||
|
||||
// Shuffle the available numbers
|
||||
for (let i = availableArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[availableArray[i], availableArray[j]] = [
|
||||
availableArray[j],
|
||||
availableArray[i]
|
||||
];
|
||||
}
|
||||
|
||||
// Take the first 'count' numbers
|
||||
for (let i = 0; i < Math.min(count, availableArray.length); i++) {
|
||||
numbers.push(availableArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort if requested
|
||||
if (sortResults) {
|
||||
numbers.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
return {
|
||||
numbers,
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
count,
|
||||
hasDuplicates: !allowDuplicates && hasDuplicatesInArray(numbers),
|
||||
isSorted: sortResults
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a single random number within the specified range
|
||||
*/
|
||||
function generateRandomNumber(
|
||||
min: number,
|
||||
max: number,
|
||||
allowDecimals: boolean
|
||||
): number {
|
||||
if (allowDecimals) {
|
||||
return Math.random() * (max - min) + min;
|
||||
} else {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an array has duplicate values
|
||||
*/
|
||||
function hasDuplicatesInArray(arr: number[]): boolean {
|
||||
const seen = new Set<number>();
|
||||
for (const num of arr) {
|
||||
if (seen.has(num)) {
|
||||
return true;
|
||||
}
|
||||
seen.add(num);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format numbers for display
|
||||
*/
|
||||
export function formatNumbers(
|
||||
numbers: number[],
|
||||
separator: string,
|
||||
allowDecimals: boolean
|
||||
): string {
|
||||
return numbers
|
||||
.map((num) => (allowDecimals ? num.toFixed(2) : Math.round(num).toString()))
|
||||
.join(separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input parameters
|
||||
*/
|
||||
export function validateInput(options: InitialValuesType): string | null {
|
||||
const { minValue, maxValue, count } = options;
|
||||
|
||||
if (minValue >= maxValue) {
|
||||
return 'Minimum value must be less than maximum value';
|
||||
}
|
||||
|
||||
if (count <= 0) {
|
||||
return 'Count must be greater than 0';
|
||||
}
|
||||
|
||||
if (count > 10000) {
|
||||
return 'Count cannot exceed 10,000';
|
||||
}
|
||||
|
||||
if (maxValue - minValue > 1000000) {
|
||||
return 'Range cannot exceed 1,000,000';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
export type InitialValuesType = {
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
count: number;
|
||||
allowDecimals: boolean;
|
||||
allowDuplicates: boolean;
|
||||
sortResults: boolean;
|
||||
separator: string;
|
||||
};
|
||||
|
||||
export type RandomNumberResult = {
|
||||
numbers: number[];
|
||||
min: number;
|
||||
max: number;
|
||||
count: number;
|
||||
hasDuplicates: boolean;
|
||||
isSorted: boolean;
|
||||
};
|
@@ -1,295 +0,0 @@
|
||||
import { Box, Alert, Chip } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import {
|
||||
generateRandomPorts,
|
||||
validateInput,
|
||||
formatPorts,
|
||||
getPortRangeInfo,
|
||||
isCommonPort,
|
||||
getPortService
|
||||
} from './service';
|
||||
import { InitialValuesType, RandomPortResult } from './types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import RadioWithTextField from '@components/options/RadioWithTextField';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
portRange: 'registered',
|
||||
minPort: 1024,
|
||||
maxPort: 49151,
|
||||
count: 5,
|
||||
allowDuplicates: false,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
export default function RandomPortGenerator({
|
||||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [result, setResult] = useState<RandomPortResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [formattedResult, setFormattedResult] = useState<string>('');
|
||||
|
||||
const compute = (values: InitialValuesType) => {
|
||||
try {
|
||||
setError(null);
|
||||
setResult(null);
|
||||
setFormattedResult('');
|
||||
|
||||
// Validate input
|
||||
const validationError = validateInput(values);
|
||||
if (validationError) {
|
||||
setError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate random ports
|
||||
const randomResult = generateRandomPorts(values);
|
||||
setResult(randomResult);
|
||||
|
||||
// Format for display
|
||||
const formatted = formatPorts(randomResult.ports, values.separator);
|
||||
setFormattedResult(formatted);
|
||||
} catch (err) {
|
||||
console.error('Random port generation failed:', err);
|
||||
setError(t('number:randomPortGenerator.error.generationFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const getGroups: GetGroupsType<InitialValuesType> | null = ({
|
||||
values,
|
||||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: t('number:randomPortGenerator.options.range.title'),
|
||||
component: (
|
||||
<Box>
|
||||
<RadioWithTextField
|
||||
value={values.portRange}
|
||||
onTextChange={(value: any) => updateField('portRange', value)}
|
||||
options={[
|
||||
{
|
||||
value: 'well-known',
|
||||
label: t('number:randomPortGenerator.options.range.wellKnown')
|
||||
},
|
||||
{
|
||||
value: 'registered',
|
||||
label: t('number:randomPortGenerator.options.range.registered')
|
||||
},
|
||||
{
|
||||
value: 'dynamic',
|
||||
label: t('number:randomPortGenerator.options.range.dynamic')
|
||||
},
|
||||
{
|
||||
value: 'custom',
|
||||
label: t('number:randomPortGenerator.options.range.custom')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{values.portRange === 'custom' && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TextFieldWithDesc
|
||||
value={values.minPort.toString()}
|
||||
onOwnChange={(value) =>
|
||||
updateField('minPort', parseInt(value) || 1024)
|
||||
}
|
||||
description={t(
|
||||
'number:randomPortGenerator.options.range.minPortDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 65535,
|
||||
'data-testid': 'min-port-input'
|
||||
}}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.maxPort.toString()}
|
||||
onOwnChange={(value) =>
|
||||
updateField('maxPort', parseInt(value) || 49151)
|
||||
}
|
||||
description={t(
|
||||
'number:randomPortGenerator.options.range.maxPortDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 65535,
|
||||
'data-testid': 'max-port-input'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{ mt: 2, p: 2, bgcolor: 'background.paper', borderRadius: 1 }}
|
||||
>
|
||||
<strong>{getPortRangeInfo(values.portRange).name}</strong>
|
||||
<br />
|
||||
{getPortRangeInfo(values.portRange).description}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('number:randomPortGenerator.options.generation.title'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.count.toString()}
|
||||
onOwnChange={(value) => updateField('count', parseInt(value) || 5)}
|
||||
description={t(
|
||||
'number:randomPortGenerator.options.generation.countDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 1000,
|
||||
'data-testid': 'count-input'
|
||||
}}
|
||||
/>
|
||||
|
||||
<CheckboxWithDesc
|
||||
title={t(
|
||||
'number:randomPortGenerator.options.generation.allowDuplicates.title'
|
||||
)}
|
||||
checked={values.allowDuplicates}
|
||||
onChange={(value) => updateField('allowDuplicates', value)}
|
||||
description={t(
|
||||
'number:randomPortGenerator.options.generation.allowDuplicates.description'
|
||||
)}
|
||||
/>
|
||||
|
||||
<CheckboxWithDesc
|
||||
title={t(
|
||||
'number:randomPortGenerator.options.generation.sortResults.title'
|
||||
)}
|
||||
checked={values.sortResults}
|
||||
onChange={(value) => updateField('sortResults', value)}
|
||||
description={t(
|
||||
'number:randomPortGenerator.options.generation.sortResults.description'
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('number:randomPortGenerator.options.output.title'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.separator}
|
||||
onOwnChange={(value) => updateField('separator', value)}
|
||||
description={t(
|
||||
'number:randomPortGenerator.options.output.separatorDescription'
|
||||
)}
|
||||
inputProps={{
|
||||
'data-testid': 'separator-input'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
initialValues={initialValues}
|
||||
compute={compute}
|
||||
getGroups={getGroups}
|
||||
resultComponent={
|
||||
<Box>
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{result && (
|
||||
<Box>
|
||||
<ToolTextResult
|
||||
title={t('number:randomPortGenerator.result.title')}
|
||||
value={formattedResult}
|
||||
/>
|
||||
|
||||
<Box sx={{ mt: 2, display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||
<Chip
|
||||
label={`${t('number:randomPortGenerator.result.range')}: ${
|
||||
result.range.min
|
||||
} - ${result.range.max}`}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
<Chip
|
||||
label={`${t('number:randomPortGenerator.result.count')}: ${
|
||||
result.count
|
||||
}`}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
/>
|
||||
{result.hasDuplicates && (
|
||||
<Chip
|
||||
label={t('number:randomPortGenerator.result.hasDuplicates')}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
/>
|
||||
)}
|
||||
{result.isSorted && (
|
||||
<Chip
|
||||
label={t('number:randomPortGenerator.result.isSorted')}
|
||||
variant="outlined"
|
||||
color="success"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{result.ports.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<strong>
|
||||
{t('number:randomPortGenerator.result.portDetails')}:
|
||||
</strong>
|
||||
<Box
|
||||
sx={{ mt: 1, display: 'flex', gap: 1, flexWrap: 'wrap' }}
|
||||
>
|
||||
{result.ports.slice(0, 10).map((port, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={`${port}${
|
||||
isCommonPort(port) ? ` (${getPortService(port)})` : ''
|
||||
}`}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
color={isCommonPort(port) ? 'warning' : 'default'}
|
||||
/>
|
||||
))}
|
||||
{result.ports.length > 10 && (
|
||||
<Chip
|
||||
label={`+${result.ports.length - 10} more`}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
toolInfo={{
|
||||
title: t('number:randomPortGenerator.info.title'),
|
||||
description:
|
||||
longDescription || t('number:randomPortGenerator.info.description')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('number', {
|
||||
i18n: {
|
||||
name: 'number:randomPortGenerator.title',
|
||||
description: 'number:randomPortGenerator.description',
|
||||
shortDescription: 'number:randomPortGenerator.shortDescription',
|
||||
longDescription: 'number:randomPortGenerator.longDescription'
|
||||
},
|
||||
path: 'random-port-generator',
|
||||
icon: 'mdi:network',
|
||||
keywords: [
|
||||
'random',
|
||||
'port',
|
||||
'generator',
|
||||
'network',
|
||||
'tcp',
|
||||
'udp',
|
||||
'server',
|
||||
'client',
|
||||
'development'
|
||||
],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
@@ -1,315 +0,0 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import {
|
||||
generateRandomPorts,
|
||||
validateInput,
|
||||
formatPorts,
|
||||
getPortRangeInfo,
|
||||
isCommonPort,
|
||||
getPortService,
|
||||
PORT_RANGES
|
||||
} from './service';
|
||||
import { InitialValuesType } from './types';
|
||||
|
||||
describe('Random Port Generator Service', () => {
|
||||
describe('generateRandomPorts', () => {
|
||||
it('should generate random ports within the well-known range', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'well-known',
|
||||
minPort: 1,
|
||||
maxPort: 1023,
|
||||
count: 5,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = generateRandomPorts(options);
|
||||
|
||||
expect(result.ports).toHaveLength(5);
|
||||
expect(result.range.min).toBe(1);
|
||||
expect(result.range.max).toBe(1023);
|
||||
expect(result.count).toBe(5);
|
||||
|
||||
// Check that all ports are within range
|
||||
result.ports.forEach((port) => {
|
||||
expect(port).toBeGreaterThanOrEqual(1);
|
||||
expect(port).toBeLessThanOrEqual(1023);
|
||||
expect(Number.isInteger(port)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate random ports within the registered range', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'registered',
|
||||
minPort: 1024,
|
||||
maxPort: 49151,
|
||||
count: 3,
|
||||
allowDuplicates: false,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = generateRandomPorts(options);
|
||||
|
||||
expect(result.ports).toHaveLength(3);
|
||||
expect(result.range.min).toBe(1024);
|
||||
expect(result.range.max).toBe(49151);
|
||||
|
||||
// Check for uniqueness
|
||||
const uniquePorts = new Set(result.ports);
|
||||
expect(uniquePorts.size).toBe(3);
|
||||
});
|
||||
|
||||
it('should generate random ports within custom range', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'custom',
|
||||
minPort: 8000,
|
||||
maxPort: 8100,
|
||||
count: 4,
|
||||
allowDuplicates: true,
|
||||
sortResults: true,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = generateRandomPorts(options);
|
||||
|
||||
expect(result.ports).toHaveLength(4);
|
||||
expect(result.range.min).toBe(8000);
|
||||
expect(result.range.max).toBe(8100);
|
||||
expect(result.isSorted).toBe(true);
|
||||
|
||||
// Check that numbers are sorted
|
||||
for (let i = 1; i < result.ports.length; i++) {
|
||||
expect(result.ports[i]).toBeGreaterThanOrEqual(result.ports[i - 1]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw error when minPort >= maxPort', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'custom',
|
||||
minPort: 1000,
|
||||
maxPort: 500,
|
||||
count: 5,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
expect(() => generateRandomPorts(options)).toThrow(
|
||||
'Minimum port must be less than maximum port'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when count <= 0', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'registered',
|
||||
minPort: 1024,
|
||||
maxPort: 49151,
|
||||
count: 0,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
expect(() => generateRandomPorts(options)).toThrow(
|
||||
'Count must be greater than 0'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when ports are outside valid range', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'custom',
|
||||
minPort: 0,
|
||||
maxPort: 70000,
|
||||
count: 5,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
expect(() => generateRandomPorts(options)).toThrow(
|
||||
'Ports must be between 1 and 65535'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when unique count exceeds available range', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'custom',
|
||||
minPort: 1,
|
||||
maxPort: 5,
|
||||
count: 10,
|
||||
allowDuplicates: false,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
expect(() => generateRandomPorts(options)).toThrow(
|
||||
'Cannot generate unique ports: count exceeds available range'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateInput', () => {
|
||||
it('should return null for valid input', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'registered',
|
||||
minPort: 1024,
|
||||
maxPort: 49151,
|
||||
count: 5,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return error when count <= 0', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'registered',
|
||||
minPort: 1024,
|
||||
maxPort: 49151,
|
||||
count: 0,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Count must be greater than 0');
|
||||
});
|
||||
|
||||
it('should return error when count > 1000', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'registered',
|
||||
minPort: 1024,
|
||||
maxPort: 49151,
|
||||
count: 1001,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Count cannot exceed 1,000');
|
||||
});
|
||||
|
||||
it('should return error when custom range has invalid ports', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'custom',
|
||||
minPort: 0,
|
||||
maxPort: 70000,
|
||||
count: 5,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Ports must be between 1 and 65535');
|
||||
});
|
||||
|
||||
it('should return error when custom range has minPort >= maxPort', () => {
|
||||
const options: InitialValuesType = {
|
||||
portRange: 'custom',
|
||||
minPort: 1000,
|
||||
maxPort: 500,
|
||||
count: 5,
|
||||
allowDuplicates: true,
|
||||
sortResults: false,
|
||||
separator: ', '
|
||||
};
|
||||
|
||||
const result = validateInput(options);
|
||||
expect(result).toBe('Minimum port must be less than maximum port');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatPorts', () => {
|
||||
it('should format ports correctly', () => {
|
||||
const ports = [80, 443, 8080, 3000];
|
||||
const result = formatPorts(ports, ', ');
|
||||
expect(result).toBe('80, 443, 8080, 3000');
|
||||
});
|
||||
|
||||
it('should handle custom separators', () => {
|
||||
const ports = [80, 443, 8080];
|
||||
const result = formatPorts(ports, ' -> ');
|
||||
expect(result).toBe('80 -> 443 -> 8080');
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
const ports: number[] = [];
|
||||
const result = formatPorts(ports, ', ');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPortRangeInfo', () => {
|
||||
it('should return correct port range info for well-known', () => {
|
||||
const result = getPortRangeInfo('well-known');
|
||||
expect(result.name).toBe('Well-Known Ports');
|
||||
expect(result.min).toBe(1);
|
||||
expect(result.max).toBe(1023);
|
||||
});
|
||||
|
||||
it('should return correct port range info for registered', () => {
|
||||
const result = getPortRangeInfo('registered');
|
||||
expect(result.name).toBe('Registered Ports');
|
||||
expect(result.min).toBe(1024);
|
||||
expect(result.max).toBe(49151);
|
||||
});
|
||||
|
||||
it('should return correct port range info for dynamic', () => {
|
||||
const result = getPortRangeInfo('dynamic');
|
||||
expect(result.name).toBe('Dynamic Ports');
|
||||
expect(result.min).toBe(49152);
|
||||
expect(result.max).toBe(65535);
|
||||
});
|
||||
|
||||
it('should return custom range for unknown range', () => {
|
||||
const result = getPortRangeInfo('unknown');
|
||||
expect(result.name).toBe('Custom Range');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isCommonPort', () => {
|
||||
it('should identify common ports correctly', () => {
|
||||
expect(isCommonPort(80)).toBe(true);
|
||||
expect(isCommonPort(443)).toBe(true);
|
||||
expect(isCommonPort(22)).toBe(true);
|
||||
expect(isCommonPort(3306)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for uncommon ports', () => {
|
||||
expect(isCommonPort(12345)).toBe(false);
|
||||
expect(isCommonPort(54321)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPortService', () => {
|
||||
it('should return correct service names for common ports', () => {
|
||||
expect(getPortService(80)).toBe('HTTP');
|
||||
expect(getPortService(443)).toBe('HTTPS');
|
||||
expect(getPortService(22)).toBe('SSH');
|
||||
expect(getPortService(3306)).toBe('MySQL');
|
||||
});
|
||||
|
||||
it('should return "Unknown" for uncommon ports', () => {
|
||||
expect(getPortService(12345)).toBe('Unknown');
|
||||
expect(getPortService(54321)).toBe('Unknown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PORT_RANGES', () => {
|
||||
it('should have correct port range definitions', () => {
|
||||
expect(PORT_RANGES['well-known'].min).toBe(1);
|
||||
expect(PORT_RANGES['well-known'].max).toBe(1023);
|
||||
expect(PORT_RANGES['registered'].min).toBe(1024);
|
||||
expect(PORT_RANGES['registered'].max).toBe(49151);
|
||||
expect(PORT_RANGES['dynamic'].min).toBe(49152);
|
||||
expect(PORT_RANGES['dynamic'].max).toBe(65535);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,214 +0,0 @@
|
||||
import { InitialValuesType, RandomPortResult, PortRange } from './types';
|
||||
|
||||
// Standard port ranges according to IANA
|
||||
export const PORT_RANGES: Record<string, PortRange> = {
|
||||
'well-known': {
|
||||
name: 'Well-Known Ports',
|
||||
min: 1,
|
||||
max: 1023,
|
||||
description:
|
||||
'System ports (1-1023) - Reserved for common services like HTTP, HTTPS, SSH, etc.'
|
||||
},
|
||||
registered: {
|
||||
name: 'Registered Ports',
|
||||
min: 1024,
|
||||
max: 49151,
|
||||
description:
|
||||
'User ports (1024-49151) - Available for applications and services'
|
||||
},
|
||||
dynamic: {
|
||||
name: 'Dynamic Ports',
|
||||
min: 49152,
|
||||
max: 65535,
|
||||
description:
|
||||
'Private ports (49152-65535) - Available for temporary or private use'
|
||||
},
|
||||
custom: {
|
||||
name: 'Custom Range',
|
||||
min: 1,
|
||||
max: 65535,
|
||||
description: 'Custom port range - Specify your own min and max values'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate random network ports within a specified range
|
||||
*/
|
||||
export function generateRandomPorts(
|
||||
options: InitialValuesType
|
||||
): RandomPortResult {
|
||||
const { portRange, minPort, maxPort, count, allowDuplicates, sortResults } =
|
||||
options;
|
||||
|
||||
// Get the appropriate port range
|
||||
const range = PORT_RANGES[portRange];
|
||||
const actualMin = portRange === 'custom' ? minPort : range.min;
|
||||
const actualMax = portRange === 'custom' ? maxPort : range.max;
|
||||
|
||||
if (actualMin >= actualMax) {
|
||||
throw new Error('Minimum port must be less than maximum port');
|
||||
}
|
||||
|
||||
if (count <= 0) {
|
||||
throw new Error('Count must be greater than 0');
|
||||
}
|
||||
|
||||
if (actualMin < 1 || actualMax > 65535) {
|
||||
throw new Error('Ports must be between 1 and 65535');
|
||||
}
|
||||
|
||||
if (!allowDuplicates && count > actualMax - actualMin + 1) {
|
||||
throw new Error(
|
||||
'Cannot generate unique ports: count exceeds available range'
|
||||
);
|
||||
}
|
||||
|
||||
const ports: number[] = [];
|
||||
|
||||
if (allowDuplicates) {
|
||||
// Generate random ports with possible duplicates
|
||||
for (let i = 0; i < count; i++) {
|
||||
const randomPort = generateRandomPort(actualMin, actualMax);
|
||||
ports.push(randomPort);
|
||||
}
|
||||
} else {
|
||||
// Generate unique random ports
|
||||
const availablePorts = new Set<number>();
|
||||
|
||||
// Create a pool of available ports
|
||||
for (let i = actualMin; i <= actualMax; i++) {
|
||||
availablePorts.add(i);
|
||||
}
|
||||
|
||||
const availableArray = Array.from(availablePorts);
|
||||
|
||||
// Shuffle the available ports
|
||||
for (let i = availableArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[availableArray[i], availableArray[j]] = [
|
||||
availableArray[j],
|
||||
availableArray[i]
|
||||
];
|
||||
}
|
||||
|
||||
// Take the first 'count' ports
|
||||
for (let i = 0; i < Math.min(count, availableArray.length); i++) {
|
||||
ports.push(availableArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort if requested
|
||||
if (sortResults) {
|
||||
ports.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
return {
|
||||
ports,
|
||||
range: {
|
||||
...range,
|
||||
min: actualMin,
|
||||
max: actualMax
|
||||
},
|
||||
count,
|
||||
hasDuplicates: !allowDuplicates && hasDuplicatesInArray(ports),
|
||||
isSorted: sortResults
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a single random port within the specified range
|
||||
*/
|
||||
function generateRandomPort(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an array has duplicate values
|
||||
*/
|
||||
function hasDuplicatesInArray(arr: number[]): boolean {
|
||||
const seen = new Set<number>();
|
||||
for (const num of arr) {
|
||||
if (seen.has(num)) {
|
||||
return true;
|
||||
}
|
||||
seen.add(num);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format ports for display
|
||||
*/
|
||||
export function formatPorts(ports: number[], separator: string): string {
|
||||
return ports.map((port) => port.toString()).join(separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input parameters
|
||||
*/
|
||||
export function validateInput(options: InitialValuesType): string | null {
|
||||
const { portRange, minPort, maxPort, count } = options;
|
||||
|
||||
if (count <= 0) {
|
||||
return 'Count must be greater than 0';
|
||||
}
|
||||
|
||||
if (count > 1000) {
|
||||
return 'Count cannot exceed 1,000';
|
||||
}
|
||||
|
||||
if (portRange === 'custom') {
|
||||
if (minPort >= maxPort) {
|
||||
return 'Minimum port must be less than maximum port';
|
||||
}
|
||||
|
||||
if (minPort < 1 || maxPort > 65535) {
|
||||
return 'Ports must be between 1 and 65535';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get port range information
|
||||
*/
|
||||
export function getPortRangeInfo(portRange: string): PortRange {
|
||||
return PORT_RANGES[portRange] || PORT_RANGES['custom'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a port is commonly used
|
||||
*/
|
||||
export function isCommonPort(port: number): boolean {
|
||||
const commonPorts = [
|
||||
20, 21, 22, 23, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080
|
||||
];
|
||||
return commonPorts.includes(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get port service information
|
||||
*/
|
||||
export function getPortService(port: number): string {
|
||||
const portServices: Record<number, string> = {
|
||||
20: 'FTP Data',
|
||||
21: 'FTP Control',
|
||||
22: 'SSH',
|
||||
23: 'Telnet',
|
||||
25: 'SMTP',
|
||||
53: 'DNS',
|
||||
80: 'HTTP',
|
||||
110: 'POP3',
|
||||
143: 'IMAP',
|
||||
443: 'HTTPS',
|
||||
993: 'IMAPS',
|
||||
995: 'POP3S',
|
||||
3306: 'MySQL',
|
||||
5432: 'PostgreSQL',
|
||||
6379: 'Redis',
|
||||
8080: 'HTTP Alternative'
|
||||
};
|
||||
|
||||
return portServices[port] || 'Unknown';
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
export type InitialValuesType = {
|
||||
portRange: 'well-known' | 'registered' | 'dynamic' | 'custom';
|
||||
minPort: number;
|
||||
maxPort: number;
|
||||
count: number;
|
||||
allowDuplicates: boolean;
|
||||
sortResults: boolean;
|
||||
separator: string;
|
||||
};
|
||||
|
||||
export type PortRange = {
|
||||
name: string;
|
||||
min: number;
|
||||
max: number;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type RandomPortResult = {
|
||||
ports: number[];
|
||||
range: PortRange;
|
||||
count: number;
|
||||
hasDuplicates: boolean;
|
||||
isSorted: boolean;
|
||||
};
|
Reference in New Issue
Block a user