Skip to main content

edc_web_components/pages/
show_transfer_process.rs

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