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 0c243601e95a..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 @@ -8,7 +8,8 @@ "tableHeaderPrivacy": "Confidentialité ", "networkSecureTitle": "Privé", "networkPublicTitle": "Public", - "tableHeaderDuration": "Durée de fonctionnement", + "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/create/Messages_fr_FR.json b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/create/Messages_fr_FR.json index 61c1938c535c..991232f9ec85 100644 --- a/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/create/Messages_fr_FR.json +++ b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/create/Messages_fr_FR.json @@ -53,6 +53,6 @@ "cliCode": "Equivalent CLI", "errorGetCommandCli": "Une erreur est survenue lors de la génération de code équivalent pour la CLI", "cliEquivalentModalTitle": "Création d’un notebook équivalent", - "cliEquivalentModalDescription": "Créer le même notebook via la CLI", + "cliEquivalentModalDescription": "Commande CLI", "cliEquivalentModalToastMessage": "Le code a été copié" } diff --git a/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/Messages_fr_FR.json b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/Messages_fr_FR.json index afeff6751635..36d7b5cbc248 100644 --- a/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/Messages_fr_FR.json +++ b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/Messages_fr_FR.json @@ -1,4 +1,8 @@ { + "dashboardTab": "Dashboard", + "dataTab": "Données attachées", + "backupTab": "Backup", + "logsTab": "Logs", "deleteNotebookTitle": "Supprimer le notebook", "deleteNotebookDescription": "Etes-vous sur de vouloir supprimer le notebook {{name}} ?", "notebookButtonCancel": "Annuler", 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 new file mode 100644 index 000000000000..ff3838a7d758 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/public/translations/pci-ai-notebooks/notebooks/notebook/dashboard/Messages_fr_FR.json @@ -0,0 +1,29 @@ +{ + "dashboardTitle": "Dashboard", + "resourcesTitle": "Ressources", + "powerTitleSection": "Power", + "computeTitleSection": "Compute", + "storageTitleSection": "Stockage", + "gpuMemoryField": "{{ gpu }} x {{ memory }} RAM", + "gcuComputeField": "CPU: {{ cpu }} vCores ", + "memoryField": "RAM: {{ memory }}", + "publicNetworkField": "Réseau public: {{ network }}/s", + "temporaryLocalStorageField": "Stockage local éphémère: {{ storage }} SSD", + "temporaryLocalStorageHelper": "Stockage local performant, mais non sauvegardé", + "workspaceStorage": "Espace de travail {{ storage }} SSD inclus", + "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", + "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/__tests__/helpers/mocks/label.ts b/packages/manager/apps/pci-ai-notebooks/src/__tests__/helpers/mocks/label.ts new file mode 100644 index 000000000000..4aad2e74b23b --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/__tests__/helpers/mocks/label.ts @@ -0,0 +1,6 @@ +import * as ai from '@/types/cloud/project/ai'; + +export const mockedLabel: ai.Label = { + name: 'labelName', + value: 'labelValue', +}; diff --git a/packages/manager/apps/pci-ai-notebooks/src/components/cli-code-block/CliCodeBlock.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/components/cli-code-block/CliCodeBlock.component.tsx index a38aceed6c35..5ffb8326e28a 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/components/cli-code-block/CliCodeBlock.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/components/cli-code-block/CliCodeBlock.component.tsx @@ -8,9 +8,15 @@ interface CliCodeBlockProps { title: string; code: string; toastMessage?: string; + size?: string; } -const CliCodeBlock = ({ title, code, toastMessage }: CliCodeBlockProps) => { +const CliCodeBlock = ({ + title, + code, + toastMessage, + size, +}: CliCodeBlockProps) => { const { t } = useTranslation('common'); const toast = useToast(); const handleCopyPass = (valueToCopy: string) => { @@ -22,7 +28,7 @@ const CliCodeBlock = ({ title, code, toastMessage }: CliCodeBlockProps) => { return (
{title}
- - + )) Slider.displayName = SliderPrimitive.Root.displayName diff --git a/packages/manager/apps/pci-ai-notebooks/src/data/api/ai/notebook/label/label.api.ts b/packages/manager/apps/pci-ai-notebooks/src/data/api/ai/notebook/label/label.api.ts new file mode 100644 index 000000000000..cd10779e8a16 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/data/api/ai/notebook/label/label.api.ts @@ -0,0 +1,16 @@ +import { apiClient } from '@ovh-ux/manager-core-api'; +import { NotebookData } from '@/data/api'; +import * as ai from '@/types/cloud/project/ai'; + +export interface EditLabelProps extends NotebookData { + label: ai.Label; +} + +export const editLabel = async ({ + projectId, + notebookId, + label, +}: EditLabelProps) => + apiClient.v6 + .put(`/cloud/project/${projectId}/ai/notebook/${notebookId}/label`, label) + .then((res) => res.data); diff --git a/packages/manager/apps/pci-ai-notebooks/src/data/api/ai/notebook/label/label.spec.tsx b/packages/manager/apps/pci-ai-notebooks/src/data/api/ai/notebook/label/label.spec.tsx new file mode 100644 index 000000000000..92ebaf77445b --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/data/api/ai/notebook/label/label.spec.tsx @@ -0,0 +1,36 @@ +import { apiClient } from '@ovh-ux/manager-core-api'; +import { describe, expect, vi } from 'vitest'; +import { editLabel } from './label.api'; +import { mockedLabel } from '@/__tests__/helpers/mocks/label'; + +vi.mock('@ovh-ux/manager-core-api', () => { + const put = vi.fn(() => { + return Promise.resolve({ data: null }); + }); + return { + apiClient: { + v6: { + put, + }, + }, + }; +}); + +describe('label functions', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should call editLabel with labelInput', async () => { + expect(apiClient.v6.put).not.toHaveBeenCalled(); + await editLabel({ + projectId: 'projectId', + notebookId: 'notebookId', + label: mockedLabel, + }); + expect(apiClient.v6.put).toHaveBeenCalledWith( + '/cloud/project/projectId/ai/notebook/notebookId/label', + mockedLabel, + ); + }); +}); diff --git a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/label/useEditLabel.hook.tsx b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/label/useEditLabel.hook.tsx new file mode 100644 index 000000000000..c7ad2f1213f7 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/label/useEditLabel.hook.tsx @@ -0,0 +1,28 @@ +import { useMutation } from '@tanstack/react-query'; +import { AIError } from '@/data/api'; +import { + EditLabelProps, + editLabel, +} from '@/data/api/ai/notebook/label/label.api'; + +export interface MutateLabelProps { + onError: (cause: AIError) => void; + onSuccess: () => void; +} + +export function useEditLabel({ onError, onSuccess }: MutateLabelProps) { + const mutation = useMutation({ + mutationFn: (labelInfo: EditLabelProps) => { + return editLabel(labelInfo); + }, + onError, + onSuccess, + }); + + return { + editLabel: (labelInfo: EditLabelProps) => { + return mutation.mutate(labelInfo); + }, + ...mutation, + }; +} diff --git a/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/label/useEditLabel.spec.tsx b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/label/useEditLabel.spec.tsx new file mode 100644 index 000000000000..55a3b5bde15f --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/hooks/api/ai/notebook/label/useEditLabel.spec.tsx @@ -0,0 +1,40 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { QueryClientWrapper } from '@/__tests__/helpers/wrappers/QueryClientWrapper'; +import * as labelApi from '@/data/api/ai/notebook/label/label.api'; +import { useEditLabel } from './useEditLabel.hook'; +import { mockedLabel } from '@/__tests__/helpers/mocks/label'; + +vi.mock('@/data/api/ai/notebook/label/label.api', () => ({ + editLabel: vi.fn(), +})); + +describe('useEditLabel', () => { + it('should call useEditLabel on mutation with data', async () => { + const projectId = 'projectId'; + const notebookId = 'notebookId'; + const onSuccess = vi.fn(); + const onError = vi.fn(); + + vi.mocked(labelApi.editLabel).mockResolvedValue(mockedLabel); + const { result } = renderHook(() => useEditLabel({ onError, onSuccess }), { + wrapper: QueryClientWrapper, + }); + + const editLabelProps = { + projectId, + notebookId, + label: mockedLabel, + }; + result.current.editLabel(editLabelProps); + + await waitFor(() => { + expect(labelApi.editLabel).toHaveBeenCalledWith(editLabelProps); + expect(onSuccess).toHaveBeenCalledWith( + mockedLabel, + editLabelProps, + undefined, + ); + }); + }); +}); 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/lib/statusHelper.ts b/packages/manager/apps/pci-ai-notebooks/src/lib/notebookHelper.ts similarity index 72% rename from packages/manager/apps/pci-ai-notebooks/src/lib/statusHelper.ts rename to packages/manager/apps/pci-ai-notebooks/src/lib/notebookHelper.ts index 8f58386e1e08..fbd97ae518d0 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/lib/statusHelper.ts +++ b/packages/manager/apps/pci-ai-notebooks/src/lib/notebookHelper.ts @@ -13,3 +13,12 @@ export function isDeletingNotebook( ) { return currentState === ai.notebook.NotebookStateEnum.DELETING; } + +export const OVH_TAGS_CONFIG = { + id: 'ovh/id', + type: 'ovh/type', +}; + +export function isOvhTags(key: string) { + return key === OVH_TAGS_CONFIG.id || key === OVH_TAGS_CONFIG.type; +} diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/Notebook.layout.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/Notebook.layout.tsx index 28be211174d1..bd120e297903 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/Notebook.layout.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/Notebook.layout.tsx @@ -7,6 +7,8 @@ import { useUserActivityContext } from '@/contexts/UserActivityContext'; import { getNotebook } from '@/data/api/ai/notebook/notebook.api'; import { useGetNotebook } from '@/hooks/api/ai/notebook/useGetNotebook.hook'; import { NotebookLayoutContext } from './Notebook.context'; +import { NotebookHeader } from './_components/NotebookHeader.component'; +import NotebookTabs from './_components/NotebookTabs.component'; interface NotebookLayoutProps { params: { @@ -55,6 +57,7 @@ export default function NotebookLayout() { if (!notebook) { return ( <> + Loading your notebook data > @@ -67,7 +70,8 @@ export default function NotebookLayout() { return ( <> - {notebook.spec.name}++ 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 new file mode 100644 index 000000000000..00989378f677 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookHeader.component.tsx @@ -0,0 +1,114 @@ +import { useTranslation } from 'react-i18next'; +import { + NotebookText, + Play, + PlayCircle, + PlayIcon, + Square, + Trash2, +} from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +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, +}: { + notebook: ai.notebook.Notebook; +}) => { + const { t } = useTranslation('regions'); + const startModale = useModale('start'); + const stopModale = useModale('stop'); + return ( + ++ ); +}; + +NotebookHeader.Skeleton = function NotebookHeaderSkeleton() { + return ( ++++ ++++{notebook.spec.name ?? 'Dashboard'}
++ + +++++ + {notebook.spec.env.frameworkId} + ++ {notebook.spec.env.frameworkVersion} + ++ {t(`region_${notebook.spec.region}`)} + ++ {notebook.spec.env.editorId} + +{ + startModale.close(); + }} + /> + { + stopModale.close(); + }} + /> + ++ ); +}; diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookTabs.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookTabs.component.tsx new file mode 100644 index 000000000000..bf977ff641a6 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/_components/NotebookTabs.component.tsx @@ -0,0 +1,22 @@ +import { useTranslation } from 'react-i18next'; +import TabsMenu from '@/components/tabs-menu/TabsMenu.component'; +import * as ai from '@/types/cloud/project/ai'; + +interface NotebookTabsProps { + notebook: ai.notebook.Notebook; +} + +const NotebookTabs = ({ notebook }: NotebookTabsProps) => { + const { t } = useTranslation('pci-ai-notebooks/notebooks/notebook'); + + const tabs = [ + { href: '', label: t('dashboardTab'), end: true }, + { href: 'attach-data', label: t('dataTab') }, + { href: 'backup', label: t('backupTab') }, + { href: 'logs', label: t('logsTab') }, + ].filter((tab) => tab); + + return+ ++Dashboard
++++ + + + ; +}; + +export default NotebookTabs; 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 new file mode 100644 index 000000000000..cb3f9f45c128 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/Dashboard.page.tsx @@ -0,0 +1,141 @@ +import { useTranslation } from 'react-i18next'; +import { + ArrowRight, + Atom, + Globe2, + Settings2, + TerminalSquare, + UserCheck, +} from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; +import { Card, CardContent, CardHeader } from '@/components/ui/card'; +import { useNotebookData } from '../Notebook.context'; +import Resources from './_components/Resources.component'; +import Configurations from './_components/Configuration.component'; +import Privacy from './_components/Privacy.component'; +import { useGetCommand } from '@/hooks/api/ai/notebook/useGetCommand.hook'; +import { getAIApiErrorMessage } from '@/lib/apiHelper'; +import { useToast } from '@/components/ui/use-toast'; +import * as ai from '@/types/cloud/project/ai'; +import CliCodeBlock from '@/components/cli-code-block/CliCodeBlock.component'; +import OvhLink from '@/components/links/OvhLink.component'; + +const Dashboard = () => { + const { notebook, projectId } = useNotebookData(); + const { t } = useTranslation('pci-ai-notebooks/notebooks/notebook/dashboard'); + const { toast } = useToast(); + const [command, setCommand] = useState ({}); + + const { getCommand, isPending: isPendingCommand } = useGetCommand({ + onError: (err) => { + toast({ + title: t('errorGetCommandCli'), + variant: 'destructive', + description: getAIApiErrorMessage(err), + }); + }, + onSuccess: (cliCommand) => { + setCommand(cliCommand); + }, + }); + + useEffect(() => { + const filteredVolume: ai.volume.Volume[] = notebook.spec.volumes.filter( + (vol) => vol.dataStore.internal === false, + ); + getCommand({ ...notebook.spec, volumes: filteredVolume }); + }, [notebook]); + + return ( + <> + {t('dashboardTitle')}
++++ ++ ++
++ {t('resourcesTitle')} + + ++ Cycle de vie ++ ++ ++
++ Privacy + + ++ +++ ++ ++
++ Support & Facturation + + ++++ {t('billingLink')} + ++ +++ {t('supportLink')} + ++ + ++ ++
++ Configuration + + ++ + + > + ); +}; + +export default Dashboard; 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+ ++
++ CLI + + {command && ( + ++ )} + >({ + 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 ( + + ); +}; + +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 new file mode 100644 index 000000000000..b13ea0c1d621 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Configuration.component.tsx @@ -0,0 +1,70 @@ +import { Files } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { useNotebookData } from '../../Notebook.context'; +import { Button } from '@/components/ui/button'; +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(); + const { t } = useTranslation('pci-ai-notebooks/notebooks/notebook/dashboard'); + const navigate = useNavigate(); + const toast = useToast(); + const deleteModale = useModale('delete'); + + const getNotebooksQuery = useGetNotebooks(projectId, { + enabled: false, + }); + + return ( + ++ ); +}; + +export default Configurations; 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 new file mode 100644 index 000000000000..28058246e3da --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Privacy.component.tsx @@ -0,0 +1,111 @@ +import { Plus, ShieldAlert, ShieldCheck, X } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { useNotebookData } from '../../Notebook.context'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +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) => { + toast.toast({ + title: t('notebookToastErrorTitle'), + variant: 'destructive', + description: getAIApiErrorMessage(err), + }); + }, + onSuccess: () => { + toast.toast({ + title: t('notebookToastSuccessTitle'), + description: t('deleteNotebookSuccess'), + }); + notebookQuery.refetch(); + }, + }); + + const handleDeleteLabel = (key: string) => { + editLabel({ + projectId, + notebookId: notebook.id, + label: { + name: key, + }, + }); + }; + + return ( + <> +++ +++ +{t('notebookIdLabel')}
+{notebook.id}
+{ + deleteModale.close(); + notebookQuery.refetch(); + getNotebooksQuery.refetch(); + navigate(`../../../`); + }} + /> + ++++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é
++ { + addModale.close(); + notebookQuery.refetch(); + }} + /> + > + ); +}; + +export default Privacy; diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Resources.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Resources.component.tsx new file mode 100644 index 000000000000..ebb72f05e129 --- /dev/null +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/[notebookId]/dashboard/_components/Resources.component.tsx @@ -0,0 +1,151 @@ +import { Cpu, HardDrive, HelpCircle, MemoryStick, Zap } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { useNotebookData } from '../../Notebook.context'; +import { bytesConverter } from '@/lib/bytesHelper'; +import { Slider } from '@/components/ui/slider'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; + +const Resources = () => { + const { notebook } = useNotebookData(); + const { t } = useTranslation('pci-ai-notebooks/notebooks/notebook/dashboard'); + return ( + + {notebook.spec.resources.gpu > 0 ? ( ++ ); +}; + +export default Resources; 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 fbb0b68c22e9..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, @@ -23,7 +24,12 @@ import { SortableHeader } from '@/components/ui/data-table'; import Link from '@/components/links/Link.component'; import { convertSecondsToTimeString } from '@/lib/durationHelper'; import NotebookStatusBadge from './NotebookStatusBadge.component'; -import { isDeletingNotebook, isRunningNotebook } from '@/lib/statusHelper'; +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 }) => ( -++ ) : ( +++ + {`${notebook.spec.resources.gpu} x ${notebook.spec.resources.flavor}`} + + + {`${notebook.spec.resources.gpu} x ${notebook.spec.resources.gpuModel}`} + + + {t('gpuMemoryField', { + gpu: notebook.spec.resources.gpu, + memory: bytesConverter( + notebook.spec.resources.gpuMemory, + false, + 0, + ), + })} + +Power
++ ++ )} +++ + {`${notebook.spec.resources.cpu} x ${notebook.spec.resources.flavor}`} + + {`${notebook.spec.resources.cpu} x INTEL CPU VCORES`} +{t('powerTitleSection')}
++ ++++ {notebook.spec.resources.gpu > 0 && ( + + {t('gcuComputeField', { + cpu: notebook.spec.resources.cpu, + })} + + )} + + {t('memoryField', { + memory: bytesConverter(notebook.spec.resources.memory, false, 0), + })} + + + {t('publicNetworkField', { + network: bytesConverter( + notebook.spec.resources.publicNetwork, + true, + 2, + ), + })} + + +{t('computeTitleSection')}
++ ++{t('storageTitleSection')}
++ + + {t('temporaryLocalStorageField', { + storage: bytesConverter( + notebook.spec.resources.ephemeralStorage, + false, + 0, + ), + })} + +++ ++ ++ + +{t('temporaryLocalStorageHelper')}
++ + {t('workspaceStorage', { + storage: bytesConverter( + notebook.status.workspace.storageFree, + false, + 1, + ), + })} + +++ ++ ++ + +{t('workspaceStorageHelper')}
++++ + {t('sliderInfo', { + usedStorage: bytesConverter( + notebook.status.workspace.storageUsed, + false, + 1, + ), + totalStorage: bytesConverter( + notebook.status.workspace.storageFree, + false, + 1, + ), + })} + + - {t('tableHeaderDuration')} - + header: () => ( ++ {t('tableHeaderDuration')} +), }, { diff --git a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/create/_components/CliEquivalent.component.tsx b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/create/_components/CliEquivalent.component.tsx index 03ce10313f1b..b9e8ccd930f4 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/create/_components/CliEquivalent.component.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/pages/notebooks/create/_components/CliEquivalent.component.tsx @@ -28,6 +28,7 @@ const CliEquivalent = ({ command, controller }: CliEquivalentModalProps) => { title={t('cliEquivalentModalDescription')} code={command.command} toastMessage={t('cliEquivalentModalToastMessage')} + size="max-h-[60vh] px-6" /> diff --git a/packages/manager/apps/pci-ai-notebooks/src/routes/routes.tsx b/packages/manager/apps/pci-ai-notebooks/src/routes/routes.tsx index 8164e37b43f1..fdd624492653 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/routes/routes.tsx +++ b/packages/manager/apps/pci-ai-notebooks/src/routes/routes.tsx @@ -42,6 +42,15 @@ export default [ ...lazyRouteConfig(() => import('@/pages/notebooks/[notebookId]/Notebook.layout'), ), + children: [ + { + path: '', + id: 'notebook.dashboard', + ...lazyRouteConfig(() => + import('@/pages/notebooks/[notebookId]/dashboard/Dashboard.page'), + ), + }, + ], }, ], }, diff --git a/packages/manager/apps/pci-ai-notebooks/src/types/cloud/project/ai/Label.ts b/packages/manager/apps/pci-ai-notebooks/src/types/cloud/project/ai/Label.ts index d05f8a7a9f85..23ec8bb75de8 100644 --- a/packages/manager/apps/pci-ai-notebooks/src/types/cloud/project/ai/Label.ts +++ b/packages/manager/apps/pci-ai-notebooks/src/types/cloud/project/ai/Label.ts @@ -3,5 +3,5 @@ export interface Label { /** Name of the label to update/add */ name: string; /** Value of the label to update/add, is there is no value the label is deleted */ - value: string; + value?: string; }+ ++ ++ + +{t('durationHelper')}
+