diff --git a/messages/en.json b/messages/en.json index efc7eb674..96ec413c6 100644 --- a/messages/en.json +++ b/messages/en.json @@ -193,6 +193,7 @@ "Darcs": "Darcs", "Decision report": "Decision report", "Default vendor": "Default vendor", + "Delete Project": "Delete Project", "Delete Attachment": "Delete Attachment", "Delete component success!": "Delete component success!", "Delete Component": "Delete Component", @@ -210,6 +211,7 @@ "Delete": "Delete", "Delete Vulnerability": "Delete Vulnerability", "Do you really want to delete the vulnerability?": "Do you really want to delete the vulnerability {id}?", + "Do you really want to delete the project?": "Do you really want to delete the project {name}?", "Do you really want to delete the component?": "Do you really want to delete the component {name}?", "Do you really want to delete the release?": "Do you really want to delete the release: {name}?", "Document Id": "Document Id", @@ -748,6 +750,10 @@ "Creator Group": "Creator Group", "ECC Assessor": "ECC Assessor", "ECC Assessor Group": "ECC Assessor Group", - "ECC Assessment Date": "ECC Assessment Date" + "ECC Assessment Date": "ECC Assessment Date", + "This project contains": "This project {name} contains:", + "Delete project successful!": "Delete project successful!", + "The project cannot be deleted, since it is used by another project!": "The project cannot be deleted, since it is used by another project!", + "Moderation request is created!": "Moderation request is created!" } } diff --git a/messages/ja.json b/messages/ja.json index b8d45c142..6fe916f96 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -202,6 +202,7 @@ "Deselect All": "Deselect All", "Design document": "設計書", "Do not create monitoring list, but use list from external id": "監視リストを作成せず、外部IDからのリストを使用します。", + "Do you really want to delete the project?": "Do you really want to delete the project {name}?", "Do you really want to delete the attachment?": "您确实要删除该附件吗?", "Do you really want to delete the component?": "本当にコンポーネント {name} を削除しますか?", "Do you really want to delete the release?": "本当にリリース: {name}?", @@ -749,6 +750,11 @@ "Creator Group": "Creator Group", "ECC Assessor": "ECC Assessor", "ECC Assessor Group": "ECC Assessor Group", - "ECC Assessment Date": "ECC Assessment Date" + "ECC Assessment Date": "ECC Assessment Date", + "Delete Project": "プロジェクトの削除", + "This project contains": "このプロジェクト {name} には次のものが含まれます:", + "Delete project successful!": "プロジェクトの削除が成功しました。", + "The project cannot be deleted, since it is used by another project!": "プロジェクトは別のプロジェクトで使用されているため削除できません。", + "Moderation request is created!": "モデレーションリクエストが作成されました。" } } diff --git a/messages/pt-BR.json b/messages/pt-BR.json index b99ac7276..6143ae7a7 100644 --- a/messages/pt-BR.json +++ b/messages/pt-BR.json @@ -203,6 +203,7 @@ "Design document": "-Documento de Concepção do FIP", "Do not create monitoring list, but use list from external id": "Não criar lista de monitoramento, mas usar lista de id externo", "Do you really want to delete the attachment?": "Tem certeza de que deseja excluir este anexo?", + "Do you really want to delete the project?": "Do you really want to delete the project {name}?", "Do you really want to delete the component?": "Deseja realmente excluir o componente {name}?", "Do you really want to delete the release?": "Deseja realmente excluir a versão: {name}?", "Do you really want to delete the vulnerability?": "Deseja realmente excluir a vulnerabilidade {id}?", @@ -748,6 +749,11 @@ "Creator Group": "Creator Group", "ECC Assessor": "ECC Assessor", "ECC Assessor Group": "ECC Assessor Group", - "ECC Assessment Date": "ECC Assessment Date" + "ECC Assessment Date": "ECC Assessment Date", + "Delete Project": "Excluir projeto", + "This project contains": "Este projeto {name} contém:", + "Delete project successful": "Excluir projeto com sucesso!", + "The project cannot be deleted, since it is used by another project!": "O projeto não pode ser excluído, pois está sendo utilizado por outro projeto!", + "Moderation request is created!": "A solicitação de moderação foi criada !" } } diff --git a/messages/vi.json b/messages/vi.json index f320b220e..162aa7138 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -277,6 +277,7 @@ "Design document": "Thiết kế văn bản", "Do not create monitoring list, but use list from external id": "Không tạo danh sách theo dõi mà sử dụng danh sách từ id bên ngoài", "Do you really want to delete the attachment?": "Bạn có thực sự muốn xóa tệp đính kèm không?", + "Do you really want to delete the project?": "Do you really want to delete the project {name}?", "Do you really want to delete the component?": "Bạn có thực sự muốn xóa thành phần {name} không?", "Do you really want to delete the release?": "Bạn có thực sự muốn xóa bản phát hành: {name} không?", "Do you really want to delete the vulnerability?": "Bạn có thực sự muốn xóa lỗ hổng {id} không?", @@ -747,6 +748,11 @@ "Creator Group": "Creator Group", "ECC Assessor": "ECC Assessor", "ECC Assessor Group": "ECC Assessor Group", - "ECC Assessment Date": "ECC Assessment Date" + "ECC Assessment Date": "ECC Assessment Date", + "Delete Project": "Xóa dự án", + "This project contains": "Dự án này {name bao gồm:", + "Delete project successful!": "Xóa dự án thành công!", + "The project cannot be deleted, as it is used by another project!": "Dự án không thể bị xóa vì nó được sử dụng bởi một dự án khác!", + "Moderation request is created!": "Yêu cầu kiểm duyệt được tạo!" } } diff --git a/messages/zh-CN.json b/messages/zh-CN.json index b7b35c011..6dd614361 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -278,6 +278,7 @@ "Design document": "设计文件", "Do not create monitoring list, but use list from external id": "不创建监控列表,而是使用外部 id 的列表", "Do you really want to delete the attachment?": "您确实要删除该附件吗?", + "Do you really want to delete the project?": "Do you really want to delete the project {name}?", "Do you really want to delete the component?": "您确实要删除组件{name}吗?", "Do you really want to delete the release?": "您确实要删除版本:{name}吗?", "Do you really want to delete the vulnerability?": "您确实要删除漏洞{id}吗?", @@ -747,6 +748,11 @@ "Creator Group": "Creator Group", "ECC Assessor": "ECC Assessor", "ECC Assessor Group": "ECC Assessor Group", - "ECC Assessment Date": "ECC Assessment Date" + "ECC Assessment Date": "ECC Assessment Date", + "Delete Project": "删除项目", + "This project contains": "该项目{name}包含:", + "Delete project successful": "删除项目成功!", + "The project cannot be deleted, as it is used by another project!": "該項目無法刪除,因為它已被另一個項目使用!", + "Moderation request is created!": "审核请求已创建!" } } diff --git a/src/app/[locale]/projects/components/DeleteProjectDialog.tsx b/src/app/[locale]/projects/components/DeleteProjectDialog.tsx new file mode 100644 index 000000000..f29e86869 --- /dev/null +++ b/src/app/[locale]/projects/components/DeleteProjectDialog.tsx @@ -0,0 +1,216 @@ +// Copyright (C) Siemens AG, 2023. Part of the SW360 Frontend Project. + +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ + +// SPDX-License-Identifier: EPL-2.0 +// License-Filename: LICENSE + +'use client' + +import { HttpStatus, Project } from '@/object-types' +import { ApiUtils } from '@/utils' +import { signOut, useSession } from 'next-auth/react' +import { useTranslations } from 'next-intl' +import { useRouter } from 'next/navigation' +import { useCallback, useEffect, useState } from 'react' +import { Alert, Button, Form, Modal } from 'react-bootstrap' +import { AiOutlineQuestionCircle } from 'react-icons/ai' + +const DEFAULT_PROJECT_DATA: Project = { + name: '', + _embedded: { + 'sw360:releases': [], + 'sw360:projects': [], + 'sw360:attachments': [], + }, +} +interface Data { + attachment?: number + project?: number + release?: number +} + +interface Props { + projectId?: string + show?: boolean + setShow?: React.Dispatch> +} + +const DeleteProjectDialog = ({ projectId, show, setShow }: Props) => { + const { data: session } = useSession() + const t = useTranslations('default') + const router = useRouter() + const [project, setProject] = useState(DEFAULT_PROJECT_DATA) + const [internalData, setInternalData] = useState({ attachment: 0, project: 0, release: 0 }) + const [variant, setVariant] = useState('success') + const [message, setMessage] = useState('') + const [showMessage, setShowMessage] = useState(false) + const [reloadPage, setReloadPage] = useState(false) + const [hasSubProject, setHasSubProject] = useState(false) + const [comment, setComment] = useState('') + + const displayMessage = (variant: string, message: string) => { + setVariant(variant) + setMessage(message) + setShowMessage(true) + } + + const handleError = useCallback(() => { + displayMessage('danger', t('Error when processing')) + setReloadPage(true) + }, [t]) + + const deleteProject = async () => { + const response = await ApiUtils.DELETE(`projects/${projectId}`, session.user.access_token) + try { + if (response.status == HttpStatus.OK) { + displayMessage('success', t('Delete project successful!')) + router.push('/projects') + setReloadPage(true) + } else if (response.status == HttpStatus.ACCEPTED) { + displayMessage('info', t('Moderation request is created!')) + } else if (response.status == HttpStatus.CONFLICT) { + displayMessage('danger', t('The project cannot be deleted, since it is used by another project!')) + } else if (response.status == HttpStatus.UNAUTHORIZED) { + await signOut() + } else { + displayMessage('danger', t('Error when processing')) + } + } catch (err) { + handleError() + } + } + + const fetchData = useCallback( + async (signal: AbortSignal) => { + if (session) { + const projectsResponse = await ApiUtils.GET(`projects/${projectId}`, session.user.access_token, signal) + if (projectsResponse.status == HttpStatus.OK) { + const project = (await projectsResponse.json()) as Project + setProject(project) + handleInternalDataCount() + } else if (projectsResponse.status == HttpStatus.UNAUTHORIZED) { + await signOut() + } else { + setProject(DEFAULT_PROJECT_DATA) + handleError() + } + } + }, + [projectId, handleError, session] + ) + + useEffect(() => { + const controller = new AbortController() + const signal = controller.signal + fetchData(signal).catch((err) => { + console.error(err) + }) + + return () => { + controller.abort() + } + }, [show, projectId, fetchData]) + + const handleSubmit = () => { + deleteProject().catch((err) => { + console.log(err) + }) + } + + const handleCloseDialog = () => { + setShow(!show) + setShowMessage(false) + setComment('') + if (reloadPage === true) { + window.location.reload() + } + } + + const handleInternalDataCount = () => { + const dataCount: Data = {} + if (project._embedded['sw360:attachments']) { + const attachmentCount = project._embedded['sw360:attachments'].length + dataCount.attachment = attachmentCount + } else if (project._embedded['sw360:projects']) { + const projectCount = project._embedded['sw360:projects'].length + dataCount.project = projectCount + setHasSubProject(true) + } else if (project._embedded['sw360:releases']) { + const releaseCount = project._embedded['sw360:releases'].length + dataCount.release = releaseCount + } + setInternalData(dataCount) + } + + const handleUserComment = (e: any) => { + setComment(e.target.value) + } + + return ( + + + + + {t('Delete Project')} ? + + + + setShowMessage(false)} dismissible show={showMessage}> + {message} + +
+ + + {t.rich('Do you really want to delete the project?', { + name: project.name, + strong: (data) => {data}, + })} + + + {t.rich('This project contains', { + name: project.name, + strong: (data) => {data}, + hasSubProject, + })} +
    + {Object.entries(internalData).map(([key, value]) => ( +
  • {`${value} linked ${key}`}
  • + ))} +
+
+
+
+ + {t('Please comment your changes')} + + +
+
+ + + + +
+ ) +} + +export default DeleteProjectDialog diff --git a/src/app/[locale]/projects/components/Projects.tsx b/src/app/[locale]/projects/components/Projects.tsx index a3776a509..9a2335df6 100644 --- a/src/app/[locale]/projects/components/Projects.tsx +++ b/src/app/[locale]/projects/components/Projects.tsx @@ -15,12 +15,14 @@ import { CommonUtils } from '@/utils' import { SW360_API_URL } from '@/utils/env' import { useSession } from 'next-auth/react' import { useTranslations } from 'next-intl' -import { AdvancedSearch, PageButtonHeader, Table, _ } from 'next-sw360' +import { AdvancedSearch, Table, _ } from 'next-sw360' import Link from 'next/link' -import { useSearchParams } from 'next/navigation' +import { useRouter, useSearchParams } from 'next/navigation' +import { useState } from 'react' import { Dropdown, OverlayTrigger, Spinner, Tooltip } from 'react-bootstrap' import { FaClipboard, FaPencilAlt, FaTrashAlt } from 'react-icons/fa' import { MdOutlineTask } from 'react-icons/md' +import DeleteProjectDialog from './DeleteProjectDialog' type EmbeddedProjects = Embedded @@ -31,6 +33,18 @@ function Project() { const { data: session, status } = useSession() const t = useTranslations('default') const params = useSearchParams() + const router = useRouter() + const [deleteProjectId, setDeleteProjectId] = useState('') + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + + const handleDeleteProject = (projectId: string) => { + setDeleteProjectId(projectId) + setDeleteDialogOpen(true) + } + + const handleAddProject = () => { + router.push('/projects/add') + } const columns = [ { @@ -137,7 +151,11 @@ function Project() { {t('Delete')}}> - + handleDeleteProject(id)} + style={{ color: 'gray', fontSize: '18px' }} + /> @@ -266,38 +284,41 @@ function Project() { }, ] - const headerbuttons = { - 'Add Project': { link: '/projects/add', type: 'primary', name: t('Add Project') }, - } - return ( -
-
-
- -
-
-
- -
- - {t('Import SBOM')} - - {t('SPDX')} - {t('CycloneDX')} - - - - {t('Export Spreadsheet')} - - {t('Projects only')} - {t('Projects with linked releases')} - - -
-
+ <> + +
+
+
+
-
+
+
+
+
+
+ + + {t('Import SBOM')} + + {t('SPDX')} + {t('CycloneDX')} + + +
+ + {t('Export Spreadsheet')} + + {t('Projects only')} + {t('Projects with linked releases')} + + +
+
+
{t('PROJECTS')}
+
{status === 'authenticated' ? ( ) : ( @@ -308,7 +329,7 @@ function Project() { - + ) } diff --git a/src/components/sw360/Table/Table.tsx b/src/components/sw360/Table/Table.tsx index 1756337ea..101c0b8a0 100644 --- a/src/components/sw360/Table/Table.tsx +++ b/src/components/sw360/Table/Table.tsx @@ -33,14 +33,15 @@ class Table extends Component { private wrapper: RefObject = createRef() // Grid.js instance private readonly instance: Grid = null + private tableProps: TableProps = {} constructor(props: TableProps) { super(props) - let tableProps = { ...defaultOptions, ...props } + this.tableProps = { ...defaultOptions, ...props } - if (tableProps.server) { - tableProps = { - ...tableProps, + if (this.tableProps.server) { + this.tableProps = { + ...this.tableProps, pagination: { limit: 10, server: { @@ -52,7 +53,7 @@ class Table extends Component { } } - this.instance = new Grid(tableProps) + this.instance = new Grid(this.tableProps) } getInstance(): Grid { @@ -79,10 +80,12 @@ class Table extends Component { .updateConfig({ pagination: { limit: pageSize, - server: { - url: (prev: string, page: number, limit: number) => - `${prev}${prev.includes('?') ? '&' : '?'}page=${page}&page_entries=${limit}`, - }, + server: this.tableProps.server + ? { + url: (prev: string, page: number, limit: number) => + `${prev}${prev.includes('?') ? '&' : '?'}page=${page}&page_entries=${limit}`, + } + : undefined, }, }) .forceRender() diff --git a/src/object-types/Project.ts b/src/object-types/Project.ts index d16b645ce..805f2a13a 100644 --- a/src/object-types/Project.ts +++ b/src/object-types/Project.ts @@ -9,7 +9,7 @@ // SPDX-License-Identifier: EPL-2.0 // License-Filename: LICENSE -import { Links, User } from '@/object-types' +import { Attachment, Links, Release, User } from '@/object-types' interface Project { id?: string @@ -34,7 +34,7 @@ interface Project { phaseOutSince?: string preevaluationDeadline?: string projectResponsible?: string - projectType: string + projectType?: string remarksAdditionalRequirements?: string specialRisks3rdParty?: string specialRisksOSS?: string @@ -55,6 +55,9 @@ interface Project { _embedded?: { leadArchitect?: User createdBy?: User + 'sw360:releases'?: Array + 'sw360:attachments'?: Array + 'sw360:projects'?: Array } }