edc_web_components/pages/
show_transfer_process.rs1use 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 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 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 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}