From 58bbf86600eabf04d835872adf9ddfa44ca76c17 Mon Sep 17 00:00:00 2001 From: Vincent T Date: Wed, 14 Feb 2024 15:15:28 -0500 Subject: [PATCH] frontend: Add create namespace ui Signed-off-by: Vincent T --- .../namespace/CreateNamespaceButton.tsx | 161 ++++++++++++++++++ frontend/src/components/namespace/List.tsx | 18 +- frontend/src/i18n/locales/de/translation.json | 9 + frontend/src/i18n/locales/en/translation.json | 9 + frontend/src/i18n/locales/es/translation.json | 9 + frontend/src/i18n/locales/fr/translation.json | 9 + frontend/src/i18n/locales/pt/translation.json | 9 + 7 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/namespace/CreateNamespaceButton.tsx diff --git a/frontend/src/components/namespace/CreateNamespaceButton.tsx b/frontend/src/components/namespace/CreateNamespaceButton.tsx new file mode 100644 index 00000000000..b9a07a04512 --- /dev/null +++ b/frontend/src/components/namespace/CreateNamespaceButton.tsx @@ -0,0 +1,161 @@ +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, +} from '@mui/material'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; +import { getCluster } from '../../lib/cluster'; +import { post } from '../../lib/k8s/apiProxy'; +import Namespace from '../../lib/k8s/namespace'; +import { clusterAction } from '../../redux/clusterActionSlice'; +import { EventStatus, HeadlampEventType, useEventCallback } from '../../redux/headlampEventSlice'; +import { ActionButton, AuthVisible } from '../common'; + +export function CreateNamespaceButton() { + const { t } = useTranslation(['glossary', 'translation']); + const [namespaceName, setNamespaceName] = useState(''); + const [namespaceDialogOpen, setNamespaceDialogOpen] = useState(false); + const dispatchCreateEvent = useEventCallback(HeadlampEventType.CREATE_RESOURCE); + const dispatch = useDispatch(); + + function createNewNamespace() { + function validateNamespaceName(namespaceName: string) { + const namespaceRegex = /^[a-z]([-a-z0-9]*[a-z0-9])?$/; + + // Check if the namespace name is too long + if (namespaceName.length > 63) { + alert(t('translation|Invalid namespace name: name must be 63 characters or shorter')); + return false; + } + + // Check if the namespace name starts with a lowercase letter + const startsLowercase = /^[a-z]/.test(namespaceName); + if (!startsLowercase) { + alert(t('translation|Invalid namespace name: name must start with a lowercase letter')); + return false; + } + + // Check if the namespace name ends with an alphanumeric character + const endsAlphanumeric = /[a-z0-9]$/.test(namespaceName); + if (!endsAlphanumeric) { + alert( + t('translation|Invalid namespace name: name must end with an alphanumeric character') + ); + return false; + } + + // Check if the namespace name contains only lowercase letters, numbers, or hyphens + const validChars = namespaceRegex.test(namespaceName); + if (!validChars) { + alert( + t( + 'translation|Invalid namespace name: name must consist of lower case alphanumeric characters or -' + ) + ); + return false; + } + + return true; + } + + if (!validateNamespaceName(namespaceName)) { + return; + } + + const cluster = getCluster(); + const newNamespaceData = { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name: namespaceName, + }, + }; + const newNamespaceName = newNamespaceData.metadata.name; + + async function namespaceRequest() { + try { + const response = await post('/api/v1/namespaces', newNamespaceData, true, { + cluster: `${cluster}`, + }); + return response; + } catch (error) { + console.error('Error creating namespace', error); + throw error; + } + } + + setNamespaceDialogOpen(false); + dispatch( + clusterAction(() => namespaceRequest(), { + startMessage: t('translation|Creating namespace {{ name }}…', { name: newNamespaceName }), + cancelledMessage: t('translation|Cancelled creation of {{ name }}.', { + name: newNamespaceName, + }), + successMessage: t('translation|Created namespace {{ name }}.', { + name: newNamespaceName, + }), + errorMessage: t('translation|Error creating namespace {{ name }}.', { + name: newNamespaceName, + }), + cancelCallback: () => { + setNamespaceDialogOpen(true); + }, + }) + ); + } + + return ( + + { + setNamespaceDialogOpen(true); + }} + /> + + setNamespaceDialogOpen(false)}> + {t('translation|Create Namespace')} + + + setNamespaceName(event.target.value)} + /> + + + + + + + + + ); +} diff --git a/frontend/src/components/namespace/List.tsx b/frontend/src/components/namespace/List.tsx index bdb29c2ff7f..9e3cbd88880 100644 --- a/frontend/src/components/namespace/List.tsx +++ b/frontend/src/components/namespace/List.tsx @@ -10,6 +10,7 @@ import { ResourceTableFromResourceClassProps, ResourceTableProps, } from '../common/Resource/ResourceTable'; +import { CreateNamespaceButton } from './CreateNamespaceButton'; export default function NamespacesList() { const { t } = useTranslation(['glossary', 'translation']); @@ -88,12 +89,15 @@ export default function NamespacesList() { }, [allowedNamespaces]); return ( - + <> + ], + noNamespaceFilter: true, + }} + {...resourceTableProps} + /> + ); } diff --git a/frontend/src/i18n/locales/de/translation.json b/frontend/src/i18n/locales/de/translation.json index cab16f9ef4f..1a6c1f5db73 100644 --- a/frontend/src/i18n/locales/de/translation.json +++ b/frontend/src/i18n/locales/de/translation.json @@ -315,6 +315,15 @@ "Default Request": "Standardanforderung", "Max": "Max", "Min": "Min", + "Invalid namespace name: name must be 63 characters or shorter": "", + "Invalid namespace name: name must start with a lowercase letter": "", + "Invalid namespace name: name must end with an alphanumeric character": "", + "Invalid namespace name: name must consist of lower case alphanumeric characters or -": "", + "Creating namespace {{ name }}…": "", + "Cancelled creation of {{ name }}.": "", + "Created namespace {{ name }}.": "", + "Error creating namespace {{ name }}.": "", + "Create Namespace": "", "To": "An", "used": "benutzt", "Scheduling Disabled": "Planung deaktiviert", diff --git a/frontend/src/i18n/locales/en/translation.json b/frontend/src/i18n/locales/en/translation.json index 8f1418aaa70..c668f44e6d8 100644 --- a/frontend/src/i18n/locales/en/translation.json +++ b/frontend/src/i18n/locales/en/translation.json @@ -315,6 +315,15 @@ "Default Request": "Default Request", "Max": "Max", "Min": "Min", + "Invalid namespace name: name must be 63 characters or shorter": "Invalid namespace name: name must be 63 characters or shorter", + "Invalid namespace name: name must start with a lowercase letter": "Invalid namespace name: name must start with a lowercase letter", + "Invalid namespace name: name must end with an alphanumeric character": "Invalid namespace name: name must end with an alphanumeric character", + "Invalid namespace name: name must consist of lower case alphanumeric characters or -": "Invalid namespace name: name must consist of lower case alphanumeric characters or -", + "Creating namespace {{ name }}…": "Creating namespace {{ name }}…", + "Cancelled creation of {{ name }}.": "Cancelled creation of {{ name }}.", + "Created namespace {{ name }}.": "Created namespace {{ name }}.", + "Error creating namespace {{ name }}.": "Error creating namespace {{ name }}.", + "Create Namespace": "Create Namespace", "To": "To", "used": "used", "Scheduling Disabled": "Scheduling Disabled", diff --git a/frontend/src/i18n/locales/es/translation.json b/frontend/src/i18n/locales/es/translation.json index 6c7f2c28d9f..05f062a2b2c 100644 --- a/frontend/src/i18n/locales/es/translation.json +++ b/frontend/src/i18n/locales/es/translation.json @@ -315,6 +315,15 @@ "Default Request": "Solicitud por defecto", "Max": "Máx.", "Min": "Mín.", + "Invalid namespace name: name must be 63 characters or shorter": "", + "Invalid namespace name: name must start with a lowercase letter": "", + "Invalid namespace name: name must end with an alphanumeric character": "", + "Invalid namespace name: name must consist of lower case alphanumeric characters or -": "", + "Creating namespace {{ name }}…": "", + "Cancelled creation of {{ name }}.": "", + "Created namespace {{ name }}.": "", + "Error creating namespace {{ name }}.": "", + "Create Namespace": "", "To": "A", "used": "usado", "Scheduling Disabled": "Agendamiento deshabilitado", diff --git a/frontend/src/i18n/locales/fr/translation.json b/frontend/src/i18n/locales/fr/translation.json index c69c097257a..1e39eb68b2d 100644 --- a/frontend/src/i18n/locales/fr/translation.json +++ b/frontend/src/i18n/locales/fr/translation.json @@ -315,6 +315,15 @@ "Default Request": "Demande par défaut", "Max": "Max", "Min": "Min", + "Invalid namespace name: name must be 63 characters or shorter": "", + "Invalid namespace name: name must start with a lowercase letter": "", + "Invalid namespace name: name must end with an alphanumeric character": "", + "Invalid namespace name: name must consist of lower case alphanumeric characters or -": "", + "Creating namespace {{ name }}…": "", + "Cancelled creation of {{ name }}.": "", + "Created namespace {{ name }}.": "", + "Error creating namespace {{ name }}.": "", + "Create Namespace": "", "To": "À", "used": "utilisé", "Scheduling Disabled": "Planification désactivée", diff --git a/frontend/src/i18n/locales/pt/translation.json b/frontend/src/i18n/locales/pt/translation.json index ce7cdb94a1e..dabf1689282 100644 --- a/frontend/src/i18n/locales/pt/translation.json +++ b/frontend/src/i18n/locales/pt/translation.json @@ -315,6 +315,15 @@ "Default Request": "Pedido por defeito", "Max": "Máx.", "Min": "Mín.", + "Invalid namespace name: name must be 63 characters or shorter": "", + "Invalid namespace name: name must start with a lowercase letter": "", + "Invalid namespace name: name must end with an alphanumeric character": "", + "Invalid namespace name: name must consist of lower case alphanumeric characters or -": "", + "Creating namespace {{ name }}…": "", + "Cancelled creation of {{ name }}.": "", + "Created namespace {{ name }}.": "", + "Error creating namespace {{ name }}.": "", + "Create Namespace": "", "To": "Para", "used": "usado", "Scheduling Disabled": "Agendamento Desactivado",