added i18next

main
alex 2023-06-28 23:42:04 +02:00
parent 2ce8b86b5d
commit 88acf8c6ba
18 changed files with 929 additions and 318 deletions

131
package-lock.json generated
View File

@ -14,8 +14,12 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"antd": "^5.4.2", "antd": "^5.4.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"i18next": "^23.2.3",
"i18next-browser-languagedetector": "^7.1.0",
"i18next-http-backend": "^2.2.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^13.0.1",
"react-qr-scanner": "^1.0.0-alpha.11", "react-qr-scanner": "^1.0.0-alpha.11",
"react-router-dom": "^6.10.0", "react-router-dom": "^6.10.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
@ -1860,9 +1864,9 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.21.0", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.11" "regenerator-runtime": "^0.13.11"
}, },
@ -6413,6 +6417,14 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/cross-fetch": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
"integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
"dependencies": {
"node-fetch": "^2.6.11"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -9202,6 +9214,14 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/html-webpack-plugin": { "node_modules/html-webpack-plugin": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@ -9336,6 +9356,44 @@
"node": ">=10.17.0" "node": ">=10.17.0"
} }
}, },
"node_modules/i18next": {
"version": "23.2.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.2.3.tgz",
"integrity": "sha512-5spO7L0rNmW0jFuNhz+gfirlFt1anle4mTy4+gFkgsH0+T3R5++4oncBrzeKa7v8pweRyGBoGmOpboqlxovg6A==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.22.5"
}
},
"node_modules/i18next-browser-languagedetector": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.1.0.tgz",
"integrity": "sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA==",
"dependencies": {
"@babel/runtime": "^7.19.4"
}
},
"node_modules/i18next-http-backend": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.2.1.tgz",
"integrity": "sha512-ZXIdn/8NJIBJ0X4hzXfc3STYxKrCKh1fYjji9HPyIpEJfvTvy8/ZlTl8RuTizzCPj2ZcWrfaecyOMKs6bQ7u5A==",
"dependencies": {
"cross-fetch": "3.1.6"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@ -12650,6 +12708,44 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/node-fetch": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
"integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-fetch/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/node-fetch/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/node-fetch/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/node-forge": { "node_modules/node-forge": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@ -15435,6 +15531,27 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
}, },
"node_modules/react-i18next": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.0.1.tgz",
"integrity": "sha512-gMO6N2GfSfuH7xlHSsZ/mZf+Py9bLm/+EDKIn5fNTuDTjcCcwmMU5UEuGCDk5mdfivbo7ySyYXBN7B9tbGUxiA==",
"dependencies": {
"@babel/runtime": "^7.22.5",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@ -17683,6 +17800,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/w3c-hr-time": { "node_modules/w3c-hr-time": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",

View File

@ -9,8 +9,12 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"antd": "^5.4.2", "antd": "^5.4.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"i18next": "^23.2.3",
"i18next-browser-languagedetector": "^7.1.0",
"i18next-http-backend": "^2.2.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^13.0.1",
"react-qr-scanner": "^1.0.0-alpha.11", "react-qr-scanner": "^1.0.0-alpha.11",
"react-router-dom": "^6.10.0", "react-router-dom": "^6.10.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",

View File

@ -0,0 +1,156 @@
{
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
"buttonCancel": "Abbrechen",
"buttonClose": "Schließen",
"buttonSave": "Speichern",
"sideMenu.dashboard": "Dashboard",
"sideMenu.groupTasks": "Gruppenaufgaben",
"sideMenu.groupTasks.overview": "Übersicht",
"sideMenu.groupTasks.history": "Verlauf",
"sideMenu.adminArea": "Adminbereich",
"sideMenu.adminArea.roles": "Rollen",
"sideMenu.adminArea.logs": "Logs",
"sideMenu.adminArea.noScannerSelected": "Kein Scanner ausgewählt",
"sideMenu.usersCount": "Benutzer online",
"sideMenu.usersCount.multiple": "Benutzer online",
"sideMenu.logout": "Abmelden",
"groupTasks.categoryGroups.length0": "Keine Gruppenaufgaben gefunden",
"groupTasks.categoryGroups.assignedToNoTask.title": "Sie wurden keiner Gruppenaufgabe zugewiesen",
"groupTasks.groupTypeSelectionModal.notification.globalInputsCannotBeEmpty.message": "Globale Eingaben können nicht leer sein",
"groupTasks.groupTypeSelectionModal.notification.globalInputsCannotBeEmpty.description": "Bitte füllen Sie alle globalen Eingaben aus",
"groupTasks.groupTypeSelectionModal.title": "Wählen Sie einen Gruppentyp",
"groupTasks.groupTypeSelectionModal.button.startTask": "Aufgabe starten",
"groupTasks.groupTypeSelectionModal.select.placeholder": "Wählen Sie einen Gruppentyp",
"groupTasks.groupTypeSelectionModal.form.item.groupTypeDescription.label": "Beschreibung",
"groupTasks.groupTypeSelectionModal.form.item.groupTypeDescription.tooltip.title": "Diese Beschreibung hilft, im Nachhinein zu verstehen, worum es bei dieser Aufgabe ging",
"groupTasks.tag.global": "Global",
"groupTasks.groupTypeSelectionModal.notification.globalInputTypeNotImplemented.message": "Typ {{globalInputType}} nicht implementiert",
"groupTasks.groupTypeSelectionModal.notification.globalInputTypeNotImplemented.description": "Wurde festgelegt in: {{globalInputDisplayName}}",
"groupTasks.groupTypeSelectionModal.h3": "Füllen Sie die globalen Werte aus",
"groupTasks.groupTasksViewModal.groupTaskNotFound": "Gruppenaufgabe nicht gefunden",
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.message": "Eingaben können nicht leer sein",
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.description": "Bitte füllen Sie alle Felder aus",
"groupTasks.groupTasksViewModal.button.tryAgain": "Erneut versuchen",
"groupTasks.groupTasksViewModal.button.continue": "Weiter",
"groupTasks.groupTasksViewModal.popover.specifiedTaskInputs": "Spezifizierte Aufgaben-Eingaben",
"groupTasks.groupTasksViewModal.startedAt": "Gestartet am",
"groupTasks.groupTasksViewModal.endedAt": "Beendet am",
"groupTasks.groupTasksViewModal.duration": "Laufzeit",
"groupTasks.groupTasksViewModal.category": "Kategorie",
"groupTasks.groupTasksViewModal.popover.details": "Einzelheiten",
"groupTasks.groupTasksViewModal.popover.specifiedGlobalInputs": "Spezifizierte globale Eingaben",
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.message": "Typ {{groupTaskParameterType}} nicht implementiert",
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.description": "Wurde festgelegt in: {{groupTaskParameterDisplayName}}",
"groupTasks.groupTasksViewModal.alertMessage.successful": "Erfolgreich",
"groupTasks.groupTasksViewModal.alertMessage.taskIsRunning": "Aufgabe wird ausgeführt",
"groupTasks.groupTasksViewModal.alertMessage.taskCanceled": "Aufgabe abgebrochen",
"groupTasks.groupTasksViewModal.alertMessage.taskFailed": "Aufgabe fehlgeschlagen",
"groupTasks.groupTasksViewModal.alertMessage.taskInputRequired": "Eingabe erforderlich",
"groupTasks.groupTasksTableList.column.creator": "Ersteller",
"groupTasks.groupTasksTableList.column.groupName": "Gruppenname",
"groupTasks.groupTasksTableList.column.description": "Beschreibung",
"groupTasks.groupTasksTableList.column.step": "Schritt",
"groupTasks.groupTasksTableList.column.status": "Status",
"groupTasks.groupTasksTableList.column.startedAt": "Gestartet am",
"groupTasks.groupTasksTableList.column.endedAt": "Beendet am",
"groupTasks.groupTasksTableList.column.duration": "Laufzeit",
"groupTasks.groupTasksTableList.column.action": "Maßnahme",
"groupTasks.groupTasksTableList.column.action.view": "Ansehen",
"groupTasks.groupTasksTableList.statusBadge.finished": "Abgeschlossen",
"groupTasks.groupTasksTableList.statusBadge.running": "Laufend",
"groupTasks.groupTasksTableList.statusBadge.canceled": "Abgebrochen",
"groupTasks.groupTasksTableList.statusBadge.failed": "Fehlgeschlagen",
"groupTasks.groupTasksTableList.statusBadge.inputRequired": "Eingabe erforderlich",
"groupTasks.groupTasksTableList.button.newTask": "Neue Aufgabe",
"groupTasks.groupTasksTableList.button.reload": "Neu laden",
"groupTasks.groupTasksTableList.popover.title": "Sind Sie sicher, dass Sie die Gruppenkonfigurationen neu laden wollen?",
"groupTasks.groupTasksTableList.popover.buttonOk": "Ja",
"logCard.popover.groupTaskId.title": "Gruppenaufgabe",
"logCard.popover.groupTaskStep.title": "Gruppenaufgabe Schritt",
"logCard.popover.role.title": "Rolle",
"logCard.card.checkbox.info": "INFO",
"logCard.card.checkbox.error": "FEHLER",
"logCard.tooltip.previous": "Vorige",
"logCard.tooltip.next": "Nächste",
"logCard.tooltip.reload": "Neu laden",
"pageNotFound.subTitle": "Die Seite, die Sie besucht haben, existiert leider nicht.",
"pageNotFound.buttonBackHome": "Zurück zur Startseite",
"allUsers.column.avatar": "Avatar",
"allUsers.column.username": "Benutzername",
"allUsers.column.role": "Rolle",
"allUsers.column.connectionStatus": "Verbindungsstatus",
"allUsers.column.lastOnline": "Zuletzt online",
"allUsers.column.action": "Maßnahme",
"allUsers.column.action.roleChange.popconfirm.title": "Rolle ändern in",
"allUsers.column.action.roleChange.popconfirm.okText": "Ändern",
"allUsers.column.action.changeRole": "Rolle ändern",
"allUsers.column.action.delete.popconfirm.okText": "User löschen",
"allUsers.column.action.delete.popconfirm.title": "Sind Sie sicher, dass Sie den Benutzer löschen wollen?",
"allUsers.column.action.delete": "Löschen",
"allUsers.column.action.deactivate.popconfirm.okText": "Benutzer deaktivieren",
"allUsers.column.action.deactivate.popconfirm.title": "Sind Sie sicher, dass Sie den Benutzer deaktivieren wollen?",
"allUsers.column.action.deactivate": "Deaktivieren",
"allUsers.column.action.activate.popconfirm.okText": "Benutzer aktivieren",
"allUsers.column.action.activate.popconfirm.title": "Sind Sie sicher, dass Sie den Benutzer aktivieren wollen?",
"allUsers.column.action.activate": "Aktivieren",
"allUsers.roleChangeError.notification.message": "Benutzerrolle konnte nicht geändert werden",
"allUsers.roleChangeError.notification.description": "Die Rolle existiert nicht mehr",
"allUsers.header.allUsers": "Alle Benutzer",
"allUsers.button.createNewUser": "Neuen Benutzer anlegen",
"allUsers.header.deactivatedUsers": "Deaktivierte Benutzer",
"allUsers.createUserModal.title": "Einen neuen Benutzer anlegen",
"allUsers.createUserModal.okText": "Benutzer anlegen",
"allUsers.createUserModal.form.username": "Benutzername",
"allUsers.createUserModal.form.email": "E-Mail",
"allUsers.createUserModal.form.password": "Passwort",
"allUsers.createUserModal.form.role": "Rolle",
"allUsers.createUserModal.form.role.placeholder": "Wählen Sie eine Rolle",
"adminArea.createNewRole.popconfirm.okText": "Erstellen",
"adminArea.createNewRole.popconfirm.title": "Sind Sie sicher, dass Sie eine neue Rolle anlegen wollen?",
"adminArea.createNewRole.button": "Neue Rolle erstellen",
"adminArea.roleUpdate.errorDisplayNameToShort.notification.message": "Der angezeigte Name muss größer sein als {{MIN_ROLE_DISPLAY_NAME}} sein",
"adminArea.roleUpdate.errorDisplayNameToShort.notification.description": "Bitte geben Sie einen längeren Anzeigenamen ein",
"adminArea.roleUpdate.errorDisplayNameToLong.notification.message": "Der Anzeigename muss kleiner sein als {{MAX_ROLE_DISPLAY_NAME}} sein",
"adminArea.roleUpdate.errorDisplayNameToLong.notification.description": "Bitte geben Sie einen kürzeren Anzeigenamen ein",
"adminArea.roleUpdate.errorDescriptionToLong.notification.message": "Die Beschreibung muss kleiner sein als {{MAX_ROLE_DESCRIPTION}} sein",
"adminArea.roleUpdate.errorDescriptionToLong.notification.description": "Bitte geben Sie eine kürzere Beschreibung ein",
"adminArea.deleteRole.popconfirm.title": "Rolle löschen",
"adminArea.deleteRole.popconfirm.description": "Sind Sie sicher, dass Sie diese Rolle löschen wollen?",
"adminArea.deleteRole.tooltip.title": "Löschen",
"adminArea.moveRoleUp.tooltip.title": "Rolle nach oben verschieben",
"adminArea.moveRoleDown.tooltip.title": "Rolle nach unten verschieben",
"adminArea.save.tooltip.title": "Speichern",
"adminArea.close.tooltip.title": "Schließen",
"adminArea.edit.tooltip.title": "Bearbeiten",
"adminArea.masterRoleRightsMessage": "Rechte können nicht bearbeitet werden, da diese Rolle der Master ist und alle Rechte besitzt.",
"userProfile.header.yourProfile": "Ihr Profil",
"userProfile.column.userAgent": "User-Agent",
"userProfile.column.connectionStatus": "Verbindungsstatus",
"userProfile.column.lastUsed": "Zuletzt verwendet",
"userProfile.column.expiresAt": "Läuft ab am",
"userProfile.column.action": "Maßnahme",
"userProfile.column.action.signOut": "Abmelden",
"userProfile.changeAvatarError.notification.message": "Ihr Avatar konnte nicht geändert werden",
"userProfile.changeAvatarError.notification.description": "Avatar muss kleiner sein als {{MAX_AVATAR_SIZE}} MB sein",
"userProfile.form.username": "Benutzername",
"userProfile.form.email": "E-Mail",
"userProfile.form.oldPassword": "Altes passwort",
"userProfile.form.newPassword": "Neues passwort",
"userProfile.form.repeatNewPassword": "Neues Passwort wiederholen",
"userProfile.form.language": "Sprache",
"userProfile.header.yourSessions": "Ihre Sitzungen",
"scanners.column.name": "Name",
"scanners.column.usedBy": "Verwendet von",
"scanners.column.lastUsed": "Zuletzt verwendet",
"scanners.column.registeredAt": "Registriert am",
"scanners.column.userAgent": "User-Agent",
"scanners.column.action": "Maßnahme",
"scanners.column.action.disconnect.popconfirm.title": "Sind Sie sicher, dass Sie die Verbindung zu diesem Scanner trennen wollen?",
"scanners.column.action.disconnect.popconfirm.okText": "Verbindung trennen",
"scanners.column.action.disconnect": "Verbindung trennen",
"scanners.column.action.use.popconfirm.title": "Sind Sie sicher, dass Sie diesen Scanner verwenden möchten?",
"scanners.column.action.use.popconfirm.description": "Benutzer, die mit diesem Scanner verbunden sind, werden von der Verbindung getrennt.",
"scanners.column.action.use.popconfirm.okText": "Verwenden",
"scanners.column.action.use": "Scanner verwenden",
"scanners.header.scanners": "Scanner"
}

View File

@ -0,0 +1,156 @@
{
"contactAdmin": "Please contact an administrator",
"buttonCancel": "Cancel",
"buttonClose": "Close",
"buttonSave": "Save",
"sideMenu.dashboard": "Dashboard",
"sideMenu.groupTasks": "Group Tasks",
"sideMenu.groupTasks.overview": "Overview",
"sideMenu.groupTasks.history": "History",
"sideMenu.adminArea": "Admin Area",
"sideMenu.adminArea.roles": "Roles",
"sideMenu.adminArea.logs": "Logs",
"sideMenu.adminArea.noScannerSelected": "No scanner selected",
"sideMenu.usersCount": "user connected",
"sideMenu.usersCount.multiple": "users connected",
"sideMenu.logout": "Logout",
"groupTasks.categoryGroups.length0": "No group tasks found",
"groupTasks.categoryGroups.assignedToNoTask.title": "You were not assigned to any group tasks",
"groupTasks.groupTypeSelectionModal.notification.globalInputsCannotBeEmpty.message": "Global inputs cannot be empty",
"groupTasks.groupTypeSelectionModal.notification.globalInputsCannotBeEmpty.description": "Please fill in all global inputs",
"groupTasks.groupTypeSelectionModal.title": "Select a group type",
"groupTasks.groupTypeSelectionModal.button.startTask": "Start task",
"groupTasks.groupTypeSelectionModal.select.placeholder": "Choose a group type",
"groupTasks.groupTypeSelectionModal.form.item.groupTypeDescription.label": "Description",
"groupTasks.groupTypeSelectionModal.form.item.groupTypeDescription.tooltip.title": "This description helps to understand afterwards what this task was about",
"groupTasks.tag.global": "Global",
"groupTasks.groupTypeSelectionModal.notification.globalInputTypeNotImplemented.message": "Type {{globalInputType}} not implemented",
"groupTasks.groupTypeSelectionModal.notification.globalInputTypeNotImplemented.description": "Was specified in: {{globalInputDisplayName}}",
"groupTasks.groupTypeSelectionModal.h3": "Fill in the global values",
"groupTasks.groupTasksViewModal.groupTaskNotFound": "Group Task not found",
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.message": "Inputs cannot be empty",
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.description": "Please fill in all inputs",
"groupTasks.groupTasksViewModal.button.tryAgain": "Try again",
"groupTasks.groupTasksViewModal.button.continue": "Continue",
"groupTasks.groupTasksViewModal.popover.specifiedTaskInputs": "Specified Task Inputs",
"groupTasks.groupTasksViewModal.startedAt": "Started at",
"groupTasks.groupTasksViewModal.endedAt": "Ended at",
"groupTasks.groupTasksViewModal.duration": "Duration",
"groupTasks.groupTasksViewModal.category": "Category",
"groupTasks.groupTasksViewModal.popover.details": "Details",
"groupTasks.groupTasksViewModal.popover.specifiedGlobalInputs": "Specified Global Inputs",
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.message": "Type {{groupTaskParameterType}} not implemented",
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.description": "Was specified in: {{groupTaskParameterDisplayName}}",
"groupTasks.groupTasksViewModal.alertMessage.successful": "Successful",
"groupTasks.groupTasksViewModal.alertMessage.taskIsRunning": "Task is running",
"groupTasks.groupTasksViewModal.alertMessage.taskCanceled": "Task canceled",
"groupTasks.groupTasksViewModal.alertMessage.taskFailed": "Task failed",
"groupTasks.groupTasksViewModal.alertMessage.taskInputRequired": "Input required",
"groupTasks.groupTasksTableList.column.creator": "Creator",
"groupTasks.groupTasksTableList.column.groupName": "Group Name",
"groupTasks.groupTasksTableList.column.description": "Description",
"groupTasks.groupTasksTableList.column.step": "Step",
"groupTasks.groupTasksTableList.column.status": "Status",
"groupTasks.groupTasksTableList.column.startedAt": "Started At",
"groupTasks.groupTasksTableList.column.endedAt": "Ended At",
"groupTasks.groupTasksTableList.column.duration": "Duration",
"groupTasks.groupTasksTableList.column.action": "Action",
"groupTasks.groupTasksTableList.column.action.view": "View",
"groupTasks.groupTasksTableList.statusBadge.finished": "Finished",
"groupTasks.groupTasksTableList.statusBadge.running": "Running",
"groupTasks.groupTasksTableList.statusBadge.canceled": "Canceled",
"groupTasks.groupTasksTableList.statusBadge.failed": "Failed",
"groupTasks.groupTasksTableList.statusBadge.inputRequired": "Input required",
"groupTasks.groupTasksTableList.button.newTask": "New task",
"groupTasks.groupTasksTableList.button.reload": "Reload",
"groupTasks.groupTasksTableList.popover.title": "Are you sure you want to reload the group configs?",
"groupTasks.groupTasksTableList.popover.buttonOk": "Yes",
"logCard.popover.groupTaskId.title": "Group Task",
"logCard.popover.groupTaskStep.title": "Group Task Step",
"logCard.popover.role.title": "Role",
"logCard.card.checkbox.info": "INFO",
"logCard.card.checkbox.error": "ERROR",
"logCard.tooltip.previous": "Previous",
"logCard.tooltip.next": "Next",
"logCard.tooltip.reload": "Reload",
"pageNotFound.subTitle": "Sorry, the page you visited does not exist.",
"pageNotFound.buttonBackHome": "Back Home",
"allUsers.column.avatar": "Avatar",
"allUsers.column.username": "Username",
"allUsers.column.role": "Role",
"allUsers.column.connectionStatus": "Connection status",
"allUsers.column.lastOnline": "Last online",
"allUsers.column.action": "Action",
"allUsers.column.action.roleChange.popconfirm.title": "Change role to",
"allUsers.column.action.roleChange.popconfirm.okText": "Change",
"allUsers.column.action.changeRole": "Change role",
"allUsers.column.action.delete.popconfirm.okText": "Delete user",
"allUsers.column.action.delete.popconfirm.title": "Are you sure you want to delete the user?",
"allUsers.column.action.delete": "Delete",
"allUsers.column.action.deactivate.popconfirm.okText": "Deactivate user",
"allUsers.column.action.deactivate.popconfirm.title": "Are you sure you want to deactivate the user?",
"allUsers.column.action.deactivate": "Deactivate",
"allUsers.column.action.activate.popconfirm.okText": "Activate user",
"allUsers.column.action.activate.popconfirm.title": "Are you sure you want to activate the user?",
"allUsers.column.action.activate": "Activate",
"allUsers.roleChangeError.notification.message": "User role could not be changed",
"allUsers.roleChangeError.notification.description": "The role does not exist anymore",
"allUsers.header.allUsers": "All users",
"allUsers.button.createNewUser": "Create new user",
"allUsers.header.deactivatedUsers": "Deactivated users",
"allUsers.createUserModal.title": "Create a new user",
"allUsers.createUserModal.okText": "Create user",
"allUsers.createUserModal.form.username": "Username",
"allUsers.createUserModal.form.email": "E-Mail",
"allUsers.createUserModal.form.password": "Password",
"allUsers.createUserModal.form.role": "Role",
"allUsers.createUserModal.form.role.placeholder": "Select a role",
"adminArea.createNewRole.popconfirm.okText": "Create",
"adminArea.createNewRole.popconfirm.title": "Are you sure you want to create a new role?",
"adminArea.createNewRole.button": "Create new role",
"adminArea.roleUpdate.errorDisplayNameToShort.notification.message": "Display name must be greater than {{MIN_ROLE_DISPLAY_NAME}}",
"adminArea.roleUpdate.errorDisplayNameToShort.notification.description": "Please enter a longer display name",
"adminArea.roleUpdate.errorDisplayNameToLong.notification.message": "Display name must be less than {{MIN_ROLE_DISPLAY_NAME}}",
"adminArea.roleUpdate.errorDisplayNameToLong.notification.description": "Please enter a shorter display name",
"adminArea.roleUpdate.errorDescriptionToLong.notification.message": "Description must be less than {{MAX_ROLE_DESCRIPTION}}",
"adminArea.roleUpdate.errorDescriptionToLong.notification.description": "Please enter a shorter description",
"adminArea.deleteRole.popconfirm.title": "Delete role",
"adminArea.deleteRole.popconfirm.description": "Are you sure to delete this role?",
"adminArea.deleteRole.tooltip.title": "Delete",
"adminArea.moveRoleUp.tooltip.title": "Move role up",
"adminArea.moveRoleDown.tooltip.title": "Move role down",
"adminArea.save.tooltip.title": "Save",
"adminArea.close.tooltip.title": "Close",
"adminArea.edit.tooltip.title": "Edit",
"adminArea.masterRoleRightsMessage": "Rights cannot be edited, because this role is the master and has all rights.",
"userProfile.header.yourProfile": "Your profile",
"userProfile.column.userAgent": "User-Agent",
"userProfile.column.connectionStatus": "Connection status",
"userProfile.column.lastUsed": "Last used",
"userProfile.column.expiresAt": "Expires at",
"userProfile.column.action": "Action",
"userProfile.column.action.signOut": "Sign out",
"userProfile.changeAvatarError.notification.message": "Your avatar could not be changed",
"userProfile.changeAvatarError.notification.description": "Avatar must be smaller than {{MAX_AVATAR_SIZE}} MB",
"userProfile.form.username": "Username",
"userProfile.form.email": "E-Mail",
"userProfile.form.oldPassword": "Old password",
"userProfile.form.newPassword": "New password",
"userProfile.form.repeatNewPassword": "Repeat new password",
"userProfile.form.language": "Language",
"userProfile.header.yourSessions": "Your sessions",
"scanners.column.name": "Name",
"scanners.column.usedBy": "Used by",
"scanners.column.lastUsed": "Last used",
"scanners.column.registeredAt": "Registered at",
"scanners.column.userAgent": "User-Agent",
"scanners.column.action": "Action",
"scanners.column.action.disconnect.popconfirm.title": "Are you sure you want to disconnect from this scanner?",
"scanners.column.action.disconnect.popconfirm.okText": "Disconnect",
"scanners.column.action.disconnect": "Disconnect",
"scanners.column.action.use.popconfirm.title": "Are you sure you want to use this scanner?",
"scanners.column.action.use.popconfirm.description": "Users connected to this scanner will be disconnected",
"scanners.column.action.use.popconfirm.okText": "Use",
"scanners.column.action.use": "Use scanner",
"scanners.header.scanners": "Scanners"
}

View File

@ -13,9 +13,11 @@ import {
handleUnauthorizedStatus, handleUnauthorizedStatus,
} from "../../utils"; } from "../../utils";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
export default function LogCard({ type }) { export default function LogCard({ type }) {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
const [checkboxInfoChecked, setCheckboxInfoChecked] = useState(true); const [checkboxInfoChecked, setCheckboxInfoChecked] = useState(true);
const [checkboxErrorChecked, setCheckboxErrorChecked] = useState(true); const [checkboxErrorChecked, setCheckboxErrorChecked] = useState(true);
@ -38,14 +40,6 @@ export default function LogCard({ type }) {
const colorIndex = index % colorCodes.length; const colorIndex = index % colorCodes.length;
return colorCodes[colorIndex]; return colorCodes[colorIndex];
}; };
/*
const getLogDataValue = (logData, splittedMessage) => {
const logDataIndex = logData.findIndex(
(data) => data.Type === splittedMessage.replace(new RegExp("%", "g"), "")
);
return logData[logDataIndex].Value;
};*/
const ColoredSpanItem = ({ dataLogs, splittedMessage, children }) => { const ColoredSpanItem = ({ dataLogs, splittedMessage, children }) => {
const logDataIndex = dataLogs.LogData.findIndex( const logDataIndex = dataLogs.LogData.findIndex(
@ -187,7 +181,7 @@ export default function LogCard({ type }) {
</Link> </Link>
</div> </div>
} }
title="Group Task" title={t("logCard.popover.groupTaskId.title")}
trigger="click" trigger="click"
> >
<span style={{ cursor: "pointer" }}> <span style={{ cursor: "pointer" }}>
@ -236,7 +230,7 @@ export default function LogCard({ type }) {
</span> </span>
</div> </div>
} }
title="Group Task Step" title={t("logCard.popover.groupTaskStep.title")}
trigger="click" trigger="click"
> >
{" "} {" "}
@ -281,7 +275,7 @@ export default function LogCard({ type }) {
<span>{foundData.Value}</span> <span>{foundData.Value}</span>
</div> </div>
} }
title="Role" title={t("logCard.popover.role.title")}
trigger="click" trigger="click"
> >
{" "} {" "}
@ -358,17 +352,17 @@ export default function LogCard({ type }) {
checked={checkboxInfoChecked} checked={checkboxInfoChecked}
onChange={(e) => setCheckboxInfoChecked(e.target.checked)} onChange={(e) => setCheckboxInfoChecked(e.target.checked)}
> >
INFO {t("logCard.card.checkbox.info")}
</Checkbox> </Checkbox>
<Checkbox <Checkbox
checked={checkboxErrorChecked} checked={checkboxErrorChecked}
onChange={(e) => setCheckboxErrorChecked(e.target.checked)} onChange={(e) => setCheckboxErrorChecked(e.target.checked)}
> >
ERROR {t("logCard.card.checkbox.error")}
</Checkbox> </Checkbox>
{logData.Dates.findIndex((date) => date === selectedDate) > 0 ? ( {logData.Dates.findIndex((date) => date === selectedDate) > 0 ? (
<Tooltip title="Previous"> <Tooltip title={t("logCard.tooltip.previous")}>
<ArrowLeftOutlined <ArrowLeftOutlined
onClick={() => onClick={() =>
setSelectedDate( setSelectedDate(
@ -391,7 +385,7 @@ export default function LogCard({ type }) {
{logData.Dates.findIndex((date) => date === selectedDate) + 1 < {logData.Dates.findIndex((date) => date === selectedDate) + 1 <
logData.Dates.length ? ( logData.Dates.length ? (
<Tooltip title="Next"> <Tooltip title={t("logCard.tooltip.next")}>
<ArrowRightOutlined <ArrowRightOutlined
onClick={() => onClick={() =>
setSelectedDate( setSelectedDate(
@ -412,7 +406,7 @@ export default function LogCard({ type }) {
/> />
)} )}
<Tooltip title="Reload"> <Tooltip title={t("logCard.tooltip.reload")}>
<ReloadOutlined onClick={() => loadLogs(selectedDate)} /> <ReloadOutlined onClick={() => loadLogs(selectedDate)} />
</Tooltip> </Tooltip>
</Space> </Space>

View File

@ -21,11 +21,13 @@ import {
hasOnePermission, hasOnePermission,
hasPermission, hasPermission,
} from "../../utils"; } from "../../utils";
import { useTranslation } from "react-i18next";
export default function SideMenu({ userSession, setUserSession }) { export default function SideMenu({ userSession, setUserSession }) {
const location = useLocation(); const location = useLocation();
const [selectedKeys, setSelectedKeys] = useState("/"); const [selectedKeys, setSelectedKeys] = useState("/");
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
const pathName = location.pathname; const pathName = location.pathname;
@ -39,24 +41,26 @@ export default function SideMenu({ userSession, setUserSession }) {
(scanner) => scanner.UsedByUserId === getUserId() (scanner) => scanner.UsedByUserId === getUserId()
)?.Name; )?.Name;
return scannerName === undefined ? "No scanner selected" : scannerName; return scannerName === undefined
? t("sideMenu.adminArea.noScannerSelected")
: scannerName;
}; };
const getFirstMenuItems = () => { const getFirstMenuItems = () => {
let items = [ let items = [
{ {
label: "Dashboard", label: t("sideMenu.dashboard"),
icon: <AppstoreOutlined />, icon: <AppstoreOutlined />,
key: "/", key: "/",
}, },
]; ];
let groupTasks = { let groupTasks = {
label: "Group Tasks", label: t("sideMenu.groupTasks"),
type: "group", type: "group",
children: [ children: [
{ {
label: "Overview", label: t("sideMenu.groupTasks.overview"),
icon: <SnippetsOutlined />, icon: <SnippetsOutlined />,
key: "/group-tasks", key: "/group-tasks",
}, },
@ -70,7 +74,7 @@ export default function SideMenu({ userSession, setUserSession }) {
) )
) { ) {
groupTasks.children.push({ groupTasks.children.push({
label: "History", label: t("sideMenu.groupTasks.history"),
icon: <HistoryOutlined />, icon: <HistoryOutlined />,
key: "/group-tasks-history", key: "/group-tasks-history",
}); });
@ -93,7 +97,7 @@ export default function SideMenu({ userSession, setUserSession }) {
) )
) { ) {
let adminArea = { let adminArea = {
label: "Admin Area", label: t("sideMenu.adminArea"),
icon: <SettingOutlined />, icon: <SettingOutlined />,
key: "/admin-area", key: "/admin-area",
children: [], children: [],
@ -109,7 +113,7 @@ export default function SideMenu({ userSession, setUserSession }) {
) )
) { ) {
adminArea.children.push({ adminArea.children.push({
label: "Roles", label: t("sideMenu.adminArea.roles"),
icon: <UsergroupAddOutlined />, icon: <UsergroupAddOutlined />,
key: "/admin-area/roles", key: "/admin-area/roles",
}); });
@ -122,7 +126,7 @@ export default function SideMenu({ userSession, setUserSession }) {
) )
) { ) {
adminArea.children.push({ adminArea.children.push({
label: "Logs", label: t("sideMenu.adminArea.logs"),
icon: <FileTextOutlined />, icon: <FileTextOutlined />,
key: "/admin-area/logs", key: "/admin-area/logs",
}); });
@ -157,9 +161,9 @@ export default function SideMenu({ userSession, setUserSession }) {
status={webSocketContext.ConnectionBadgeStatus} status={webSocketContext.ConnectionBadgeStatus}
text={`${webSocketContext.ConnectedWebSocketUsersCount} ${ text={`${webSocketContext.ConnectedWebSocketUsersCount} ${
webSocketContext.ConnectedWebSocketUsersCount === 1 webSocketContext.ConnectedWebSocketUsersCount === 1
? "user" ? t("sideMenu.usersCount")
: "users" : t("sideMenu.usersCount.multiple")
} connected`} }`}
/> />
), ),
key: "/users", key: "/users",
@ -175,7 +179,7 @@ export default function SideMenu({ userSession, setUserSession }) {
key: "/user-profile", key: "/user-profile",
}, },
{ {
label: "Logout", label: t("sideMenu.logout"),
icon: <LogoutOutlined />, icon: <LogoutOutlined />,
onClick: () => { onClick: () => {
setUserSession(); setUserSession();

View File

@ -28,8 +28,10 @@ import {
SaveOutlined, SaveOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
export default function AdminAreaRoles() { export default function AdminAreaRoles() {
const { t } = useTranslation();
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const [notificationApi, notificationContextHolder] = const [notificationApi, notificationContextHolder] =
notification.useNotification(); notification.useNotification();
@ -133,12 +135,13 @@ export default function AdminAreaRoles() {
> >
<Popconfirm <Popconfirm
placement="top" placement="top"
okText="Create" okText={t("adminArea.createNewRole.popconfirm.okText")}
title="Are you sure you want to create a new role?" cancelText={t("buttonCancel")}
title={t("adminArea.createNewRole.popconfirm.title")}
onConfirm={() => onCreateNewRoleClick()} onConfirm={() => onCreateNewRoleClick()}
> >
<Button shape="round" icon={<PlusOutlined />} size="large"> <Button shape="round" icon={<PlusOutlined />} size="large">
Create new role {t("adminArea.createNewRole.button")}
</Button> </Button>
</Popconfirm> </Popconfirm>
</div> </div>
@ -148,6 +151,7 @@ export default function AdminAreaRoles() {
} }
function Role({ treeData, role, webSocketContext, notificationApi }) { function Role({ treeData, role, webSocketContext, notificationApi }) {
const { t } = useTranslation();
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);
const [roleDisplayName, setRoleDisplayName] = useState(""); const [roleDisplayName, setRoleDisplayName] = useState("");
const [roleDescription, setRoleDescription] = useState(""); const [roleDescription, setRoleDescription] = useState("");
@ -169,7 +173,7 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
const [checkedTreeKeyPermissions, setCheckedTreeKeyPermissions] = const [checkedTreeKeyPermissions, setCheckedTreeKeyPermissions] =
useState(rolePermissions); useState(rolePermissions);
const onTreeCheck = (checkedKeys, info) => const onTreeCheck = (checkedKeys) =>
setCheckedTreeKeyPermissions(checkedKeys); setCheckedTreeKeyPermissions(checkedKeys);
const getMasterPermissions = () => { const getMasterPermissions = () => {
@ -245,8 +249,13 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
changes.DisplayName.length < Constants.GLOBALS.MIN_ROLE_DISPLAY_NAME changes.DisplayName.length < Constants.GLOBALS.MIN_ROLE_DISPLAY_NAME
) { ) {
notificationApi["error"]({ notificationApi["error"]({
message: `Display name must be greater than ${Constants.GLOBALS.MIN_ROLE_DISPLAY_NAME}`, message: t(
description: `Please enter a longer display name`, "adminArea.roleUpdate.errorDisplayNameToShort.notification.message",
{ MIN_ROLE_DISPLAY_NAME: Constants.GLOBALS.MIN_ROLE_DISPLAY_NAME }
),
description: t(
"adminArea.roleUpdate.errorDisplayNameToShort.notification.description"
),
}); });
return; return;
} }
@ -254,8 +263,13 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
changes.DisplayName.length > Constants.GLOBALS.MAX_ROLE_DISPLAY_NAME changes.DisplayName.length > Constants.GLOBALS.MAX_ROLE_DISPLAY_NAME
) { ) {
notificationApi["error"]({ notificationApi["error"]({
message: `Display name must be less than ${Constants.GLOBALS.MAX_ROLE_DISPLAY_NAME}`, message: t(
description: `Please enter a shorter display name`, "adminArea.roleUpdate.errorDisplayNameToLong.notification.message",
{ MAX_ROLE_DISPLAY_NAME: Constants.GLOBALS.MAX_ROLE_DISPLAY_NAME }
),
description: t(
"adminArea.roleUpdate.errorDisplayNameToLong.notification.description"
),
}); });
return; return;
} }
@ -266,8 +280,13 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
changes.Description.length > Constants.GLOBALS.MAX_ROLE_DESCRIPTION changes.Description.length > Constants.GLOBALS.MAX_ROLE_DESCRIPTION
) { ) {
notificationApi["error"]({ notificationApi["error"]({
message: `Description must be less than ${Constants.GLOBALS.MAX_ROLE_DESCRIPTION}`, message: t(
description: `Please enter a shorter description`, "adminArea.roleUpdate.errorDescriptionToLong.notification.message",
{ MAX_ROLE_DESCRIPTION: Constants.GLOBALS.MAX_ROLE_DESCRIPTION }
),
description: t(
"adminArea.roleUpdate.errorDescriptionToLong.notification.description"
),
}); });
return; return;
} }
@ -381,12 +400,15 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
/> />
) : ( ) : (
<Popconfirm <Popconfirm
title="Delete role" title={t("adminArea.deleteRole.popconfirm.title")}
description="Are you sure to delete this role?" description={t(
"adminArea.deleteRole.popconfirm.description"
)}
icon={<QuestionCircleOutlined style={{ color: "red" }} />} icon={<QuestionCircleOutlined style={{ color: "red" }} />}
cancelText={t("buttonCancel")}
onConfirm={() => onDeleteClick()} onConfirm={() => onDeleteClick()}
> >
<Tooltip title="Delete"> <Tooltip title={t("adminArea.deleteRole.tooltip.title")}>
<DeleteOutlined /> <DeleteOutlined />
</Tooltip> </Tooltip>
</Popconfirm> </Popconfirm>
@ -407,7 +429,7 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
}} }}
/> />
) : ( ) : (
<Tooltip title="Move role up"> <Tooltip title={t("adminArea.moveRoleUp.tooltip.title")}>
<ArrowUpOutlined onClick={() => onMoveUpClick()} /> <ArrowUpOutlined onClick={() => onMoveUpClick()} />
</Tooltip> </Tooltip>
)} )}
@ -419,7 +441,7 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
}} }}
/> />
) : ( ) : (
<Tooltip title="Move role down"> <Tooltip title={t("adminArea.moveRoleDown.tooltip.title")}>
<ArrowDownOutlined onClick={() => onMoveDownClick()} /> <ArrowDownOutlined onClick={() => onMoveDownClick()} />
</Tooltip> </Tooltip>
)} )}
@ -430,12 +452,12 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
webSocketContext.User.Permissions, webSocketContext.User.Permissions,
Constants.PERMISSIONS.ADMIN_AREA.ROLES.UPDATE_ROLE Constants.PERMISSIONS.ADMIN_AREA.ROLES.UPDATE_ROLE
) && ( ) && (
<Tooltip title="Save"> <Tooltip title={t("adminArea.save.tooltip.title")}>
<SaveOutlined onClick={() => onSaveClick()} /> <SaveOutlined onClick={() => onSaveClick()} />
</Tooltip> </Tooltip>
)} )}
<Tooltip title="Close"> <Tooltip title={t("adminArea.close.tooltip.title")}>
<CloseOutlined onClick={() => onCloseClick()} /> <CloseOutlined onClick={() => onCloseClick()} />
</Tooltip> </Tooltip>
</Space> </Space>
@ -443,7 +465,7 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
<Space style={{ paddingLeft: 10 }} align="start"> <Space style={{ paddingLeft: 10 }} align="start">
<UserAvatarsInRole /> <UserAvatarsInRole />
<Tooltip title="Edit"> <Tooltip title={t("adminArea.edit.tooltip.title")}>
<EditOutlined onClick={() => onEditClick()} /> <EditOutlined onClick={() => onEditClick()} />
</Tooltip> </Tooltip>
</Space> </Space>
@ -491,8 +513,7 @@ function Role({ treeData, role, webSocketContext, notificationApi }) {
<> <>
{role.Master && editMode && ( {role.Master && editMode && (
<p style={{ fontStyle: "italic" }}> <p style={{ fontStyle: "italic" }}>
Rights cannot be edited, because this role is the master and {t("adminArea.masterRoleRightsMessage")}
has all rights.
</p> </p>
)} )}

View File

@ -8,9 +8,11 @@ import {
isEmailValid, isEmailValid,
} from "../../utils"; } from "../../utils";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
export default function CreateUserModal({ isModalOpen, setIsModalOpen }) { export default function CreateUserModal({ isModalOpen, setIsModalOpen }) {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
@ -55,18 +57,19 @@ export default function CreateUserModal({ isModalOpen, setIsModalOpen }) {
return ( return (
<Modal <Modal
title="Create a new user" title={t("allUsers.createUserModal.title")}
open={isModalOpen} open={isModalOpen}
centered centered
maskClosable={false} maskClosable={false}
okText="Create user" okText={t("allUsers.createUserModal.okText")}
cancelText={t("buttonCancel")}
okButtonProps={{ disabled: !isCreateUserPossible() }} okButtonProps={{ disabled: !isCreateUserPossible() }}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
onOk={() => onConfirmUserCreation()} onOk={() => onConfirmUserCreation()}
> >
<Form layout="vertical"> <Form layout="vertical">
<Form.Item <Form.Item
label="Username" label={t("allUsers.createUserModal.form.username")}
hasFeedback hasFeedback
validateStatus={ validateStatus={
username.length !== 0 && username.length !== 0 &&
@ -75,7 +78,7 @@ export default function CreateUserModal({ isModalOpen, setIsModalOpen }) {
} }
> >
<Input <Input
placeholder="Username" placeholder={t("allUsers.createUserModal.form.username")}
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH} minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
@ -83,19 +86,19 @@ export default function CreateUserModal({ isModalOpen, setIsModalOpen }) {
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="E-Mail" label={t("allUsers.createUserModal.form.email")}
hasFeedback hasFeedback
validateStatus={email.length !== 0 && !isEmailValid(email) && "error"} validateStatus={email.length !== 0 && !isEmailValid(email) && "error"}
> >
<Input <Input
placeholder="E-Mail" placeholder={t("allUsers.createUserModal.form.email")}
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
type="email" type="email"
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Password" label={t("allUsers.createUserModal.form.password")}
hasFeedback hasFeedback
validateStatus={ validateStatus={
password.length !== 0 && password.length !== 0 &&
@ -104,19 +107,19 @@ export default function CreateUserModal({ isModalOpen, setIsModalOpen }) {
} }
> >
<Input.Password <Input.Password
placeholder="Password" placeholder={t("allUsers.createUserModal.form.password")}
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
minLength={Constants.GLOBALS.MIN_PASSWORD_LENGTH} minLength={Constants.GLOBALS.MIN_PASSWORD_LENGTH}
maxLength={Constants.GLOBALS.MAX_PASSWORD_LENGTH} maxLength={Constants.GLOBALS.MAX_PASSWORD_LENGTH}
/> />
</Form.Item> </Form.Item>
<Form.Item label="Role"> <Form.Item label={t("allUsers.createUserModal.form.role")}>
<Select <Select
getPopupContainer={(node) => node.parentNode} getPopupContainer={(node) => node.parentNode}
value={selectedRoleId} value={selectedRoleId}
onChange={(e) => setSelectedRoleId(e)} onChange={(e) => setSelectedRoleId(e)}
placeholder="Select a role" placeholder={t("allUsers.createUserModal.form.role.placeholder")}
> >
{webSocketContext.AllRoles.map((role) => ( {webSocketContext.AllRoles.map((role) => (
<Select.Option key={role.Id}>{role.DisplayName}</Select.Option> <Select.Option key={role.Id}>{role.DisplayName}</Select.Option>

View File

@ -21,9 +21,11 @@ import { useContext, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { UserAddOutlined } from "@ant-design/icons"; import { UserAddOutlined } from "@ant-design/icons";
import CreateUserModal from "./CreateUserModal"; import CreateUserModal from "./CreateUserModal";
import { useTranslation } from "react-i18next";
export default function AllUsers() { export default function AllUsers() {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
const [selectedRoleId, setSelectedRoleId] = useState(""); const [selectedRoleId, setSelectedRoleId] = useState("");
const [notificationApi, notificationContextHolder] = const [notificationApi, notificationContextHolder] =
notification.useNotification(); notification.useNotification();
@ -32,27 +34,27 @@ export default function AllUsers() {
const getTableContent = () => { const getTableContent = () => {
let items = [ let items = [
{ {
title: "Avatar", title: t("allUsers.column.avatar"),
dataIndex: "avatar", dataIndex: "avatar",
key: "avatar", key: "avatar",
}, },
{ {
title: "Username", title: t("allUsers.column.username"),
dataIndex: "username", dataIndex: "username",
key: "username", key: "username",
}, },
{ {
title: "Role", title: t("allUsers.column.role"),
dataIndex: "role", dataIndex: "role",
key: "role", key: "role",
}, },
{ {
title: "Connection status", title: t("allUsers.column.connectionStatus"),
dataIndex: "connectionStatus", dataIndex: "connectionStatus",
key: "connectionStatus", key: "connectionStatus",
}, },
{ {
title: "Last online", title: t("allUsers.column.lastOnline"),
dataIndex: "lastOnline", dataIndex: "lastOnline",
key: "lastOnline", key: "lastOnline",
}, },
@ -67,7 +69,7 @@ export default function AllUsers() {
) )
) { ) {
items.push({ items.push({
title: "Action", title: t("allUsers.column.action"),
key: "action", key: "action",
render: (_, record) => ( render: (_, record) => (
<Space size="middle"> <Space size="middle">
@ -85,8 +87,13 @@ export default function AllUsers() {
(role) => role.Id === webSocketContext.User.RoleId (role) => role.Id === webSocketContext.User.RoleId
).Master) && ( ).Master) && (
<Popconfirm <Popconfirm
title="Change role to" title={t(
okText="Change" "allUsers.column.action.roleChange.popconfirm.title"
)}
cancelText={t("buttonCancel")}
okText={t(
"allUsers.column.action.roleChange.popconfirm.okText"
)}
onConfirm={() => onRoleChangeConfirm(record.key)} onConfirm={() => onRoleChangeConfirm(record.key)}
okButtonProps={{ okButtonProps={{
disabled: disabled:
@ -138,7 +145,7 @@ export default function AllUsers() {
); );
}} }}
> >
Change role {t("allUsers.column.action.changeRole")}
</Link> </Link>
</Popconfirm> </Popconfirm>
)} )}
@ -158,11 +165,12 @@ export default function AllUsers() {
).Master) && ( ).Master) && (
<Popconfirm <Popconfirm
placement="top" placement="top"
okText="Delete user" cancelText={t("buttonCancel")}
title="Are you sure you want to delete the user?" okText={t("allUsers.column.action.delete.popconfirm.okText")}
title={t("allUsers.column.action.delete.popconfirm.title")}
onConfirm={() => onUserDeletionConfirm(record.key)} onConfirm={() => onUserDeletionConfirm(record.key)}
> >
<Link to="#">Delete</Link> <Link to="#">{t("allUsers.column.action.delete")}</Link>
</Popconfirm> </Popconfirm>
)} )}
@ -183,13 +191,18 @@ export default function AllUsers() {
).Master) && ( ).Master) && (
<Popconfirm <Popconfirm
placement="top" placement="top"
okText="Deactivate user" okText={t(
title="Are you sure you want to deactivate the user?" "allUsers.column.action.deactivate.popconfirm.okText"
)}
cancelText={t("buttonCancel")}
title={t(
"allUsers.column.action.deactivate.popconfirm.title"
)}
onConfirm={() => onConfirm={() =>
onUserDeactivationConfirm(record.key, true) onUserDeactivationConfirm(record.key, true)
} }
> >
<Link to="#">Deactivate</Link> <Link to="#">{t("allUsers.column.action.deactivate")}</Link>
</Popconfirm> </Popconfirm>
) )
: hasPermission( : hasPermission(
@ -207,13 +220,18 @@ export default function AllUsers() {
).Master) && ( ).Master) && (
<Popconfirm <Popconfirm
placement="top" placement="top"
okText="Activate user" okText={t(
title="Are you sure you want to activate the user?" "allUsers.column.action.activate.popconfirm.okText"
)}
title={t(
"allUsers.column.action.activate.popconfirm.title"
)}
cancelText={t("buttonCancel")}
onConfirm={() => onConfirm={() =>
onUserDeactivationConfirm(record.key, false) onUserDeactivationConfirm(record.key, false)
} }
> >
<Link to="#">Activate</Link> <Link to="#">{t("allUsers.column.action.activate")}</Link>
</Popconfirm> </Popconfirm>
)} )}
</Space> </Space>
@ -273,8 +291,8 @@ export default function AllUsers() {
if (existsRole === undefined) { if (existsRole === undefined) {
notificationApi["error"]({ notificationApi["error"]({
message: `User role could not be changed`, message: t("allUsers.roleChangeError.notification.message"),
description: `The role does not exist anymore`, description: t("allUsers.roleChangeError.notification.description"),
}); });
return; return;
} }
@ -318,7 +336,7 @@ export default function AllUsers() {
}} }}
> >
<h1 style={{ fontWeight: "bold" }}> <h1 style={{ fontWeight: "bold" }}>
All users ({activatedUsers.length}) {t("allUsers.header.allUsers")} ({activatedUsers.length})
</h1> </h1>
{hasPermission( {hasPermission(
webSocketContext.User.Permissions, webSocketContext.User.Permissions,
@ -328,7 +346,7 @@ export default function AllUsers() {
icon={<UserAddOutlined />} icon={<UserAddOutlined />}
onClick={() => setIsCreateUserModalOpen(true)} onClick={() => setIsCreateUserModalOpen(true)}
> >
Create new user {t("allUsers.button.createNewUser")}
</Button> </Button>
)} )}
</div> </div>
@ -345,7 +363,8 @@ export default function AllUsers() {
deactivatedUsers.length > 0 && ( deactivatedUsers.length > 0 && (
<> <>
<h1 style={{ fontWeight: "bold" }}> <h1 style={{ fontWeight: "bold" }}>
Deactivated users ({deactivatedUsers.length}) {t("allUsers.header.deactivatedUsers")} ({deactivatedUsers.length}
)
</h1> </h1>
<Table <Table
columns={getTableContent()} columns={getTableContent()}

View File

@ -21,69 +21,7 @@ import {
hasXYPermission, hasXYPermission,
} from "../../../utils"; } from "../../../utils";
import { useContext } from "react"; import { useContext } from "react";
import { useTranslation } from "react-i18next";
const columns = [
{
title: "Creator",
dataIndex: "creator",
key: "creator",
},
{
title: "Group Name",
dataIndex: "groupName",
key: "groupName",
},
{
title: "Description",
dataIndex: "description",
key: "description",
ellipsis: {
showTitle: false,
},
render: (description) => (
<Tooltip placement="topLeft" title={description}>
{description}
</Tooltip>
),
},
{
title: "Step",
dataIndex: "step",
key: "step",
},
{
title: "Status",
dataIndex: "status",
key: "status",
},
{
title: "Started At",
dataIndex: "startedAt",
key: "startedAt",
},
{
title: "Ended At",
dataIndex: "endedAt",
key: "endedAt",
},
{
title: "Duration",
dataIndex: "duration",
key: "duration",
},
{
title: "Action",
dataIndex: "action",
key: "action",
render: (_, record) => (
<Space size="middle">
<Link to={Constants.ROUTE_PATHS.GROUP_TASKS_VIEW + record.key}>
View
</Link>
</Space>
),
},
];
export default function GroupTaskTableList({ export default function GroupTaskTableList({
categoryGroup, categoryGroup,
@ -91,19 +29,110 @@ export default function GroupTaskTableList({
groupTasks, groupTasks,
}) { }) {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
const getTableColumns = () => {
return [
{
title: t("groupTasks.groupTasksTableList.column.creator"),
dataIndex: "creator",
key: "creator",
},
{
title: t("groupTasks.groupTasksTableList.column.groupName"),
dataIndex: "groupName",
key: "groupName",
},
{
title: t("groupTasks.groupTasksTableList.column.description"),
dataIndex: "description",
key: "description",
ellipsis: {
showTitle: false,
},
render: (description) => (
<Tooltip placement="topLeft" title={description}>
{description}
</Tooltip>
),
},
{
title: t("groupTasks.groupTasksTableList.column.step"),
dataIndex: "step",
key: "step",
},
{
title: t("groupTasks.groupTasksTableList.column.status"),
dataIndex: "status",
key: "status",
},
{
title: t("groupTasks.groupTasksTableList.column.startedAt"),
dataIndex: "startedAt",
key: "startedAt",
},
{
title: t("groupTasks.groupTasksTableList.column.endedAt"),
dataIndex: "endedAt",
key: "endedAt",
},
{
title: t("groupTasks.groupTasksTableList.column.duration"),
dataIndex: "duration",
key: "duration",
},
{
title: t("groupTasks.groupTasksTableList.column.action"),
dataIndex: "action",
key: "action",
render: (_, record) => (
<Space size="middle">
<Link to={Constants.ROUTE_PATHS.GROUP_TASKS_VIEW + record.key}>
{t("groupTasks.groupTasksTableList.column.action.view")}
</Link>
</Space>
),
},
];
};
const getStatusBadge = (status) => { const getStatusBadge = (status) => {
switch (status) { switch (status) {
case Constants.GROUP_TASKS_STATUS.FINISHED: case Constants.GROUP_TASKS_STATUS.FINISHED:
return <Badge status="success" text="Finished" />; return (
<Badge
status="success"
text={t("groupTasks.groupTasksTableList.statusBadge.finished")}
/>
);
case Constants.GROUP_TASKS_STATUS.RUNNING: case Constants.GROUP_TASKS_STATUS.RUNNING:
return <Badge status="processing" text="Running" />; return (
<Badge
status="processing"
text={t("groupTasks.groupTasksTableList.statusBadge.running")}
/>
);
case Constants.GROUP_TASKS_STATUS.CANCELED: case Constants.GROUP_TASKS_STATUS.CANCELED:
return <Badge status="warning" text="Canceled" />; return (
<Badge
status="warning"
text={t("groupTasks.groupTasksTableList.statusBadge.canceled")}
/>
);
case Constants.GROUP_TASKS_STATUS.FAILED: case Constants.GROUP_TASKS_STATUS.FAILED:
return <Badge status="error" text="Failed" />; return (
<Badge
status="error"
text={t("groupTasks.groupTasksTableList.statusBadge.failed")}
/>
);
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED: case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
return <Badge status="warning" text="Input required" />; return (
<Badge
status="warning"
text={t("groupTasks.groupTasksTableList.statusBadge.inputRequired")}
/>
);
default: default:
return <Badge status="error" text="Status not found" />; return <Badge status="error" text="Status not found" />;
} }
@ -188,7 +217,7 @@ export default function GroupTaskTableList({
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={() => showGroupTypeSelectionModal(categoryGroup)} onClick={() => showGroupTypeSelectionModal(categoryGroup)}
> >
New task {t("groupTasks.groupTasksTableList.button.newTask")}
</Button> </Button>
)} )}
@ -199,17 +228,20 @@ export default function GroupTaskTableList({
) && ( ) && (
<Popconfirm <Popconfirm
placement="left" placement="left"
title="Are you sure you want to reload the group configs?" title={t("groupTasks.groupTasksTableList.popover.title")}
okText="Yes" cancelText={t("buttonCancel")}
okText={t("groupTasks.groupTasksTableList.popover.buttonOk")}
onConfirm={() => handleOnConfirm(categoryGroup.category)} onConfirm={() => handleOnConfirm(categoryGroup.category)}
> >
<Button icon={<ReloadOutlined />}>Reload</Button> <Button icon={<ReloadOutlined />}>
{t("groupTasks.groupTasksTableList.button.reload")}
</Button>
</Popconfirm> </Popconfirm>
)} )}
</div> </div>
)} )}
<Table columns={columns} dataSource={getTableItems()} /> <Table columns={getTableColumns()} dataSource={getTableItems()} />
</> </>
); );
} }

View File

@ -33,6 +33,7 @@ import {
} from "@ant-design/icons"; } from "@ant-design/icons";
import { StlViewer } from "react-stl-viewer"; import { StlViewer } from "react-stl-viewer";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
import { useTranslation } from "react-i18next";
export default function GroupTasksViewModal({ isOpen }) { export default function GroupTasksViewModal({ isOpen }) {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
@ -40,6 +41,7 @@ export default function GroupTasksViewModal({ isOpen }) {
const [notificationApi, notificationContextHolder] = const [notificationApi, notificationContextHolder] =
notification.useNotification(); notification.useNotification();
let { paramGroupTaskId } = useParams(); let { paramGroupTaskId } = useParams();
const { t } = useTranslation();
const handleCancel = () => navigate(Constants.ROUTE_PATHS.GROUP_TASKS); const handleCancel = () => navigate(Constants.ROUTE_PATHS.GROUP_TASKS);
let currentGroupTask; let currentGroupTask;
@ -60,7 +62,10 @@ export default function GroupTasksViewModal({ isOpen }) {
onCancel={handleCancel} onCancel={handleCancel}
footer={<Button onClick={handleCancel}>Close</Button>} footer={<Button onClick={handleCancel}>Close</Button>}
> >
<Result status="500" title="Group Task not found" />{" "} <Result
status="500"
title={t("groupTasks.groupTasksViewModal.groupTaskNotFound")}
/>{" "}
</Modal> </Modal>
); );
} }
@ -85,15 +90,17 @@ export default function GroupTasksViewModal({ isOpen }) {
const getAlertMessage = (status) => { const getAlertMessage = (status) => {
switch (status) { switch (status) {
case Constants.GROUP_TASKS_STATUS.FINISHED: case Constants.GROUP_TASKS_STATUS.FINISHED:
return "Success"; return t("groupTasks.groupTasksViewModal.alertMessage.successful");
case Constants.GROUP_TASKS_STATUS.RUNNING: case Constants.GROUP_TASKS_STATUS.RUNNING:
return "Task is running"; return t("groupTasks.groupTasksViewModal.alertMessage.taskIsRunning");
case Constants.GROUP_TASKS_STATUS.CANCELED: case Constants.GROUP_TASKS_STATUS.CANCELED:
return "Task cancelled"; return t("groupTasks.groupTasksViewModal.alertMessage.taskCanceled");
case Constants.GROUP_TASKS_STATUS.FAILED: case Constants.GROUP_TASKS_STATUS.FAILED:
return "Task failed"; return t("groupTasks.groupTasksViewModal.alertMessage.taskFailed");
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED: case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
return "Input required"; return t(
"groupTasks.groupTasksViewModal.alertMessage.taskInputRequired"
);
default: default:
return "Alert message not found"; return "Alert message not found";
} }
@ -180,8 +187,12 @@ export default function GroupTasksViewModal({ isOpen }) {
if (!canTaskContinued) { if (!canTaskContinued) {
notificationApi["error"]({ notificationApi["error"]({
message: `Inputs cannot be empty`, message: t(
description: `Please fill in all inputs`, "groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.message"
),
description: t(
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.description"
),
}); });
return; return;
} }
@ -211,7 +222,7 @@ export default function GroupTasksViewModal({ isOpen }) {
handleTaskFailedTryAgainRunTaskStep(taskStepId, index + 1) handleTaskFailedTryAgainRunTaskStep(taskStepId, index + 1)
} }
> >
Try again {t("groupTasks.groupTasksViewModal.button.tryAgain")}
</Button> </Button>
); );
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED: case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
@ -221,7 +232,7 @@ export default function GroupTasksViewModal({ isOpen }) {
disabled={taskLocked} disabled={taskLocked}
onClick={() => handleTaskContinueTaskStep(taskStepId, index + 1)} onClick={() => handleTaskContinueTaskStep(taskStepId, index + 1)}
> >
Continue {t("groupTasks.groupTasksViewModal.button.continue")}
</Button> </Button>
); );
default: default:
@ -241,8 +252,6 @@ export default function GroupTasksViewModal({ isOpen }) {
groupTaskSteps.sort((a, b) => a.Step - b.Step); groupTaskSteps.sort((a, b) => a.Step - b.Step);
console.log("currentGroupTask", currentGroupTask.NumberOfSteps);
webSocketContext.CategoryGroups.forEach((categoryGroup) => { webSocketContext.CategoryGroups.forEach((categoryGroup) => {
if (categoryGroup.category === currentGroupTask.Category) { if (categoryGroup.category === currentGroupTask.Category) {
categoryGroup.groups.forEach((group) => { categoryGroup.groups.forEach((group) => {
@ -253,8 +262,6 @@ export default function GroupTasksViewModal({ isOpen }) {
} }
}); });
console.log("groupTasks", groupTasks, groupTaskSteps);
const getStepItem = (groupTask, index) => { const getStepItem = (groupTask, index) => {
return { return {
key: index, key: index,
@ -276,7 +283,9 @@ export default function GroupTasksViewModal({ isOpen }) {
color: Constants.COLORS.SECONDARY, color: Constants.COLORS.SECONDARY,
}} }}
> >
Specified Task Inputs {t(
"groupTasks.groupTasksViewModal.popover.specifiedTaskInputs"
)}
</h2> </h2>
} }
content={ content={
@ -340,17 +349,17 @@ export default function GroupTasksViewModal({ isOpen }) {
groupTaskSteps[index] !== undefined ? ( groupTaskSteps[index] !== undefined ? (
<> <>
<p style={{ color: "#000" }}> <p style={{ color: "#000" }}>
<b>Id:</b> {groupTaskSteps[index].Id} <b>ID:</b> {groupTaskSteps[index].Id}
<br /> <br />
<b>Started at:</b>{" "} <b>{t("groupTasks.groupTasksViewModal.startedAt")}:</b>{" "}
{FormatDatetime(groupTaskSteps[index].StartedAt)} {FormatDatetime(groupTaskSteps[index].StartedAt)}
<br /> <br />
<b>Ended at:</b>{" "} <b>{t("groupTasks.groupTasksViewModal.endedAt")}:</b>{" "}
{groupTaskSteps[index].EndedAt !== "0001-01-01T00:00:00Z" {groupTaskSteps[index].EndedAt !== "0001-01-01T00:00:00Z"
? FormatDatetime(groupTaskSteps[index].EndedAt) ? FormatDatetime(groupTaskSteps[index].EndedAt)
: Constants.TEXT_EMPTY_PLACEHOLDER} : Constants.TEXT_EMPTY_PLACEHOLDER}
<br /> <br />
<b>Duration:</b>{" "} <b>{t("groupTasks.groupTasksViewModal.duration")}:</b>{" "}
{GetDuration( {GetDuration(
groupTaskSteps[index].StartedAt, groupTaskSteps[index].StartedAt,
groupTaskSteps[index].EndedAt groupTaskSteps[index].EndedAt
@ -456,7 +465,7 @@ export default function GroupTasksViewModal({ isOpen }) {
width="70%" width="70%"
onCancel={handleCancel} onCancel={handleCancel}
maskClosable={false} maskClosable={false}
footer={<Button onClick={handleCancel}>Close</Button>} footer={<Button onClick={handleCancel}>{t("buttonClose")}</Button>}
> >
{notificationContextHolder} {notificationContextHolder}
{webSocketContext.GroupTasks.map((groupTask) => { {webSocketContext.GroupTasks.map((groupTask) => {
@ -482,7 +491,7 @@ export default function GroupTasksViewModal({ isOpen }) {
color: Constants.COLORS.SECONDARY, color: Constants.COLORS.SECONDARY,
}} }}
> >
Details {t("groupTasks.groupTasksViewModal.popover.details")}
</h2> </h2>
} }
content={ content={
@ -491,22 +500,22 @@ export default function GroupTasksViewModal({ isOpen }) {
<span style={{ fontWeight: "bold" }}>ID:</span>{" "} <span style={{ fontWeight: "bold" }}>ID:</span>{" "}
{paramGroupTaskId} <br /> {paramGroupTaskId} <br />
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
Category: {t("groupTasks.groupTasksViewModal.category")}:
</span>{" "} </span>{" "}
{currentGroupTask.Category} {currentGroupTask.Category}
<br /> <br />
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
Started at: {t("groupTasks.groupTasksViewModal.startedAt")}:
</span>{" "} </span>{" "}
{FormatDatetime(currentGroupTask.StartedAt)} {FormatDatetime(currentGroupTask.StartedAt)}
<br /> <br />
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
Endet at: {t("groupTasks.groupTasksViewModal.endedAt")}:
</span>{" "} </span>{" "}
{FormatDatetime(currentGroupTask.EndedAt)} {FormatDatetime(currentGroupTask.EndedAt)}
<br /> <br />
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
Duration: {t("groupTasks.groupTasksViewModal.duration")}:
</span>{" "} </span>{" "}
{GetDuration( {GetDuration(
currentGroupTask.StartedAt, currentGroupTask.StartedAt,
@ -522,7 +531,9 @@ export default function GroupTasksViewModal({ isOpen }) {
color: Constants.COLORS.SECONDARY, color: Constants.COLORS.SECONDARY,
}} }}
> >
Specified Global Inputs {t(
"groupTasks.groupTasksViewModal.popover.specifiedGlobalInputs"
)}
</h2> </h2>
<span> <span>
@ -610,6 +621,7 @@ function InputRequiredHandler({
step, step,
taskLockedByUserId, taskLockedByUserId,
}) { }) {
const { t } = useTranslation();
const [inputFields, setInputFields] = useState({}); const [inputFields, setInputFields] = useState({});
const globalInputs = JSON.parse(currentGroupTask.GlobalInputs); const globalInputs = JSON.parse(currentGroupTask.GlobalInputs);
@ -642,7 +654,7 @@ function InputRequiredHandler({
<> <>
{displayName} {displayName}
<Tag style={{ marginLeft: 6 }} color="purple"> <Tag style={{ marginLeft: 6 }} color="purple">
Global {t("groupTasks.tag.global")}
</Tag> </Tag>
</> </>
) : ( ) : (
@ -783,8 +795,16 @@ function InputRequiredHandler({
); );
default: default:
notificationApi["error"]({ notificationApi["error"]({
message: `Type ${groupTaskParameter.type} not implemented`, message: t(
description: `Was specified in: ${groupTaskParameter.displayName}`, "groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.message",
{ groupTaskParameterType: groupTaskParameter.type }
),
description: t(
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.description",
{
groupTaskParameterDisplayName: groupTaskParameter.displayName,
}
),
}); });
return ( return (
<p> <p>

View File

@ -18,6 +18,7 @@ import {
import { useContext } from "react"; import { useContext } from "react";
import { InfoCircleOutlined } from "@ant-design/icons"; import { InfoCircleOutlined } from "@ant-design/icons";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
import { useTranslation } from "react-i18next";
export default function GroupTypeSelectionModal({ export default function GroupTypeSelectionModal({
isOpen, isOpen,
@ -29,6 +30,7 @@ export default function GroupTypeSelectionModal({
const [notificationApi, notificationContextHolder] = const [notificationApi, notificationContextHolder] =
notification.useNotification(); notification.useNotification();
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
const handleCancel = () => setIsOpen(false); const handleCancel = () => setIsOpen(false);
const isStartTaskPossible = () => { const isStartTaskPossible = () => {
@ -108,8 +110,12 @@ export default function GroupTypeSelectionModal({
if (!canTaskBeStarted) { if (!canTaskBeStarted) {
notificationApi["error"]({ notificationApi["error"]({
message: `Global inputs cannot be empty`, message: t(
description: `Please fill in all global inputs`, "groupTasks.groupTypeSelectionModal.notification.globalInputsCannotBeEmpty.message"
),
description: t(
"groupTasks.groupTypeSelectionModal.notification.globalInputsCannotBeEmpty.description"
),
}); });
return; return;
} }
@ -144,7 +150,7 @@ export default function GroupTypeSelectionModal({
return ( return (
<Modal <Modal
title="Select a group type" title={t("groupTasks.groupTypeSelectionModal.title")}
open={isOpen} open={isOpen}
onCancel={handleCancel} onCancel={handleCancel}
centered centered
@ -152,7 +158,7 @@ export default function GroupTypeSelectionModal({
footer={[ footer={[
<Space key={0}> <Space key={0}>
<Button key="back" onClick={handleCancel}> <Button key="back" onClick={handleCancel}>
Cancel {t("buttonCancel")}
</Button> </Button>
<Button <Button
@ -161,14 +167,14 @@ export default function GroupTypeSelectionModal({
disabled={isStartTaskPossible()} disabled={isStartTaskPossible()}
onClick={handleStartTask} onClick={handleStartTask}
> >
Start task {t("groupTasks.groupTypeSelectionModal.button.startTask")}
</Button> </Button>
</Space>, </Space>,
]} ]}
> >
{notificationContextHolder} {notificationContextHolder}
<Select <Select
placeholder="Choose a group type" placeholder={t("groupTasks.groupTypeSelectionModal.select.placeholder")}
style={{ width: "100%" }} style={{ width: "100%" }}
value={currentSelectedModalGroupType} value={currentSelectedModalGroupType}
onChange={(value) => setCurrentSelectedModalGroupType(value)} onChange={(value) => setCurrentSelectedModalGroupType(value)}
@ -188,10 +194,13 @@ export default function GroupTypeSelectionModal({
<Form layout="vertical"> <Form layout="vertical">
<br /> <br />
<Form.Item <Form.Item
label="Description" label={t(
"groupTasks.groupTypeSelectionModal.form.item.groupTypeDescription.label"
)}
tooltip={{ tooltip={{
title: title: t(
"This description helps to understand afterwards what this task was about", "groupTasks.groupTypeSelectionModal.form.item.groupTypeDescription.tooltip.title"
),
icon: <InfoCircleOutlined />, icon: <InfoCircleOutlined />,
}} }}
required required
@ -215,12 +224,14 @@ function GroupGlobalInputs({
currentSelectedModalGroupType, currentSelectedModalGroupType,
notificationApi, notificationApi,
}) { }) {
const { t } = useTranslation();
const getLabel = (displayName) => { const getLabel = (displayName) => {
return ( return (
<> <>
{displayName} {displayName}
<Tag style={{ marginLeft: 6 }} color="purple"> <Tag style={{ marginLeft: 6 }} color="purple">
Global {t("groupTasks.tag.global")}
</Tag> </Tag>
</> </>
); );
@ -275,8 +286,14 @@ function GroupGlobalInputs({
break; break;
default: default:
notificationApi["error"]({ notificationApi["error"]({
message: `Type ${globalInput.type} not implemented`, message: t(
description: `Was specified in: ${globalInput.displayName}`, "groupTasks.groupTypeSelectionModal.notification.globalInputTypeNotImplemented.message",
{ globalInputType: globalInput.type }
),
description: t(
"groupTasks.groupTypeSelectionModal.notification.globalInputTypeNotImplemented.description",
{ globalInputDisplayName: globalInput.displayName }
),
}); });
break; break;
} }
@ -291,7 +308,9 @@ function GroupGlobalInputs({
{elements.length > 0 ? ( {elements.length > 0 ? (
<div> <div>
<Divider /> <Divider />
<h3 style={{ fontWeight: "bold" }}>Fill in the global values</h3> <h3 style={{ fontWeight: "bold" }}>
{t("groupTasks.groupTypeSelectionModal.h3")}
</h3>
<Form <Form
layout="vertical" layout="vertical"
id="groupTypeSelectionUserSpecifiedGlobalInputForm" id="groupTypeSelectionUserSpecifiedGlobalInputForm"

View File

@ -4,6 +4,7 @@ import GroupTasksViewModal from "./GroupTasksViewModal";
import GroupTypeSelectionModal from "./GroupTypeSelectionModal"; import GroupTypeSelectionModal from "./GroupTypeSelectionModal";
import GroupTaskTableList from "./GroupTasksTableList"; import GroupTaskTableList from "./GroupTasksTableList";
import { Constants, WebSocketContext, hasXYPermission } from "../../../utils"; import { Constants, WebSocketContext, hasXYPermission } from "../../../utils";
import { useTranslation } from "react-i18next";
export default function GroupTasks({ isGroupTasksViewModalOpen }) { export default function GroupTasks({ isGroupTasksViewModalOpen }) {
const [isGroupTypeSelectionModalOpen, setIsGroupTypeSelectionModalOpen] = const [isGroupTypeSelectionModalOpen, setIsGroupTypeSelectionModalOpen] =
@ -12,6 +13,7 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
const [currentSelectedModalGroupType, setCurrentSelectedModalGroupType] = const [currentSelectedModalGroupType, setCurrentSelectedModalGroupType] =
useState(); useState();
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
const showGroupTypeSelectionModal = (categoryGroup) => { const showGroupTypeSelectionModal = (categoryGroup) => {
setCurrentCategoryGroup(categoryGroup); setCurrentCategoryGroup(categoryGroup);
@ -31,7 +33,7 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
return ( return (
<> <>
{webSocketContext.CategoryGroups.length === 0 ? ( {webSocketContext.CategoryGroups.length === 0 ? (
<Result status="404" title="No group tasks found" /> <Result status="404" title={t("groupTasks.categoryGroups.length0")} />
) : ( ) : (
<> <>
{filteredCategoryGroups.length > 0 ? ( {filteredCategoryGroups.length > 0 ? (
@ -51,8 +53,8 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
<Result <Result
key="result" key="result"
status="403" status="403"
title="You were not assigned to any group tasks" title={t("groupTasks.categoryGroups.assignedToNoTask.title")}
subTitle="Please contact an administrator" subTitle={t("contactAdmin")}
/> />
)} )}
</> </>

View File

@ -1,15 +1,18 @@
import { Button, Result } from "antd"; import { Button, Result } from "antd";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
export default function PageNotFound() { export default function PageNotFound() {
const { t } = useTranslation();
return ( return (
<Result <Result
status="404" status="404"
title="404" title="404"
subTitle="Sorry, the page you visited does not exist." subTitle={t("pageNotFound.subTitle")}
extra={ extra={
<Link to="/"> <Link to="/">
<Button type="primary">Back Home</Button> <Button type="primary">{t("pageNotFound.buttonBackHome")}</Button>
</Link> </Link>
} }
/> />

View File

@ -9,39 +9,41 @@ import {
import { useContext } from "react"; import { useContext } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Constants } from "../../utils"; import { Constants } from "../../utils";
import { useTranslation } from "react-i18next";
export default function Scanners() { export default function Scanners() {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { t } = useTranslation();
const getTableColumns = () => { const getTableColumns = () => {
return [ return [
{ {
title: "Name", title: t("scanners.column.name"),
dataIndex: "name", dataIndex: "name",
key: "name", key: "name",
}, },
{ {
title: "Used by", title: t("scanners.column.usedBy"),
dataIndex: "usedByUserId", dataIndex: "usedByUserId",
key: "usedByUserId", key: "usedByUserId",
}, },
{ {
title: "Last used", title: t("scanners.column.lastUsed"),
dataIndex: "lastUsed", dataIndex: "lastUsed",
key: "lastUsed", key: "lastUsed",
}, },
{ {
title: "Registered at", title: t("scanners.column.registeredAt"),
dataIndex: "registeredAt", dataIndex: "registeredAt",
key: "registeredAt", key: "registeredAt",
}, },
{ {
title: "User-Agent", title: t("scanners.column.userAgent"),
dataIndex: "userAgent", dataIndex: "userAgent",
key: "userAgent", key: "userAgent",
}, },
{ {
title: "Action", title: t("scanners.column.action"),
dataIndex: "action", dataIndex: "action",
key: "action", key: "action",
render: (_, record) => ( render: (_, record) => (
@ -49,8 +51,11 @@ export default function Scanners() {
{record._usedByUserId === getUserId() ? ( {record._usedByUserId === getUserId() ? (
<Popconfirm <Popconfirm
placement="left" placement="left"
title="Are you sure you want to disconnect from this scanner?" title={t("scanners.column.action.disconnect.popconfirm.title")}
okText="Disconnect" okText={t(
"scanners.column.action.disconnect.popconfirm.okText"
)}
cancelText={t("buttonCancel")}
onConfirm={() => { onConfirm={() => {
webSocketContext.SendSocketMessage( webSocketContext.SendSocketMessage(
SentMessagesCommands.ScannersDisconnectScanner, SentMessagesCommands.ScannersDisconnectScanner,
@ -60,14 +65,17 @@ export default function Scanners() {
); );
}} }}
> >
<Link to="#">Disconnect</Link> <Link to="#">{t("scanners.column.action.disconnect")}</Link>
</Popconfirm> </Popconfirm>
) : ( ) : (
<Popconfirm <Popconfirm
placement="left" placement="left"
title="Are you sure you want to use this scanner?" title={t("scanners.column.action.use.popconfirm.title")}
description="Users connected to this scanner will be disconnected" description={t(
okText="Use" "scanners.column.action.use.popconfirm.description"
)}
okText={t("scanners.column.action.use.popconfirm.okText")}
cancelText={t("buttonCancel")}
onConfirm={() => { onConfirm={() => {
webSocketContext.SendSocketMessage( webSocketContext.SendSocketMessage(
SentMessagesCommands.ScannersUseScanners, SentMessagesCommands.ScannersUseScanners,
@ -77,7 +85,7 @@ export default function Scanners() {
); );
}} }}
> >
<Link to="#">Use scanner</Link> <Link to="#">{t("scanners.column.action.use")}</Link>
</Popconfirm> </Popconfirm>
)} )}
</Space> </Space>
@ -121,7 +129,7 @@ export default function Scanners() {
return ( return (
<> <>
<h1 style={{ fontWeight: "bold", float: "left" }}> <h1 style={{ fontWeight: "bold", float: "left" }}>
Scanners ( {t("scanners.header.scanners")} (
{webSocketContext.Scanners === null {webSocketContext.Scanners === null
? "0" ? "0"
: webSocketContext.Scanners.length} : webSocketContext.Scanners.length}
@ -132,18 +140,3 @@ export default function Scanners() {
</> </>
); );
} }
/*
<Popover
title="Scan this on mobile"
content={<QRCode value={"test"} bordered={false} />}
>
<Button
style={{ float: "right" }}
type="primary"
icon={<PlusOutlined />}
>
Add scanner
</Button>
</Popover>
*/

View File

@ -1,8 +1,11 @@
import { import {
Button, Button,
Card, Card,
Col,
Form, Form,
Input, Input,
Row,
Select,
Space, Space,
Table, Table,
Upload, Upload,
@ -22,68 +25,72 @@ import {
isEmailValid, isEmailValid,
} from "../../utils"; } from "../../utils";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
const columns = [
{
title: "User-Agent",
dataIndex: "userAgent",
key: "userAgent",
},
{
title: "Connection status",
dataIndex: "connectionStatus",
key: "connectionStatus",
},
{
title: "Last used",
dataIndex: "lastUsed",
key: "lastUsed",
},
{
title: "Expires at",
dataIndex: "expiresAt",
key: "expiresAt",
},
{
title: "Action",
dataIndex: "action",
key: "action",
render: (_, record) => {
return (
<Space size="middle">
<Link
href="#"
onClick={() => {
fetch(`${Constants.API_ADDRESS}/user/session/${record.key}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"X-Authorization": getUserSessionFromLocalStorage(),
},
})
.then((res) => handleUnauthorizedStatus(res.status))
.catch((err) => {
console.error(err);
});
}}
>
Sign out
</Link>
</Space>
);
},
},
];
export default function UserProfile() { export default function UserProfile() {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const [notificationApi, notificationContextHolder] = const [notificationApi, notificationContextHolder] =
notification.useNotification(); notification.useNotification();
const { t, i18n } = useTranslation();
const [oldPassword, setOldPassword] = useState(""); const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState(""); const [newPassword, setNewPassword] = useState("");
const [repeatedNewPassword, setRepeatedNewPassword] = useState(""); const [repeatedNewPassword, setRepeatedNewPassword] = useState("");
const getTableColumns = () => {
return [
{
title: t("userProfile.column.userAgent"),
dataIndex: "userAgent",
key: "userAgent",
},
{
title: t("userProfile.column.connectionStatus"),
dataIndex: "connectionStatus",
key: "connectionStatus",
},
{
title: t("userProfile.column.lastUsed"),
dataIndex: "lastUsed",
key: "lastUsed",
},
{
title: t("userProfile.column.expiresAt"),
dataIndex: "expiresAt",
key: "expiresAt",
},
{
title: t("userProfile.column.action"),
dataIndex: "action",
key: "action",
render: (_, record) => {
return (
<Space size="middle">
<Link
href="#"
onClick={() => {
fetch(`${Constants.API_ADDRESS}/user/session/${record.key}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"X-Authorization": getUserSessionFromLocalStorage(),
},
})
.then((res) => handleUnauthorizedStatus(res.status))
.catch((err) => {
console.error(err);
});
}}
>
{t("userProfile.column.action.signOut")}
</Link>
</Space>
);
},
},
];
};
const getTableItems = () => { const getTableItems = () => {
let items = []; let items = [];
@ -107,10 +114,11 @@ export default function UserProfile() {
const beforeUpload = (file) => { const beforeUpload = (file) => {
if (file.size > Constants.MAX_AVATAR_SIZE) { if (file.size > Constants.MAX_AVATAR_SIZE) {
notificationApi["error"]({ notificationApi["error"]({
message: `Your profile could not be changed`, message: t("userProfile.changeAvatarError.notification.message"),
description: `Avatar must be smaller than ${ description: t(
Constants.MAX_AVATAR_SIZE / 1024 / 1024 "userProfile.changeAvatarError.notification.description",
} MB`, { MAX_AVATAR_SIZE: Constants.MAX_AVATAR_SIZE / 1024 / 1024 }
),
}); });
return false; return false;
} }
@ -174,29 +182,55 @@ export default function UserProfile() {
<> <>
{notificationContextHolder} {notificationContextHolder}
<h1 style={{ fontWeight: "bold" }}>Your Profile</h1> <h1 style={{ fontWeight: "bold" }}>
{t("userProfile.header.yourProfile")}
</h1>
<Card> <Card>
<Upload
accept={Constants.ACCEPTED_FILE_TYPES.join(",")}
action={Constants.API_ADDRESS + "/user/avatar"}
maxCount={1}
showUploadList={false}
beforeUpload={beforeUpload}
headers={{
"X-Authorization": getUserSessionFromLocalStorage(),
}}
>
<MyAvatar
avatarWidth={200}
allUsers={webSocketContext.AllUsers}
userId={webSocketContext.User.Id}
/>
</Upload>
<Form layout="vertical"> <Form layout="vertical">
<Row>
<Col span={4}>
<Upload
accept={Constants.ACCEPTED_FILE_TYPES.join(",")}
action={Constants.API_ADDRESS + "/user/avatar"}
maxCount={1}
showUploadList={false}
beforeUpload={beforeUpload}
headers={{
"X-Authorization": getUserSessionFromLocalStorage(),
}}
>
<MyAvatar
avatarWidth={200}
allUsers={webSocketContext.AllUsers}
userId={webSocketContext.User.Id}
/>
</Upload>
</Col>
<Col span={4} offset={16}>
<Form.Item label={t("userProfile.form.language")}>
<Select
style={{ width: "100%" }}
defaultValue={i18n.language}
options={[
{
value: "en",
label: "English",
},
{
value: "de",
label: "Deutsch",
},
]}
onChange={(e) => i18n.changeLanguage(e)}
/>
</Form.Item>
</Col>
</Row>
<Form.Item <Form.Item
label="Username" label={t("userProfile.form.username")}
hasFeedback hasFeedback
validateStatus={ validateStatus={
webSocketContext.UserProfileStateUsername.length < webSocketContext.UserProfileStateUsername.length <
@ -213,7 +247,7 @@ export default function UserProfile() {
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="E-Mail" label={t("userProfile.form.email")}
hasFeedback hasFeedback
validateStatus={ validateStatus={
!isEmailValid(webSocketContext.UserProfileStateEmail) && "error" !isEmailValid(webSocketContext.UserProfileStateEmail) && "error"
@ -228,7 +262,7 @@ export default function UserProfile() {
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Old password" label={t("userProfile.form.oldPassword")}
hasFeedback hasFeedback
validateStatus={ validateStatus={
oldPassword !== "" && oldPassword !== "" &&
@ -244,7 +278,7 @@ export default function UserProfile() {
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="New password" label={t("userProfile.form.newPassword")}
hasFeedback hasFeedback
validateStatus={ validateStatus={
newPassword !== "" && newPassword !== "" &&
@ -260,7 +294,7 @@ export default function UserProfile() {
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Repeat new password" label={t("userProfile.form.repeatNewPassword")}
hasFeedback hasFeedback
validateStatus={newPassword !== repeatedNewPassword && "error"} validateStatus={newPassword !== repeatedNewPassword && "error"}
> >
@ -278,7 +312,7 @@ export default function UserProfile() {
onClick={() => handleOnSubmit()} onClick={() => handleOnSubmit()}
disabled={isButtonDisabled()} disabled={isButtonDisabled()}
> >
Save {t("buttonSave")}
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>
@ -287,10 +321,11 @@ export default function UserProfile() {
<br /> <br />
<h1 style={{ fontWeight: "bold" }}> <h1 style={{ fontWeight: "bold" }}>
Your Sessions ({webSocketContext.User.Sessions.length}) {t("userProfile.header.yourSessions")} (
{webSocketContext.User.Sessions.length})
</h1> </h1>
<Table columns={columns} dataSource={getTableItems()} /> <Table columns={getTableColumns()} dataSource={getTableItems()} />
</> </>
); );
} }

22
src/i18n.js Normal file
View File

@ -0,0 +1,22 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
// the translations
// (tip move them in a JSON file and import them,
// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files)
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.use(LanguageDetector)
.use(Backend)
.init({
debug: true,
fallbackLng: "en",
interpolation: {
escapeValue: false, // react already safes from xss
},
});
export default i18n;

View File

@ -3,13 +3,16 @@ import ReactDOM from "react-dom/client";
import "./index.css"; import "./index.css";
import App from "./App"; import App from "./App";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import "./i18n";
//import reportWebVitals from './reportWebVitals'; //import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById("root")); const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( root.render(
<BrowserRouter> <React.Suspense fallback="loading">
<App /> <BrowserRouter>
</BrowserRouter> <App />
</BrowserRouter>
</React.Suspense>
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function