diff --git a/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/Messages_fr_FR.json b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/Messages_fr_FR.json index 7bc5f777d904..80c62dfa68a3 100644 --- a/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/Messages_fr_FR.json +++ b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/Messages_fr_FR.json @@ -9,6 +9,7 @@ "networkSecureTitle": "Privé", "networkPublicTitle": "Public", "tableHeaderDuration": "Durée", + "durationHelper": "Durée de fonctionnement du notebook", "tableHeaderStatus": "Statut", "tableActionManage": "Gérer", "tableActionStart": "Démarrer", diff --git a/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/dashboard/Messages_fr_FR.json b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/dashboard/Messages_fr_FR.json index 691aa0179608..ff3838a7d758 100644 --- a/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/dashboard/Messages_fr_FR.json +++ b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/dashboard/Messages_fr_FR.json @@ -9,13 +9,21 @@ "memoryField": "RAM: {{ memory }}", "publicNetworkField": "Réseau public: {{ network }}/s", "temporaryLocalStorageField": "Stockage local éphémère: {{ storage }} SSD", - "temporaryLocalStorageHelper": "Not that much for now", + "temporaryLocalStorageHelper": "Stockage local performant, mais non sauvegardé", "workspaceStorage": "Espace de travail {{ storage }} SSD inclus", - "workspaceStorageHelper": "Nothing for now", + "workspaceStorageHelper": "Stockage distant sauvegardé. Au-delà de 30 jours consécutifs de stockage ou/et du dépassement de ce quota de 10 Gio, les tarifs appliqués seront ceux de Public Cloud Object Storage.", "sliderInfo": "{{ usedStorage }} / {{ totalStorage }}", "notebookIdLabel": "Id du notebook", "notebookIdCopyToast": "L'identifiant du notebook a été copié", "billingLink": "Gérer la facturation", "supportLink": "Contacter le support", - "deleteNotebookButton": "Supprimer" + "deleteNotebookButton": "Supprimer", + "addLabelTitle": "Ajouter un label", + "keyFieldLabel": "Clé", + "valueFieldLabel": "Valeur", + "addLabelButtonCancel": "Annuler", + "addLabelNameSubmit": "Ajouter", + "notebookToastErrorTitle": "Erreur", + "notebookToastSuccessTitle": "Succés", + "deleteNotebookSuccess": "Le label a été correctement ajouté à votre notebook" } diff --git a/packages/manager/apps/pci-ai-notebooks/src/components/ui/button.tsx b/packages/manager/apps/pci-ai-notebooks/src/components/ui/button.tsx index 42b401b559fc..e6598b7171e6 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/components/ui/button.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/components/ui/button.tsx @@ -32,7 +32,7 @@ const buttonVariants = cva( table: "h-4 w-4 my-auto", menu: 'size-8 p-0', input: "h-10 w-full rounded-md px-3 py-2 text-sm", - roundedIcon: "h-8 w-8 rounded-full", + roundedIcon: "h-6 w-6 rounded-full", }, }, defaultVariants: { diff --git a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useGetNotebooks.hook.tsx b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useGetNotebooks.hook.tsx index f5ffed2cf769..18f495617aa6 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useGetNotebooks.hook.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useGetNotebooks.hook.tsx @@ -8,7 +8,7 @@ export function useGetNotebooks( projectId: string, options: Omit = {}, ) { - const queryKey = [projectId, 'ai', 'notebook']; + const queryKey = [projectId, 'ai/notebook']; return useQueryImmediateRefetch({ queryKey, queryFn: () => getNotebooks({ projectId }), diff --git a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStartNotebook.hook.tsx b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStartNotebook.hook.tsx index 1155f03ff555..70b83ff31935 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStartNotebook.hook.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStartNotebook.hook.tsx @@ -20,11 +20,11 @@ export function useStartNotebook({ }, onError, onSuccess: () => { - onStartSuccess(); // Invalidate service list query to get the latest data queryClient.invalidateQueries({ queryKey: [projectId, 'ai/notebook'], }); + onStartSuccess(); }, }); diff --git a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStopNotebook.hook.tsx b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStopNotebook.hook.tsx index f88429cf370e..4e93859e7c60 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStopNotebook.hook.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/useStopNotebook.hook.tsx @@ -17,11 +17,11 @@ export function useStopNotebook({ onError, onStopSuccess }: UseStopNotebook) { }, onError, onSuccess: () => { - onStopSuccess(); // Invalidate service list query to get the latest data queryClient.invalidateQueries({ queryKey: [projectId, 'ai/notebook'], }); + onStopSuccess(); }, }); diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookHeader.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookHeader.component.tsx index 7e2a0005a9ee..00989378f677 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookHeader.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookHeader.component.tsx @@ -12,6 +12,10 @@ import { Skeleton } from '@/components/ui/skeleton'; import * as ai from '@/types/cloud/project/ai'; import NotebookStatusBadge from '../../_components/NotebookStatusBadge.component'; import { Button } from '@/components/ui/button'; +import StartNotebook from './StartNotebook.component'; +import { useModale } from '@/hooks/useModale'; +import StopNotebook from './StopNotebook.component'; +import { isDeletingNotebook, isRunningNotebook } from '@/lib/notebookHelper'; export const NotebookHeader = ({ notebook, @@ -19,6 +23,8 @@ export const NotebookHeader = ({ notebook: ai.notebook.Notebook; }) => { const { t } = useTranslation('regions'); + const startModale = useModale('start'); + const stopModale = useModale('stop'); return (
-
+

{notebook.spec.name ?? 'Dashboard'}

- -
@@ -55,6 +78,20 @@ export const NotebookHeader = ({
+ { + startModale.close(); + }} + /> + { + stopModale.close(); + }} + /> ); }; diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StartNotebook.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StartNotebook.component.tsx index be8b5c726b76..9f7b98f882ca 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StartNotebook.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StartNotebook.component.tsx @@ -18,7 +18,7 @@ import { useStartNotebook } from '@/hooks/api/ai/notebook/useStartNotebook.hook' interface StartNotebookModalProps { notebook: ai.notebook.Notebook; controller: ModalController; - onSuccess?: (notebook: ai.notebook.Notebook) => void; + onSuccess?: () => void; onError?: (service: Error) => void; } @@ -52,7 +52,7 @@ const StartNotebook = ({ }), }); if (onSuccess) { - onSuccess(notebook); + onSuccess(); } }, }); diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StopNotebook.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StopNotebook.component.tsx index 599f871adf14..4084dcbdc9d1 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StopNotebook.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/StopNotebook.component.tsx @@ -18,7 +18,7 @@ import { useStopNotebook } from '@/hooks/api/ai/notebook/useStopNotebook.hook'; interface StopNotebookModalProps { notebook: ai.notebook.Notebook; controller: ModalController; - onSuccess?: (notebook: ai.notebook.Notebook) => void; + onSuccess?: () => void; onError?: (service: Error) => void; } @@ -52,7 +52,7 @@ const StopNotebook = ({ }), }); if (onSuccess) { - onSuccess(notebook); + onSuccess(); } }, }); diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/Dashboard.page.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/Dashboard.page.tsx index 9104193b0d48..cb3f9f45c128 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/Dashboard.page.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/Dashboard.page.tsx @@ -53,7 +53,7 @@ const Dashboard = () => {

- + {t('resourcesTitle')}

diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/AddLabel.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/AddLabel.component.tsx new file mode 100644 index 000000000000..2c7762ee5655 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/AddLabel.component.tsx @@ -0,0 +1,173 @@ +import { useParams } from 'react-router-dom'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { useTranslation } from 'react-i18next'; +import { useForm } from 'react-hook-form'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { ModalController } from '@/hooks/useModale'; +import { useToast } from '@/components/ui/use-toast'; +import * as ai from '@/types/cloud/project/ai'; +import { useEditLabel } from '@/hooks/api/ai/notebook/label/useEditLabel.hook'; +import { getAIApiErrorMessage } from '@/lib/apiHelper'; + +interface AddLabelProps { + notebook: ai.notebook.Notebook; + controller: ModalController; + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +const AddLabel = ({ + notebook, + controller, + onError, + onSuccess, +}: AddLabelProps) => { + // import translations + const { projectId } = useParams(); + const { t } = useTranslation('pci-ai-notebooks/notebooks/notebook/dashboard'); + const toast = useToast(); + + const { editLabel, isPending } = useEditLabel({ + onError: (err) => { + toast.toast({ + title: t('notebookToastErrorTitle'), + variant: 'destructive', + description: getAIApiErrorMessage(err), + }); + if (onError) { + onError(err); + } + }, + onSuccess: () => { + toast.toast({ + title: t('notebookToastSuccessTitle'), + description: t('deleteNotebookSuccess'), + }); + if (onSuccess) { + onSuccess(); + } + }, + }); + // define the schema for the form + const labelSchema = z.object({ + name: z + .string() + .min(1) + .max(15) + .refine( + (newKey) => notebook.spec.labels && !(newKey in notebook.spec.labels), + { + message: t('existingKeyError'), + }, + ), + value: z + .string() + .min(1) + .max(15), + }); + // generate a form controller + const form = useForm>({ + resolver: zodResolver(labelSchema), + defaultValues: { + name: '', + value: '', + }, + }); + + const onSubmit = form.handleSubmit((formValues) => { + editLabel({ + projectId: projectId, + notebookId: notebook.id, + label: { + name: formValues.name, + value: formValues.value, + }, + }); + }); + + return ( + + + + + {t('addLabelTitle')} + + + +
+ + ( + + + {t('keyFieldLabel')} + + + + + + + )} + /> + ( + + {t('valueFieldLabel')} + + + + + + )} + /> + + + + + + + + +
+
+ ); +}; + +export default AddLabel; diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Configuration.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Configuration.component.tsx index 63dbb9967299..b13ea0c1d621 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Configuration.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Configuration.component.tsx @@ -7,6 +7,7 @@ import { useToast } from '@/components/ui/use-toast'; import DeleteNotebook from '../../_components/DeleteNotebook.component'; import { useGetNotebooks } from '@/hooks/api/ai/notebook/useGetNotebooks.hook'; import { useModale } from '@/hooks/useModale'; +import { isRunningNotebook } from '@/lib/notebookHelper'; const Configurations = () => { const { notebook, notebookQuery, projectId } = useNotebookData(); @@ -46,6 +47,9 @@ const Configurations = () => { variant="destructive" className="w-full bg-background border-2 hover:bg-destructive/10 font-semibold border-destructive text-destructive mt-4" onClick={() => deleteModale.open()} + disabled={ + isRunningNotebook(notebook.status.state) + } > {t('deleteNotebookButton')} @@ -56,7 +60,7 @@ const Configurations = () => { deleteModale.close(); notebookQuery.refetch(); getNotebooksQuery.refetch(); - navigate(`../../`); + navigate(`../../../`); }} /> diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Privacy.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Privacy.component.tsx index 5e96d2ea7641..28058246e3da 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Privacy.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Privacy.component.tsx @@ -7,11 +7,15 @@ import { isOvhTags } from '@/lib/notebookHelper'; import { useEditLabel } from '@/hooks/api/ai/notebook/label/useEditLabel.hook'; import { useToast } from '@/components/ui/use-toast'; import { getAIApiErrorMessage } from '@/lib/apiHelper'; +import AddLabel from './AddLabel.component'; +import { useModale } from '@/hooks/useModale'; +import { CONFIGURATION_CONFIG } from '@/components/order/configuration/configuration.constants'; const Privacy = () => { const { notebook, notebookQuery, projectId } = useNotebookData(); const { t } = useTranslation('pci-ai-notebooks/notebooks/notebook/dashboard'); const toast = useToast(); + const addModale = useModale('add'); const { editLabel, isPending } = useEditLabel({ onError: (err) => { @@ -41,54 +45,66 @@ const Privacy = () => { }; return ( -
-
-
Tags
- + <> +
+
+
Tags
+ +
+
+ {notebook.spec.labels && + Object.entries(notebook.spec.labels).map(([key, value]) => ( + +
+ + {key} = {value} + + {!isOvhTags(key) && ( + + )} +
+
+ ))} +
+
+ {notebook.spec.unsecureHttp ? ( +
+
Accès publique
+ +
+ ) : ( +
+
Accès privé
+ +
+ )} +
-
- {notebook.spec.labels && - Object.entries(notebook.spec.labels).map(([key, value]) => ( - -
- - {key} = {value} - - {!isOvhTags(key) && ( - - )} -
-
- ))} -
-
- {notebook.spec.unsecureHttp ? ( -
-
Accès publique
- -
- ) : ( -
-
Accès privé
- -
- )} -
-
+ { + addModale.close(); + notebookQuery.refetch(); + }} + /> + ); }; diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/_components/NotebooksListColumns.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/_components/NotebooksListColumns.component.tsx index 0b7854c5a76b..dbc7851f3edf 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/_components/NotebooksListColumns.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/_components/NotebooksListColumns.component.tsx @@ -1,6 +1,7 @@ import { ColumnDef } from '@tanstack/react-table'; import { Cpu, + HelpCircle, MoreHorizontal, ShieldAlert, ShieldCheck, @@ -24,6 +25,11 @@ import Link from '@/components/links/Link.component'; import { convertSecondsToTimeString } from '@/lib/durationHelper'; import NotebookStatusBadge from './NotebookStatusBadge.component'; import { isDeletingNotebook, isRunningNotebook } from '@/lib/notebookHelper'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; interface NotebooksListColumnsProps { onStartClicked: (notebook: ai.notebook.Notebook) => void; @@ -120,10 +126,18 @@ export const getColumns = ({ { id: 'Operating time', accessorFn: (row) => convertSecondsToTimeString(row.status.duration), - header: ({ column }) => ( - - {t('tableHeaderDuration')} - + header: () => ( +
+ {t('tableHeaderDuration')} + + + + + +

{t('durationHelper')}

+
+
+
), }, {