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