mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-11-01 19:27:58 +00:00
Merge pull request #161 from stephb9959/main
[WIFI-12261] Added system secrets on the system page
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.9.0(7)",
|
"version": "2.9.0(9)",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.9.0(7)",
|
"version": "2.9.0(9)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.11",
|
"@chakra-ui/icons": "^2.0.11",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.9.0(7)",
|
"version": "2.9.0(9)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
@@ -1037,6 +1037,7 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"backend_logs": "Back-End-Protokolle",
|
"backend_logs": "Back-End-Protokolle",
|
||||||
|
"configuration": "Aufbau",
|
||||||
"could_not_retrieve": "Fehler: {{name}} Systeminformationen konnten nicht abgerufen werden",
|
"could_not_retrieve": "Fehler: {{name}} Systeminformationen konnten nicht abgerufen werden",
|
||||||
"endpoint": "Endpunkt",
|
"endpoint": "Endpunkt",
|
||||||
"hostname": "Hostname",
|
"hostname": "Hostname",
|
||||||
@@ -1047,9 +1048,10 @@
|
|||||||
"os": "Betriebssystem",
|
"os": "Betriebssystem",
|
||||||
"processors": "Prozessoren",
|
"processors": "Prozessoren",
|
||||||
"reload_chosen_subsystems": "Ausgewählte Subsysteme neu laden",
|
"reload_chosen_subsystems": "Ausgewählte Subsysteme neu laden",
|
||||||
"secrets": "Systemgeheimnisse",
|
"secrets": "Geheimnisse",
|
||||||
"secrets_create": "Geheimnis erstellen",
|
"secrets_create": "Geheimnis erstellen",
|
||||||
"secrets_one": "Systemgeheimnis",
|
"secrets_one": "Geheimnis",
|
||||||
|
"services": "dienstleistungen",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
"subsystems": "Subsysteme",
|
"subsystems": "Subsysteme",
|
||||||
"success_reload": "Reload-Befehl erfolgreich gesendet!",
|
"success_reload": "Reload-Befehl erfolgreich gesendet!",
|
||||||
|
|||||||
@@ -1037,6 +1037,7 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"backend_logs": "Back-End Logs",
|
"backend_logs": "Back-End Logs",
|
||||||
|
"configuration": "Configuration",
|
||||||
"could_not_retrieve": "Error: could not retrieve {{name}} system information",
|
"could_not_retrieve": "Error: could not retrieve {{name}} system information",
|
||||||
"endpoint": "Endpoint",
|
"endpoint": "Endpoint",
|
||||||
"hostname": "Host Name",
|
"hostname": "Host Name",
|
||||||
@@ -1047,9 +1048,10 @@
|
|||||||
"os": "Operating System",
|
"os": "Operating System",
|
||||||
"processors": "Processors",
|
"processors": "Processors",
|
||||||
"reload_chosen_subsystems": "Reload Chosen Subsystems",
|
"reload_chosen_subsystems": "Reload Chosen Subsystems",
|
||||||
"secrets": "System Secrets",
|
"secrets": "Secrets",
|
||||||
"secrets_create": "Create Secret",
|
"secrets_create": "Create Secret",
|
||||||
"secrets_one": "System Secret",
|
"secrets_one": "Secret",
|
||||||
|
"services": "Services",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
"subsystems": "Subsystems",
|
"subsystems": "Subsystems",
|
||||||
"success_reload": "Successfully sent reload command!",
|
"success_reload": "Successfully sent reload command!",
|
||||||
|
|||||||
@@ -1037,6 +1037,7 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"backend_logs": "Registros de back-end",
|
"backend_logs": "Registros de back-end",
|
||||||
|
"configuration": "Configuración",
|
||||||
"could_not_retrieve": "Error: no se pudo recuperar la información del sistema {{name}} ",
|
"could_not_retrieve": "Error: no se pudo recuperar la información del sistema {{name}} ",
|
||||||
"endpoint": "punto final",
|
"endpoint": "punto final",
|
||||||
"hostname": "Nombre de host",
|
"hostname": "Nombre de host",
|
||||||
@@ -1047,9 +1048,10 @@
|
|||||||
"os": "sistema operativo",
|
"os": "sistema operativo",
|
||||||
"processors": "Procesadores",
|
"processors": "Procesadores",
|
||||||
"reload_chosen_subsystems": "Recargar subsistemas elegidos",
|
"reload_chosen_subsystems": "Recargar subsistemas elegidos",
|
||||||
"secrets": "Secretos del sistema",
|
"secrets": "Misterios",
|
||||||
"secrets_create": "Crear secreto",
|
"secrets_create": "Crear secreto",
|
||||||
"secrets_one": "Secreto del sistema",
|
"secrets_one": "secreto",
|
||||||
|
"services": "Servicios",
|
||||||
"start": "comienzo",
|
"start": "comienzo",
|
||||||
"subsystems": "Subsistemas",
|
"subsystems": "Subsistemas",
|
||||||
"success_reload": "¡Comando de recarga enviado con éxito!",
|
"success_reload": "¡Comando de recarga enviado con éxito!",
|
||||||
|
|||||||
@@ -1037,6 +1037,7 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"backend_logs": "Journaux principaux",
|
"backend_logs": "Journaux principaux",
|
||||||
|
"configuration": "Configuration",
|
||||||
"could_not_retrieve": "Erreur : impossible de récupérer les informations système {{name}} ",
|
"could_not_retrieve": "Erreur : impossible de récupérer les informations système {{name}} ",
|
||||||
"endpoint": "Point final",
|
"endpoint": "Point final",
|
||||||
"hostname": "nom d'hôte",
|
"hostname": "nom d'hôte",
|
||||||
@@ -1047,9 +1048,10 @@
|
|||||||
"os": "Système opérateur",
|
"os": "Système opérateur",
|
||||||
"processors": "Processeurs",
|
"processors": "Processeurs",
|
||||||
"reload_chosen_subsystems": "Recharger les sous-systèmes choisis",
|
"reload_chosen_subsystems": "Recharger les sous-systèmes choisis",
|
||||||
"secrets": "Secrets du système",
|
"secrets": "Secrets",
|
||||||
"secrets_create": "Créer un secret",
|
"secrets_create": "Créer un secret",
|
||||||
"secrets_one": "Code secret du système",
|
"secrets_one": "Secret",
|
||||||
|
"services": "Prestations de service",
|
||||||
"start": "Début",
|
"start": "Début",
|
||||||
"subsystems": "Sous-systèmes",
|
"subsystems": "Sous-systèmes",
|
||||||
"success_reload": "Commande de rechargement envoyée avec succès !",
|
"success_reload": "Commande de rechargement envoyée avec succès !",
|
||||||
|
|||||||
@@ -1037,6 +1037,7 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"backend_logs": "Registros de back-end",
|
"backend_logs": "Registros de back-end",
|
||||||
|
"configuration": "Configuração",
|
||||||
"could_not_retrieve": "Erro: não foi possível recuperar {{name}} informações do sistema",
|
"could_not_retrieve": "Erro: não foi possível recuperar {{name}} informações do sistema",
|
||||||
"endpoint": "Ponto final",
|
"endpoint": "Ponto final",
|
||||||
"hostname": "Nome de anfitrião",
|
"hostname": "Nome de anfitrião",
|
||||||
@@ -1047,9 +1048,10 @@
|
|||||||
"os": "Sistema Operacional",
|
"os": "Sistema Operacional",
|
||||||
"processors": "Processadores",
|
"processors": "Processadores",
|
||||||
"reload_chosen_subsystems": "Recarregar Subsistemas Escolhidos",
|
"reload_chosen_subsystems": "Recarregar Subsistemas Escolhidos",
|
||||||
"secrets": "Segredos do sistema",
|
"secrets": "Segredos",
|
||||||
"secrets_create": "Criar Segredo",
|
"secrets_create": "Criar Segredo",
|
||||||
"secrets_one": "Segredo do sistema",
|
"secrets_one": "Segredo",
|
||||||
|
"services": "Serviços",
|
||||||
"start": "Começar",
|
"start": "Começar",
|
||||||
"subsystems": "Subsistemas",
|
"subsystems": "Subsistemas",
|
||||||
"success_reload": "Comando de recarga enviado com sucesso!",
|
"success_reload": "Comando de recarga enviado com sucesso!",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export type DataTableProps = {
|
|||||||
obj?: string;
|
obj?: string;
|
||||||
sortBy?: { id: string; desc: boolean }[];
|
sortBy?: { id: string; desc: boolean }[];
|
||||||
hiddenColumns?: string[];
|
hiddenColumns?: string[];
|
||||||
|
hideEmptyListText?: boolean;
|
||||||
hideControls?: boolean;
|
hideControls?: boolean;
|
||||||
minHeight?: string | number;
|
minHeight?: string | number;
|
||||||
fullScreen?: boolean;
|
fullScreen?: boolean;
|
||||||
@@ -77,6 +78,7 @@ const _DataTable = ({
|
|||||||
sortBy,
|
sortBy,
|
||||||
hiddenColumns,
|
hiddenColumns,
|
||||||
hideControls,
|
hideControls,
|
||||||
|
hideEmptyListText,
|
||||||
count,
|
count,
|
||||||
setPageInfo,
|
setPageInfo,
|
||||||
isManual,
|
isManual,
|
||||||
@@ -86,6 +88,7 @@ const _DataTable = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
const textColor = useColorModeValue('gray.700', 'white');
|
const textColor = useColorModeValue('gray.700', 'white');
|
||||||
|
const hoveredRowBg = useColorModeValue('gray.100', 'gray.600');
|
||||||
const getPageSize = () => {
|
const getPageSize = () => {
|
||||||
try {
|
try {
|
||||||
if (showAllRows) return 1000000;
|
if (showAllRows) return 1000000;
|
||||||
@@ -142,6 +145,10 @@ const _DataTable = ({
|
|||||||
usePagination,
|
usePagination,
|
||||||
) as TableInstanceWithHooks<object>;
|
) as TableInstanceWithHooks<object>;
|
||||||
|
|
||||||
|
const handleGoToPage = (newPage: number) => {
|
||||||
|
if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(newPage));
|
||||||
|
gotoPage(newPage);
|
||||||
|
};
|
||||||
const handleNextPage = () => {
|
const handleNextPage = () => {
|
||||||
nextPage();
|
nextPage();
|
||||||
if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(pageIndex + 1));
|
if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(pageIndex + 1));
|
||||||
@@ -256,7 +263,13 @@ const _DataTable = ({
|
|||||||
{page.map((row: Row) => {
|
{page.map((row: Row) => {
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
return (
|
return (
|
||||||
<Tr {...row.getRowProps()} key={uuid()}>
|
<Tr
|
||||||
|
{...row.getRowProps()}
|
||||||
|
key={uuid()}
|
||||||
|
_hover={{
|
||||||
|
backgroundColor: hoveredRowBg,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
row.cells.map((cell) => (
|
row.cells.map((cell) => (
|
||||||
@@ -288,7 +301,7 @@ const _DataTable = ({
|
|||||||
</Tbody>
|
</Tbody>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
{!isLoading && data.length === 0 && (
|
{!isLoading && data.length === 0 && !hideEmptyListText && (
|
||||||
<Center>
|
<Center>
|
||||||
{obj ? (
|
{obj ? (
|
||||||
<Heading size="md" pt={12}>
|
<Heading size="md" pt={12}>
|
||||||
@@ -309,7 +322,7 @@ const _DataTable = ({
|
|||||||
<Tooltip label={t('table.first_page')}>
|
<Tooltip label={t('table.first_page')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Go to first page"
|
aria-label="Go to first page"
|
||||||
onClick={() => gotoPage(0)}
|
onClick={() => handleGoToPage(0)}
|
||||||
isDisabled={!canPreviousPage}
|
isDisabled={!canPreviousPage}
|
||||||
icon={<ArrowLeftIcon h={3} w={3} />}
|
icon={<ArrowLeftIcon h={3} w={3} />}
|
||||||
mr={4}
|
mr={4}
|
||||||
@@ -347,7 +360,7 @@ const _DataTable = ({
|
|||||||
max={pageOptions.length}
|
max={pageOptions.length}
|
||||||
onChange={(_: unknown, numberValue: number) => {
|
onChange={(_: unknown, numberValue: number) => {
|
||||||
const newPage = numberValue ? numberValue - 1 : 0;
|
const newPage = numberValue ? numberValue - 1 : 0;
|
||||||
gotoPage(newPage);
|
handleGoToPage(newPage);
|
||||||
}}
|
}}
|
||||||
defaultValue={pageIndex + 1}
|
defaultValue={pageIndex + 1}
|
||||||
>
|
>
|
||||||
@@ -386,7 +399,7 @@ const _DataTable = ({
|
|||||||
<Tooltip label={t('table.last_page')}>
|
<Tooltip label={t('table.last_page')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Go to last page"
|
aria-label="Go to last page"
|
||||||
onClick={() => gotoPage(pageCount - 1)}
|
onClick={() => handleGoToPage(pageCount - 1)}
|
||||||
isDisabled={!canNextPage}
|
isDisabled={!canNextPage}
|
||||||
icon={<ArrowRightIcon h={3} w={3} />}
|
icon={<ArrowRightIcon h={3} w={3} />}
|
||||||
ml={4}
|
ml={4}
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ const SortableDataTable: React.FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
|
const hoveredRowBg = useColorModeValue('gray.100', 'gray.600');
|
||||||
const textColor = useColorModeValue('gray.700', 'white');
|
const textColor = useColorModeValue('gray.700', 'white');
|
||||||
const getPageSize = () => {
|
const getPageSize = () => {
|
||||||
const saved = saveSettingsId ? localStorage.getItem(saveSettingsId) : undefined;
|
const saved = saveSettingsId ? localStorage.getItem(saveSettingsId) : undefined;
|
||||||
@@ -223,7 +224,13 @@ const SortableDataTable: React.FC<Props> = ({
|
|||||||
{page.map((row: Row) => {
|
{page.map((row: Row) => {
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
return (
|
return (
|
||||||
<Tr {...row.getRowProps()} key={uuid()}>
|
<Tr
|
||||||
|
{...row.getRowProps()}
|
||||||
|
key={uuid()}
|
||||||
|
_hover={{
|
||||||
|
backgroundColor: hoveredRowBg,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
row.cells.map((cell) => (
|
row.cells.map((cell) => (
|
||||||
|
|||||||
139
src/pages/SystemPage/SystemSecrets/Actions.tsx
Normal file
139
src/pages/SystemPage/SystemSecrets/Actions.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CopyIcon } from '@chakra-ui/icons';
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverCloseButton,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverFooter,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverTrigger,
|
||||||
|
Center,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
useDisclosure,
|
||||||
|
HStack,
|
||||||
|
Text,
|
||||||
|
useClipboard,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Eye, Trash } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import EditSecretButton from './EditButton';
|
||||||
|
import { Secret, useDeleteSystemSecret } from 'hooks/Network/Secrets';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
secret: Secret;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SystemSecretActions = ({ secret, isDisabled }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const deleteSecret = useDeleteSystemSecret();
|
||||||
|
const { hasCopied, onCopy } = useClipboard(secret.value);
|
||||||
|
|
||||||
|
const handleDeleteClick = React.useCallback(() => {
|
||||||
|
deleteSecret.mutate(secret.key, {
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack mx="auto">
|
||||||
|
<Popover isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
|
||||||
|
<Tooltip hasArrow label={t('crud.delete')} placement="top" isDisabled={isOpen}>
|
||||||
|
<Box>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton
|
||||||
|
aria-label="delete-device"
|
||||||
|
colorScheme="red"
|
||||||
|
icon={<Trash size={20} />}
|
||||||
|
size="sm"
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent w="340px">
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('crud.delete')} {secret.key}
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
<Text whiteSpace="break-spaces">{t('crud.delete_confirm', { obj: t('system.secrets_one') })}</Text>
|
||||||
|
</PopoverBody>
|
||||||
|
<PopoverFooter>
|
||||||
|
<Center>
|
||||||
|
<Button colorScheme="gray" mr="1" onClick={onClose}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="red" ml="1" onClick={handleDeleteClick} isLoading={deleteSecret.isLoading}>
|
||||||
|
{t('common.yes')}
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</PopoverFooter>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Tooltip
|
||||||
|
label={hasCopied ? `${t('common.copied')}!` : `${t('common.copy')} ${t('system.secrets_one')}`}
|
||||||
|
hasArrow
|
||||||
|
closeOnClick={false}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('common.copy')}
|
||||||
|
icon={<CopyIcon h={5} w={5} />}
|
||||||
|
onClick={onCopy}
|
||||||
|
size="sm"
|
||||||
|
colorScheme="teal"
|
||||||
|
mr={2}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<EditSecretButton secret={secret} />
|
||||||
|
<Popover>
|
||||||
|
<Tooltip label={`${t('common.view')} ${t('system.secrets_one')}`} hasArrow closeOnClick={false}>
|
||||||
|
<Box>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton aria-label={t('common.view')} icon={<Eye size={20} />} size="sm" colorScheme="purple" />
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent w="560px">
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('common.view')} {secret.key}
|
||||||
|
<Tooltip
|
||||||
|
label={hasCopied ? `${t('common.copied')}!` : `${t('common.copy')} ${t('system.secrets_one')}`}
|
||||||
|
hasArrow
|
||||||
|
closeOnClick={false}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('common.copy')}
|
||||||
|
icon={<CopyIcon h={4} w={4} />}
|
||||||
|
onClick={onCopy}
|
||||||
|
size="xs"
|
||||||
|
colorScheme="teal"
|
||||||
|
ml={2}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
<Text whiteSpace="break-spaces">
|
||||||
|
<Center>
|
||||||
|
<pre style={{ fontFamily: 'monospace' }}>{secret.value}</pre>
|
||||||
|
</Center>
|
||||||
|
</Text>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemSecretActions;
|
||||||
118
src/pages/SystemPage/SystemSecrets/CreateButton.tsx
Normal file
118
src/pages/SystemPage/SystemSecrets/CreateButton.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
FormControl,
|
||||||
|
FormErrorMessage,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { CreateButton } from '../../../components/Buttons/CreateButton';
|
||||||
|
import { SaveButton } from '../../../components/Buttons/SaveButton';
|
||||||
|
import { Modal } from '../../../components/Modals/Modal';
|
||||||
|
import { useCreateSystemSecret } from 'hooks/Network/Secrets';
|
||||||
|
import { AxiosError } from 'models/Axios';
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_FORM_VALUES: FormValues = {
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const SystemSecretCreateButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [form, setForm] = React.useState<FormValues>(DEFAULT_FORM_VALUES);
|
||||||
|
const [isNameChanged, setIsNameChanged] = React.useState(false);
|
||||||
|
const [isValueChanged, setIsValueChanged] = React.useState(false);
|
||||||
|
const create = useCreateSystemSecret();
|
||||||
|
|
||||||
|
const onKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setForm({ ...form, key: e.target.value });
|
||||||
|
if (!isNameChanged) setIsNameChanged(true);
|
||||||
|
};
|
||||||
|
const onValueChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setForm({ ...form, value: e.target.value });
|
||||||
|
if (!isValueChanged) setIsValueChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNameError = form.key.length === 0;
|
||||||
|
const isValueError = form.value.length === 0;
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
create.mutate(form, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
id: 'create-system-secret-success',
|
||||||
|
title: t('common.success'),
|
||||||
|
description: t('crud.success_update_obj', {
|
||||||
|
obj: t('system.secrets_one'),
|
||||||
|
}),
|
||||||
|
status: 'success',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
toast({
|
||||||
|
id: 'create-system-secret-error',
|
||||||
|
title: t('common.error'),
|
||||||
|
description: (e as AxiosError)?.response?.data?.ErrorDescription,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenClick = () => {
|
||||||
|
setIsNameChanged(false);
|
||||||
|
setIsValueChanged(false);
|
||||||
|
setForm(DEFAULT_FORM_VALUES);
|
||||||
|
onOpen();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CreateButton onClick={handleOpenClick} isCompact />
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t('system.secrets_create')}
|
||||||
|
topRightButtons={
|
||||||
|
<SaveButton onClick={onSubmit} isDisabled={isNameError || isValueError} isLoading={create.isLoading} />
|
||||||
|
}
|
||||||
|
options={{
|
||||||
|
modalSize: 'sm',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<FormControl mb={2} isInvalid={isNameError && isNameChanged}>
|
||||||
|
<FormLabel>{t('common.name')}</FormLabel>
|
||||||
|
<Input value={form.key} onChange={onKeyChange} />
|
||||||
|
<FormErrorMessage>{t('form.required')}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mb={2} isInvalid={isValueError && isValueChanged}>
|
||||||
|
<FormLabel>{t('common.value')}</FormLabel>
|
||||||
|
<Textarea value={form.value} onChange={onValueChange} rows={2} />
|
||||||
|
<FormErrorMessage>{t('form.required')}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemSecretCreateButton;
|
||||||
146
src/pages/SystemPage/SystemSecrets/EditButton.tsx
Normal file
146
src/pages/SystemPage/SystemSecrets/EditButton.tsx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
FormControl,
|
||||||
|
FormErrorMessage,
|
||||||
|
FormLabel,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverCloseButton,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverFooter,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverTrigger,
|
||||||
|
Text,
|
||||||
|
Textarea,
|
||||||
|
Tooltip,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Pencil } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Secret, useUpdateSystemSecret } from 'hooks/Network/Secrets';
|
||||||
|
import { AxiosError } from 'models/Axios';
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secret: Secret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditSecretButton = ({ secret }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [form, setForm] = React.useState<FormValues>({
|
||||||
|
key: secret.key,
|
||||||
|
value: secret.value,
|
||||||
|
});
|
||||||
|
const [isNameChanged, setIsNameChanged] = React.useState(false);
|
||||||
|
const [isValueChanged, setIsValueChanged] = React.useState(false);
|
||||||
|
const update = useUpdateSystemSecret();
|
||||||
|
|
||||||
|
const onKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setForm({ ...form, key: e.target.value });
|
||||||
|
if (!isNameChanged) setIsNameChanged(true);
|
||||||
|
};
|
||||||
|
const onValueChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setForm({ ...form, value: e.target.value });
|
||||||
|
if (!isValueChanged) setIsValueChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNameError = form.key.length === 0;
|
||||||
|
const isValueError = form.value.length === 0;
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
update.mutate(form, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
id: 'create-system-secret-success',
|
||||||
|
title: t('common.success'),
|
||||||
|
status: 'success',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
toast({
|
||||||
|
id: 'create-system-secret-error',
|
||||||
|
title: t('common.error'),
|
||||||
|
description: (e as AxiosError)?.response?.data?.ErrorDescription,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenClick = () => {
|
||||||
|
setIsNameChanged(false);
|
||||||
|
setIsValueChanged(false);
|
||||||
|
setForm({
|
||||||
|
key: secret.key,
|
||||||
|
value: secret.value,
|
||||||
|
});
|
||||||
|
onOpen();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover isOpen={isOpen} onOpen={handleOpenClick} onClose={onClose}>
|
||||||
|
<Tooltip hasArrow label={t('crud.edit')} placement="top" isDisabled={isOpen}>
|
||||||
|
<Box>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton aria-label="delete-device" colorScheme="blue" icon={<Pencil size={20} />} size="sm" />
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent w="340px">
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('crud.edit')} {secret.key}
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
<Text whiteSpace="break-spaces">
|
||||||
|
<Box>
|
||||||
|
<FormControl mb={2} isInvalid={isNameError && isNameChanged}>
|
||||||
|
<FormLabel>{t('common.name')}</FormLabel>
|
||||||
|
<Input value={form.key} onChange={onKeyChange} />
|
||||||
|
<FormErrorMessage>{t('form.required')}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mb={2} isInvalid={isValueError && isValueChanged}>
|
||||||
|
<FormLabel>{t('common.value')}</FormLabel>
|
||||||
|
<Textarea value={form.value} onChange={onValueChange} rows={2} />
|
||||||
|
<FormErrorMessage>{t('form.required')}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
</Text>
|
||||||
|
</PopoverBody>
|
||||||
|
<PopoverFooter>
|
||||||
|
<Center>
|
||||||
|
<Button colorScheme="gray" mr="1" onClick={onClose}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="blue" ml="1" onClick={onSubmit} isLoading={update.isLoading}>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</PopoverFooter>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditSecretButton;
|
||||||
70
src/pages/SystemPage/SystemSecrets/Table.tsx
Normal file
70
src/pages/SystemPage/SystemSecrets/Table.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DataTable } from '../../../components/DataTables/DataTable';
|
||||||
|
import SystemSecretActions from './Actions';
|
||||||
|
import { Secret, useGetAllSystemSecrets, useGetSystemSecretsDictionary } from 'hooks/Network/Secrets';
|
||||||
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
|
const SystemSecretsTable = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const getSecrets = useGetAllSystemSecrets();
|
||||||
|
const getDictionary = useGetSystemSecretsDictionary();
|
||||||
|
|
||||||
|
const descriptionCell = React.useCallback(
|
||||||
|
(secret: Secret) => {
|
||||||
|
if (!getDictionary.data) return '-';
|
||||||
|
|
||||||
|
return getDictionary.data.find((d) => d.key === secret.key)?.description ?? '-';
|
||||||
|
},
|
||||||
|
[getDictionary.data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const actionCell = React.useCallback((secret: Secret) => <SystemSecretActions secret={secret} />, []);
|
||||||
|
|
||||||
|
const columns = React.useMemo(
|
||||||
|
(): Column<Secret>[] => [
|
||||||
|
{
|
||||||
|
id: 'key',
|
||||||
|
Header: t('common.name'),
|
||||||
|
Footer: '',
|
||||||
|
accessor: 'key',
|
||||||
|
alwaysShow: true,
|
||||||
|
customWidth: '120px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'description',
|
||||||
|
Header: t('common.description'),
|
||||||
|
Footer: '',
|
||||||
|
Cell: ({ cell }) => descriptionCell(cell.row.original),
|
||||||
|
accessor: 'description',
|
||||||
|
hasPopover: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
Header: t('common.actions'),
|
||||||
|
Footer: '',
|
||||||
|
Cell: (v) => actionCell(v.cell.row.original),
|
||||||
|
disableSortBy: true,
|
||||||
|
customWidth: '120px',
|
||||||
|
alwaysShow: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t, descriptionCell],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Box w="100%">
|
||||||
|
<DataTable
|
||||||
|
columns={columns as Column<object>[]}
|
||||||
|
saveSettingsId="system.secrets.table"
|
||||||
|
data={getSecrets.data ?? []}
|
||||||
|
obj={t('keys.other')}
|
||||||
|
sortBy={[{ id: 'key', desc: false }]}
|
||||||
|
showAllRows
|
||||||
|
hideControls
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemSecretsTable;
|
||||||
53
src/pages/SystemPage/SystemSecrets/index.tsx
Normal file
53
src/pages/SystemPage/SystemSecrets/index.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
BackgroundProps,
|
||||||
|
Box,
|
||||||
|
EffectProps,
|
||||||
|
Heading,
|
||||||
|
InteractivityProps,
|
||||||
|
LayoutProps,
|
||||||
|
PositionProps,
|
||||||
|
SpaceProps,
|
||||||
|
Spacer,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import SystemSecretCreateButton from './CreateButton';
|
||||||
|
import SystemSecretsTable from './Table';
|
||||||
|
import { Card } from 'components/Containers/Card';
|
||||||
|
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||||
|
import { CardHeader } from 'components/Containers/Card/CardHeader';
|
||||||
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
|
|
||||||
|
export interface SystemSecretsCardProps
|
||||||
|
extends LayoutProps,
|
||||||
|
SpaceProps,
|
||||||
|
BackgroundProps,
|
||||||
|
InteractivityProps,
|
||||||
|
PositionProps,
|
||||||
|
EffectProps {}
|
||||||
|
|
||||||
|
export const SystemSecretsCard = ({ ...props }: SystemSecretsCardProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
if (!user || user.userRole !== 'root') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={4} py={4}>
|
||||||
|
<Card variant="widget" {...props}>
|
||||||
|
<CardHeader>
|
||||||
|
<Heading size="md" my="auto">
|
||||||
|
{t('system.secrets')}
|
||||||
|
</Heading>
|
||||||
|
<Spacer />
|
||||||
|
<SystemSecretCreateButton />
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody p={4}>
|
||||||
|
<SystemSecretsTable />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -21,8 +21,8 @@ import axios from 'axios';
|
|||||||
import { FloppyDisk } from 'phosphor-react';
|
import { FloppyDisk } from 'phosphor-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { Modal } from '../../../../components/Modals/Modal';
|
||||||
import { LoadingOverlay } from 'components/LoadingOverlay';
|
import { LoadingOverlay } from 'components/LoadingOverlay';
|
||||||
import { Modal } from 'components/Modals/Modal';
|
|
||||||
import { useGetSystemLogLevelNames, useGetSystemLogLevels, useUpdateSystemLogLevels } from 'hooks/Network/System';
|
import { useGetSystemLogLevelNames, useGetSystemLogLevels, useUpdateSystemLogLevels } from 'hooks/Network/System';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DataTable } from 'components/DataTables/DataTable';
|
import { DataTable } from '../../../components/DataTables/DataTable';
|
||||||
import { compactDate } from 'helpers/dateFormatting';
|
import { compactDate } from 'helpers/dateFormatting';
|
||||||
import { Column } from 'models/Table';
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ import {
|
|||||||
import { MultiValue, Select } from 'chakra-react-select';
|
import { MultiValue, Select } from 'chakra-react-select';
|
||||||
import { ArrowsClockwise } from 'phosphor-react';
|
import { ArrowsClockwise } from 'phosphor-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import FormattedDate from '../../../components/InformationDisplays/FormattedDate';
|
||||||
import SystemLoggingButton from './LoggingButton';
|
import SystemLoggingButton from './LoggingButton';
|
||||||
import SystemCertificatesTable from './SystemCertificatesTable';
|
import SystemCertificatesTable from './SystemCertificatesTable';
|
||||||
import { Card } from 'components/Containers/Card';
|
import { Card } from 'components/Containers/Card';
|
||||||
import { CardBody } from 'components/Containers/Card/CardBody';
|
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||||
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
|
||||||
import { compactSecondsToDetailed } from 'helpers/dateFormatting';
|
import { compactSecondsToDetailed } from 'helpers/dateFormatting';
|
||||||
import { EndpointApiResponse } from 'hooks/Network/Endpoints';
|
import { EndpointApiResponse } from 'hooks/Network/Endpoints';
|
||||||
import { useGetSubsystems, useGetSystemInfo, useReloadSubsystems } from 'hooks/Network/System';
|
import { useGetSubsystems, useGetSystemInfo, useReloadSubsystems } from 'hooks/Network/System';
|
||||||
@@ -65,7 +65,7 @@ const SystemTile = ({ endpoint, token }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card>
|
<Card variant="widget">
|
||||||
<Box display="flex" mb={2}>
|
<Box display="flex" mb={2}>
|
||||||
<Heading pt={0}>{endpoint.type}</Heading>
|
<Heading pt={0}>{endpoint.type}</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
@@ -73,7 +73,7 @@ const SystemTile = ({ endpoint, token }: Props) => {
|
|||||||
<Button
|
<Button
|
||||||
mt={1}
|
mt={1}
|
||||||
minWidth="112px"
|
minWidth="112px"
|
||||||
colorScheme="gray"
|
colorScheme="blue"
|
||||||
rightIcon={<ArrowsClockwise />}
|
rightIcon={<ArrowsClockwise />}
|
||||||
onClick={refresh}
|
onClick={refresh}
|
||||||
isLoading={isFetchingSystem || isFetchingSubsystems}
|
isLoading={isFetchingSystem || isFetchingSubsystems}
|
||||||
@@ -179,7 +179,7 @@ const SystemTile = ({ endpoint, token }: Props) => {
|
|||||||
ml={2}
|
ml={2}
|
||||||
onClick={handleReloadClick}
|
onClick={handleReloadClick}
|
||||||
icon={<ArrowsClockwise size={20} />}
|
icon={<ArrowsClockwise size={20} />}
|
||||||
colorScheme="gray"
|
colorScheme="blue"
|
||||||
isLoading={isReloading}
|
isLoading={isReloading}
|
||||||
isDisabled={subs.length === 0}
|
isDisabled={subs.length === 0}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Heading, SimpleGrid, Spacer } from '@chakra-ui/react';
|
import { Box, SimpleGrid, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { RefreshButton } from '../../components/Buttons/RefreshButton';
|
||||||
|
import { SystemSecretsCard } from './SystemSecrets';
|
||||||
import SystemTile from './SystemTile';
|
import SystemTile from './SystemTile';
|
||||||
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
|
||||||
import { Card } from 'components/Containers/Card';
|
import { Card } from 'components/Containers/Card';
|
||||||
import { CardHeader } from 'components/Containers/Card/CardHeader';
|
import { CardHeader } from 'components/Containers/Card/CardHeader';
|
||||||
import { axiosSec } from 'constants/axiosInstances';
|
import { axiosSec } from 'constants/axiosInstances';
|
||||||
import { useAuth } from 'contexts/AuthProvider';
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
import { useGetEndpoints } from 'hooks/Network/Endpoints';
|
import { useGetEndpoints } from 'hooks/Network/Endpoints';
|
||||||
|
|
||||||
const SystemPage = () => {
|
const getDefaultTabIndex = () => {
|
||||||
|
const index = localStorage.getItem('system-tab-index') || '0';
|
||||||
|
try {
|
||||||
|
return parseInt(index, 10);
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
type Props = {
|
||||||
|
isOnlySec?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SystemPage = ({ isOnlySec }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { token } = useAuth();
|
const { token, user } = useAuth();
|
||||||
const { data: endpoints, refetch, isFetching } = useGetEndpoints({ onSuccess: () => {} });
|
const { data: endpoints, refetch, isFetching } = useGetEndpoints({ onSuccess: () => {} });
|
||||||
|
const [tabIndex, setTabIndex] = React.useState(getDefaultTabIndex());
|
||||||
|
const handleTabChange = (index: number) => {
|
||||||
|
setTabIndex(index);
|
||||||
|
localStorage.setItem('system-tab-index', index.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRoot = user && user.userRole === 'root';
|
||||||
|
|
||||||
const endpointsList = React.useMemo(() => {
|
const endpointsList = React.useMemo(() => {
|
||||||
if (!endpoints || !token) return null;
|
if (!token || (!isOnlySec && !endpoints)) return null;
|
||||||
|
|
||||||
const endpointList = [...endpoints];
|
const endpointList = endpoints ? [...endpoints] : [];
|
||||||
endpointList.push({
|
endpointList.push({
|
||||||
uri: axiosSec.defaults.baseURL?.split('/api/v1')[0] ?? '',
|
uri: axiosSec.defaults.baseURL?.split('/api/v1')[0] ?? '',
|
||||||
type: 'owsec',
|
type: isOnlySec ? '' : 'owsec',
|
||||||
id: 0,
|
id: 0,
|
||||||
vendor: 'owsec',
|
vendor: 'owsec',
|
||||||
authenticationType: '',
|
authenticationType: '',
|
||||||
@@ -37,20 +57,51 @@ const SystemPage = () => {
|
|||||||
}, [endpoints, token]);
|
}, [endpoints, token]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Card p={0}>
|
||||||
<Card mb={4} py={2} px={4}>
|
<Tabs index={tabIndex} onChange={handleTabChange} variant="enclosed" isLazy>
|
||||||
<CardHeader>
|
<TabList>
|
||||||
<Heading size="md" my="auto">
|
<CardHeader>
|
||||||
{t('controller.firmware.endpoints')}
|
<Tab>{t('system.services')}</Tab>
|
||||||
</Heading>
|
<Tab hidden={!isRoot}>{t('system.configuration')}</Tab>
|
||||||
<Spacer />
|
</CardHeader>
|
||||||
<RefreshButton onClick={refetch} isFetching={isFetching} />
|
</TabList>
|
||||||
</CardHeader>
|
<TabPanels>
|
||||||
</Card>
|
<TabPanel p={0}>
|
||||||
<SimpleGrid minChildWidth="500px" spacing="20px" mb={3}>
|
<Box
|
||||||
{endpointsList}
|
borderLeft="1px solid"
|
||||||
</SimpleGrid>
|
borderRight="1px solid"
|
||||||
</>
|
borderBottom="1px solid"
|
||||||
|
borderColor="var(--chakra-colors-chakra-border-color)"
|
||||||
|
borderBottomLeftRadius="15px"
|
||||||
|
borderBottomRightRadius="15px"
|
||||||
|
>
|
||||||
|
{!isOnlySec && (
|
||||||
|
<CardHeader px={4} pt={4}>
|
||||||
|
<Spacer />
|
||||||
|
<RefreshButton onClick={refetch} isFetching={isFetching} />
|
||||||
|
</CardHeader>
|
||||||
|
)}
|
||||||
|
<SimpleGrid minChildWidth="500px" spacing="20px" p={4}>
|
||||||
|
{endpointsList}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel p={0}>
|
||||||
|
<Box
|
||||||
|
borderLeft="1px solid"
|
||||||
|
borderRight="1px solid"
|
||||||
|
borderBottom="1px solid"
|
||||||
|
borderColor="var(--chakra-colors-chakra-border-color)"
|
||||||
|
borderBottomLeftRadius="15px"
|
||||||
|
borderBottomRightRadius="15px"
|
||||||
|
>
|
||||||
|
<SystemSecretsCard />
|
||||||
|
</Box>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SystemPage;
|
export default SystemPage;
|
||||||
|
|||||||
Reference in New Issue
Block a user