mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-05 22:07:10 -04:00
WIP(translate): translate ulid-generator, encryption, bip39-generator, hmac-generator, rsa-key-pair-generator, password-strength-analyser and pdf-signature-checker tools
This commit is contained in:
parent
70515d32a5
commit
2ee3b01105
30 changed files with 383 additions and 88 deletions
|
@ -21,6 +21,7 @@ const messages = _.merge(
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
|
fallbackLocale: 'en',
|
||||||
messages,
|
messages,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,6 +33,5 @@ export const i18nPlugin: Plugin = {
|
||||||
|
|
||||||
export const translate = function (localeKey: string) {
|
export const translate = function (localeKey: string) {
|
||||||
// @ts-expect-error global
|
// @ts-expect-error global
|
||||||
const hasKey = i18n.global.te(localeKey, i18n.global.locale);
|
return i18n.global.t(localeKey);
|
||||||
return hasKey ? i18n.global.t(localeKey) : localeKey;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,6 +36,7 @@ const languages = {
|
||||||
|
|
||||||
const entropy = ref(generateEntropy());
|
const entropy = ref(generateEntropy());
|
||||||
const passphraseInput = ref('');
|
const passphraseInput = ref('');
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const language = ref<keyof typeof languages>('English');
|
const language = ref<keyof typeof languages>('English');
|
||||||
const passphrase = computed({
|
const passphrase = computed({
|
||||||
|
@ -53,11 +54,11 @@ const entropyValidation = useValidation({
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
validator: value => value === '' || (value.length <= 32 && value.length >= 16 && value.length % 4 === 0),
|
validator: value => value === '' || (value.length <= 32 && value.length >= 16 && value.length % 4 === 0),
|
||||||
message: 'Entropy length should be >= 16, <= 32 and be a multiple of 4',
|
message: t('tools.bip39-generator.validation.lengthError'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: value => /^[a-fA-F0-9]*$/.test(value),
|
validator: value => /^[a-fA-F0-9]*$/.test(value),
|
||||||
message: 'Entropy should be an hexadecimal string',
|
message: t('tools.bip39-generator.validation.stringTypeError'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -67,7 +68,7 @@ const mnemonicValidation = useValidation({
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
validator: value => isNotThrowing(() => mnemonicToEntropy(value, languages[language.value])),
|
validator: value => isNotThrowing(() => mnemonicToEntropy(value, languages[language.value])),
|
||||||
message: 'Invalid mnemonic',
|
message: t('tools.bip39-generator.validation.mnemonicError'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -76,8 +77,8 @@ function refreshEntropy() {
|
||||||
entropy.value = generateEntropy();
|
entropy.value = generateEntropy();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { copy: copyEntropy } = useCopy({ source: entropy, text: 'Entropy copied to the clipboard' });
|
const { copy: copyEntropy } = useCopy({ source: entropy, text: t('tools.bip39-generator.copied.entropy') });
|
||||||
const { copy: copyPassphrase } = useCopy({ source: passphrase, text: 'Passphrase copied to the clipboard' });
|
const { copy: copyPassphrase } = useCopy({ source: passphrase, text: t('tools.bip39-generator.copied.passphrase') });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -87,18 +88,18 @@ const { copy: copyPassphrase } = useCopy({ source: passphrase, text: 'Passphrase
|
||||||
<c-select
|
<c-select
|
||||||
v-model:value="language"
|
v-model:value="language"
|
||||||
searchable
|
searchable
|
||||||
label="Language:"
|
:label="t('tools.bip39-generator.languageLabel')"
|
||||||
:options="Object.keys(languages)"
|
:options="Object.keys(languages)"
|
||||||
/>
|
/>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi span="2">
|
<n-gi span="2">
|
||||||
<n-form-item
|
<n-form-item
|
||||||
label="Entropy (seed):"
|
:label="t('tools.bip39-generator.entropyLabel')"
|
||||||
:feedback="entropyValidation.message"
|
:feedback="entropyValidation.message"
|
||||||
:validation-status="entropyValidation.status"
|
:validation-status="entropyValidation.status"
|
||||||
>
|
>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<c-input-text v-model:value="entropy" placeholder="Your string..." />
|
<c-input-text v-model:value="entropy" :placeholder="t('tools.bip39-generator.entropyPlaceholder')" />
|
||||||
|
|
||||||
<c-button @click="refreshEntropy()">
|
<c-button @click="refreshEntropy()">
|
||||||
<n-icon size="22">
|
<n-icon size="22">
|
||||||
|
@ -115,12 +116,12 @@ const { copy: copyPassphrase } = useCopy({ source: passphrase, text: 'Passphrase
|
||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
<n-form-item
|
<n-form-item
|
||||||
label="Passphrase (mnemonic):"
|
:label="t('tools.bip39-generator.passphraseLabel')"
|
||||||
:feedback="mnemonicValidation.message"
|
:feedback="mnemonicValidation.message"
|
||||||
:validation-status="mnemonicValidation.status"
|
:validation-status="mnemonicValidation.status"
|
||||||
>
|
>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<c-input-text v-model:value="passphrase" placeholder="Your mnemonic..." raw-text />
|
<c-input-text v-model:value="passphrase" :placeholder="t('tools.bip39-generator.passphrasePlaceholder')" raw-text />
|
||||||
|
|
||||||
<c-button @click="copyPassphrase()">
|
<c-button @click="copyPassphrase()">
|
||||||
<n-icon size="22" :component="Copy" />
|
<n-icon size="22" :component="Copy" />
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { AlignJustified } from '@vicons/tabler';
|
import { AlignJustified } from '@vicons/tabler';
|
||||||
import { defineTool } from '../tool';
|
import { defineTool } from '../tool';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'BIP39 passphrase generator',
|
name: translate('tools.bip39-generator.title'),
|
||||||
path: '/bip39-generator',
|
path: '/bip39-generator',
|
||||||
description: 'Generate BIP39 passphrase from existing or random mnemonic, or get the mnemonic from the passphrase.',
|
description: translate('tools.bip39-generator.description'),
|
||||||
keywords: ['BIP39', 'passphrase', 'generator', 'mnemonic', 'entropy'],
|
keywords: ['BIP39', 'passphrase', 'generator', 'mnemonic', 'entropy'],
|
||||||
component: () => import('./bip39-generator.vue'),
|
component: () => import('./bip39-generator.vue'),
|
||||||
icon: AlignJustified,
|
icon: AlignJustified,
|
||||||
|
|
18
src/tools/bip39-generator/locales/en.yml
Normal file
18
src/tools/bip39-generator/locales/en.yml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
tools:
|
||||||
|
bip39-generator:
|
||||||
|
title: BIP39 passphrase generator
|
||||||
|
description: Generate BIP39 passphrase from existing or random mnemonic, or get the mnemonic from the passphrase.
|
||||||
|
|
||||||
|
languageLabel: 'Language:'
|
||||||
|
entropyLabel: 'Entropy (seed):'
|
||||||
|
entropyPlaceholder: Your string...
|
||||||
|
passphraseLabel: 'Passphrase (mnemonic):'
|
||||||
|
passphrasePlaceholder: Your mnemonic...
|
||||||
|
|
||||||
|
validation:
|
||||||
|
lengthError: 'Entropy length should be >= 16, <= 32 and be a multiple of 4'
|
||||||
|
stringTypeError: Entropy should be an hexadecimal string
|
||||||
|
mnemonicError: Invalid mnemonic
|
||||||
|
copied:
|
||||||
|
entropy: Entropy copied to the clipboard
|
||||||
|
passphrase: Passphrase copied to the clipboard
|
18
src/tools/bip39-generator/locales/zh.yml
Normal file
18
src/tools/bip39-generator/locales/zh.yml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
tools:
|
||||||
|
bip39-generator:
|
||||||
|
title: BIP39 助记词生成器
|
||||||
|
description: 从现有或随机助记词生成 BIP39 助记词,或从助记词获取助记词。
|
||||||
|
|
||||||
|
languageLabel: '语言:'
|
||||||
|
entropyLabel: '熵(种子):'
|
||||||
|
entropyPlaceholder: 您的字符串...
|
||||||
|
passphraseLabel: '助记词(口令):'
|
||||||
|
passphrasePlaceholder: 您的助记词...
|
||||||
|
|
||||||
|
validation:
|
||||||
|
lengthError: '熵的长度应为 >= 16,<= 32,并且是4的倍数'
|
||||||
|
stringTypeError: 熵应为十六进制字符串
|
||||||
|
mnemonicError: 无效的助记词
|
||||||
|
copied:
|
||||||
|
entropy: 熵已复制到剪贴板
|
||||||
|
passphrase: 助记词已复制到剪贴板
|
|
@ -4,45 +4,47 @@ import { computedCatch } from '@/composable/computed/catchedComputed';
|
||||||
|
|
||||||
const algos = { AES, TripleDES, Rabbit, RC4 };
|
const algos = { AES, TripleDES, Rabbit, RC4 };
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const cypherInput = ref('Lorem ipsum dolor sit amet');
|
const cypherInput = ref('Lorem ipsum dolor sit amet');
|
||||||
const cypherAlgo = ref<keyof typeof algos>('AES');
|
const cypherAlgo = ref<keyof typeof algos>('AES');
|
||||||
const cypherSecret = ref('my secret key');
|
const cypherSecret = ref('my secret key');
|
||||||
const cypherOutput = computed(() => algos[cypherAlgo.value].encrypt(cypherInput.value, cypherSecret.value).toString());
|
const cypherOutput = computed(() => algos[cypherAlgo.value].encrypt(cypherInput.value, cypherSecret.value).toString());
|
||||||
|
|
||||||
const decryptInput = ref('U2FsdGVkX1/EC3+6P5dbbkZ3e1kQ5o2yzuU0NHTjmrKnLBEwreV489Kr0DIB+uBs');
|
const decryptInput = ref('U2FsdGVkX18oguRKlUyr7Po25uzcRyz1DVR6+3ULIoQ/gvlXJ+JT+uVJkz+WmSEZ');
|
||||||
const decryptAlgo = ref<keyof typeof algos>('AES');
|
const decryptAlgo = ref<keyof typeof algos>('AES');
|
||||||
const decryptSecret = ref('my secret key');
|
const decryptSecret = ref('my secret key');
|
||||||
const [decryptOutput, decryptError] = computedCatch(() => algos[decryptAlgo.value].decrypt(decryptInput.value, decryptSecret.value).toString(enc.Utf8), {
|
const [decryptOutput, decryptError] = computedCatch(() => algos[decryptAlgo.value].decrypt(decryptInput.value, decryptSecret.value).toString(enc.Utf8), {
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
defaultErrorMessage: 'Unable to decrypt your text',
|
defaultErrorMessage: t('tools.encryption.errorMessage'),
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<c-card title="Encrypt">
|
<c-card :title="t('tools.encryption.encrypt.title')">
|
||||||
<div flex gap-3>
|
<div flex gap-3>
|
||||||
<c-input-text
|
<c-input-text
|
||||||
v-model:value="cypherInput"
|
v-model:value="cypherInput"
|
||||||
label="Your text:"
|
:label="t('tools.encryption.encrypt.inputLabel')"
|
||||||
placeholder="The string to cypher"
|
:placeholder="t('tools.encryption.encrypt.inputPlaceholder')"
|
||||||
rows="4"
|
rows="4"
|
||||||
multiline raw-text monospace autosize flex-1
|
multiline raw-text monospace autosize flex-1
|
||||||
/>
|
/>
|
||||||
<div flex flex-1 flex-col gap-2>
|
<div flex flex-1 flex-col gap-2>
|
||||||
<c-input-text v-model:value="cypherSecret" label="Your secret key:" clearable raw-text />
|
<c-input-text v-model:value="cypherSecret" :label="t('tools.encryption.encrypt.secretLabel')" clearable raw-text />
|
||||||
|
|
||||||
<c-select
|
<c-select
|
||||||
v-model:value="cypherAlgo"
|
v-model:value="cypherAlgo"
|
||||||
label="Encryption algorithm:"
|
:label="t('tools.encryption.encrypt.algoLabel')"
|
||||||
:options="Object.keys(algos).map((label) => ({ label, value: label }))"
|
:options="Object.keys(algos).map((label) => ({ label, value: label }))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<c-input-text
|
<c-input-text
|
||||||
label="Your text encrypted:"
|
:label="t('tools.encryption.encrypt.outputLabel')"
|
||||||
:value="cypherOutput"
|
:value="cypherOutput"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Your string hash"
|
:placeholder="t('tools.encryption.encrypt.outputPlaceholder')"
|
||||||
multiline monospace readonly autosize mt-5
|
multiline monospace readonly autosize mt-5
|
||||||
/>
|
/>
|
||||||
</c-card>
|
</c-card>
|
||||||
|
@ -50,29 +52,29 @@ const [decryptOutput, decryptError] = computedCatch(() => algos[decryptAlgo.valu
|
||||||
<div flex gap-3>
|
<div flex gap-3>
|
||||||
<c-input-text
|
<c-input-text
|
||||||
v-model:value="decryptInput"
|
v-model:value="decryptInput"
|
||||||
label="Your encrypted text:"
|
:label="t('tools.encryption.decrypt.inputLabel')"
|
||||||
placeholder="The string to cypher"
|
:placeholder="t('tools.encryption.decrypt.inputPlaceholder')"
|
||||||
rows="4"
|
rows="4"
|
||||||
multiline raw-text monospace autosize flex-1
|
multiline raw-text monospace autosize flex-1
|
||||||
/>
|
/>
|
||||||
<div flex flex-1 flex-col gap-2>
|
<div flex flex-1 flex-col gap-2>
|
||||||
<c-input-text v-model:value="decryptSecret" label="Your secret key:" clearable raw-text />
|
<c-input-text v-model:value="decryptSecret" :label="t('tools.encryption.decrypt.secretLabel')" clearable raw-text />
|
||||||
|
|
||||||
<c-select
|
<c-select
|
||||||
v-model:value="decryptAlgo"
|
v-model:value="decryptAlgo"
|
||||||
label="Encryption algorithm:"
|
:label="t('tools.encryption.decrypt.algoLabel')"
|
||||||
:options="Object.keys(algos).map((label) => ({ label, value: label }))"
|
:options="Object.keys(algos).map((label) => ({ label, value: label }))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<c-alert v-if="decryptError" type="error" mt-12 title="Error while decrypting">
|
<c-alert v-if="decryptError" type="error" mt-12 :title="t('tools.encryption.decrypt.decryptError')">
|
||||||
{{ decryptError }}
|
{{ decryptError }}
|
||||||
</c-alert>
|
</c-alert>
|
||||||
<c-input-text
|
<c-input-text
|
||||||
v-else
|
v-else
|
||||||
label="Your decrypted text:"
|
:label="t('tools.encryption.decrypt.outputLabel')"
|
||||||
:value="decryptOutput"
|
:value="decryptOutput"
|
||||||
placeholder="Your string hash"
|
:placeholder="t('tools.encryption.decrypt.outputPlaceholder')"
|
||||||
rows="3"
|
rows="3"
|
||||||
multiline monospace readonly autosize mt-5
|
multiline monospace readonly autosize mt-5
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Lock } from '@vicons/tabler';
|
import { Lock } from '@vicons/tabler';
|
||||||
import { defineTool } from '../tool';
|
import { defineTool } from '../tool';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'Encrypt / decrypt text',
|
name: translate('tools.encryption.title'),
|
||||||
path: '/encryption',
|
path: '/encryption',
|
||||||
description: 'Encrypt and decrypt text clear text using crypto algorithm like AES, TripleDES, Rabbit or RC4.',
|
description: translate('tools.encryption.description'),
|
||||||
keywords: ['cypher', 'encipher', 'text', 'AES', 'TripleDES', 'Rabbit', 'RC4'],
|
keywords: ['cypher', 'encipher', 'text', 'AES', 'TripleDES', 'Rabbit', 'RC4'],
|
||||||
component: () => import('./encryption.vue'),
|
component: () => import('./encryption.vue'),
|
||||||
icon: Lock,
|
icon: Lock,
|
||||||
|
|
24
src/tools/encryption/locales/en.yml
Normal file
24
src/tools/encryption/locales/en.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
tools:
|
||||||
|
encryption:
|
||||||
|
title: Encrypt / decrypt text
|
||||||
|
description: Encrypt and decrypt text clear text using crypto algorithm like AES, TripleDES, Rabbit or RC4.
|
||||||
|
|
||||||
|
encrypt:
|
||||||
|
title: Encrypt
|
||||||
|
inputLabel: 'Your text:'
|
||||||
|
inputPlaceholder: The string to cypher
|
||||||
|
secretLabel: 'Your secret key:'
|
||||||
|
algoLabel: 'Encryption algorithm:'
|
||||||
|
outputLabel: 'Your text encrypted:'
|
||||||
|
outputPlaceholder: Your string hash
|
||||||
|
decrypt:
|
||||||
|
title: Decrypt
|
||||||
|
inputLabel: 'Your encrypted text:'
|
||||||
|
inputPlaceholder: The string to cypher
|
||||||
|
secretLabel: 'Your secret key:'
|
||||||
|
algoLabel: 'Encryption algorithm:'
|
||||||
|
outputLabel: 'Your decrypted text:'
|
||||||
|
outputPlaceholder: Your string hash
|
||||||
|
decryptError: Error while decrypting
|
||||||
|
|
||||||
|
errorMessage: Unable to decrypt your text
|
24
src/tools/encryption/locales/zh.yml
Normal file
24
src/tools/encryption/locales/zh.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
tools:
|
||||||
|
encryption:
|
||||||
|
title: 加密/解密文本
|
||||||
|
description: 使用 AES、TripleDES、Rabbit 或 RC4 等加密算法对文本进行加密和解密。
|
||||||
|
|
||||||
|
encrypt:
|
||||||
|
title: 加密
|
||||||
|
inputLabel: '您的文本:'
|
||||||
|
inputPlaceholder: 要加密的字符串
|
||||||
|
secretLabel: '您的密钥:'
|
||||||
|
algoLabel: '加密算法:'
|
||||||
|
outputLabel: '加密后的文本:'
|
||||||
|
outputPlaceholder: 您的字符串哈希值
|
||||||
|
decrypt:
|
||||||
|
title: 解密
|
||||||
|
inputLabel: '您的加密文本:'
|
||||||
|
inputPlaceholder: 要解密的字符串
|
||||||
|
secretLabel: '您的密钥:'
|
||||||
|
algoLabel: '加密算法:'
|
||||||
|
outputLabel: '解密后的文本:'
|
||||||
|
outputPlaceholder: 您的字符串哈希值
|
||||||
|
decryptError: 解密时出错
|
||||||
|
|
||||||
|
errorMessage: 无法解密您的文本
|
|
@ -42,49 +42,50 @@ const encoding = ref<Encoding>('Hex');
|
||||||
const hmac = computed(() =>
|
const hmac = computed(() =>
|
||||||
formatWithEncoding(algos[hashFunction.value](plainText.value, secret.value), encoding.value),
|
formatWithEncoding(algos[hashFunction.value](plainText.value, secret.value), encoding.value),
|
||||||
);
|
);
|
||||||
|
const { t } = useI18n();
|
||||||
const { copy } = useCopy({ source: hmac });
|
const { copy } = useCopy({ source: hmac });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div flex flex-col gap-4>
|
<div flex flex-col gap-4>
|
||||||
<c-input-text v-model:value="plainText" multiline raw-text placeholder="Plain text to compute the hash..." rows="3" autosize autofocus label="Plain text to compute the hash" />
|
<c-input-text v-model:value="plainText" multiline raw-text :placeholder="t('tools.hmac-generator.plainText.placeholder')" rows="3" autosize autofocus :label="t('tools.hmac-generator.plainText.label')" />
|
||||||
<c-input-text v-model:value="secret" raw-text placeholder="Enter the secret key..." label="Secret key" clearable />
|
<c-input-text v-model:value="secret" raw-text :placeholder="t('tools.hmac-generator.secret.placeholder')" :label="t('tools.hmac-generator.secret.label')" clearable />
|
||||||
|
|
||||||
<div flex gap-2>
|
<div flex gap-2>
|
||||||
<c-select
|
<c-select
|
||||||
v-model:value="hashFunction" label="Hashing function"
|
v-model:value="hashFunction" :label="t('tools.hmac-generator.hashFunction.label')"
|
||||||
flex-1
|
flex-1
|
||||||
placeholder="Select an hashing function..."
|
:placeholder="t('tools.hmac-generator.hashFunction.placeholder')"
|
||||||
:options="Object.keys(algos).map((label) => ({ label, value: label }))"
|
:options="Object.keys(algos).map((label) => ({ label, value: label }))"
|
||||||
/>
|
/>
|
||||||
<c-select
|
<c-select
|
||||||
v-model:value="encoding" label="Output encoding"
|
v-model:value="encoding" :label="t('tools.hmac-generator.encoding.label')"
|
||||||
flex-1
|
flex-1
|
||||||
placeholder="Select the result encoding..."
|
:placeholder="t('tools.hmac-generator.encoding.placeholder')"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{
|
||||||
label: 'Binary (base 2)',
|
label: t('tools.hmac-generator.encoding.binary'),
|
||||||
value: 'Bin',
|
value: 'Bin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Hexadecimal (base 16)',
|
label: t('tools.hmac-generator.encoding.hexadecimal'),
|
||||||
value: 'Hex',
|
value: 'Hex',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Base64 (base 64)',
|
label: t('tools.hmac-generator.encoding.base64'),
|
||||||
value: 'Base64',
|
value: 'Base64',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Base64-url (base 64 with url safe chars)',
|
label: t('tools.hmac-generator.encoding.Base64Url'),
|
||||||
value: 'Base64url',
|
value: 'Base64url',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input-copyable v-model:value="hmac" type="textarea" placeholder="The result of the HMAC..." label="HMAC of your text" />
|
<input-copyable v-model:value="hmac" type="textarea" :placeholder="t('tools.hmac-generator.result.placeholder')" :label="t('tools.hmac-generator.result.label')" />
|
||||||
<div flex justify-center>
|
<div flex justify-center>
|
||||||
<c-button @click="copy()">
|
<c-button @click="copy()">
|
||||||
Copy HMAC
|
{{ t('tools.hmac-generator.button.copy') }}
|
||||||
</c-button>
|
</c-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { ShortTextRound } from '@vicons/material';
|
import { ShortTextRound } from '@vicons/material';
|
||||||
import { defineTool } from '../tool';
|
import { defineTool } from '../tool';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'Hmac generator',
|
name: translate('tools.hmac-generator.title'),
|
||||||
path: '/hmac-generator',
|
path: '/hmac-generator',
|
||||||
description:
|
description: translate('tools.hmac-generator.description'),
|
||||||
'Computes a hash-based message authentication code (HMAC) using a secret key and your favorite hashing function.',
|
|
||||||
keywords: ['hmac', 'generator', 'MD5', 'SHA1', 'SHA256', 'SHA224', 'SHA512', 'SHA384', 'SHA3', 'RIPEMD160'],
|
keywords: ['hmac', 'generator', 'MD5', 'SHA1', 'SHA256', 'SHA224', 'SHA512', 'SHA384', 'SHA3', 'RIPEMD160'],
|
||||||
component: () => import('./hmac-generator.vue'),
|
component: () => import('./hmac-generator.vue'),
|
||||||
icon: ShortTextRound,
|
icon: ShortTextRound,
|
||||||
|
|
26
src/tools/hmac-generator/locales/en.yml
Normal file
26
src/tools/hmac-generator/locales/en.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
tools:
|
||||||
|
hmac-generator:
|
||||||
|
title: Hmac generator
|
||||||
|
description: Computes a hash-based message authentication code (HMAC) using a secret key and your favorite hashing function.
|
||||||
|
|
||||||
|
plainText:
|
||||||
|
label: Plain text to compute the hash
|
||||||
|
placeholder: Plain text to compute the hash...
|
||||||
|
secret:
|
||||||
|
label: Secret key
|
||||||
|
placeholder: Enter the secret key...
|
||||||
|
hashFunction:
|
||||||
|
label: Hashing function
|
||||||
|
placeholder: Select an hashing function...
|
||||||
|
encoding:
|
||||||
|
label: Output encoding
|
||||||
|
placeholder: Select the result encoding...
|
||||||
|
binary: Binary (base 2)
|
||||||
|
hexadecimal: Hexadecimal (base 16)
|
||||||
|
base64: Base64 (base 64)
|
||||||
|
Base64Url: Base64-url (base 64 with url safe chars)
|
||||||
|
result:
|
||||||
|
label: HMAC of your text
|
||||||
|
placeholder: The result of the HMAC...
|
||||||
|
button:
|
||||||
|
copy: Copy HMAC
|
26
src/tools/hmac-generator/locales/zh.yml
Normal file
26
src/tools/hmac-generator/locales/zh.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
tools:
|
||||||
|
hmac-generator:
|
||||||
|
title: HMAC 生成器
|
||||||
|
description: 使用密钥和您喜欢的哈希函数计算基于哈希的消息认证码(HMAC)。
|
||||||
|
|
||||||
|
plainText:
|
||||||
|
label: 要计算哈希的明文
|
||||||
|
placeholder: 要计算哈希的明文...
|
||||||
|
secret:
|
||||||
|
label: 密钥
|
||||||
|
placeholder: 输入密钥...
|
||||||
|
hashFunction:
|
||||||
|
label: 哈希函数
|
||||||
|
placeholder: 选择一个哈希函数...
|
||||||
|
encoding:
|
||||||
|
label: 输出编码
|
||||||
|
placeholder: 选择结果的编码方式...
|
||||||
|
binary: 二进制(基数2)
|
||||||
|
hexadecimal: 十六进制(基数16)
|
||||||
|
base64: Base64(基数64)
|
||||||
|
Base64Url: Base64-url(带有URL安全字符的基数64)
|
||||||
|
result:
|
||||||
|
label: 您的文本的 HMAC
|
||||||
|
placeholder: HMAC 的结果...
|
||||||
|
button:
|
||||||
|
copy: 复制 HMAC
|
|
@ -1,10 +1,11 @@
|
||||||
import { defineTool } from '../tool';
|
import { defineTool } from '../tool';
|
||||||
import PasswordIcon from '~icons/mdi/form-textbox-password';
|
import PasswordIcon from '~icons/mdi/form-textbox-password';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'Password strength analyser',
|
name: translate('tools.password-strength-analyser.title'),
|
||||||
path: '/password-strength-analyser',
|
path: '/password-strength-analyser',
|
||||||
description: 'Discover the strength of your password with this client side only password strength analyser and crack time estimation tool.',
|
description: translate('tools.password-strength-analyser.description'),
|
||||||
keywords: ['password', 'strength', 'analyser', 'and', 'crack', 'time', 'estimation', 'brute', 'force', 'attack', 'entropy', 'cracking', 'hash', 'hashing', 'algorithm', 'algorithms', 'md5', 'sha1', 'sha256', 'sha512', 'bcrypt', 'scrypt', 'argon2', 'argon2id', 'argon2i', 'argon2d'],
|
keywords: ['password', 'strength', 'analyser', 'and', 'crack', 'time', 'estimation', 'brute', 'force', 'attack', 'entropy', 'cracking', 'hash', 'hashing', 'algorithm', 'algorithms', 'md5', 'sha1', 'sha256', 'sha512', 'bcrypt', 'scrypt', 'argon2', 'argon2id', 'argon2i', 'argon2d'],
|
||||||
component: () => import('./password-strength-analyser.vue'),
|
component: () => import('./password-strength-analyser.vue'),
|
||||||
icon: PasswordIcon,
|
icon: PasswordIcon,
|
||||||
|
|
38
src/tools/password-strength-analyser/locales/en.yml
Normal file
38
src/tools/password-strength-analyser/locales/en.yml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
tools:
|
||||||
|
password-strength-analyser:
|
||||||
|
title: Password strength analyser
|
||||||
|
description: Discover the strength of your password with this client side only password strength analyser and crack time estimation tool.
|
||||||
|
|
||||||
|
passwordPlaceholder: Enter a password...
|
||||||
|
bruteForceDuration: Duration to crack this password with brute force
|
||||||
|
note: 'Note: '
|
||||||
|
noteInfor: The computed strength is based on the time it would take to crack the password using a brute force approach, it does not take into account the possibility of a dictionary attack.
|
||||||
|
|
||||||
|
details:
|
||||||
|
length: 'Password length:'
|
||||||
|
entropy: 'Entropy:'
|
||||||
|
characterSize: 'Character set size:'
|
||||||
|
score: 'Score:'
|
||||||
|
|
||||||
|
instantly: Instantly
|
||||||
|
lessThanASecond: Less than a second
|
||||||
|
millenium: millenium
|
||||||
|
century: century
|
||||||
|
decade: decade
|
||||||
|
year: year
|
||||||
|
month: month
|
||||||
|
week: week
|
||||||
|
day: day
|
||||||
|
hour: hour
|
||||||
|
minute: minute
|
||||||
|
second: second
|
||||||
|
millennia: millennia
|
||||||
|
centuries: centuries
|
||||||
|
decades: decades
|
||||||
|
years: years
|
||||||
|
months: months
|
||||||
|
weeks: weeks
|
||||||
|
days: days
|
||||||
|
hours: hours
|
||||||
|
minutes: minutes
|
||||||
|
seconds: seconds
|
38
src/tools/password-strength-analyser/locales/zh.yml
Normal file
38
src/tools/password-strength-analyser/locales/zh.yml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
tools:
|
||||||
|
password-strength-analyser:
|
||||||
|
title: 密码强度分析器
|
||||||
|
description: 使用此仅在客户端运行的密码强度分析器和破解时间估算工具来了解您的密码强度。
|
||||||
|
|
||||||
|
passwordPlaceholder: 输入密码...
|
||||||
|
bruteForceDuration: 使用暴力破解破解此密码所需时间
|
||||||
|
note: '注意:'
|
||||||
|
noteInfor: 计算的强度是基于使用暴力破解方法破解密码所需的时间,不考虑字典攻击的可能性。
|
||||||
|
|
||||||
|
details:
|
||||||
|
length: '密码长度:'
|
||||||
|
entropy: '熵:'
|
||||||
|
characterSize: '字符集大小:'
|
||||||
|
score: '得分:'
|
||||||
|
|
||||||
|
instantly: 瞬间
|
||||||
|
lessThanASecond: 不到一秒钟
|
||||||
|
millenium: 千年
|
||||||
|
century: 世纪
|
||||||
|
decade: 十年
|
||||||
|
year: 年
|
||||||
|
month: 月
|
||||||
|
week: 周
|
||||||
|
day: 天
|
||||||
|
hour: 小时
|
||||||
|
minute: 分钟
|
||||||
|
second: 秒
|
||||||
|
millennia: 千年
|
||||||
|
centuries: 世纪
|
||||||
|
decades: 十年
|
||||||
|
years: 年
|
||||||
|
months: 月
|
||||||
|
weeks: 周
|
||||||
|
days: 天
|
||||||
|
hours: 小时
|
||||||
|
minutes: 分钟
|
||||||
|
seconds: 秒
|
|
@ -1,4 +1,5 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export { getPasswordCrackTimeEstimation, getCharsetLength };
|
export { getPasswordCrackTimeEstimation, getCharsetLength };
|
||||||
|
|
||||||
|
@ -11,24 +12,24 @@ function prettifyExponentialNotation(exponentialNotation: number) {
|
||||||
|
|
||||||
function getHumanFriendlyDuration({ seconds }: { seconds: number }) {
|
function getHumanFriendlyDuration({ seconds }: { seconds: number }) {
|
||||||
if (seconds <= 0.001) {
|
if (seconds <= 0.001) {
|
||||||
return 'Instantly';
|
return translate('tools.password-strength-analyser.instantly');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seconds <= 1) {
|
if (seconds <= 1) {
|
||||||
return 'Less than a second';
|
return translate('tools.password-strength-analyser.lessThanASecond');
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeUnits = [
|
const timeUnits = [
|
||||||
{ unit: 'millenium', secondsInUnit: 31536000000, format: prettifyExponentialNotation, plural: 'millennia' },
|
{ unit: translate('tools.password-strength-analyser.millenium'), secondsInUnit: 31536000000, format: prettifyExponentialNotation, plural: translate('tools.password-strength-analyser.millennia') },
|
||||||
{ unit: 'century', secondsInUnit: 3153600000, plural: 'centuries' },
|
{ unit: translate('tools.password-strength-analyser.century'), secondsInUnit: 3153600000, plural: translate('tools.password-strength-analyser.centuries') },
|
||||||
{ unit: 'decade', secondsInUnit: 315360000, plural: 'decades' },
|
{ unit: translate('tools.password-strength-analyser.decade'), secondsInUnit: 315360000, plural: translate('tools.password-strength-analyser.decades') },
|
||||||
{ unit: 'year', secondsInUnit: 31536000, plural: 'years' },
|
{ unit: translate('tools.password-strength-analyser.year'), secondsInUnit: 31536000, plural: translate('tools.password-strength-analyser.years') },
|
||||||
{ unit: 'month', secondsInUnit: 2592000, plural: 'months' },
|
{ unit: translate('tools.password-strength-analyser.month'), secondsInUnit: 2592000, plural: translate('tools.password-strength-analyser.months') },
|
||||||
{ unit: 'week', secondsInUnit: 604800, plural: 'weeks' },
|
{ unit: translate('tools.password-strength-analyser.week'), secondsInUnit: 604800, plural: translate('tools.password-strength-analyser.weeks') },
|
||||||
{ unit: 'day', secondsInUnit: 86400, plural: 'days' },
|
{ unit: translate('tools.password-strength-analyser.day'), secondsInUnit: 86400, plural: translate('tools.password-strength-analyser.days') },
|
||||||
{ unit: 'hour', secondsInUnit: 3600, plural: 'hours' },
|
{ unit: translate('tools.password-strength-analyser.hour'), secondsInUnit: 3600, plural: translate('tools.password-strength-analyser.hours') },
|
||||||
{ unit: 'minute', secondsInUnit: 60, plural: 'minutes' },
|
{ unit: translate('tools.password-strength-analyser.minute'), secondsInUnit: 60, plural: translate('tools.password-strength-analyser.minutes') },
|
||||||
{ unit: 'second', secondsInUnit: 1, plural: 'seconds' },
|
{ unit: translate('tools.password-strength-analyser.second'), secondsInUnit: 1, plural: translate('tools.password-strength-analyser.seconds') },
|
||||||
];
|
];
|
||||||
|
|
||||||
return _.chain(timeUnits)
|
return _.chain(timeUnits)
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getPasswordCrackTimeEstimation } from './password-strength-analyser.service';
|
import { getPasswordCrackTimeEstimation } from './password-strength-analyser.service';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const crackTimeEstimation = computed(() => getPasswordCrackTimeEstimation({ password: password.value }));
|
const crackTimeEstimation = computed(() => getPasswordCrackTimeEstimation({ password: password.value }));
|
||||||
|
|
||||||
const details = computed(() => [
|
const details = computed(() => [
|
||||||
{
|
{
|
||||||
label: 'Password length:',
|
label: t('tools.password-strength-analyser.details.length'),
|
||||||
value: crackTimeEstimation.value.passwordLength,
|
value: crackTimeEstimation.value.passwordLength,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Entropy:',
|
label: t('tools.password-strength-analyser.details.entropy'),
|
||||||
value: Math.round(crackTimeEstimation.value.entropy * 100) / 100,
|
value: Math.round(crackTimeEstimation.value.entropy * 100) / 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Character set size:',
|
label: t('tools.password-strength-analyser.details.characterSize'),
|
||||||
value: crackTimeEstimation.value.charsetLength,
|
value: crackTimeEstimation.value.charsetLength,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Score:',
|
label: t('tools.password-strength-analyser.details.score'),
|
||||||
value: `${Math.round(crackTimeEstimation.value.score * 100)} / 100`,
|
value: `${Math.round(crackTimeEstimation.value.score * 100)} / 100`,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
@ -29,7 +30,7 @@ const details = computed(() => [
|
||||||
<c-input-text
|
<c-input-text
|
||||||
v-model:value="password"
|
v-model:value="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Enter a password..."
|
:placeholder="t('tools.password-strength-analyser.passwordPlaceholder')"
|
||||||
clearable
|
clearable
|
||||||
autofocus
|
autofocus
|
||||||
raw-text
|
raw-text
|
||||||
|
@ -38,7 +39,7 @@ const details = computed(() => [
|
||||||
|
|
||||||
<c-card text-center>
|
<c-card text-center>
|
||||||
<div op-60>
|
<div op-60>
|
||||||
Duration to crack this password with brute force
|
{{ t('tools.password-strength-analyser.bruteForceDuration') }}
|
||||||
</div>
|
</div>
|
||||||
<div text-2xl data-test-id="crack-duration">
|
<div text-2xl data-test-id="crack-duration">
|
||||||
{{ crackTimeEstimation.crackDurationFormatted }}
|
{{ crackTimeEstimation.crackDurationFormatted }}
|
||||||
|
@ -55,8 +56,8 @@ const details = computed(() => [
|
||||||
</div>
|
</div>
|
||||||
</c-card>
|
</c-card>
|
||||||
<div op-70>
|
<div op-70>
|
||||||
<span font-bold>Note: </span>
|
<span font-bold>{{ t('tools.password-strength-analyser.note') }}</span>
|
||||||
The computed strength is based on the time it would take to crack the password using a brute force approach, it does not take into account the possibility of a dictionary attack.
|
{{ t('tools.password-strength-analyser.noteInfor') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { defineTool } from '../tool';
|
import { defineTool } from '../tool';
|
||||||
import FileCertIcon from '~icons/mdi/file-certificate-outline';
|
import FileCertIcon from '~icons/mdi/file-certificate-outline';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'PDF signature checker',
|
name: translate('tools.pdf-signature-checker.title'),
|
||||||
path: '/pdf-signature-checker',
|
path: '/pdf-signature-checker',
|
||||||
description: 'Verify the signatures of a PDF file. A signed PDF file contains one or more signatures that may be used to determine whether the contents of the file have been altered since the file was signed.',
|
description: translate('tools.pdf-signature-checker.description'),
|
||||||
keywords: ['pdf', 'signature', 'checker', 'verify', 'validate', 'sign'],
|
keywords: ['pdf', 'signature', 'checker', 'verify', 'validate', 'sign'],
|
||||||
component: () => import('./pdf-signature-checker.vue'),
|
component: () => import('./pdf-signature-checker.vue'),
|
||||||
icon: FileCertIcon,
|
icon: FileCertIcon,
|
||||||
|
|
9
src/tools/pdf-signature-checker/locales/en.yml
Normal file
9
src/tools/pdf-signature-checker/locales/en.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
tools:
|
||||||
|
pdf-signature-checker:
|
||||||
|
title: PDF signature checker
|
||||||
|
description: Verify the signatures of a PDF file. A signed PDF file contains one or more signatures that may be used to determine whether the contents of the file have been altered since the file was signed.
|
||||||
|
|
||||||
|
upload:
|
||||||
|
tip: Drag and drop a PDF file here, or click to select a file
|
||||||
|
error: No signatures found in the provided file.
|
||||||
|
parsed: 'Signature {index} certificates :'
|
9
src/tools/pdf-signature-checker/locales/zh.yml
Normal file
9
src/tools/pdf-signature-checker/locales/zh.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
tools:
|
||||||
|
pdf-signature-checker:
|
||||||
|
title: PDF 签名检查器
|
||||||
|
description: 验证 PDF 文件的签名。签名的 PDF 文件包含一个或多个签名,可用于确定文件内容是否自签名后被更改。
|
||||||
|
|
||||||
|
upload:
|
||||||
|
tip: 拖放PDF文件到此处,或点击选择文件
|
||||||
|
error: 在提供的文件中未找到签名。
|
||||||
|
parsed: '第 {index} 个签名证书:'
|
|
@ -7,6 +7,8 @@ const signatures = ref<SignatureInfo[]>([]);
|
||||||
const status = ref<'idle' | 'parsed' | 'error' | 'loading'>('idle');
|
const status = ref<'idle' | 'parsed' | 'error' | 'loading'>('idle');
|
||||||
const file = ref<File | null>(null);
|
const file = ref<File | null>(null);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
async function onVerifyClicked(uploadedFile: File) {
|
async function onVerifyClicked(uploadedFile: File) {
|
||||||
file.value = uploadedFile;
|
file.value = uploadedFile;
|
||||||
const fileBuffer = await uploadedFile.arrayBuffer();
|
const fileBuffer = await uploadedFile.arrayBuffer();
|
||||||
|
@ -27,7 +29,7 @@ async function onVerifyClicked(uploadedFile: File) {
|
||||||
<template>
|
<template>
|
||||||
<div style="flex: 0 0 100%">
|
<div style="flex: 0 0 100%">
|
||||||
<div mx-auto max-w-600px>
|
<div mx-auto max-w-600px>
|
||||||
<c-file-upload title="Drag and drop a PDF file here, or click to select a file" accept=".pdf" @file-upload="onVerifyClicked" />
|
<c-file-upload :title="t('tools.pdf-signature-checker.upload.tip')" accept=".pdf" @file-upload="onVerifyClicked" />
|
||||||
|
|
||||||
<c-card v-if="file" mt-4 flex gap-2>
|
<c-card v-if="file" mt-4 flex gap-2>
|
||||||
<div font-bold>
|
<div font-bold>
|
||||||
|
@ -41,7 +43,7 @@ async function onVerifyClicked(uploadedFile: File) {
|
||||||
|
|
||||||
<div v-if="status === 'error'">
|
<div v-if="status === 'error'">
|
||||||
<c-alert mt-4>
|
<c-alert mt-4>
|
||||||
No signatures found in the provided file.
|
{{ t('tools.pdf-signature-checker.upload.error') }}
|
||||||
</c-alert>
|
</c-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,7 @@ async function onVerifyClicked(uploadedFile: File) {
|
||||||
<div v-if="status === 'parsed' && signatures.length" style="flex: 0 0 100%" mt-5 flex flex-col gap-4>
|
<div v-if="status === 'parsed' && signatures.length" style="flex: 0 0 100%" mt-5 flex flex-col gap-4>
|
||||||
<div v-for="(signature, index) of signatures" :key="index">
|
<div v-for="(signature, index) of signatures" :key="index">
|
||||||
<div mb-2 font-bold>
|
<div mb-2 font-bold>
|
||||||
Signature {{ index + 1 }} certificates :
|
{{ t('tools.pdf-signature-checker.upload.parsed', { index: index + 1 }) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<pdf-signature-details :signature="signature" />
|
<pdf-signature-details :signature="signature" />
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Certificate } from '@vicons/tabler';
|
import { Certificate } from '@vicons/tabler';
|
||||||
import { defineTool } from '../tool';
|
import { defineTool } from '../tool';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'RSA key pair generator',
|
name: translate('tools.rsa-key-pair-generator.title'),
|
||||||
path: '/rsa-key-pair-generator',
|
path: '/rsa-key-pair-generator',
|
||||||
description: 'Generate new random RSA private and public key pem certificates.',
|
description: translate('tools.rsa-key-pair-generator.description'),
|
||||||
keywords: ['rsa', 'key', 'pair', 'generator', 'public', 'private', 'secret', 'ssh', 'pem'],
|
keywords: ['rsa', 'key', 'pair', 'generator', 'public', 'private', 'secret', 'ssh', 'pem'],
|
||||||
component: () => import('./rsa-key-pair-generator.vue'),
|
component: () => import('./rsa-key-pair-generator.vue'),
|
||||||
icon: Certificate,
|
icon: Certificate,
|
||||||
|
|
12
src/tools/rsa-key-pair-generator/locales/en.yml
Normal file
12
src/tools/rsa-key-pair-generator/locales/en.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
tools:
|
||||||
|
rsa-key-pair-generator:
|
||||||
|
title: RSA key pair generator
|
||||||
|
description: Generate new random RSA private and public key pem certificates.
|
||||||
|
|
||||||
|
bits: 'Bits :'
|
||||||
|
publicKey: Public key
|
||||||
|
privateKey: Private key
|
||||||
|
button:
|
||||||
|
refresh: Refresh key-pair
|
||||||
|
|
||||||
|
validationError: 'Bits should be 256 <= bits <= 16384 and be a multiple of 8'
|
12
src/tools/rsa-key-pair-generator/locales/zh.yml
Normal file
12
src/tools/rsa-key-pair-generator/locales/zh.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
tools:
|
||||||
|
rsa-key-pair-generator:
|
||||||
|
title: RSA 密钥对生成器
|
||||||
|
description: 生成新的随机 RSA 私钥和公钥 PEM 证书。
|
||||||
|
|
||||||
|
bits: '位数:'
|
||||||
|
publicKey: 公钥
|
||||||
|
privateKey: 私钥
|
||||||
|
button:
|
||||||
|
refresh: 刷新密钥对
|
||||||
|
|
||||||
|
validationError: '位数应为256 <= bits <= 16384,并且是8的倍数'
|
|
@ -8,11 +8,13 @@ import { computedRefreshableAsync } from '@/composable/computedRefreshable';
|
||||||
const bits = ref(2048);
|
const bits = ref(2048);
|
||||||
const emptyCerts = { publicKeyPem: '', privateKeyPem: '' };
|
const emptyCerts = { publicKeyPem: '', privateKeyPem: '' };
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const { attrs: bitsValidationAttrs } = useValidation({
|
const { attrs: bitsValidationAttrs } = useValidation({
|
||||||
source: bits,
|
source: bits,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
message: 'Bits should be 256 <= bits <= 16384 and be a multiple of 8',
|
message: t('tools.rsa-key-pair-generator.validationError'),
|
||||||
validator: value => value >= 256 && value <= 16384 && value % 8 === 0,
|
validator: value => value >= 256 && value <= 16384 && value % 8 === 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -27,23 +29,23 @@ const [certs, refreshCerts] = computedRefreshableAsync(
|
||||||
<template>
|
<template>
|
||||||
<div style="flex: 0 0 100%">
|
<div style="flex: 0 0 100%">
|
||||||
<div item-style="flex: 1 1 0" style="max-width: 600px" mx-auto flex gap-3>
|
<div item-style="flex: 1 1 0" style="max-width: 600px" mx-auto flex gap-3>
|
||||||
<n-form-item label="Bits :" v-bind="bitsValidationAttrs as any" label-placement="left" label-width="100">
|
<n-form-item :label="t('tools.rsa-key-pair-generator.bits')" v-bind="bitsValidationAttrs as any" label-placement="left" label-width="100">
|
||||||
<n-input-number v-model:value="bits" min="256" max="16384" step="8" />
|
<n-input-number v-model:value="bits" min="256" max="16384" step="8" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<c-button @click="refreshCerts">
|
<c-button @click="refreshCerts">
|
||||||
Refresh key-pair
|
{{ t('tools.rsa-key-pair-generator.button.refresh') }}
|
||||||
</c-button>
|
</c-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>Public key</h3>
|
<h3>{{ t('tools.rsa-key-pair-generator.publicKey') }}</h3>
|
||||||
<TextareaCopyable :value="certs.publicKeyPem" />
|
<TextareaCopyable :value="certs.publicKeyPem" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>Private key</h3>
|
<h3>{{ t('tools.rsa-key-pair-generator.privateKey') }}</h3>
|
||||||
<TextareaCopyable :value="certs.privateKeyPem" />
|
<TextareaCopyable :value="certs.privateKeyPem" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { SortDescendingNumbers } from '@vicons/tabler';
|
import { SortDescendingNumbers } from '@vicons/tabler';
|
||||||
import { defineTool } from '../tool';
|
import { defineTool } from '../tool';
|
||||||
|
import { translate } from '@/plugins/i18n.plugin';
|
||||||
|
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'ULID generator',
|
name: translate('tools.ulid-generator.title'),
|
||||||
path: '/ulid-generator',
|
path: '/ulid-generator',
|
||||||
description: 'Generate random Universally Unique Lexicographically Sortable Identifier (ULID).',
|
description: translate('tools.ulid-generator.description'),
|
||||||
keywords: ['ulid', 'generator', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
|
keywords: ['ulid', 'generator', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
|
||||||
component: () => import('./ulid-generator.vue'),
|
component: () => import('./ulid-generator.vue'),
|
||||||
icon: SortDescendingNumbers,
|
icon: SortDescendingNumbers,
|
||||||
|
|
12
src/tools/ulid-generator/locales/en.yml
Normal file
12
src/tools/ulid-generator/locales/en.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
tools:
|
||||||
|
ulid-generator:
|
||||||
|
title: ULID generator
|
||||||
|
description: Generate random Universally Unique Lexicographically Sortable Identifier (ULID).
|
||||||
|
|
||||||
|
quantity: 'Quantity: '
|
||||||
|
format: 'Format: '
|
||||||
|
|
||||||
|
copied: ULIDs copied to the clipboard
|
||||||
|
button:
|
||||||
|
refresh: Refresh
|
||||||
|
copy: Copy
|
12
src/tools/ulid-generator/locales/zh.yml
Normal file
12
src/tools/ulid-generator/locales/zh.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
tools:
|
||||||
|
ulid-generator:
|
||||||
|
title: ULID 生成器
|
||||||
|
description: 生成随机的通用唯一词典排序标识符(ULID)。
|
||||||
|
|
||||||
|
quantity: '数量:'
|
||||||
|
format: '格式:'
|
||||||
|
|
||||||
|
copied: ULID 已复制到剪贴板
|
||||||
|
button:
|
||||||
|
refresh: 刷新
|
||||||
|
copy: 复制
|
|
@ -7,6 +7,7 @@ import { useCopy } from '@/composable/copy';
|
||||||
const amount = useStorage('ulid-generator-amount', 1);
|
const amount = useStorage('ulid-generator-amount', 1);
|
||||||
const formats = [{ label: 'Raw', value: 'raw' }, { label: 'JSON', value: 'json' }] as const;
|
const formats = [{ label: 'Raw', value: 'raw' }, { label: 'JSON', value: 'json' }] as const;
|
||||||
const format = useStorage<typeof formats[number]['value']>('ulid-generator-format', formats[0].value);
|
const format = useStorage<typeof formats[number]['value']>('ulid-generator-format', formats[0].value);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const [ulids, refreshUlids] = computedRefreshable(() => {
|
const [ulids, refreshUlids] = computedRefreshable(() => {
|
||||||
const ids = _.times(amount.value, () => ulid());
|
const ids = _.times(amount.value, () => ulid());
|
||||||
|
@ -18,17 +19,17 @@ const [ulids, refreshUlids] = computedRefreshable(() => {
|
||||||
return ids.join('\n');
|
return ids.join('\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
const { copy } = useCopy({ source: ulids, text: 'ULIDs copied to the clipboard' });
|
const { copy } = useCopy({ source: ulids, text: t('tools.ulid-generator.copied') });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div flex flex-col justify-center gap-2>
|
<div flex flex-col justify-center gap-2>
|
||||||
<div flex items-center>
|
<div flex items-center>
|
||||||
<label w-75px> Quantity:</label>
|
<label w-75px>{{ t('tools.ulid-generator.quantity') }}</label>
|
||||||
<n-input-number v-model:value="amount" min="1" max="100" flex-1 />
|
<n-input-number v-model:value="amount" min="1" max="100" flex-1 />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<c-buttons-select v-model:value="format" :options="formats" label="Format: " label-width="75px" />
|
<c-buttons-select v-model:value="format" :options="formats" :label="t('tools.ulid-generator.format')" label-width="75px" />
|
||||||
|
|
||||||
<c-card mt-5 flex data-test-id="ulids">
|
<c-card mt-5 flex data-test-id="ulids">
|
||||||
<pre m-0 m-x-auto>{{ ulids }}</pre>
|
<pre m-0 m-x-auto>{{ ulids }}</pre>
|
||||||
|
@ -36,10 +37,10 @@ const { copy } = useCopy({ source: ulids, text: 'ULIDs copied to the clipboard'
|
||||||
|
|
||||||
<div flex justify-center gap-2>
|
<div flex justify-center gap-2>
|
||||||
<c-button data-test-id="refresh" @click="refreshUlids()">
|
<c-button data-test-id="refresh" @click="refreshUlids()">
|
||||||
Refresh
|
{{ t('tools.ulid-generator.button.refresh') }}
|
||||||
</c-button>
|
</c-button>
|
||||||
<c-button @click="copy()">
|
<c-button @click="copy()">
|
||||||
Copy
|
{{ t('tools.ulid-generator.button.copy') }}
|
||||||
</c-button>
|
</c-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue