mirror of https://github.com/portainer/portainer
				
				
				
			
		
			
				
	
	
		
			182 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
| import { useMemo } from 'react';
 | |
| import { Trash2, CalendarCheck2 } from 'lucide-react';
 | |
| import { useRouter } from '@uirouter/react';
 | |
| 
 | |
| import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
 | |
| import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
 | |
| import { notifyError, notifySuccess } from '@/portainer/services/notifications';
 | |
| import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
 | |
| import {
 | |
|   DefaultDatatableSettings,
 | |
|   TableSettings as KubeTableSettings,
 | |
| } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
 | |
| import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
 | |
| import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton';
 | |
| 
 | |
| import { confirmDelete } from '@@/modals/confirm';
 | |
| import { Datatable, TableSettingsMenu } from '@@/datatables';
 | |
| import { LoadingButton } from '@@/buttons';
 | |
| import {
 | |
|   type FilteredColumnsTableSettings,
 | |
|   filteredColumnsSettings,
 | |
| } from '@@/datatables/types';
 | |
| import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
 | |
| import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
 | |
| 
 | |
| import { useJobs } from './queries/useJobs';
 | |
| import { columns } from './columns';
 | |
| import { Job } from './types';
 | |
| import { useDeleteJobsMutation } from './queries/useDeleteJobsMutation';
 | |
| 
 | |
| const storageKey = 'jobs';
 | |
| 
 | |
| interface TableSettings
 | |
|   extends KubeTableSettings,
 | |
|     FilteredColumnsTableSettings {}
 | |
| 
 | |
| export function JobsDatatable() {
 | |
|   const environmentId = useEnvironmentId();
 | |
|   const tableState = useKubeStore<TableSettings>(
 | |
|     storageKey,
 | |
|     undefined,
 | |
|     (set) => ({
 | |
|       ...filteredColumnsSettings(set),
 | |
|     })
 | |
|   );
 | |
| 
 | |
|   const jobsQuery = useJobs(environmentId, {
 | |
|     refetchInterval: tableState.autoRefreshRate * 1000,
 | |
|   });
 | |
|   const jobsRowData = jobsQuery.data;
 | |
| 
 | |
|   const { authorized: canAccessSystemResources } = useAuthorizations(
 | |
|     'K8sAccessSystemNamespaces'
 | |
|   );
 | |
|   const filteredJobs = useMemo(
 | |
|     () =>
 | |
|       tableState.showSystemResources
 | |
|         ? jobsRowData
 | |
|         : jobsRowData?.filter(
 | |
|             (job) =>
 | |
|               // show everything if we can access system resources and the table is set to show them
 | |
|               (canAccessSystemResources && tableState.showSystemResources) ||
 | |
|               // otherwise, only show non-system resources
 | |
|               !job.IsSystem
 | |
|           ),
 | |
|     [jobsRowData, tableState.showSystemResources, canAccessSystemResources]
 | |
|   );
 | |
| 
 | |
|   return (
 | |
|     <Datatable
 | |
|       dataset={filteredJobs || []}
 | |
|       columns={columns}
 | |
|       settingsManager={tableState}
 | |
|       isLoading={jobsQuery.isLoading}
 | |
|       title="Jobs"
 | |
|       titleIcon={CalendarCheck2}
 | |
|       getRowId={(row) => row.Id}
 | |
|       isRowSelectable={(row) => !row.original.IsSystem}
 | |
|       renderTableActions={(selectedRows) => (
 | |
|         <TableActions selectedItems={selectedRows} />
 | |
|       )}
 | |
|       renderTableSettings={() => (
 | |
|         <TableSettingsMenu>
 | |
|           <DefaultDatatableSettings settings={tableState} />
 | |
|         </TableSettingsMenu>
 | |
|       )}
 | |
|       description={
 | |
|         <SystemResourceDescription
 | |
|           showSystemResources={tableState.showSystemResources}
 | |
|         />
 | |
|       }
 | |
|       data-cy="k8s-jobs-datatable"
 | |
|       extendTableOptions={mergeOptions(
 | |
|         withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
 | |
|       )}
 | |
|     />
 | |
|   );
 | |
| }
 | |
| 
 | |
| interface SelectedJob {
 | |
|   Namespace: string;
 | |
|   Name: string;
 | |
| }
 | |
| 
 | |
| type TableActionsProps = {
 | |
|   selectedItems: Job[];
 | |
| };
 | |
| 
 | |
| function TableActions({ selectedItems }: TableActionsProps) {
 | |
|   const environmentId = useEnvironmentId();
 | |
|   const deleteJobsMutation = useDeleteJobsMutation(environmentId);
 | |
|   const router = useRouter();
 | |
| 
 | |
|   return (
 | |
|     <Authorized authorizations="K8sCronJobsW">
 | |
|       <LoadingButton
 | |
|         className="btn-wrapper"
 | |
|         color="dangerlight"
 | |
|         disabled={selectedItems.length === 0}
 | |
|         onClick={() => handleRemoveClick(selectedItems)}
 | |
|         icon={Trash2}
 | |
|         isLoading={deleteJobsMutation.isLoading}
 | |
|         loadingText="Removing jobs..."
 | |
|         data-cy="k8s-jobs-removeJobButton"
 | |
|       >
 | |
|         Remove
 | |
|       </LoadingButton>
 | |
| 
 | |
|       <CreateFromManifestButton
 | |
|         params={{ tab: 'jobs' }}
 | |
|         data-cy="k8s-jobs-deploy-button"
 | |
|       />
 | |
|     </Authorized>
 | |
|   );
 | |
| 
 | |
|   async function handleRemoveClick(jobs: SelectedJob[]) {
 | |
|     const confirmed = await confirmDelete(
 | |
|       <>
 | |
|         <p>Are you sure you want to delete the selected job(s)?</p>
 | |
|         <ul className="mt-2 max-h-96 list-inside overflow-hidden overflow-y-auto text-sm">
 | |
|           {jobs.map((s, index) => (
 | |
|             <li key={index}>
 | |
|               {s.Namespace}/{s.Name}
 | |
|             </li>
 | |
|           ))}
 | |
|         </ul>
 | |
|       </>
 | |
|     );
 | |
|     if (!confirmed) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     const payload: Record<string, string[]> = {};
 | |
|     jobs.forEach((r) => {
 | |
|       payload[r.Namespace] = payload[r.Namespace] || [];
 | |
|       payload[r.Namespace].push(r.Name);
 | |
|     });
 | |
| 
 | |
|     deleteJobsMutation.mutate(
 | |
|       { environmentId, data: payload },
 | |
|       {
 | |
|         onSuccess: () => {
 | |
|           notifySuccess(
 | |
|             'Jobs successfully removed',
 | |
|             jobs.map((r) => `${r.Namespace}/${r.Name}`).join(', ')
 | |
|           );
 | |
|           router.stateService.reload();
 | |
|         },
 | |
|         onError: (error) => {
 | |
|           notifyError(
 | |
|             'Unable to delete jobs',
 | |
|             error as Error,
 | |
|             jobs.map((r) => `${r.Namespace}/${r.Name}`).join(', ')
 | |
|           );
 | |
|         },
 | |
|       }
 | |
|     );
 | |
| 
 | |
|     return jobs;
 | |
|   }
 | |
| }
 |