edc_web_components/pages/
show_transfer_process.rs1use crate::components::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 Process" }</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 Id">
219 { transfer_process.contract_id() }
220 </DescriptionGroup>
221 <DescriptionGroup term="Correlation Transfer Process ID">
222 { transfer_process.correlation_id() }
223 </DescriptionGroup>
224 <DescriptionGroup term="Asset ID">{ transfer_process.asset_id() }</DescriptionGroup>
225 <DescriptionGroup term="Transfer Type">
226 { transfer_process.transfer_type() }
227 </DescriptionGroup>
228 </DescriptionList>
229 </StackItem>
230 <StackItem>
231 <TransferProcessStatus
232 transfer_process_id={props.transfer_process_id.clone()}
233 {on_started}
234 {on_finalized}
235 />
236 </StackItem>
237 <StackItem>{ actions }</StackItem>
238 <StackItem />
239 </Stack>
240 ))
241 } else {
242 Ok(html!(
243 format!(
244 "Transfer Process with id {} not found.",
245 props.transfer_process_id
246 )
247 ))
248 }
249}
250
251fn save_byte_array(name: &str, mime_type: &str, data: &[u8]) -> Result<(), JsValue> {
252 use web_sys::{Blob, Url, js_sys::Uint8Array};
253
254 let props = BlobPropertyBag::new();
256 props.set_type(mime_type);
257
258 let blob = Blob::new_with_u8_array_sequence_and_options(
259 &JsValue::from(vec![Uint8Array::new_from_slice(data)]),
260 &props,
261 )?;
262
263 let document = web_sys::window()
265 .and_then(|window| window.document())
266 .ok_or(JsValue::null())?;
267
268 let link = document.create_element("a")?;
269
270 let url = Url::create_object_url_with_blob(&blob)?;
272 link.set_attribute("href", &url)?;
273 link.set_attribute("download", name)?;
274
275 link.dyn_into::<HtmlAnchorElement>()?.click();
276 Url::revoke_object_url(&url)?;
277
278 Ok(())
279}