diff --git a/src/app/(rucio)/transfer/page/[source]/[dest]/page.tsx b/src/app/(rucio)/transfer/page/[source]/[dest]/page.tsx new file mode 100644 index 00000000..3581e405 --- /dev/null +++ b/src/app/(rucio)/transfer/page/[source]/[dest]/page.tsx @@ -0,0 +1,30 @@ +import { ListTransfer as ListTransferStory } from "@/component-library/Pages/Transfers/ListTransfer"; +import { TransferViewModel } from "@/lib/infrastructure/data/view-model/request"; +import useComDOM from "@/lib/infrastructure/hooks/useComDOM"; + +export default function Page({ params }: { params: { source: string, dest: string }}) { + const TransferSearchComDOM = useComDOM( + 'list-transfer-query', + [], + false, + Infinity, + 200, + true + ) + TransferSearchComDOM.setRequest({ + url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-transfers`), + method: 'GET', + headers: new Headers({ + 'Content-Type': 'application/json' + } as HeadersInit), + params: { + "sourceRSE": params.source, + "destRSE": params.dest + } + }) + return ( + + ) +} \ No newline at end of file diff --git a/src/component-library/Pages/Transfers/ListTransfer.tsx b/src/component-library/Pages/Transfers/ListTransfer.tsx index 3b943267..094784e2 100644 --- a/src/component-library/Pages/Transfers/ListTransfer.tsx +++ b/src/component-library/Pages/Transfers/ListTransfer.tsx @@ -1,6 +1,5 @@ import useReponsiveHook from "@/component-library/Helpers/ResponsiveHook" import { TableFilterDiscrete } from "@/component-library/StreamedTables/TableFilterDiscrete" -import { TableFilterString } from "@/component-library/StreamedTables/TableFilterString" import { RequestTypeTag } from "@/component-library/Tags/RequestTypeTag" import { RequestType } from "@/lib/core/entity/rucio" import { TransferViewModel } from "@/lib/infrastructure/data/view-model/request" @@ -59,6 +58,7 @@ export const ListTransfer = ( > diff --git a/src/component-library/Pages/Transfers/ListTransferStatistics.tsx b/src/component-library/Pages/Transfers/ListTransferStatistics.tsx index 17bc4ed9..58119278 100644 --- a/src/component-library/Pages/Transfers/ListTransferStatistics.tsx +++ b/src/component-library/Pages/Transfers/ListTransferStatistics.tsx @@ -4,9 +4,10 @@ import { UseComDOM } from "@/lib/infrastructure/hooks/useComDOM" import { Heading } from "../Helpers/Heading" import { createColumnHelper } from "@tanstack/react-table" import { Body } from "../Helpers/Body" -import { Contenttd, Generaltable, Titleth } from "@/component-library/Helpers/Metatable" -import { H3 } from "@/component-library/Text/Headings/H3" -import { TransferStatistics } from "./TransferStatistics" +import { RequestState } from "@/lib/core/entity/rucio" +import { TableFilterDiscrete } from "@/component-library/StreamedTables/TableFilterDiscrete" +import { RequestStateTag } from "@/component-library/Tags/RequestStateTag" +import { HiDotsHorizontal } from "react-icons/hi" export const ListTransferStatistics = ( props: { @@ -15,17 +16,30 @@ export const ListTransferStatistics = ( ) => { const columnHelper = createColumnHelper() const tablecolumns = [ - columnHelper.accessor("source_rse", { - }), - columnHelper.accessor("dest_rse", { - }), - columnHelper.accessor("request_stats", { - id: "request_stats", - header: info =>

Transfer Statistics

, - cell: info => { - return + columnHelper.accessor("source_rse", {}), + columnHelper.accessor("dest_rse", {}), + columnHelper.accessor("activity", {}), + columnHelper.accessor("state", { + id: "state", + header: info => { + return ( + + name="Request Type" + keys={Object.values(RequestState)} + renderFunc={key => key === undefined ? : } + column={info.column} + stack + /> + ) + }, + cell: info => , + meta: { + style: "w-8 md:w-32" } - }) + }), + columnHelper.accessor("activity", {}), + columnHelper.accessor("counter", {}), + columnHelper.accessor("bytes", {}), ] return ( diff --git a/src/component-library/Pages/Transfers/TransferStatistics.stories.tsx b/src/component-library/Pages/Transfers/TransferStatistics.stories.tsx deleted file mode 100644 index 7e26089a..00000000 --- a/src/component-library/Pages/Transfers/TransferStatistics.stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Meta, StoryFn } from "@storybook/react"; -import { TransferStatistics as T } from "./TransferStatistics"; -import { fixtureRequestStatsPerPair } from "test/fixtures/table-fixtures"; - -export default { - title: 'Components/Pages/Transfers', - component: T, -} as Meta - -const Template: StoryFn = (args) => ; - -export const TransferStatistics = Template.bind({}) -TransferStatistics.args = { - request_stats: fixtureRequestStatsPerPair() -} \ No newline at end of file diff --git a/src/component-library/Pages/Transfers/TransferStatistics.tsx b/src/component-library/Pages/Transfers/TransferStatistics.tsx deleted file mode 100644 index 80770926..00000000 --- a/src/component-library/Pages/Transfers/TransferStatistics.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { NormalTable } from "@/component-library/StreamedTables/NormalTable" -import { TableStyling } from "@/component-library/StreamedTables/types" -import { RequestStatsPerPair } from "@/lib/core/entity/rucio" -import { createColumnHelper } from "@tanstack/react-table" - -export const TransferStatistics = ( - props: { - request_stats: RequestStatsPerPair[] - } -) => { - const columnHelper = createColumnHelper() - const tablecolumns: any[] = [ - columnHelper.accessor("activity", {}), - columnHelper.accessor("counter", {}), - columnHelper.accessor("bytes", {}), - ] - return ( - - tabledata={props.request_stats} - tablecolumns={tablecolumns} - tablestyling={{ - pageSize: 5 - } as TableStyling} - /> - ) -} \ No newline at end of file diff --git a/src/component-library/Tags/RequestStateTag.stories.tsx b/src/component-library/Tags/RequestStateTag.stories.tsx new file mode 100644 index 00000000..a421ec67 --- /dev/null +++ b/src/component-library/Tags/RequestStateTag.stories.tsx @@ -0,0 +1,15 @@ +import { RequestState } from "@/lib/core/entity/rucio"; +import { Meta, StoryFn } from "@storybook/react"; +import { RequestStateTag as R } from "./RequestStateTag"; + +export default { + title: 'Components/Tags', + component: R, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const RequestStateTag = Template.bind({}); +RequestStateTag.args = { + state: RequestState.FAILED +} \ No newline at end of file diff --git a/src/component-library/Tags/RequestStateTag.tsx b/src/component-library/Tags/RequestStateTag.tsx new file mode 100644 index 00000000..99cb21b3 --- /dev/null +++ b/src/component-library/Tags/RequestStateTag.tsx @@ -0,0 +1,83 @@ +import { RequestState } from "@/lib/core/entity/rucio" +import React, { useEffect, useState } from "react"; +import { twMerge } from "tailwind-merge"; + +type RequestStateTagProps = JSX.IntrinsicElements["span"] & { + state: RequestState; + forcesmall?: boolean; + neversmall?: boolean; +}; + +export const RequestStateTag: React.FC = ( + { + state = RequestState.SUBMITTED, + forcesmall = false, + neversmall = false, + ...props + } +) => { + const { className, ...restprops } = props + + const [windowWidth, setWindowWidth] = useState(720) + useEffect(() => { + const handleResize = () => setWindowWidth(window.innerWidth) + handleResize() + window.addEventListener('resize', handleResize) + }, []) + const belowMedium = (windowWidth < 768) || forcesmall + const stringMatch = { + [RequestState.PREPARING]: "Preparing", + [RequestState.DONE]: "Done", + [RequestState.FAILED]: "Failed", + [RequestState.LOST]: "Lost", + [RequestState.MISMATCH_SCHEME]: "Mismatch Scheme", + [RequestState.NO_SOURCES]: "No Source", + [RequestState.ONLY_TAPE_SOURCES]: "Only Tape Sources", + [RequestState.QUEUED]: "Queued", + [RequestState.SUBMISSION_FAILED]: "Submission Failed", + [RequestState.SUBMITTED]: "Submitted", + [RequestState.SUBMITTING]: "Submitting", + [RequestState.SUSPEND]: "Suspend", + [RequestState.WAITING]: "Waiting" + } + + const colPicker = (state: RequestState) => { + switch (state) { + case RequestState.SUBMITTED: + return "emerald" + case RequestState.DONE: + return "blue" + case RequestState.PREPARING: + case RequestState.SUSPEND: + case RequestState.QUEUED: + case RequestState.SUBMITTING: + case RequestState.WAITING: + return "rose" + case RequestState.FAILED: + case RequestState.LOST: + case RequestState.SUBMISSION_FAILED: + return "amber" + case RequestState.MISMATCH_SCHEME: + case RequestState.NO_SOURCES: + case RequestState.ONLY_TAPE_SOURCES: + return "gray" + } + } + + const color = colPicker(state) + + return ( + + {belowMedium && !neversmall ? state : stringMatch[state]} + + ); +} \ No newline at end of file diff --git a/src/component-library/Tags/RequestTypeTag.tsx b/src/component-library/Tags/RequestTypeTag.tsx index 0016488e..7b1c5327 100644 --- a/src/component-library/Tags/RequestTypeTag.tsx +++ b/src/component-library/Tags/RequestTypeTag.tsx @@ -1,5 +1,4 @@ import { RequestType } from "@/lib/core/entity/rucio"; -import { RowSelection } from "@tanstack/react-table"; import React, { useEffect, useState } from "react"; import { twMerge } from "tailwind-merge"; diff --git a/src/lib/core/dto/transfer-dto.ts b/src/lib/core/dto/transfer-dto.ts new file mode 100644 index 00000000..484c767f --- /dev/null +++ b/src/lib/core/dto/transfer-dto.ts @@ -0,0 +1,60 @@ +import { BaseDTO, BaseStreamableDTO } from "@/lib/sdk/dto"; +import { DIDType, Request, RequestState, RequestStats, RequestType } from "../entity/rucio"; + +/** + * The Data Transfer Object for the ListTransferStatistics which contains the stream + */ +export interface ListTransferStatisticsDTO extends BaseStreamableDTO {} + +/** + * The Data Transfer Object for the Statistics of a single Transfer Group + */ +export interface TransferStatisticsDTO extends BaseDTO, RequestStats {} + +/** + * The Data Transfer Object for the ListTransfer which contains the stream + */ +export interface ListTransfersDTO extends BaseStreamableDTO {} + +/** + * The Data Transfer Object for a single Transfer + */ +export interface TransferDTO extends BaseDTO, Request {} + +export function getEmptyTransferStatsDTO(): TransferStatisticsDTO { + return { + status: 'error', + account: '', + source_rse: '', + dest_rse: '', + source_rse_id: '', + dest_rse_id: '', + state: RequestState.FAILED, + activity: '', + counter: 0, + bytes: 0, + } +} + +export function getEmptyTransferDTO(): TransferDTO { + return { + status: 'error', + id: '', + account: '', + request_type: RequestType.TRANSFER, + scope: '', + name: '', + did_type: DIDType.UNKNOWN, + source_rse_id: '', + dest_rse_id: '', + source_rse: '', + dest_rse: '', + state: RequestState.FAILED, + activity: '', + attributes: '', + priority: 0, + bytes: 0, + requested_at: '', + transfertool: '', + } +} \ No newline at end of file diff --git a/src/lib/core/entity/rucio.ts b/src/lib/core/entity/rucio.ts index b03c46a0..e6295973 100644 --- a/src/lib/core/entity/rucio.ts +++ b/src/lib/core/entity/rucio.ts @@ -264,20 +264,16 @@ export type Request = { * Represents statistics for the nubmer of transfer requests over dest_rse and * source_rse in Rucio. */ -export type RequestStatsPerPair = { - activity: string, - counter: number, - bytes: number, -} - export type RequestStats = { account: string, - state: RequestState, dest_rse_id: string, source_rse_id: string, dest_rse: string, source_rse: string, - request_stats: RequestStatsPerPair[], + activity: string, + state: RequestState, + counter: number, + bytes: number, } /* diff --git a/src/lib/core/port/primary/list-transfer-stats-ports.ts b/src/lib/core/port/primary/list-transfer-stats-ports.ts new file mode 100644 index 00000000..f4cdc49b --- /dev/null +++ b/src/lib/core/port/primary/list-transfer-stats-ports.ts @@ -0,0 +1,13 @@ +import { TransferStatsViewModel } from "@/lib/infrastructure/data/view-model/request-stats"; +import { BaseAuthenticatedInputPort, BaseStreamingOutputPort } from "@/lib/sdk/primary-ports"; +import { ListTransferStatsError, ListTransferStatsRequest, ListTransferStatsResponse } from "../../usecase-models/list-transfer-stats-usecase-models"; + +/** + * @interface ListTransferStatsInputPort that abstracts usecase. + */ +export interface ListTransferStatsInputPort extends BaseAuthenticatedInputPort {} + +/** + * @interface ListTransferStatsOutputPort that abstracts usecase. + */ +export interface ListTransferStatsOutputPort extends BaseStreamingOutputPort {} \ No newline at end of file diff --git a/src/lib/core/port/primary/list-transfers-ports.ts b/src/lib/core/port/primary/list-transfers-ports.ts new file mode 100644 index 00000000..51f8d3f2 --- /dev/null +++ b/src/lib/core/port/primary/list-transfers-ports.ts @@ -0,0 +1,13 @@ +import { BaseAuthenticatedInputPort, BaseStreamingOutputPort } from "@/lib/sdk/primary-ports"; +import { ListTransfersError, ListTransfersRequest, ListTransfersResponse } from "../../usecase-models/list-transfers-usecase-models"; +import { TransferViewModel } from "@/lib/infrastructure/data/view-model/request"; + +/** + * @interface ListTransfersInputPort that abstracts the usecase. + */ +export interface ListTransfersInputPort extends BaseAuthenticatedInputPort {} + +/** + * @interface ListTransfersOutputPort that abstracts the usecase. + */ +export interface ListTransfersOutputPort extends BaseStreamingOutputPort {} diff --git a/src/lib/core/port/secondary/transfer-gateway-output-port.ts b/src/lib/core/port/secondary/transfer-gateway-output-port.ts new file mode 100644 index 00000000..6923f9a2 --- /dev/null +++ b/src/lib/core/port/secondary/transfer-gateway-output-port.ts @@ -0,0 +1,18 @@ +import { ListTransfersDTO, ListTransferStatisticsDTO } from "../../dto/transfer-dto"; + +export default interface TransferGatewayOutputPort { + + /** + * Gets the list of all transfer statistics + * @param rucioAuthToken A valid Rucio Auth Token. + */ + listTransferStatistics(rucioAuthToken: string): Promise + + /** + * Lists all active transfers over a given source-destination RSE pair + * @param rucioAuthToken A valid Rucio Auth Token. + * @param sourceRse The source RSE. + * @param destRse The destination RSE. + */ + listTransfers(rucioAuthToken: string, sourceRse: string, destRse: string): Promise +} \ No newline at end of file diff --git a/src/lib/core/use-case/list-transfer-stats-usecase.ts b/src/lib/core/use-case/list-transfer-stats-usecase.ts new file mode 100644 index 00000000..eb8a42c5 --- /dev/null +++ b/src/lib/core/use-case/list-transfer-stats-usecase.ts @@ -0,0 +1,70 @@ +import { TransferStatsViewModel } from "@/lib/infrastructure/data/view-model/request-stats"; +import { BaseSingleEndpointStreamingUseCase } from "@/lib/sdk/usecase"; +import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; +import { injectable } from "inversify"; +import { ListTransferStatisticsDTO, TransferStatisticsDTO } from "../dto/transfer-dto"; +import { ListTransferStatsInputPort, type ListTransferStatsOutputPort } from "../port/primary/list-transfer-stats-ports"; +import type TransferGatewayOutputPort from "../port/secondary/transfer-gateway-output-port"; +import { ListTransferStatsError, ListTransferStatsRequest, ListTransferStatsResponse } from "../usecase-models/list-transfer-stats-usecase-models"; + +@injectable() +export default class ListTransferStatsUseCase extends BaseSingleEndpointStreamingUseCase< + AuthenticatedRequestModel, + ListTransferStatsResponse, + ListTransferStatsError, + ListTransferStatisticsDTO, + TransferStatisticsDTO, + TransferStatsViewModel + > implements ListTransferStatsInputPort { + + constructor( + protected readonly presenter: ListTransferStatsOutputPort, + private readonly gateway: TransferGatewayOutputPort, + ) { + super(presenter) + } + + validateRequestModel(requestModel: AuthenticatedRequestModel): ListTransferStatsError | undefined { + return undefined; + } + + async makeGatewayRequest(requestModel: AuthenticatedRequestModel): Promise { + const { rucioAuthToken } = requestModel; + const dto: ListTransferStatisticsDTO = await this.gateway.listTransferStatistics(rucioAuthToken) + return dto; + } + + handleGatewayError(error: ListTransferStatisticsDTO): ListTransferStatsError { + return { + status: 'error', + name: 'Gateway Error', + code: error.errorCode || 500, + message: error.errorMessage? error.errorMessage : 'Gateway Error', + } as ListTransferStatsError + } + + processStreamedData(dto: TransferStatisticsDTO): { data: ListTransferStatsResponse | ListTransferStatsError; status: "success" | "error"; } { + if (dto.status === 'error') { + const errorModel: ListTransferStatsError = { + status: 'error', + code: dto.errorCode || 500, + message: dto.errorMessage || 'Could not fetch or process the fetched data', + name: dto.errorName || 'Gateway Error', + } + return { + status: 'error', + data: errorModel, + } + } + + const responseModel: ListTransferStatsResponse = { + ...dto, + status: 'success', + } + + return { + status: 'success', + data: responseModel, + } + } +} \ No newline at end of file diff --git a/src/lib/core/use-case/list-transfers-usecase.ts b/src/lib/core/use-case/list-transfers-usecase.ts new file mode 100644 index 00000000..c6a6a98f --- /dev/null +++ b/src/lib/core/use-case/list-transfers-usecase.ts @@ -0,0 +1,90 @@ +import { TransferViewModel } from "@/lib/infrastructure/data/view-model/request"; +import { BaseSingleEndpointStreamingUseCase } from "@/lib/sdk/usecase"; +import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; +import { injectable } from "inversify"; +import { ListTransfersDTO, TransferDTO } from "../dto/transfer-dto"; +import { ListTransfersInputPort, type ListTransfersOutputPort } from "../port/primary/list-transfers-ports"; +import type TransferGatewayOutputPort from "../port/secondary/transfer-gateway-output-port"; +import { ListTransfersError, ListTransfersRequest, ListTransfersResponse } from "../usecase-models/list-transfers-usecase-models"; + +@injectable() +export default class ListTransfersUseCase extends BaseSingleEndpointStreamingUseCase< + AuthenticatedRequestModel, + ListTransfersResponse, + ListTransfersError, + ListTransfersDTO, + TransferDTO, + TransferViewModel + > implements ListTransfersInputPort { + + constructor( + protected readonly presenter: ListTransfersOutputPort, + private readonly gateway: TransferGatewayOutputPort, + ) { + super(presenter); + } + + validateRequestModel(requestModel: AuthenticatedRequestModel>): ListTransfersError | undefined { + if ( requestModel.sourceRSE == '' || requestModel.sourceRSE == undefined ) { + return { + status: 'error', + code: 400, + name: 'Missing Source RSE', + error: 'MISSING_SOURCE_RSE', + message: 'The source RSE in the request is missing', + } as ListTransfersError + } + else if ( requestModel.destRSE == '' || requestModel.destRSE == undefined ) { + return { + status: 'error', + code: 400, + name: 'Missing Destination RSE', + error: 'MISSING_DEST_RSE', + message: 'The destination RSE in the request is missing', + } as ListTransfersError + } + return undefined; + } + + async makeGatewayRequest(requestModel: AuthenticatedRequestModel>): Promise { + const { rucioAuthToken, sourceRSE, destRSE } = requestModel; + const dto: ListTransfersDTO = await this.gateway.listTransfers(rucioAuthToken, sourceRSE, destRSE) + return dto; + } + + handleGatewayError(error: ListTransfersDTO): ListTransfersError { + return { + status: 'error', + error: `Gateway returned with ${error.errorCode}: ${error.errorMessage}`, + message: error.errorMessage? error.errorMessage : 'Gateway Error', + name: 'Gateway Error', + code: error.errorCode, + } as ListTransfersError + } + + processStreamedData(dto: TransferDTO): { data: ListTransfersResponse | ListTransfersError; status: "success" | "error"; } { + if (dto.status === 'error') { + const errorModel: ListTransfersError = { + status: 'error', + code: dto.errorCode || 500, + message: dto.errorMessage || 'Could not fetch or process the fetched data', + name: dto.errorName || 'Gateway Error', + } + + return { + status: 'error', + data: errorModel, + } + } + + const responseModel: ListTransfersResponse = { + ...dto, + status: 'success', + } + + return { + data: responseModel, + status: 'success', + } + } +} \ No newline at end of file diff --git a/src/lib/core/usecase-models/list-transfer-stats-usecase-models.ts b/src/lib/core/usecase-models/list-transfer-stats-usecase-models.ts new file mode 100644 index 00000000..e1b9b9bd --- /dev/null +++ b/src/lib/core/usecase-models/list-transfer-stats-usecase-models.ts @@ -0,0 +1,17 @@ +import { BaseErrorResponseModel, BaseResponseModel } from "@/lib/sdk/usecase-models"; +import { RequestStats } from "../entity/rucio"; + +/** + * @interface ListTransferStatsRequest repesents the RequestModel for list_transfer_stats usecase + */ +export interface ListTransferStatsRequest {} + +/** + * @interface ListTransferStatsResponse represents the ResponseModel for list_transfer_stats usecase + */ +export interface ListTransferStatsResponse extends RequestStats, BaseResponseModel {} + +/** + * @interface ListTransferStatsError represents the ErrorModel for list_transfer_stats usecase + */ +export interface ListTransferStatsError extends BaseErrorResponseModel {} \ No newline at end of file diff --git a/src/lib/core/usecase-models/list-transfers-usecase-models.ts b/src/lib/core/usecase-models/list-transfers-usecase-models.ts new file mode 100644 index 00000000..2af0a8bf --- /dev/null +++ b/src/lib/core/usecase-models/list-transfers-usecase-models.ts @@ -0,0 +1,20 @@ +import { BaseErrorResponseModel, BaseResponseModel } from "@/lib/sdk/usecase-models"; +import { Request } from "../entity/rucio"; + +/** + * @interface ListTransfersRequest represents the RequestModel for list_transfers usecase + */ +export interface ListTransfersRequest { + sourceRSE: string, + destRSE: string +} + +/** + * @interface ListTransfersResponse represents the ResponseModel for list_transfers usecase + */ +export interface ListTransfersResponse extends Request, BaseResponseModel {} + +/** + * @interface ListTransfersError represents the ErrorModel for list_transfers usecase + */ +export interface ListTransfersError extends BaseErrorResponseModel {} \ No newline at end of file diff --git a/src/lib/infrastructure/controller/list-transfer-stats-controller.ts b/src/lib/infrastructure/controller/list-transfer-stats-controller.ts new file mode 100644 index 00000000..5206cd1a --- /dev/null +++ b/src/lib/infrastructure/controller/list-transfer-stats-controller.ts @@ -0,0 +1,25 @@ +import { ListTransferStatsInputPort } from "@/lib/core/port/primary/list-transfer-stats-ports"; +import { ListTransferStatsRequest } from "@/lib/core/usecase-models/list-transfer-stats-usecase-models"; +import { BaseController, TAuthenticatedControllerParameters } from "@/lib/sdk/controller"; +import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; +import { inject, injectable } from "inversify"; +import { NextApiResponse } from "next"; +import USECASE_FACTORY from "../ioc/ioc-symbols-usecase-factory"; + +export type ListTransferStatsControllerParameters = TAuthenticatedControllerParameters; + +@injectable() +class ListTransferStatsController extends BaseController> { + constructor( + @inject(USECASE_FACTORY.LIST_TRANSFER_STATS) listTransferStatsUseCaseFactory: (response: NextApiResponse) => ListTransferStatsInputPort, + ) { + super(listTransferStatsUseCaseFactory) + } + prepareRequestModel(parameters: ListTransferStatsControllerParameters): AuthenticatedRequestModel { + return { + rucioAuthToken: parameters.rucioAuthToken + } + } +} + +export default ListTransferStatsController; \ No newline at end of file diff --git a/src/lib/infrastructure/controller/list-transfers-controller.ts b/src/lib/infrastructure/controller/list-transfers-controller.ts new file mode 100644 index 00000000..84a4155e --- /dev/null +++ b/src/lib/infrastructure/controller/list-transfers-controller.ts @@ -0,0 +1,30 @@ +import { ListTransfersInputPort } from "@/lib/core/port/primary/list-transfers-ports"; +import { ListTransfersRequest } from "@/lib/core/usecase-models/list-transfers-usecase-models"; +import { BaseController, TAuthenticatedControllerParameters } from "@/lib/sdk/controller"; +import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; +import { inject, injectable } from "inversify"; +import { NextApiResponse } from "next"; +import USECASE_FACTORY from "../ioc/ioc-symbols-usecase-factory"; + +export type ListTransfersControllerParameters = TAuthenticatedControllerParameters & { + sourceRSE: string; + destRSE: string; +}; + +@injectable() +class ListTransfersController extends BaseController> { + constructor( + @inject(USECASE_FACTORY.LIST_TRANSFERS) listTransfersUseCaseFactory: (response: NextApiResponse) => ListTransfersInputPort, + ) { + super(listTransfersUseCaseFactory); + } + prepareRequestModel(parameters: ListTransfersControllerParameters): AuthenticatedRequestModel { + return { + rucioAuthToken: parameters.rucioAuthToken, + sourceRSE: parameters.sourceRSE, + destRSE: parameters.destRSE, + } + } +} + +export default ListTransfersController; \ No newline at end of file diff --git a/src/lib/infrastructure/data/view-model/request-stats.ts b/src/lib/infrastructure/data/view-model/request-stats.ts index 1cdf0e16..1428b41c 100644 --- a/src/lib/infrastructure/data/view-model/request-stats.ts +++ b/src/lib/infrastructure/data/view-model/request-stats.ts @@ -1,4 +1,19 @@ -import { RequestStats, RequestStatsPerPair } from "@/lib/core/entity/rucio"; +import { RequestState, RequestStats } from "@/lib/core/entity/rucio"; import { BaseViewModel } from "@/lib/sdk/view-models"; export interface TransferStatsViewModel extends BaseViewModel, RequestStats {} + +export function generateEmptyTransferStatsViewModel(): TransferStatsViewModel { + return { + status: 'error', + account: '', + source_rse: '', + dest_rse: '', + source_rse_id: '', + dest_rse_id: '', + state: RequestState.FAILED, + activity: '', + counter: 0, + bytes: 0, + } +} diff --git a/src/lib/infrastructure/gateway/transfer-gateway/endpoints/list-transfer-stats-endpoint.ts b/src/lib/infrastructure/gateway/transfer-gateway/endpoints/list-transfer-stats-endpoint.ts new file mode 100644 index 00000000..6de65834 --- /dev/null +++ b/src/lib/infrastructure/gateway/transfer-gateway/endpoints/list-transfer-stats-endpoint.ts @@ -0,0 +1,60 @@ +import { ListTransferStatisticsDTO, TransferStatisticsDTO } from "@/lib/core/dto/transfer-dto"; +import { BaseStreamableEndpoint } from "@/lib/sdk/gateway-endpoints"; +import { HTTPRequest } from "@/lib/sdk/http"; +import { Response } from "node-fetch"; +import { convertToTransferStatistics, TRucioTransferStatistics } from "../transfer-gateway-utils"; + +export default class ListTransferStatsEndpoint extends BaseStreamableEndpoint { + constructor( + private readonly rucioAuthToken: string + ){ + super(true) + } + + async initialize(): Promise { + await super.initialize() + const rucioHost = await this.envConfigGateway.rucioHost() + const endpoint = `${rucioHost}/requests/metrics` + const request: HTTPRequest = { + method: 'GET', + url: endpoint, + headers: { + 'X-Rucio-Auth-Token': this.rucioAuthToken, + 'Content-Type': 'application/x-json-stream', + }, + body: null, + } + this.request = request + this.initialized = true + } + + /** + * If this method is called, it means that the response from Rucio was not or any of the error types in ${@link handleCommonGatewayEndpointErrors} + * @param statusCode The status code returned from Rucio + * @param response The reponse containing error data + * @returns + */ + async reportErrors(statusCode: number, response: Response): Promise { + const data = await response.json() + const errorDTO: ListTransferStatisticsDTO = { + status: 'error', + errorMessage: data, + errorCode: statusCode, + errorName: 'Unknown Error', + errorType: 'gateway-endpoint-error', + stream: null, + } + return Promise.resolve(errorDTO) + } + + /** + * Converts stream elements into TransferStatisticsDTO objects + * @param response The individual TransferStatistics object streamed from Rucio + * @returns The TransferStatisticsDTO object + */ + createDTO(response: Buffer): TransferStatisticsDTO { + const data: TRucioTransferStatistics = JSON.parse(JSON.parse(response.toString())) + const dto: TransferStatisticsDTO = convertToTransferStatistics(data) + return dto + } +} \ No newline at end of file diff --git a/src/lib/infrastructure/gateway/transfer-gateway/endpoints/list-transfers-endpoint.ts b/src/lib/infrastructure/gateway/transfer-gateway/endpoints/list-transfers-endpoint.ts new file mode 100644 index 00000000..6bba46e8 --- /dev/null +++ b/src/lib/infrastructure/gateway/transfer-gateway/endpoints/list-transfers-endpoint.ts @@ -0,0 +1,66 @@ +import { ListTransfersDTO, TransferDTO } from "@/lib/core/dto/transfer-dto"; +import { BaseStreamableEndpoint } from "@/lib/sdk/gateway-endpoints"; +import { HTTPRequest } from "@/lib/sdk/http"; +import { Response } from "node-fetch"; +import { convertToTransfer, TRucioTransfer } from "../transfer-gateway-utils"; + +export default class ListTransfersEndpoint extends BaseStreamableEndpoint { + constructor( + private readonly rucioAuthToken: string, + private readonly sourceRSE: string, + private readonly destRSE: string, + ){ + super(true) + } + + async initialize(): Promise { + await super.initialize() + const rucioHost = await this.envConfigGateway.rucioHost() + const endpoint = `${rucioHost}/requests/list` + const request: HTTPRequest = { + method: 'GET', + url: endpoint, + headers: { + 'X-Rucio-Auth-Token': this.rucioAuthToken, + 'Content-Type': 'application/x-json-stream' + }, + body: null, + params: { + src_rse: this.sourceRSE, + dest_rse: this.destRSE, + } + } + this.request = request + this.initialized = true + } + + /** + * If this method is called, it means that the response from Rucio was not or any of the error types in ${@link handleCommonGatewayEndpointErrors} + * @param statusCode The status code returned from Rucio + * @param response The reponse containing error data + * @returns + */ + async reportErrors(statusCode: number, response: Response): Promise { + const data = await response.json() + const errorDTO: ListTransfersDTO = { + status: 'error', + errorMessage: data, + errorCode: statusCode, + errorName: 'Unknown Error', + errorType: 'gateway-endpoint-error', + stream: null, + } + return Promise.resolve(errorDTO) + } + + /** + * Convert stream elements into TransferDTO objects + * @param response The individual Transfer object streamed from Rucio + * @returns The TransferDTO object + */ + createDTO(response: Buffer): TransferDTO { + const data: TRucioTransfer = JSON.parse(JSON.parse(response.toString())) + const dto: TransferDTO = convertToTransfer(data) + return dto + } +} \ No newline at end of file diff --git a/src/lib/infrastructure/gateway/transfer-gateway/transfer-gateway-utils.ts b/src/lib/infrastructure/gateway/transfer-gateway/transfer-gateway-utils.ts new file mode 100644 index 00000000..c3d27f78 --- /dev/null +++ b/src/lib/infrastructure/gateway/transfer-gateway/transfer-gateway-utils.ts @@ -0,0 +1,101 @@ +import { TransferDTO, TransferStatisticsDTO } from "@/lib/core/dto/transfer-dto"; +import { DIDType, RequestState, RequestType } from "@/lib/core/entity/rucio"; +import { tSConstructSignatureDeclaration } from "@babel/types"; + +/** + * Represents the data returned by Rucio Server for a TransferStatistics. + */ +export type TRucioTransferStatistics = { + source_rse: string; + dest_rse: string; + source_rse_id: string; + dest_rse_id: string; + account: string; + activity: string; + state: string; + counter: number; + bytes: number; +} + +export type TRucioTransfer = { + id: string; + request_type: string; + scope: string, + name: string; + did_type: string; + dest_rse_id: string; + source_rse_id: string; + attributes: string; + state: string; + external_id: string; + retry_count: number; + err_msg: string; + previous_attempt_id: string; + rule_id: string; + activity: string; + bytes: number; + md5: string; + adler32: string; + dest_url: string; + submitted_at: string; + started_at: string; + transferred_at: string; + estimated_at: string; + submitted_id: string; + estimated_stated_at: string; + estimated_transferred_at: string; + staging_started_at: string; + staging_finished_at: string; + account: string; + requested_at: string; + priority: number; + transfertool: string; + source_rse: string; + dest_rse: string; +} + +/** + * @param ts The TransferStatistics of type {@link TRucioTransferStatistics} + */ +export function convertToTransferStatistics(ts: TRucioTransferStatistics): TransferStatisticsDTO { + const dto: TransferStatisticsDTO = { + status: 'success', + account: ts.account, + source_rse: ts.source_rse, + dest_rse: ts.dest_rse, + source_rse_id: ts.source_rse_id, + dest_rse_id: ts.dest_rse_id, + state: (ts.state), + activity: ts.activity, + counter: ts.counter, + bytes: ts.bytes, + } + return dto +} + +/** + * @param t The Transfer of type {@link TRucioTransfer} + */ +export function convertToTransfer(t: TRucioTransfer): TransferDTO { + const dto: TransferDTO = { + status: 'success', + id: t.id, + request_type: (t.request_type), + scope: t.scope, + name: t.name, + did_type: (t.did_type), + state: (t.state), + account: t.account, + source_rse: t.source_rse, + dest_rse: t.dest_rse, + source_rse_id: t.source_rse_id, + dest_rse_id: t.dest_rse_id, + attributes: t.attributes, + activity: t.activity, + bytes: t.bytes, + requested_at: t.requested_at, + priority: t.priority, + transfertool: t.transfertool, + } + return dto +} \ No newline at end of file diff --git a/src/lib/infrastructure/gateway/transfer-gateway/transfer-gateway.ts b/src/lib/infrastructure/gateway/transfer-gateway/transfer-gateway.ts new file mode 100644 index 00000000..800b4561 --- /dev/null +++ b/src/lib/infrastructure/gateway/transfer-gateway/transfer-gateway.ts @@ -0,0 +1,34 @@ +import { ListTransferStatisticsDTO, ListTransfersDTO } from "@/lib/core/dto/transfer-dto"; +import TransferGatewayOutputPort from "@/lib/core/port/secondary/transfer-gateway-output-port"; +import { injectable } from "inversify"; +import ListTransferStatsEndpoint from "./endpoints/list-transfer-stats-endpoint"; +import ListTransfersEndpoint from "./endpoints/list-transfers-endpoint"; + +@injectable() +export default class TransferGateway implements TransferGatewayOutputPort { + async listTransferStatistics(rucioAuthToken: string): Promise { + const endpoint: ListTransferStatsEndpoint = new ListTransferStatsEndpoint(rucioAuthToken) + const errorDTO = await endpoint.fetch() + if (!errorDTO) { + const listTransferStatsDTO: ListTransferStatisticsDTO = { + status: 'success', + stream: endpoint + } + return listTransferStatsDTO + } + return Promise.resolve(errorDTO) + } + + async listTransfers(rucioAuthToken: string, sourceRse: string, destRse: string): Promise { + const endpoint: ListTransfersEndpoint = new ListTransfersEndpoint(rucioAuthToken, sourceRse, destRse) + const errorDTO = await endpoint.fetch() + if (!errorDTO) { + const listTransfersDTO: ListTransfersDTO = { + status: 'success', + stream: endpoint + } + return listTransfersDTO + } + return Promise.resolve(errorDTO) + } +} \ No newline at end of file diff --git a/src/lib/infrastructure/ioc/features/list-transfer-stats-feature.ts b/src/lib/infrastructure/ioc/features/list-transfer-stats-feature.ts new file mode 100644 index 00000000..631352b2 --- /dev/null +++ b/src/lib/infrastructure/ioc/features/list-transfer-stats-feature.ts @@ -0,0 +1,42 @@ +import TransferGatewayOutputPort from "@/lib/core/port/secondary/transfer-gateway-output-port"; +import ListTransferStatsUseCase from "@/lib/core/use-case/list-transfer-stats-usecase"; +import { ListTransferStatsError, ListTransferStatsRequest, ListTransferStatsResponse } from "@/lib/core/usecase-models/list-transfer-stats-usecase-models"; +import { BaseStreamableFeature, IOCSymbols } from "@/lib/sdk/ioc-helpers"; +import { Container } from "inversify"; +import ListTransferStatsController, { ListTransferStatsControllerParameters } from "../../controller/list-transfer-stats-controller"; +import { TransferStatsViewModel } from "../../data/view-model/request-stats"; +import ListTransferStatsPresenter from "../../presenter/list-transfer-stats-presenter"; +import CONTROLLERS from "../ioc-symbols-controllers"; +import GATEWAYS from "../ioc-symbols-gateway"; +import INPUT_PORT from "../ioc-symbols-input-port"; +import USECASE_FACTORY from "../ioc-symbols-usecase-factory"; + +export default class ListTransferStatsFeature extends BaseStreamableFeature< +ListTransferStatsControllerParameters, +ListTransferStatsRequest, +ListTransferStatsResponse, +ListTransferStatsError, +TransferStatsViewModel +> { + constructor(appContainer: Container) { + const transferGateway: TransferGatewayOutputPort = appContainer.get(GATEWAYS.TRANSFER) + + const symbols: IOCSymbols = { + CONTROLLER: CONTROLLERS.LIST_TRANSFER_STATS, + USECASE_FACTORY: USECASE_FACTORY.LIST_TRANSFER_STATS, + INPUT_PORT: INPUT_PORT.LIST_TRANSFER_STATS + } + const useCaseConstructorArgs = [ + transferGateway, + ] + super( + 'ListTransferStats', + ListTransferStatsController, + ListTransferStatsUseCase, + useCaseConstructorArgs, + ListTransferStatsPresenter, + false, + symbols + ) + } +} \ No newline at end of file diff --git a/src/lib/infrastructure/ioc/features/list-transfers-feature.ts b/src/lib/infrastructure/ioc/features/list-transfers-feature.ts new file mode 100644 index 00000000..4dd37053 --- /dev/null +++ b/src/lib/infrastructure/ioc/features/list-transfers-feature.ts @@ -0,0 +1,43 @@ +import TransferGatewayOutputPort from "@/lib/core/port/secondary/transfer-gateway-output-port"; +import ListTransfersUseCase from "@/lib/core/use-case/list-transfers-usecase"; +import { ListTransfersError, ListTransfersRequest, ListTransfersResponse } from "@/lib/core/usecase-models/list-transfers-usecase-models"; +import { BaseStreamableFeature, IOCSymbols } from "@/lib/sdk/ioc-helpers"; +import { Container } from "inversify"; +import ListTransfersController, { ListTransfersControllerParameters } from "../../controller/list-transfers-controller"; +import { TransferViewModel } from "../../data/view-model/request"; +import ListTransfersPresenter from "../../presenter/list-transfers-presenter"; +import CONTROLLERS from "../ioc-symbols-controllers"; +import GATEWAYS from "../ioc-symbols-gateway"; +import INPUT_PORT from "../ioc-symbols-input-port"; +import USECASE_FACTORY from "../ioc-symbols-usecase-factory"; + +export default class ListTransfersFeature extends BaseStreamableFeature< + ListTransfersControllerParameters, + ListTransfersRequest, + ListTransfersResponse, + ListTransfersError, + TransferViewModel +> { + constructor(appContainer: Container) { + const transferGateway: TransferGatewayOutputPort = appContainer.get(GATEWAYS.TRANSFER) + + const symbols: IOCSymbols = { + CONTROLLER: CONTROLLERS.LIST_TRANSFERS, + USECASE_FACTORY: USECASE_FACTORY.LIST_TRANSFERS, + INPUT_PORT: INPUT_PORT.LIST_TRANSFERS, + } + + const useCaseConstructorArgs = [ + transferGateway, + ] + super( + 'ListTransfers', + ListTransfersController, + ListTransfersUseCase, + useCaseConstructorArgs, + ListTransfersPresenter, + false, + symbols + ) + } +} \ No newline at end of file diff --git a/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts b/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts index d6bb7ac3..9c59eb77 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts @@ -21,6 +21,8 @@ const CONTROLLERS = { LIST_RSES: Symbol.for("ListRSEsController"), LIST_SUBSCRIPTIONS: Symbol.for("ListSubscriptionsController"), LIST_SUBSCRIPTION_RULE_STATES: Symbol.for("ListSubscriptionRuleStatesController"), + LIST_TRANSFER_STATS: Symbol.for("ListTransferStatsController"), + LIST_TRANSFERS: Symbol.for("ListTransfersController"), LOGIN_CONFIG: Symbol.for("LoginConfigController"), SET_X509_LOGIN_SESSION: Symbol.for("SetX509LoginSessionController"), GET_SITE_HEADER: Symbol.for("SiteHeaderController"), diff --git a/src/lib/infrastructure/ioc/ioc-symbols-gateway.ts b/src/lib/infrastructure/ioc/ioc-symbols-gateway.ts index 5b25327a..0a9c93c9 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-gateway.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-gateway.ts @@ -11,6 +11,7 @@ const GATEWAYS = { SUBSCRIPTION: Symbol.for("SubscriptionGateway"), REPLICA: Symbol.for("ReplicaGateway"), RULE: Symbol.for("RuleGateway"), + TRANSFER: Symbol.for("TransferGateway"), } export default GATEWAYS; diff --git a/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts b/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts index 31ed26c7..2b5e67af 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts @@ -19,6 +19,8 @@ const INPUT_PORT = { LIST_RSES: Symbol.for("ListRSEsInputPort"), LIST_SUBSCRIPTIONS: Symbol.for("ListSubscriptionsInputPort"), LIST_SUBSCRIPTION_RULE_STATES: Symbol.for("ListSubscriptionRuleStatesInputPort"), + LIST_TRANSFER_STATS: Symbol.for("ListTransferStatsInputPort"), + LIST_TRANSFERS: Symbol.for("ListTransfersInputPort"), LOGIN_CONFIG: Symbol.for("LoginConfigInputPort"), SET_X509_LOGIN_SESSION: Symbol.for("SetX509LoginSessionInputPort"), GET_SITE_HEADER: Symbol.for("SiteHeaderInputPort"), diff --git a/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts b/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts index ecfee632..d4123b77 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts @@ -21,6 +21,8 @@ const USECASE_FACTORY = { LIST_RSES: Symbol.for("Factory"), LIST_SUBSCRIPTIONS: Symbol.for("Factory"), LIST_SUBSCRIPTION_RULE_STATES: Symbol.for("Factory"), + LIST_TRANSFER_STATS: Symbol.for("Factory"), + LIST_TRANSFERS: Symbol.for("Factory"), LOGIN_CONFIG: Symbol.for("Factory"), SET_X509_LOGIN_SESSION: Symbol.for("Factory"), GET_SITE_HEADER: Symbol.for("Factory"), diff --git a/src/lib/infrastructure/presenter/list-transfer-stats-presenter.ts b/src/lib/infrastructure/presenter/list-transfer-stats-presenter.ts new file mode 100644 index 00000000..3fa9000e --- /dev/null +++ b/src/lib/infrastructure/presenter/list-transfer-stats-presenter.ts @@ -0,0 +1,45 @@ +import { ListTransferStatsOutputPort } from "@/lib/core/port/primary/list-transfer-stats-ports"; +import { ListTransferStatsError, ListTransferStatsResponse } from "@/lib/core/usecase-models/list-transfer-stats-usecase-models"; +import { BaseStreamingPresenter } from "@/lib/sdk/presenter"; +import { NextApiResponse } from "next"; +import { generateEmptyTransferStatsViewModel, TransferStatsViewModel } from "../data/view-model/request-stats"; + +export default class ListTransferStatsPresenter extends BaseStreamingPresenter implements ListTransferStatsOutputPort { + response: NextApiResponse + + constructor(response: NextApiResponse) { + super(response) + this.response = response + } + + streamResponseModelToViewModel(responseModel: ListTransferStatsResponse): TransferStatsViewModel { + const viewModel: TransferStatsViewModel = { + ...responseModel, + } + return viewModel + } + + streamErrorModelToViewModel(error: ListTransferStatsError): TransferStatsViewModel { + const errorViewModel: TransferStatsViewModel = generateEmptyTransferStatsViewModel(); + errorViewModel.message = `${error.name}: ${error.message}`; + return errorViewModel; + } + + /** + * Converts an error model to an error view model. + * @param errorModel The error model to convert. + * @returns The error view model that represents the error model. + */ + convertErrorModelToViewModel(errorModel: ListTransferStatsError): { status: number; viewModel: TransferStatsViewModel; } { + const viewModel: TransferStatsViewModel = generateEmptyTransferStatsViewModel(); + // gateway errors + const name = errorModel.name ? errorModel.name : ''; + const message = errorModel.message ? errorModel.message.toString() : name; + viewModel.message = message; + const errorCode = errorModel.code ? errorModel.code : 500; + return { + status: errorCode, + viewModel: viewModel + } + } +} \ No newline at end of file diff --git a/src/lib/infrastructure/presenter/list-transfers-presenter.ts b/src/lib/infrastructure/presenter/list-transfers-presenter.ts new file mode 100644 index 00000000..276dfaf0 --- /dev/null +++ b/src/lib/infrastructure/presenter/list-transfers-presenter.ts @@ -0,0 +1,41 @@ +import { ListTransfersOutputPort } from "@/lib/core/port/primary/list-transfers-ports"; +import { ListTransfersError, ListTransfersResponse } from "@/lib/core/usecase-models/list-transfers-usecase-models"; +import { BaseStreamingPresenter } from "@/lib/sdk/presenter"; +import { NextApiResponse } from "next"; +import { getEmptyTransferViewModel, TransferViewModel } from "../data/view-model/request"; + +export default class ListTransfersPresenter extends BaseStreamingPresenter implements ListTransfersOutputPort { + response: NextApiResponse + + constructor(response: NextApiResponse) { + super(response) + this.response = response + } + + streamResponseModelToViewModel(responseModel: ListTransfersResponse): TransferViewModel { + const viewModel: TransferViewModel = { + ...responseModel, + } + return viewModel + } + + streamErrorModelToViewModel(error: ListTransfersError): TransferViewModel { + const errorViewModel: TransferViewModel = getEmptyTransferViewModel(); + errorViewModel.status = 'error' + errorViewModel.message = `${error.name}: ${error.message}`; + return errorViewModel; + } + + convertErrorModelToViewModel(errorModel: ListTransfersError): { status: number; viewModel: TransferViewModel; } { + const viewModel: TransferViewModel = getEmptyTransferViewModel(); + // gateway errors + const name = errorModel.name ? errorModel.name : ''; + const message = errorModel.message ? errorModel.message.toString() : name; + viewModel.message = message; + const errorCode = errorModel.code ? errorModel.code : 500; + return { + status: errorCode, + viewModel: viewModel + } + } +} \ No newline at end of file diff --git a/src/pages/api/feature/list-transfer-stats.ts b/src/pages/api/feature/list-transfer-stats.ts new file mode 100644 index 00000000..cf5d59d1 --- /dev/null +++ b/src/pages/api/feature/list-transfer-stats.ts @@ -0,0 +1,24 @@ +import { withAuthenticatedSessionRoute } from "@/lib/infrastructure/auth/session-utils"; +import { ListTransferStatsControllerParameters } from "@/lib/infrastructure/controller/list-transfer-stats-controller"; +import appContainer from "@/lib/infrastructure/ioc/container-config"; +import CONTROLLERS from "@/lib/infrastructure/ioc/ioc-symbols-controllers"; +import { BaseController } from "@/lib/sdk/controller"; +import { NextApiRequest, NextApiResponse } from "next"; + +async function listTransferStats(req: NextApiRequest, res: NextApiResponse, rucioAuthToken: string) { + + if(req.method !== 'GET') { + res.status(405).json({ error: 'Method Not Allowed' }) + return + } + + const controllerParameters: ListTransferStatsControllerParameters = { + response: res, + rucioAuthToken: rucioAuthToken, + } + + const controller = appContainer.get>(CONTROLLERS.LIST_TRANSFER_STATS) + await controller.execute(controllerParameters) +} + +export default withAuthenticatedSessionRoute(listTransferStats) \ No newline at end of file diff --git a/src/pages/api/feature/list-transfers.ts b/src/pages/api/feature/list-transfers.ts new file mode 100644 index 00000000..9a138421 --- /dev/null +++ b/src/pages/api/feature/list-transfers.ts @@ -0,0 +1,37 @@ +import { withAuthenticatedSessionRoute } from "@/lib/infrastructure/auth/session-utils"; +import { ListTransfersControllerParameters } from "@/lib/infrastructure/controller/list-transfers-controller"; +import appContainer from "@/lib/infrastructure/ioc/container-config"; +import CONTROLLERS from "@/lib/infrastructure/ioc/ioc-symbols-controllers"; +import { BaseController } from "@/lib/sdk/controller"; +import { NextApiRequest, NextApiResponse } from "next"; + +async function listTransfers(req: NextApiRequest, res: NextApiResponse, rucioAuthToken: string) { + if (req.method !== 'GET') { + res.status(405).json({ error: 'Method Not Allowed' }) + return + } + + let { sourceRSE, destRSE } = req.query as { sourceRSE: string, destRSE: string } + + if (!sourceRSE) { + res.status(400).json({ error: 'Missing sourceRSE' }) + return + } + + if (!destRSE) { + res.status(400).json({ error: 'Missing destRSE' }) + return + } + + const controllerParameters: ListTransfersControllerParameters = { + response: res, + rucioAuthToken: rucioAuthToken, + sourceRSE: sourceRSE, + destRSE: destRSE + } + + const controller = appContainer.get>(CONTROLLERS.LIST_TRANSFERS) + await controller.execute(controllerParameters) +} + +export default withAuthenticatedSessionRoute(listTransfers) \ No newline at end of file