Merge 0f95d9bfa8 into 0de73e8971
This commit is contained in:
commit
46f35d2990
5
components.d.ts
vendored
5
components.d.ts
vendored
@ -130,18 +130,17 @@ declare module '@vue/runtime-core' {
|
|||||||
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
|
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
|
||||||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
|
||||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
NH1: typeof import('naive-ui')['NH1']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||||
NMenu: typeof import('naive-ui')['NMenu']
|
NMenu: typeof import('naive-ui')['NMenu']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
NTable: typeof import('naive-ui')['NTable']
|
NTable: typeof import('naive-ui')['NTable']
|
||||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||||
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { HmacMD5, HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA384, HmacSHA512, enc } from 'crypto-js';
|
||||||
|
import type { lib } from 'crypto-js';
|
||||||
import { decodeJwt } from './jwt-parser.service';
|
import { decodeJwt } from './jwt-parser.service';
|
||||||
import { useValidation } from '@/composable/validation';
|
import { useValidation } from '@/composable/validation';
|
||||||
import { isNotThrowing } from '@/utils/boolean';
|
import { isNotThrowing } from '@/utils/boolean';
|
||||||
@ -12,10 +14,356 @@ const decodedJWT = computed(() =>
|
|||||||
withDefaultOnError(() => decodeJwt({ jwt: rawJwt.value }), { header: [], payload: [] }),
|
withDefaultOnError(() => decodeJwt({ jwt: rawJwt.value }), { header: [], payload: [] }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showAsJson = ref(false);
|
||||||
|
const signatureSecret = ref('');
|
||||||
|
|
||||||
|
// Create editable versions of header and payload
|
||||||
|
const editableHeader = ref<Record<string, any>>({});
|
||||||
|
const editablePayload = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
// Editable JSON string
|
||||||
|
const editableJson = ref('');
|
||||||
|
|
||||||
|
// Track the original key being edited (stable reference)
|
||||||
|
const editingKeys = ref<Record<string, string>>({});
|
||||||
|
|
||||||
|
// Map of supported HMAC algorithms
|
||||||
|
const hmacAlgorithms: Record<string, (message: string, key: string) => lib.WordArray> = {
|
||||||
|
HS256: HmacSHA256,
|
||||||
|
HS384: HmacSHA384,
|
||||||
|
HS512: HmacSHA512,
|
||||||
|
HS224: HmacSHA224,
|
||||||
|
HS1: HmacSHA1,
|
||||||
|
MD5: HmacMD5,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recommended minimum secret lengths for each algorithm (in bytes)
|
||||||
|
const minimumSecretLengths: Record<string, number> = {
|
||||||
|
HS256: 32, // 256 bits
|
||||||
|
HS384: 48, // 384 bits
|
||||||
|
HS512: 64, // 512 bits
|
||||||
|
HS224: 28, // 224 bits
|
||||||
|
HS1: 20, // 160 bits
|
||||||
|
MD5: 16, // 128 bits
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the algorithm from the header
|
||||||
|
const algorithm = computed(() => {
|
||||||
|
return editableHeader.value.alg || 'HS256';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the algorithm is supported
|
||||||
|
const isAlgorithmSupported = computed(() => {
|
||||||
|
return algorithm.value in hmacAlgorithms;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate secret length in bytes
|
||||||
|
const secretLength = computed(() => {
|
||||||
|
if (!signatureSecret.value) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return new TextEncoder().encode(signatureSecret.value).byteLength;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get minimum required length for current algorithm
|
||||||
|
const minimumSecretLength = computed(() => {
|
||||||
|
return minimumSecretLengths[algorithm.value] || 32;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if secret meets minimum length requirement
|
||||||
|
const isSecretLengthValid = computed(() => {
|
||||||
|
if (!signatureSecret.value || !isAlgorithmSupported.value) {
|
||||||
|
return true; // Don't show error if no secret or unsupported algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretLength.value >= minimumSecretLength.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the required secret length message
|
||||||
|
const secretLengthMessage = computed(() => {
|
||||||
|
if (!signatureSecret.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAlgorithmSupported.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${secretLength.value}/${minimumSecretLength.value} bytes`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validation for signature secret
|
||||||
|
const secretValidation = useValidation({
|
||||||
|
source: signatureSecret,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
validator: () => {
|
||||||
|
if (!signatureSecret.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isSecretLengthValid.value;
|
||||||
|
},
|
||||||
|
message: computed(() => {
|
||||||
|
if (!isAlgorithmSupported.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `Secret must be at least ${minimumSecretLength.value} bytes for ${algorithm.value}`;
|
||||||
|
}).value,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
watch: [algorithm],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract and display signature
|
||||||
|
const currentSignature = computed(() => {
|
||||||
|
const parts = rawJwt.value.split('.');
|
||||||
|
return parts[2] || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate the expected signature based on secret and algorithm
|
||||||
|
const expectedSignature = computed(() => {
|
||||||
|
if (!signatureSecret.value || !isSecretLengthValid.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAlgorithmSupported.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parts = rawJwt.value.split('.');
|
||||||
|
const headerPayload = `${parts[0]}.${parts[1]}`;
|
||||||
|
|
||||||
|
// Sign using the algorithm specified in the header
|
||||||
|
const hmacFunction = hmacAlgorithms[algorithm.value];
|
||||||
|
const signature = hmacFunction(headerPayload, signatureSecret.value);
|
||||||
|
|
||||||
|
// Convert to base64url (JWT standard)
|
||||||
|
return signature.toString(enc.Base64).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Failed to calculate signature:', error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the signature is valid
|
||||||
|
const isSignatureValid = computed(() => {
|
||||||
|
if (!signatureSecret.value || !currentSignature.value || !isSecretLengthValid.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAlgorithmSupported.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentSignature.value === expectedSignature.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to update the signature with the expected one
|
||||||
|
function updateSignature() {
|
||||||
|
if (!expectedSignature.value || !isSecretLengthValid.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = rawJwt.value.split('.');
|
||||||
|
rawJwt.value = `${parts[0]}.${parts[1]}.${expectedSignature.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync decoded values to editable state when JWT changes
|
||||||
|
watch(
|
||||||
|
decodedJWT,
|
||||||
|
(decoded) => {
|
||||||
|
// Only update if we're not actively editing keys
|
||||||
|
const hasEditingKeys = Object.keys(editingKeys.value).length > 0;
|
||||||
|
|
||||||
|
if (!hasEditingKeys) {
|
||||||
|
editableHeader.value = decoded.header.reduce(
|
||||||
|
(acc, { claim, value }) => {
|
||||||
|
acc[claim] = value;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, any>,
|
||||||
|
);
|
||||||
|
|
||||||
|
editablePayload.value = decoded.payload.reduce(
|
||||||
|
(acc, { claim, value }) => {
|
||||||
|
acc[claim] = value;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, any>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update JSON representation
|
||||||
|
updateJsonFromObjects();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Computed table rows with stable keys
|
||||||
|
const tableRows = computed(() => {
|
||||||
|
return {
|
||||||
|
header: Object.keys(editableHeader.value).map((key, index) => {
|
||||||
|
const original = decodedJWT.value.header.find(item => item.claim === key);
|
||||||
|
const stableId = `header-${index}`;
|
||||||
|
return {
|
||||||
|
stableId,
|
||||||
|
claim: key,
|
||||||
|
value: editableHeader.value[key],
|
||||||
|
claimDescription: original?.claimDescription,
|
||||||
|
friendlyValue: original?.friendlyValue,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
payload: Object.keys(editablePayload.value).map((key, index) => {
|
||||||
|
const original = decodedJWT.value.payload.find(item => item.claim === key);
|
||||||
|
const stableId = `payload-${index}`;
|
||||||
|
return {
|
||||||
|
stableId,
|
||||||
|
claim: key,
|
||||||
|
value: editablePayload.value[key],
|
||||||
|
claimDescription: original?.claimDescription,
|
||||||
|
friendlyValue: original?.friendlyValue,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update JSON string from objects
|
||||||
|
function updateJsonFromObjects() {
|
||||||
|
editableJson.value = JSON.stringify(
|
||||||
|
{
|
||||||
|
header: editableHeader.value,
|
||||||
|
payload: editablePayload.value,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update objects from JSON string
|
||||||
|
function updateObjectsFromJson() {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(editableJson.value);
|
||||||
|
|
||||||
|
if (parsed.header && typeof parsed.header === 'object') {
|
||||||
|
editableHeader.value = parsed.header;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.payload && typeof parsed.payload === 'object') {
|
||||||
|
editablePayload.value = parsed.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateJWT();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// Invalid JSON, don't update
|
||||||
|
console.error('Invalid JSON:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation for JSON
|
||||||
|
const jsonValidation = useValidation({
|
||||||
|
source: editableJson,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
validator: (value) => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return parsed.header && parsed.payload;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: 'Invalid JSON format. Must contain "header" and "payload" properties.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to re-encode JWT when values are edited (without updating signature)
|
||||||
|
function updateJWT() {
|
||||||
|
try {
|
||||||
|
const headerBase64 = toBase64Url(JSON.stringify(editableHeader.value))
|
||||||
|
.replace(/=/g, '')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_');
|
||||||
|
const payloadBase64 = toBase64Url(JSON.stringify(editablePayload.value))
|
||||||
|
.replace(/=/g, '')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_');
|
||||||
|
|
||||||
|
// Keep the existing signature
|
||||||
|
const signature = currentSignature.value;
|
||||||
|
|
||||||
|
rawJwt.value = `${headerBase64}.${payloadBase64}.${signature}`;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Failed to encode JWT:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBase64Url(input: string): string {
|
||||||
|
const bytes = new TextEncoder().encode(input);
|
||||||
|
let binary = '';
|
||||||
|
bytes.forEach(b => (binary += String.fromCharCode(b)));
|
||||||
|
return btoa(binary).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start editing a key
|
||||||
|
function startEditingKey(stableId: string, currentKey: string) {
|
||||||
|
editingKeys.value[stableId] = currentKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update key while editing
|
||||||
|
function updateEditingKey(section: 'header' | 'payload', stableId: string, newValue: string) {
|
||||||
|
const originalKey = editingKeys.value[stableId];
|
||||||
|
if (!originalKey || !newValue.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = section === 'header' ? editableHeader.value : editablePayload.value;
|
||||||
|
|
||||||
|
// Create a new object with the updated key
|
||||||
|
const newData: Record<string, any> = {};
|
||||||
|
Object.keys(data).forEach((key) => {
|
||||||
|
if (key === originalKey) {
|
||||||
|
newData[newValue] = data[key];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newData[key] = data[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (section === 'header') {
|
||||||
|
editableHeader.value = newData;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
editablePayload.value = newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the original key reference
|
||||||
|
editingKeys.value[stableId] = newValue;
|
||||||
|
|
||||||
|
updateJWT();
|
||||||
|
updateJsonFromObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop editing a key
|
||||||
|
function stopEditingKey(stableId: string) {
|
||||||
|
delete editingKeys.value[stableId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle table value changes
|
||||||
|
function handleTableValueChange() {
|
||||||
|
updateJWT();
|
||||||
|
updateJsonFromObjects();
|
||||||
|
}
|
||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
{ key: 'header', title: 'Header' },
|
{ key: 'header' as const, title: 'Header', editableData: editableHeader },
|
||||||
{ key: 'payload', title: 'Payload' },
|
{ key: 'payload' as const, title: 'Payload', editableData: editablePayload },
|
||||||
] as const;
|
];
|
||||||
|
|
||||||
const validation = useValidation({
|
const validation = useValidation({
|
||||||
source: rawJwt,
|
source: rawJwt,
|
||||||
@ -30,28 +378,131 @@ const validation = useValidation({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<c-card>
|
<c-card>
|
||||||
<c-input-text v-model:value="rawJwt" label="JWT to decode" :validation="validation" placeholder="Put your token here..." rows="5" multiline raw-text autofocus mb-3 />
|
<c-input-text
|
||||||
|
v-model:value="rawJwt"
|
||||||
|
label="JWT to decode"
|
||||||
|
:validation="validation"
|
||||||
|
placeholder="Put your token here..."
|
||||||
|
rows="5"
|
||||||
|
multiline
|
||||||
|
raw-text
|
||||||
|
autofocus
|
||||||
|
mb-3
|
||||||
|
/>
|
||||||
|
|
||||||
<n-table v-if="validation.isValid">
|
<c-input-text
|
||||||
|
v-model:value="signatureSecret"
|
||||||
|
:label="signatureSecret ? `Signature secret (optional) - ${secretLengthMessage}` : 'Signature secret (optional)'"
|
||||||
|
:validation="secretValidation"
|
||||||
|
placeholder="Enter secret to verify/sign JWT..."
|
||||||
|
raw-text
|
||||||
|
clearable
|
||||||
|
mb-3
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="validation.isValid" class="mb-3">
|
||||||
|
<div class="mb-2 flex items-center gap-2">
|
||||||
|
<span class="font-bold">Algorithm:</span>
|
||||||
|
<span class="text-sm font-mono">{{ algorithm }}</span>
|
||||||
|
<span v-if="!isAlgorithmSupported" class="text-sm text-orange-600 dark:text-orange-400">
|
||||||
|
(Unsupported - only HMAC algorithms are supported)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2 flex items-center gap-2">
|
||||||
|
<span class="font-bold">Signature:</span>
|
||||||
|
<span class="break-all text-sm font-mono">{{ currentSignature || '(no signature)' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-bold">Signature Status:</span>
|
||||||
|
<!-- Secret too short -->
|
||||||
|
<span v-if="signatureSecret && !isSecretLengthValid" class="text-red-600 dark:text-red-400"> ✗ Secret too short </span>
|
||||||
|
<!-- No signature but has valid secret -->
|
||||||
|
<div v-else-if="!currentSignature && signatureSecret && isSecretLengthValid" class="flex items-center gap-2">
|
||||||
|
<span class="text-yellow-600 dark:text-yellow-400"> ⚠ Unverified (no signature) </span>
|
||||||
|
<c-button size="small" @click="updateSignature">
|
||||||
|
Add Signature
|
||||||
|
</c-button>
|
||||||
|
</div>
|
||||||
|
<!-- Has signature but no secret -->
|
||||||
|
<span v-else-if="!signatureSecret && currentSignature" class="text-yellow-600 dark:text-yellow-400">
|
||||||
|
⚠ Unverified (enter secret to verify)
|
||||||
|
</span>
|
||||||
|
<!-- Valid signature -->
|
||||||
|
<div v-else-if="isSignatureValid === true" class="flex items-center gap-2">
|
||||||
|
<span class="text-green-600 dark:text-green-400"> ✓ Valid </span>
|
||||||
|
</div>
|
||||||
|
<!-- Invalid signature -->
|
||||||
|
<div v-else-if="isSignatureValid === false" class="flex items-center gap-2">
|
||||||
|
<span class="text-red-600 dark:text-red-400"> ✗ Invalid </span>
|
||||||
|
<c-button size="small" @click="updateSignature">
|
||||||
|
Update Signature
|
||||||
|
</c-button>
|
||||||
|
</div>
|
||||||
|
<!-- No signature and no secret -->
|
||||||
|
<span v-else-if="!currentSignature" class="op-50"> No signature </span>
|
||||||
|
<!-- Other cases -->
|
||||||
|
<span v-else class="op-50">
|
||||||
|
{{ isAlgorithmSupported ? 'Enter secret to verify' : 'Algorithm not supported' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<hr style="height: 1px; border: 0; border-top: 1px solid #353535">
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div v-if="validation.isValid" class="mb-3 flex items-center gap-2">
|
||||||
|
<span>Display as JSON:</span>
|
||||||
|
<n-switch v-model:value="showAsJson" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<c-input-text
|
||||||
|
v-if="validation.isValid && showAsJson"
|
||||||
|
v-model:value="editableJson"
|
||||||
|
:validation="jsonValidation"
|
||||||
|
label="Decoded JWT (JSON)"
|
||||||
|
placeholder="Edit JSON here..."
|
||||||
|
rows="15"
|
||||||
|
multiline
|
||||||
|
raw-text
|
||||||
|
@update:value="updateObjectsFromJson"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-table v-if="validation.isValid && !showAsJson">
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-for="section of sections" :key="section.key">
|
<template v-for="section of sections" :key="section.key">
|
||||||
<th colspan="2" class="table-header">
|
<th colspan="2" class="table-header">
|
||||||
{{ section.title }}
|
{{ section.title }}
|
||||||
</th>
|
</th>
|
||||||
<tr v-for="{ claim, claimDescription, friendlyValue, value } in decodedJWT[section.key]" :key="claim + value">
|
<tr v-for="row in tableRows[section.key]" :key="row.stableId">
|
||||||
<td class="claims" style="vertical-align: top;">
|
<td class="claims" style="vertical-align: top; width: 30%">
|
||||||
<span font-bold>
|
<div>
|
||||||
{{ claim }}
|
<n-input
|
||||||
</span>
|
:value="row.claim"
|
||||||
<span v-if="claimDescription" ml-2 op-70>
|
size="small"
|
||||||
({{ claimDescription }})
|
style="font-weight: bold"
|
||||||
</span>
|
@focus="() => startEditingKey(row.stableId, row.claim)"
|
||||||
|
@input="(value) => updateEditingKey(section.key, row.stableId, value)"
|
||||||
|
@blur="() => stopEditingKey(row.stableId)"
|
||||||
|
/>
|
||||||
|
<div v-if="row.claimDescription" class="ml-2 mt-1 text-sm op-70">
|
||||||
|
({{ row.claimDescription }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td style="word-wrap: break-word;word-break: break-all;">
|
<td style="word-wrap: break-word; word-break: break-all; vertical-align: top">
|
||||||
<span>{{ value }}</span>
|
<div>
|
||||||
<span v-if="friendlyValue" ml-2 op-70>
|
<n-input
|
||||||
({{ friendlyValue }})
|
v-model:value="section.editableData.value[row.claim]"
|
||||||
</span>
|
size="small"
|
||||||
|
@input="handleTableValueChange"
|
||||||
|
/>
|
||||||
|
<div v-if="row.friendlyValue" class="ml-2 mt-1 text-sm op-70">
|
||||||
|
({{ row.friendlyValue }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user