Skip to main content

yggdrasil/pages/admin/system/backup/
import.rs

1use super::task::BackupState;
2use crate::components::ui::{button_class, ButtonSize, ButtonVariant};
3use leptos::prelude::*;
4
5/// 客户端预检的导入体积上限(服务端仍兜底)。
6#[cfg(target_arch = "wasm32")]
7const IMPORT_MAX_BYTES_CLIENT: u64 = 512 * 1024 * 1024;
8
9#[component]
10pub(super) fn ImportBackup(state: BackupState) -> impl IntoView {
11    let import_busy = state.importing;
12    let is_busy = move || state.busy();
13    let on_import = move |evt| {
14        #[cfg(target_arch = "wasm32")]
15        {
16            if state.busy_untracked() {
17                return;
18            }
19            let input = event_target::<web_sys::HtmlInputElement>(&evt);
20            let Some(web_file) = input.files().and_then(|files| files.get(0)) else {
21                return;
22            };
23            // 重置 input value,允许重选同一文件再次触发 change。
24            input.set_value("");
25            // 客户端预检(服务端仍兜底):.sql 后缀 + 体积上限。
26            if !web_file.name().ends_with(".sql") {
27                state.error.set(Some("仅接受 .sql 备份文件".to_string()));
28                return;
29            }
30            if web_file.size() as u64 > IMPORT_MAX_BYTES_CLIENT {
31                state.error.set(Some(format!(
32                    "文件超过导入上限({}MB)",
33                    IMPORT_MAX_BYTES_CLIENT / 1024 / 1024
34                )));
35                return;
36            }
37            import_busy.set(true);
38            state.error.set(None);
39            leptos::task::spawn_local(async move {
40                match crate::utils::web_upload::post_multipart_file(
41                    "/api/database/backups/import",
42                    "file",
43                    &web_file,
44                )
45                .await
46                {
47                    Ok(_) => state.refresh(),
48                    Err(msg) => {
49                        let _ = state.error.try_set(Some(msg));
50                    }
51                }
52                let _ = import_busy.try_set(false);
53            });
54        }
55        #[cfg(not(target_arch = "wasm32"))]
56        let _ = evt;
57    };
58
59    view! {
60        <label
61            class=format!("{} sys-file-button", button_class(ButtonVariant::Secondary, ButtonSize::Sm))
62            aria-disabled=move || (import_busy.get() || is_busy()).then_some("true")
63        >
64            {move || if import_busy.get() { "导入中…" } else { "导入备份" }}
65            <input
66                type="file"
67                accept=".sql"
68                class="sr-only"
69                disabled=move || import_busy.get() || is_busy()
70                on:change=on_import
71            />
72        </label>
73    }
74}