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