Skip to main content

edc_web_components/pages/
show_transfer_process.rs

1use crate::components::{AssetReference, ContractAgreementReference, TransferProcessStatus};
2use crate::contexts::use_edc_connector_context;
3use edc_connector_client::types::transfer_process::TransferProcessState;
4use patternfly_yew::prelude::*;
5use web_sys::wasm_bindgen::{JsCast, JsValue};
6use web_sys::{BlobPropertyBag, HtmlAnchorElement};
7use yew::platform::spawn_local;
8use yew::prelude::*;
9use yew::suspense::use_future_with;
10
11#[derive(Clone, Debug, PartialEq, Properties)]
12pub struct ShowTransferProcessPageProps {
13  pub transfer_process_id: String,
14}
15
16#[component]
17pub fn ShowTransferProcessPage(props: &ShowTransferProcessPageProps) -> Html {
18  html!(
19    <>
20      <Title level={Level::H2} size={Size::XXXLarge}>{ "Transfer" }</Title>
21      <Suspense fallback="Loading ...">
22        <ShowTransferProcessPageInner transfer_process_id={props.transfer_process_id.clone()} />
23      </Suspense>
24    </>
25  )
26}
27
28#[component]
29pub fn ShowTransferProcessPageInner(props: &ShowTransferProcessPageProps) -> HtmlResult {
30  let edc_connector_client = use_edc_connector_context();
31
32  let refresh = use_state(|| 0usize);
33
34  let transfer_process = use_future_with(
35    (
36      props.transfer_process_id.clone(),
37      edc_connector_client.clone(),
38      refresh.clone(),
39    ),
40    |properties| async move {
41      let (transfer_process_id, edc_connector_client, _) = (*properties).clone();
42
43      if let Some(client) = edc_connector_client.get_client() {
44        client
45          .transfer_processes(edc_connector_client::EdcConnectorApiVersion::V4)
46          .get(&transfer_process_id)
47          .await
48          .ok()
49      } else {
50        None
51      }
52    },
53  )?;
54
55  let on_started = use_callback(refresh.setter(), |_, refresh_setter| {
56    refresh_setter.set(1);
57  });
58
59  let on_finalized = use_callback(refresh.setter(), |_, refresh_setter| {
60    refresh_setter.set(1);
61  });
62
63  let transfer_process = (*transfer_process).clone();
64
65  let do_transfer = use_callback(
66    edc_connector_client.clone(),
67    |transfer_process_id: String, edc_connector_client| {
68      let edc_connector_client = edc_connector_client.clone();
69      let transfer_process_id = transfer_process_id.clone();
70
71      spawn_local(async move {
72        if let Some(client) = edc_connector_client.get_client()
73          && let Ok(data_address) = client
74            .edrs(edc_connector_client::EdcConnectorApiVersion::V3)
75            .get_data_address(&transfer_process_id)
76            .await
77          && let Ok(Some(endpoint)) = data_address.property::<String>("endpoint")
78          && let Ok(Some(authorization)) = data_address.property::<String>("authorization")
79        {
80          let client = reqwest::Client::new();
81
82          if let Ok(response) = client
83            .get(endpoint)
84            .header("Authorization", authorization)
85            .send()
86            .await
87          {
88            let content_type = response
89              .headers()
90              .get("Content-Type")
91              .map(|header_value| {
92                header_value
93                  .to_str()
94                  .unwrap_or("application/octet-stream")
95                  .to_string()
96              })
97              .unwrap_or("application/octet-stream".to_string());
98
99            let extension = mime2ext::mime2ext(&content_type).unwrap_or("bin");
100
101            log::info!("Response: {:?}", content_type);
102            log::info!("Response: {:?}", response.status());
103
104            if let Ok(data) = response.bytes().await
105              && let Err(error) =
106                save_byte_array(&format!("data.{extension}"), &content_type, &data)
107            {
108              log::error!("Error saving byte array: {:?}", error);
109            }
110          }
111        }
112      });
113    },
114  );
115
116  let backdropper = use_backdrop();
117  let show_enpoint_information = use_callback(
118    (edc_connector_client.clone(), backdropper),
119    |transfer_process_id: String, (edc_connector_client, backdropper)| {
120      let edc_connector_client = edc_connector_client.clone();
121      let backdropper = backdropper.clone();
122      let transfer_process_id = transfer_process_id.clone();
123
124      spawn_local(async move {
125        if let Some(client) = edc_connector_client.get_client()
126          && let Ok(data_address) = client
127            .edrs(edc_connector_client::EdcConnectorApiVersion::V3)
128            .get_data_address(&transfer_process_id)
129            .await
130          && let Ok(Some(endpoint)) = data_address.property::<String>("endpoint")
131          && let Ok(Some(authorization)) = data_address.property::<String>("authorization")
132          && let Some(backdropper) = backdropper
133        {
134          backdropper.open(Backdrop::new(html!(
135            <Bullseye>
136              <Modal title="Transfer Endpoint Information" variant={ModalVariant::Medium}>
137                <DescriptionList>
138                  <DescriptionGroup term="Endpoint URL">
139                    <Clipboard readonly=true value={endpoint} />
140                  </DescriptionGroup>
141                  <DescriptionGroup term="Authorization Header">
142                    <Clipboard readonly=true value={authorization} />
143                  </DescriptionGroup>
144                </DescriptionList>
145              </Modal>
146            </Bullseye>
147          )));
148        }
149      });
150    },
151  );
152
153  let suspend_transfer = use_callback(
154    edc_connector_client.clone(),
155    |transfer_process_id: String, edc_connector_client| {
156      let edc_connector_client = edc_connector_client.clone();
157      let transfer_process_id = transfer_process_id.clone();
158
159      spawn_local(async move {
160        if let Some(client) = edc_connector_client.get_client()
161          && let Err(error) = client
162            .transfer_processes(edc_connector_client::EdcConnectorApiVersion::V3)
163            .suspend(&transfer_process_id, "completed")
164            .await
165        {
166          log::error!("Error getting data address {error}");
167        }
168      });
169    },
170  );
171
172  if let Some(transfer_process) = transfer_process {
173    let transfer_process_id = transfer_process.id().to_string();
174    let endpoint_transfer_process_id = transfer_process.id().to_string();
175    let complete_transfer_process_id = transfer_process.id().to_string();
176
177    let actions = if transfer_process.state() == &TransferProcessState::Started {
178      html!(
179        <Flex>
180          <FlexItem>
181            <Button
182              variant={ButtonVariant::Primary}
183              onclick={do_transfer.reform(move |_| transfer_process_id.clone())}
184              icon={Icon::Download}
185            >
186              { "Retrieve Dataset" }
187            </Button>
188          </FlexItem>
189          <FlexItem>
190            <Button
191              variant={ButtonVariant::Primary}
192              onclick={show_enpoint_information.reform(move |_| endpoint_transfer_process_id.clone())}
193              icon={Icon::Code}
194            >
195              { "Endpoint Information" }
196            </Button>
197          </FlexItem>
198          <FlexItem>
199            <Button
200              variant={ButtonVariant::Warning}
201              onclick={suspend_transfer.reform(move |_| complete_transfer_process_id.clone())}
202              icon={Icon::Pause}
203            >
204              { "Suspend Transfer" }
205            </Button>
206          </FlexItem>
207        </Flex>
208      )
209    } else {
210      html!()
211    };
212
213    Ok(html!(
214      <Stack gutter=true>
215        <StackItem>
216          <DescriptionList mode={[DescriptionListMode::Horizontal]}>
217            <DescriptionGroup term="Id">{ transfer_process.id() }</DescriptionGroup>
218            <DescriptionGroup term="Contract Agreement">
219              <ContractAgreementReference
220                contract_agreement_id={transfer_process.contract_id().to_string()}
221              />
222            </DescriptionGroup>
223            <DescriptionGroup term="Correlation Transfer ID">
224              { transfer_process.correlation_id() }
225            </DescriptionGroup>
226            <DescriptionGroup term="Asset">
227              <AssetReference asset_id={transfer_process.asset_id().to_string()} />
228            </DescriptionGroup>
229            <DescriptionGroup term="Transfer Type">
230              { transfer_process.transfer_type() }
231            </DescriptionGroup>
232          </DescriptionList>
233        </StackItem>
234        <StackItem>
235          <TransferProcessStatus
236            transfer_process_id={props.transfer_process_id.clone()}
237            {on_started}
238            {on_finalized}
239          />
240        </StackItem>
241        <StackItem>{ actions }</StackItem>
242        <StackItem />
243      </Stack>
244    ))
245  } else {
246    Ok(html!(
247      format!(
248      "Transfer Process with id {} not found.",
249      props.transfer_process_id
250    )
251    ))
252  }
253}
254
255fn save_byte_array(name: &str, mime_type: &str, data: &[u8]) -> Result<(), JsValue> {
256  use web_sys::{Blob, Url, js_sys::Uint8Array};
257
258  // Build file data & metadata
259  let props = BlobPropertyBag::new();
260  props.set_type(mime_type);
261
262  let blob = Blob::new_with_u8_array_sequence_and_options(
263    &JsValue::from(vec![Uint8Array::new_from_slice(data)]),
264    &props,
265  )?;
266
267  // Add the link element
268  let document = web_sys::window()
269    .and_then(|window| window.document())
270    .ok_or(JsValue::null())?;
271
272  let link = document.create_element("a")?;
273
274  // Set link attributes
275  let url = Url::create_object_url_with_blob(&blob)?;
276  link.set_attribute("href", &url)?;
277  link.set_attribute("download", name)?;
278
279  link.dyn_into::<HtmlAnchorElement>()?.click();
280  Url::revoke_object_url(&url)?;
281
282  Ok(())
283}