diff --git a/libs/i18n/locales/en/translation.json b/libs/i18n/locales/en/translation.json index 032ddf32..b79e7db7 100644 --- a/libs/i18n/locales/en/translation.json +++ b/libs/i18n/locales/en/translation.json @@ -909,6 +909,7 @@ "Image builds cannot be edited. Use Retry to create a new image build based on this one.": "Image builds cannot be edited. Use Retry to create a new image build based on this one.", "Date": "Date", "Build failed. Please retry.": "Build failed. Please retry.", + "Build failed.": "Build failed.", "View more": "View more", "There are no image builds in your environment.": "There are no image builds in your environment.", "Generate system images for consistent deployment to edge devices.": "Generate system images for consistent deployment to edge devices.", diff --git a/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx b/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx index b693fb05..88b569a2 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx @@ -23,6 +23,8 @@ import ImageBuildLogsTab from './ImageBuildLogsTab'; const imageBuildDetailsPermissions = [ { kind: RESOURCE.IMAGE_BUILD, verb: VERB.CREATE }, { kind: RESOURCE.IMAGE_BUILD, verb: VERB.DELETE }, + // Users that can view logs for imagebuilds also can view logs for imageexports + { kind: RESOURCE.IMAGE_BUILD_LOG, verb: VERB.GET }, ]; const ImageBuildDetailsPageContent = () => { @@ -36,9 +38,14 @@ const ImageBuildDetailsPageContent = () => { const [imageBuild, isLoading, error, refetch] = useImageBuild(imageBuildId); const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState(); const { checkPermissions } = usePermissionsContext(); - const [canCreate, canDelete] = checkPermissions(imageBuildDetailsPermissions); + const [canCreate, canDelete, canViewLogs] = checkPermissions(imageBuildDetailsPermissions); const buildReason = imageBuild ? getImageBuildStatusReason(imageBuild) : undefined; + const tabKeys = React.useMemo( + () => (canViewLogs ? ['details', 'exports', 'yaml', 'logs'] : ['details', 'exports', 'yaml']), + [canViewLogs], + ); + return ( { resourceType="Image builds" resourceTypeLabel={t('Image builds')} nav={ - + - + {canViewLogs && } } actions={ @@ -81,7 +88,7 @@ const ImageBuildDetailsPageContent = () => { } /> } /> } /> - } /> + {canViewLogs && } />} {isDeleteModalOpen && ( { document.body.removeChild(link); }; +const imageBuildExportsPermissions = [ + { kind: RESOURCE.IMAGE_EXPORT, verb: VERB.CREATE }, + { kind: RESOURCE.IMAGE_EXPORT_DOWNLOAD, verb: VERB.GET }, +]; + const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGalleryProps) => { const { post, proxyFetch } = useFetch(); + const { checkPermissions } = usePermissionsContext(); + const [canCreateExport, canDownload] = checkPermissions(imageBuildExportsPermissions); const [error, setError] = React.useState<{ format: ExportFormatType; message: string; @@ -117,8 +126,8 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall isDownloading={downloadingFormat === format} isDisabled={isDisabled} onDismissError={() => setError(undefined)} - onExportImage={handleExportImage} - onDownload={handleDownload} + onExportImage={canCreateExport ? handleExportImage : undefined} + onDownload={canDownload ? handleDownload : undefined} /> ); })} diff --git a/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx b/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx index 567032bb..ce293f3d 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx @@ -18,6 +18,7 @@ type ImageBuildRowProps = { rowIndex: number; onRowSelect: (imageBuild: ImageBuild) => OnSelect; isRowSelected: (imageBuild: ImageBuild) => boolean; + canCreate: boolean; canDelete: boolean; onDeleteClick: VoidFunction; refetch: VoidFunction; @@ -29,6 +30,7 @@ const ImageBuildRow = ({ onRowSelect, isRowSelected, onDeleteClick, + canCreate, canDelete, refetch, }: ImageBuildRowProps) => { @@ -48,12 +50,14 @@ const ImageBuildRow = ({ }, ]; - actions.push({ - title: buildReason === ImageBuildConditionReason.ImageBuildConditionReasonFailed ? t('Retry') : t('Duplicate'), - onClick: () => { - navigate({ route: ROUTE.IMAGE_BUILD_EDIT, postfix: imageBuildName }); - }, - }); + if (canCreate) { + actions.push({ + title: buildReason === ImageBuildConditionReason.ImageBuildConditionReasonFailed ? t('Retry') : t('Duplicate'), + onClick: () => { + navigate({ route: ROUTE.IMAGE_BUILD_EDIT, postfix: imageBuildName }); + }, + }); + } if (canDelete) { actions.push({ @@ -114,17 +118,25 @@ const ImageBuildRow = ({ - - {t('Build failed. Please retry.')} - - - - + {canCreate ? ( + <> + + {t('Build failed. Please retry.')} + + + + + + ) : ( + + {t('Build failed.')} + + )} )} diff --git a/libs/ui-components/src/components/ImageBuilds/ImageBuildsPage.tsx b/libs/ui-components/src/components/ImageBuilds/ImageBuildsPage.tsx index 50dba450..3bfe658d 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageBuildsPage.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageBuildsPage.tsx @@ -58,18 +58,20 @@ const imageBuildTablePermissions = [ { kind: RESOURCE.IMAGE_BUILD, verb: VERB.DELETE }, ]; -const ImageBuildsEmptyState = ({ onCreateClick }: { onCreateClick: () => void }) => { +const ImageBuildsEmptyState = ({ onCreateClick }: { onCreateClick?: VoidFunction }) => { const { t } = useTranslation(); return ( {t('Generate system images for consistent deployment to edge devices.')} - - - - - + {onCreateClick && ( + + + + + + )} ); }; @@ -138,6 +140,7 @@ const ImageBuildTable = () => { key={name} imageBuild={imageBuild} rowIndex={rowIndex} + canCreate={canCreate} canDelete={canDelete} onDeleteClick={() => { setImageBuildToDeleteId(name); @@ -150,7 +153,9 @@ const ImageBuildTable = () => { })} - {!isUpdating && imageBuilds.length === 0 && !name && } + {!isUpdating && imageBuilds.length === 0 && !name && ( + + )} {imageBuildToDeleteId && ( void; - onDownload: (format: ExportFormatType) => void; + onExportImage?: (format: ExportFormatType) => void; + onDownload?: (format: ExportFormatType) => void; onDismissError: VoidFunction; isCreating: boolean; isDownloading?: boolean; @@ -60,11 +62,8 @@ type SelectImageBuildExportCardProps = { export const SelectImageBuildExportCard = ({ format, isChecked, onToggle }: SelectImageBuildExportCardProps) => { const { t } = useTranslation(); - - const title = getExportFormatLabel(t, format); - const description = getExportFormatDescription(t, format); - const id = `export-format-${format}`; + return ( - {title} + {getExportFormatLabel(t, format)} - {description} + {getExportFormatDescription(t, format)} ); }; @@ -110,15 +109,9 @@ export const ViewImageBuildExportCard = ({ } = useAppContext(); const routerNavigate = useRouterNavigate(); const exists = !!imageExport; - const exportReason = exists ? getImageExportStatusReason(imageExport) : undefined; - const title = getExportFormatLabel(t, format); - const description = getExportFormatDescription(t, format); - - const handleViewLogs = () => { - const baseRoute = appRoutes[ROUTE.IMAGE_BUILD_DETAILS]; - routerNavigate(`${baseRoute}/${imageBuildId}/logs`); - }; + const { checkPermissions } = usePermissionsContext(); + const [canViewLogs] = checkPermissions([{ kind: RESOURCE.IMAGE_EXPORT_LOG, verb: VERB.GET }]); return ( @@ -148,17 +141,17 @@ export const ViewImageBuildExportCard = ({ - {title} + {getExportFormatLabel(t, format)} - {description} + {getExportFormatDescription(t, format)} - {exportReason === ImageExportConditionReason.ImageExportConditionReasonFailed && ( + {exportReason === ImageExportConditionReason.ImageExportConditionReasonFailed && onExportImage && ( - ) : ( + + )} + {!exists && onExportImage && ( + - )} - + + )} diff --git a/libs/ui-components/src/types/rbac.ts b/libs/ui-components/src/types/rbac.ts index 62d8f693..d8d87aa1 100644 --- a/libs/ui-components/src/types/rbac.ts +++ b/libs/ui-components/src/types/rbac.ts @@ -21,4 +21,8 @@ export enum RESOURCE { ALERTS = 'alerts', AUTH_PROVIDER = 'authproviders', IMAGE_BUILD = 'imagebuilds', + IMAGE_BUILD_LOG = 'imagebuilds/log', + IMAGE_EXPORT = 'imageexports', + IMAGE_EXPORT_LOG = 'imageexports/log', + IMAGE_EXPORT_DOWNLOAD = 'imageexports/download', }