mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 21:14:21 +08:00
修复安卓端PostgreSQL连接长时间转圈无响应
This commit is contained in:
@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{AppHandle, Emitter, Manager, State};
|
use tauri::{AppHandle, Emitter, Manager, State};
|
||||||
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
use crate::db::connection::{create_postgres_connection, create_sqlite_connection};
|
use crate::db::connection::{create_postgres_connection, create_sqlite_connection};
|
||||||
use crate::db::connection::DatabaseType;
|
use crate::db::connection::DatabaseType;
|
||||||
@@ -15,6 +16,9 @@ use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
|||||||
|
|
||||||
use super::response::IpcResponse;
|
use super::response::IpcResponse;
|
||||||
|
|
||||||
|
const DB_CONNECT_TIMEOUT_SECS: u64 = 15;
|
||||||
|
const DB_MIGRATION_TIMEOUT_SECS: u64 = 20;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TestConnectionResult {
|
pub struct TestConnectionResult {
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
@@ -775,18 +779,28 @@ pub async fn db_test_connection(
|
|||||||
} else if connection_string.starts_with("postgres://")
|
} else if connection_string.starts_with("postgres://")
|
||||||
|| connection_string.starts_with("postgresql://")
|
|| connection_string.starts_with("postgresql://")
|
||||||
{
|
{
|
||||||
match create_postgres_connection(&connection_string).await {
|
let connection_result = timeout(
|
||||||
Ok(conn) => {
|
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
|
||||||
|
create_postgres_connection(&connection_string),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match connection_result {
|
||||||
|
Err(_) => TestConnectionResult {
|
||||||
|
success: false,
|
||||||
|
error: Some("PostgreSQL 连接超时,请检查网络或连接字符串".to_string()),
|
||||||
|
},
|
||||||
|
Ok(Err(e)) => TestConnectionResult {
|
||||||
|
success: false,
|
||||||
|
error: Some(e.to_string()),
|
||||||
|
},
|
||||||
|
Ok(Ok(conn)) => {
|
||||||
let _ = conn.close().await;
|
let _ = conn.close().await;
|
||||||
TestConnectionResult {
|
TestConnectionResult {
|
||||||
success: true,
|
success: true,
|
||||||
error: None,
|
error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => TestConnectionResult {
|
|
||||||
success: false,
|
|
||||||
error: Some(e.to_string()),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let path = connection_string.as_str();
|
let path = connection_string.as_str();
|
||||||
@@ -819,12 +833,20 @@ pub async fn db_switch_connection(
|
|||||||
let (db_type, saved_connection_string, saved_status, conn) = if connection_string.starts_with("postgres://")
|
let (db_type, saved_connection_string, saved_status, conn) = if connection_string.starts_with("postgres://")
|
||||||
|| connection_string.starts_with("postgresql://")
|
|| connection_string.starts_with("postgresql://")
|
||||||
{
|
{
|
||||||
let conn = create_postgres_connection(&connection_string)
|
let conn = timeout(
|
||||||
.await
|
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
|
||||||
.map_err(|e| e.to_string())?;
|
create_postgres_connection(&connection_string),
|
||||||
run_migration(&conn, DatabaseType::PostgreSQL)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|_| "PostgreSQL 连接超时,请检查网络或连接字符串".to_string())?
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
timeout(
|
||||||
|
Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS),
|
||||||
|
run_migration(&conn, DatabaseType::PostgreSQL),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| "PostgreSQL 初始化超时,请稍后重试".to_string())?
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
(
|
(
|
||||||
"postgresql".to_string(),
|
"postgresql".to_string(),
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection, DbErr};
|
use sea_orm::{ConnectOptions, Database, DatabaseConnection, DbErr};
|
||||||
|
use std::time::Duration;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
const DB_MAX_CONNECTIONS: u32 = 20;
|
||||||
|
const DB_MIN_CONNECTIONS: u32 = 1;
|
||||||
|
const DB_CONNECT_TIMEOUT_SECS: u64 = 12;
|
||||||
|
const DB_ACQUIRE_TIMEOUT_SECS: u64 = 12;
|
||||||
|
const DB_IDLE_TIMEOUT_SECS: u64 = 30;
|
||||||
|
|
||||||
|
fn apply_default_connect_options(opt: &mut ConnectOptions) {
|
||||||
|
opt.max_connections(DB_MAX_CONNECTIONS)
|
||||||
|
.min_connections(DB_MIN_CONNECTIONS)
|
||||||
|
.connect_timeout(Duration::from_secs(DB_CONNECT_TIMEOUT_SECS))
|
||||||
|
.acquire_timeout(Duration::from_secs(DB_ACQUIRE_TIMEOUT_SECS))
|
||||||
|
.idle_timeout(Duration::from_secs(DB_IDLE_TIMEOUT_SECS))
|
||||||
|
.sqlx_logging(true)
|
||||||
|
.sqlx_logging_level(tracing::log::LevelFilter::Info);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum DatabaseType {
|
pub enum DatabaseType {
|
||||||
SQLite,
|
SQLite,
|
||||||
@@ -106,10 +123,7 @@ impl ConnectionManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut opt = ConnectOptions::new(&url);
|
let mut opt = ConnectOptions::new(&url);
|
||||||
opt.max_connections(100)
|
apply_default_connect_options(&mut opt);
|
||||||
.min_connections(5)
|
|
||||||
.sqlx_logging(true)
|
|
||||||
.sqlx_logging_level(tracing::log::LevelFilter::Info);
|
|
||||||
|
|
||||||
let conn = Database::connect(opt).await?;
|
let conn = Database::connect(opt).await?;
|
||||||
|
|
||||||
@@ -195,20 +209,14 @@ impl ConnectionManager {
|
|||||||
pub async fn create_sqlite_connection(path: &str) -> Result<DatabaseConnection, DbErr> {
|
pub async fn create_sqlite_connection(path: &str) -> Result<DatabaseConnection, DbErr> {
|
||||||
let url = format!("sqlite://{}?mode=rwc", path);
|
let url = format!("sqlite://{}?mode=rwc", path);
|
||||||
let mut opt = ConnectOptions::new(&url);
|
let mut opt = ConnectOptions::new(&url);
|
||||||
opt.max_connections(100)
|
apply_default_connect_options(&mut opt);
|
||||||
.min_connections(5)
|
|
||||||
.sqlx_logging(true)
|
|
||||||
.sqlx_logging_level(tracing::log::LevelFilter::Info);
|
|
||||||
|
|
||||||
Database::connect(opt).await
|
Database::connect(opt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_postgres_connection(url: &str) -> Result<DatabaseConnection, DbErr> {
|
pub async fn create_postgres_connection(url: &str) -> Result<DatabaseConnection, DbErr> {
|
||||||
let mut opt = ConnectOptions::new(url);
|
let mut opt = ConnectOptions::new(url);
|
||||||
opt.max_connections(100)
|
apply_default_connect_options(&mut opt);
|
||||||
.min_connections(5)
|
|
||||||
.sqlx_logging(true)
|
|
||||||
.sqlx_logging_level(tracing::log::LevelFilter::Info);
|
|
||||||
|
|
||||||
Database::connect(opt).await
|
Database::connect(opt).await
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-17
@@ -13,6 +13,7 @@ use crate::db::migration::run_migration;
|
|||||||
use crate::services::settings::{SettingsKey, SettingsValue};
|
use crate::services::settings::{SettingsKey, SettingsValue};
|
||||||
use crate::{commands::*, state::AppState};
|
use crate::{commands::*, state::AppState};
|
||||||
use tauri::{App, Manager};
|
use tauri::{App, Manager};
|
||||||
|
use tokio::time::{timeout, Duration};
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
use tauri::{
|
use tauri::{
|
||||||
image::Image,
|
image::Image,
|
||||||
@@ -125,6 +126,9 @@ pub fn setup_app(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
const DB_CONNECT_TIMEOUT_SECS: u64 = 15;
|
||||||
|
const DB_MIGRATION_TIMEOUT_SECS: u64 = 20;
|
||||||
|
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
let db_path = if cfg!(all(debug_assertions, desktop)) {
|
let db_path = if cfg!(all(debug_assertions, desktop)) {
|
||||||
std::path::PathBuf::from("data.sql")
|
std::path::PathBuf::from("data.sql")
|
||||||
@@ -166,9 +170,67 @@ fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !pg_connection_string.trim().is_empty() {
|
if !pg_connection_string.trim().is_empty() {
|
||||||
match create_postgres_connection(&pg_connection_string).await {
|
match timeout(
|
||||||
Ok(pg_conn) => {
|
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
|
||||||
if let Err(e) = run_migration(&pg_conn, DatabaseType::PostgreSQL).await {
|
create_postgres_connection(&pg_connection_string),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(_) => {
|
||||||
|
let timeout_message = "PostgreSQL auto-connect timeout".to_string();
|
||||||
|
eprintln!(
|
||||||
|
"PostgreSQL auto-connect timed out on startup, fallback to sqlite"
|
||||||
|
);
|
||||||
|
settings
|
||||||
|
.set_value(
|
||||||
|
SettingsKey::PgConnectionStatus,
|
||||||
|
SettingsValue::Json(serde_json::json!({
|
||||||
|
"connected": false,
|
||||||
|
"type": "sqlite",
|
||||||
|
"error": timeout_message
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
eprintln!("PostgreSQL auto-connect failed, fallback to sqlite: {}", e);
|
||||||
|
settings
|
||||||
|
.set_value(
|
||||||
|
SettingsKey::PgConnectionStatus,
|
||||||
|
SettingsValue::Json(serde_json::json!({
|
||||||
|
"connected": false,
|
||||||
|
"type": "sqlite",
|
||||||
|
"error": e.to_string()
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||||
|
}
|
||||||
|
Ok(Ok(pg_conn)) => {
|
||||||
|
let migration_result = timeout(
|
||||||
|
Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS),
|
||||||
|
run_migration(&pg_conn, DatabaseType::PostgreSQL),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(_) = migration_result {
|
||||||
|
let timeout_message = "PostgreSQL migration timeout".to_string();
|
||||||
|
eprintln!(
|
||||||
|
"PostgreSQL migration timed out on startup, fallback to sqlite"
|
||||||
|
);
|
||||||
|
settings
|
||||||
|
.set_value(
|
||||||
|
SettingsKey::PgConnectionStatus,
|
||||||
|
SettingsValue::Json(serde_json::json!({
|
||||||
|
"connected": false,
|
||||||
|
"type": "sqlite",
|
||||||
|
"error": timeout_message
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||||
|
} else if let Ok(Err(e)) = migration_result {
|
||||||
eprintln!("PostgreSQL migration failed on startup, fallback to sqlite: {}", e);
|
eprintln!("PostgreSQL migration failed on startup, fallback to sqlite: {}", e);
|
||||||
settings
|
settings
|
||||||
.set_value(
|
.set_value(
|
||||||
@@ -196,20 +258,6 @@ fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
eprintln!("Auto connected to PostgreSQL from saved connection string");
|
eprintln!("Auto connected to PostgreSQL from saved connection string");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
eprintln!("PostgreSQL auto-connect failed, fallback to sqlite: {}", e);
|
|
||||||
settings
|
|
||||||
.set_value(
|
|
||||||
SettingsKey::PgConnectionStatus,
|
|
||||||
SettingsValue::Json(serde_json::json!({
|
|
||||||
"connected": false,
|
|
||||||
"type": "sqlite",
|
|
||||||
"error": e.to_string()
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
settings
|
settings
|
||||||
|
|||||||
@@ -34,6 +34,20 @@ interface reasonItem {
|
|||||||
|
|
||||||
const deepClone = <T,>(v: T): T => JSON.parse(JSON.stringify(v)) as T
|
const deepClone = <T,>(v: T): T => JSON.parse(JSON.stringify(v)) as T
|
||||||
|
|
||||||
|
const withTimeout = async (promise: Promise<any>, ms: number, timeoutMessage: string): Promise<any> => {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
try {
|
||||||
|
return await Promise.race<any>([
|
||||||
|
promise,
|
||||||
|
new Promise<any>((_, reject) => {
|
||||||
|
timer = setTimeout(() => reject(new Error(timeoutMessage)), ms)
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
} finally {
|
||||||
|
if (timer) clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const presetPrimaryColors = [
|
const presetPrimaryColors = [
|
||||||
"#1677FF",
|
"#1677FF",
|
||||||
"#2F54EB",
|
"#2F54EB",
|
||||||
@@ -363,18 +377,30 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
setPgAutoLoading(true)
|
setPgAutoLoading(true)
|
||||||
try {
|
try {
|
||||||
if (!(window as any).api) throw new Error("api not ready")
|
if (!(window as any).api) throw new Error("api not ready")
|
||||||
const switchRes = await (window as any).api.dbSwitchConnection(connectionString)
|
const switchRes = await withTimeout(
|
||||||
|
(window as any).api.dbSwitchConnection(connectionString),
|
||||||
|
25_000,
|
||||||
|
"连接 PostgreSQL 超时,请检查网络或连接字符串"
|
||||||
|
)
|
||||||
if (!switchRes?.success || switchRes?.data?.type !== "postgresql") {
|
if (!switchRes?.success || switchRes?.data?.type !== "postgresql") {
|
||||||
throw new Error(switchRes?.message || t("settings.database.switchFailed"))
|
throw new Error(switchRes?.message || t("settings.database.switchFailed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewRes = await (window as any).api.dbSyncPreview()
|
const previewRes = await withTimeout(
|
||||||
|
(window as any).api.dbSyncPreview(),
|
||||||
|
25_000,
|
||||||
|
"同步预检查超时,请稍后重试"
|
||||||
|
)
|
||||||
if (!previewRes?.success || !previewRes?.data?.can_sync) {
|
if (!previewRes?.success || !previewRes?.data?.can_sync) {
|
||||||
throw new Error(previewRes?.message || previewRes?.data?.message || t("settings.database.uploadFailed"))
|
throw new Error(previewRes?.message || previewRes?.data?.message || t("settings.database.uploadFailed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previewRes?.data?.need_sync) {
|
if (previewRes?.data?.need_sync) {
|
||||||
const syncApplyRes = await (window as any).api.dbSyncApply("keep_local")
|
const syncApplyRes = await withTimeout(
|
||||||
|
(window as any).api.dbSyncApply("keep_local"),
|
||||||
|
45_000,
|
||||||
|
"执行同步超时,请稍后重试"
|
||||||
|
)
|
||||||
if (!syncApplyRes?.success || !syncApplyRes?.data?.success) {
|
if (!syncApplyRes?.success || !syncApplyRes?.data?.success) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
syncApplyRes?.data?.message || syncApplyRes?.message || t("settings.database.uploadFailed")
|
syncApplyRes?.data?.message || syncApplyRes?.message || t("settings.database.uploadFailed")
|
||||||
|
|||||||
@@ -27,6 +27,20 @@ type appSettings = {
|
|||||||
auto_score_enabled?: boolean
|
auto_score_enabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const withTimeout = async (promise: Promise<any>, ms: number, timeoutMessage: string): Promise<any> => {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
try {
|
||||||
|
return await Promise.race<any>([
|
||||||
|
promise,
|
||||||
|
new Promise<any>((_, reject) => {
|
||||||
|
timer = setTimeout(() => reject(new Error(timeoutMessage)), ms)
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
} finally {
|
||||||
|
if (timer) clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission }) => {
|
export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [activeTab, setActiveTab] = useState("appearance")
|
const [activeTab, setActiveTab] = useState("appearance")
|
||||||
@@ -307,7 +321,11 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
}
|
}
|
||||||
setPgTestLoading(true)
|
setPgTestLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await (window as any).api.dbTestConnection(pgConnectionString)
|
const res = await withTimeout(
|
||||||
|
(window as any).api.dbTestConnection(pgConnectionString),
|
||||||
|
20_000,
|
||||||
|
"测试连接超时,请检查网络或连接字符串"
|
||||||
|
)
|
||||||
if (res.success && res.data?.success) {
|
if (res.success && res.data?.success) {
|
||||||
messageApi.success(t("settings.database.connectionTestSuccess"))
|
messageApi.success(t("settings.database.connectionTestSuccess"))
|
||||||
} else {
|
} else {
|
||||||
@@ -324,9 +342,17 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
|
|
||||||
const switchToPg = async () => {
|
const switchToPg = async () => {
|
||||||
if (!(window as any).api) return
|
if (!(window as any).api) return
|
||||||
|
if (!pgConnectionString.trim()) {
|
||||||
|
messageApi.warning(t("settings.database.enterConnectionString"))
|
||||||
|
return
|
||||||
|
}
|
||||||
setPgSwitchLoading(true)
|
setPgSwitchLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await (window as any).api.dbSwitchConnection(pgConnectionString)
|
const res = await withTimeout(
|
||||||
|
(window as any).api.dbSwitchConnection(pgConnectionString.trim()),
|
||||||
|
25_000,
|
||||||
|
"切换 PostgreSQL 超时,请检查网络或连接字符串"
|
||||||
|
)
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
messageApi.success(
|
messageApi.success(
|
||||||
t("settings.database.switchedTo", {
|
t("settings.database.switchedTo", {
|
||||||
@@ -348,7 +374,11 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
if (!(window as any).api) return
|
if (!(window as any).api) return
|
||||||
setPgSwitchLoading(true)
|
setPgSwitchLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await (window as any).api.dbSwitchConnection("")
|
const res = await withTimeout(
|
||||||
|
(window as any).api.dbSwitchConnection(""),
|
||||||
|
15_000,
|
||||||
|
"切换 SQLite 超时,请稍后重试"
|
||||||
|
)
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
messageApi.success(t("settings.database.switchedToSQLite"))
|
messageApi.success(t("settings.database.switchedToSQLite"))
|
||||||
setPgConnectionString("")
|
setPgConnectionString("")
|
||||||
@@ -372,7 +402,11 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewRes = await (window as any).api.dbSyncPreview()
|
const previewRes = await withTimeout(
|
||||||
|
(window as any).api.dbSyncPreview(),
|
||||||
|
25_000,
|
||||||
|
"同步预检查超时,请稍后重试"
|
||||||
|
)
|
||||||
if (!previewRes.success || !previewRes.data?.can_sync) {
|
if (!previewRes.success || !previewRes.data?.can_sync) {
|
||||||
messageApi.error(previewRes.message || previewRes.data?.message || t("settings.database.uploadFailed"))
|
messageApi.error(previewRes.message || previewRes.data?.message || t("settings.database.uploadFailed"))
|
||||||
return
|
return
|
||||||
@@ -394,7 +428,11 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
setPgUploadLoading(true)
|
setPgUploadLoading(true)
|
||||||
try {
|
try {
|
||||||
const applyRes = await (window as any).api.dbSyncApply("keep_local")
|
const applyRes = await withTimeout(
|
||||||
|
(window as any).api.dbSyncApply("keep_local"),
|
||||||
|
45_000,
|
||||||
|
"执行同步超时,请稍后重试"
|
||||||
|
)
|
||||||
if (applyRes.success && applyRes.data?.success) {
|
if (applyRes.success && applyRes.data?.success) {
|
||||||
messageApi.success(
|
messageApi.success(
|
||||||
applyRes.data?.message || t("settings.database.uploadSuccess")
|
applyRes.data?.message || t("settings.database.uploadSuccess")
|
||||||
|
|||||||
Reference in New Issue
Block a user