Feature: Validate JSON before uploading
This commit is contained in:
parent
225cab676a
commit
8019eaf182
@ -575,7 +575,7 @@
|
||||
"YieldDayCorrection": "Corrección de Rendimiento Diario",
|
||||
"YieldDayCorrectionHint": "Sumar el rendimiento diario incluso si el inversor se reinicia. El valor se restablecerá a medianoche"
|
||||
},
|
||||
"configadmin": {
|
||||
"fileadmin": {
|
||||
"ConfigManagement": "Gestión de Configuración",
|
||||
"BackupHeader": "Copia de seguridad: Copia de Seguridad del Archivo de Configuración",
|
||||
"BackupConfig": "Copia de seguridad del archivo de configuración",
|
||||
@ -592,7 +592,9 @@
|
||||
"FactoryReset": "Restablecimiento de Fábrica",
|
||||
"ResetMsg": "¿Está seguro de que desea eliminar la configuración actual y restablecer todas las configuraciones a sus valores predeterminados de fábrica?",
|
||||
"ResetConfirm": "Restablecimiento de Fábrica",
|
||||
"Cancel": "@:base.Cancel"
|
||||
"Cancel": "@:base.Cancel",
|
||||
"InvalidJson": "JSON file is formatted incorrectly.",
|
||||
"InvalidJsonContent": "JSON file has the wrong content."
|
||||
},
|
||||
"login": {
|
||||
"Login": "Iniciar Sesión",
|
||||
|
||||
@ -575,7 +575,7 @@
|
||||
"YieldDayCorrection": "Correzione energia giornaliera",
|
||||
"YieldDayCorrectionHint": "Aggiungi questo valore all'energia giornaliera se l'inverter è stato riavviato. Questo valore sarò resettato a mezzanotte"
|
||||
},
|
||||
"configadmin": {
|
||||
"fileadmin": {
|
||||
"ConfigManagement": "Configurazione Gestione",
|
||||
"BackupHeader": "Backup: Configurazione File Backup",
|
||||
"BackupConfig": "Esegui il backup del file:",
|
||||
@ -592,7 +592,9 @@
|
||||
"FactoryReset": "Factory Reset",
|
||||
"ResetMsg": "Sei sicuro di voler cancellare la configurazione attuale e applicare la configurazione di fabbrica?",
|
||||
"ResetConfirm": "Factory Reset!",
|
||||
"Cancel": "@:base.Cancel"
|
||||
"Cancel": "@:base.Cancel",
|
||||
"InvalidJson": "JSON file is formatted incorrectly.",
|
||||
"InvalidJsonContent": "JSON file has the wrong content."
|
||||
},
|
||||
"login": {
|
||||
"Login": "Login",
|
||||
|
||||
@ -582,7 +582,9 @@
|
||||
"Name": "Name",
|
||||
"Size": "Größe",
|
||||
"Action": "Aktion",
|
||||
"Cancel": "@:base.Cancel"
|
||||
"Cancel": "@:base.Cancel",
|
||||
"InvalidJson": "JSON-Datei ist falsch formatiert.",
|
||||
"InvalidJsonContent": "JSON-Datei hat den falschen Inhalt."
|
||||
},
|
||||
"login": {
|
||||
"Login": "Anmeldung",
|
||||
|
||||
@ -582,7 +582,9 @@
|
||||
"Name": "Name",
|
||||
"Size": "Size",
|
||||
"Action": "Action",
|
||||
"Cancel": "@:base.Cancel"
|
||||
"Cancel": "@:base.Cancel",
|
||||
"InvalidJson": "JSON file is formatted incorrectly.",
|
||||
"InvalidJsonContent": "JSON file has the wrong content."
|
||||
},
|
||||
"login": {
|
||||
"Login": "Login",
|
||||
|
||||
@ -564,7 +564,9 @@
|
||||
"Name": "Name",
|
||||
"Size": "Size",
|
||||
"Action": "Action",
|
||||
"Cancel": "@:base.Cancel"
|
||||
"Cancel": "@:base.Cancel",
|
||||
"InvalidJson": "JSON file is formatted incorrectly.",
|
||||
"InvalidJsonContent": "JSON file has the wrong content."
|
||||
},
|
||||
"login": {
|
||||
"Login": "Connexion",
|
||||
|
||||
25
webapp/src/utils/structure.ts
Normal file
25
webapp/src/utils/structure.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export type Schema = {
|
||||
[key: string]: 'string' | 'number' | 'boolean' | 'object' | 'array' | Schema;
|
||||
};
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export function hasStructure(obj: any, schema: Schema): boolean {
|
||||
if (typeof obj !== 'object' || obj === null) return false;
|
||||
|
||||
for (const key in schema) {
|
||||
const expectedType = schema[key];
|
||||
|
||||
if (['string', 'number', 'boolean'].includes(expectedType as string)) {
|
||||
if (typeof obj[key] !== expectedType) return false;
|
||||
} else if (expectedType === 'object') {
|
||||
if (typeof obj[key] !== 'object' || obj[key] === null) return false;
|
||||
} else if (expectedType === 'array') {
|
||||
if (!Array.isArray(obj[key])) return false;
|
||||
} else if (typeof expectedType === 'object') {
|
||||
// Recursively check nested objects
|
||||
if (!hasStructure(obj[key], expectedType as Schema)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -57,17 +57,23 @@
|
||||
<div v-else-if="!uploading">
|
||||
<div class="row g-3 align-items-center form-group pt-2">
|
||||
<div class="col-sm">
|
||||
<select class="form-select" v-model="restoreFileSelect">
|
||||
<option selected value="config.json">Main Config (config.json)</option>
|
||||
<option selected value="pin_mapping.json">Pin Mapping (pin_mapping.json)</option>
|
||||
<option selected value="pack.lang.json">Language Pack (pack.lang.json)</option>
|
||||
<select class="form-select" v-model="restoreFileSelect" @change="onUploadFileChange">
|
||||
<option v-for="file in restoreList" :key="file.name" :value="file.name">
|
||||
{{ file.descr }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<input class="form-control" type="file" ref="file" accept=".json" />
|
||||
<input
|
||||
class="form-control"
|
||||
type="file"
|
||||
ref="file"
|
||||
accept=".json"
|
||||
@change="onUploadFileChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<button class="btn btn-primary" @click="onUpload">
|
||||
<button class="btn btn-primary" @click="onUpload" :disabled="!isValidJson">
|
||||
{{ $t('fileadmin.Restore') }}
|
||||
</button>
|
||||
</div>
|
||||
@ -132,6 +138,8 @@ import ModalDialog from '@/components/ModalDialog.vue';
|
||||
import type { AlertResponse } from '@/types/AlertResponse';
|
||||
import type { FileInfo } from '@/types/File';
|
||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||
import type { Schema } from '@/utils/structure';
|
||||
import { hasStructure } from '@/utils/structure';
|
||||
import { waitRestart } from '@/utils/waitRestart';
|
||||
import * as bootstrap from 'bootstrap';
|
||||
import {
|
||||
@ -169,6 +177,24 @@ export default defineComponent({
|
||||
UploadError: '',
|
||||
UploadSuccess: false,
|
||||
restoreFileSelect: 'config.json',
|
||||
restoreList: [
|
||||
{
|
||||
name: 'config.json',
|
||||
descr: 'Main Config (config.json)',
|
||||
template: { cfg: 'object' } as Schema,
|
||||
},
|
||||
{
|
||||
name: 'pin_mapping.json',
|
||||
descr: 'Pin Mapping (pin_mapping.json)',
|
||||
template: { name: 'string' } as Schema,
|
||||
},
|
||||
{
|
||||
name: 'pack.lang.json',
|
||||
descr: 'Language Pack (pack.lang.json)',
|
||||
template: { meta: 'object' } as Schema,
|
||||
},
|
||||
],
|
||||
isValidJson: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -230,6 +256,43 @@ export default defineComponent({
|
||||
this.callFileApiEndpoint('delete', JSON.stringify({ file: this.selectedFile.name }));
|
||||
this.onCloseModal(this.modalDelete);
|
||||
},
|
||||
onUploadFileChange() {
|
||||
const target = this.$refs.file as HTMLInputElement;
|
||||
if (target.files !== null) {
|
||||
this.file = target.files[0];
|
||||
}
|
||||
if (!this.file) return;
|
||||
|
||||
// Read the file content
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const checkTemplate = this.restoreList.find((i) => i.name == this.restoreFileSelect)?.template;
|
||||
// Parse the file content as JSON
|
||||
let checkValue = JSON.parse(e.target?.result as string);
|
||||
if (Array.isArray(checkValue)) {
|
||||
checkValue = checkValue[0];
|
||||
}
|
||||
|
||||
if (checkValue && checkTemplate && hasStructure(checkValue, checkTemplate)) {
|
||||
this.isValidJson = true;
|
||||
this.alert.show = false;
|
||||
} else {
|
||||
this.isValidJson = false;
|
||||
this.alert.message = this.$t('fileadmin.InvalidJsonContent');
|
||||
}
|
||||
} catch {
|
||||
this.isValidJson = false;
|
||||
this.alert.message = this.$t('fileadmin.InvalidJson');
|
||||
}
|
||||
|
||||
if (!this.isValidJson) {
|
||||
this.alert.type = 'warning';
|
||||
this.alert.show = true;
|
||||
}
|
||||
};
|
||||
reader.readAsText(this.file);
|
||||
},
|
||||
onUpload() {
|
||||
this.uploading = true;
|
||||
const formData = new FormData();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user