feat(settings): add automatic core startup preference
- add a default-enabled “Start Core on Launch” setting - persist the preference in the GUI configuration - automatically start the CPA core when the app launches - keep the preference independent from manual core start and stop actions - add multilingual labels and migration tests
This commit is contained in:
1 parent
2888042645
commit
30fb184384
11 files changed
+81
-4
No files matched your search
@@ -178,6 +178,7 @@ pub(crate) fn software_settings(
|
||||
Ok(SoftwareSettings {
|
||||
close_behavior: config.close_behavior,
|
||||
autostart_enabled: app_autostart_enabled(app)?,
|
||||
start_core_on_launch: config.start_core_on_launch,
|
||||
silent_start_enabled: config.silent_start,
|
||||
})
|
||||
}
|
||||
@@ -206,13 +207,16 @@ pub(crate) fn save_software_settings(
|
||||
}
|
||||
|
||||
let config = if previous_config.close_behavior == settings.close_behavior
|
||||
&& previous_config.start_core_on_launch == settings.start_core_on_launch
|
||||
&& previous_config.silent_start == settings.silent_start_enabled
|
||||
{
|
||||
previous_config
|
||||
} else {
|
||||
match gui_config_state
|
||||
.set_software_preferences(settings.close_behavior, settings.silent_start_enabled)
|
||||
{
|
||||
match gui_config_state.set_software_preferences(
|
||||
settings.close_behavior,
|
||||
settings.start_core_on_launch,
|
||||
settings.silent_start_enabled,
|
||||
) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
let rollback_error = autostart_changed
|
||||
|
||||
@@ -847,6 +847,9 @@ pub(crate) fn load_or_create_gui_config() -> Result<GuiConfigFile, String> {
|
||||
if presence.close_behavior.is_none() {
|
||||
changed = true;
|
||||
}
|
||||
if presence.start_core_on_launch.is_none() {
|
||||
changed = true;
|
||||
}
|
||||
if presence.silent_start.is_none() {
|
||||
changed = true;
|
||||
}
|
||||
@@ -1178,6 +1181,7 @@ pub(crate) fn write_gui_config_to_path(
|
||||
("allow-lan", value(config.allow_lan)),
|
||||
("host", value(config.host.as_str())),
|
||||
("run-on-startup", value(config.run_on_startup)),
|
||||
("start-core-on-launch", value(config.start_core_on_launch)),
|
||||
("silent-start", value(config.silent_start)),
|
||||
("close-behavior", value(config.close_behavior.as_str())),
|
||||
("auth-dir", value(config.auth_dir.as_str())),
|
||||
|
||||
@@ -1820,6 +1820,10 @@ pub(crate) fn should_start_hidden(config: &GuiConfigFile) -> bool {
|
||||
config.silent_start && cfg!(any(target_os = "windows", target_os = "macos"))
|
||||
}
|
||||
|
||||
pub(crate) fn should_start_core_on_launch(config: &GuiConfigFile) -> bool {
|
||||
config.start_core_on_launch
|
||||
}
|
||||
|
||||
pub(crate) fn configure_initial_main_window(
|
||||
app_handle: &tauri::AppHandle,
|
||||
start_hidden: bool,
|
||||
|
||||
@@ -552,6 +552,7 @@ struct GuiConfigFile {
|
||||
allow_lan: bool,
|
||||
host: String,
|
||||
run_on_startup: bool,
|
||||
start_core_on_launch: bool,
|
||||
silent_start: bool,
|
||||
close_behavior: WindowsCloseBehavior,
|
||||
window_width: Option<u32>,
|
||||
@@ -662,6 +663,7 @@ impl Default for GuiConfigFile {
|
||||
allow_lan: false,
|
||||
host: "127.0.0.1".to_string(),
|
||||
run_on_startup: false,
|
||||
start_core_on_launch: true,
|
||||
silent_start: false,
|
||||
close_behavior: WindowsCloseBehavior::Ask,
|
||||
window_width: Some(DEFAULT_MAIN_WINDOW_WIDTH),
|
||||
@@ -698,6 +700,7 @@ struct GuiConfigPresence {
|
||||
api_access_remarks: Option<Vec<GuiApiAccessRemark>>,
|
||||
management_secret_key: Option<String>,
|
||||
close_behavior: Option<WindowsCloseBehavior>,
|
||||
start_core_on_launch: Option<bool>,
|
||||
silent_start: Option<bool>,
|
||||
usage_statistics_enabled: Option<bool>,
|
||||
plugins_enabled: Option<bool>,
|
||||
@@ -725,6 +728,7 @@ struct GuiSettings {
|
||||
struct SoftwareSettings {
|
||||
close_behavior: WindowsCloseBehavior,
|
||||
autostart_enabled: bool,
|
||||
start_core_on_launch: bool,
|
||||
silent_start_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -733,6 +737,7 @@ struct SoftwareSettings {
|
||||
struct SoftwareSettingsInput {
|
||||
close_behavior: WindowsCloseBehavior,
|
||||
autostart_enabled: bool,
|
||||
start_core_on_launch: bool,
|
||||
silent_start_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -1505,10 +1510,12 @@ impl GuiConfigState {
|
||||
fn set_software_preferences(
|
||||
&self,
|
||||
close_behavior: WindowsCloseBehavior,
|
||||
start_core_on_launch: bool,
|
||||
silent_start: bool,
|
||||
) -> Result<GuiConfigFile, String> {
|
||||
self.update(|config| {
|
||||
config.close_behavior = close_behavior;
|
||||
config.start_core_on_launch = start_core_on_launch;
|
||||
config.silent_start = silent_start;
|
||||
Ok(())
|
||||
})
|
||||
@@ -1847,7 +1854,7 @@ fn main() {
|
||||
Err(error) => eprintln!("自动安装 CPA 离线内核失败: {error}"),
|
||||
}
|
||||
|
||||
if config.run_on_startup {
|
||||
if should_start_core_on_launch(&config) {
|
||||
if let Err(error) = start_core_process_inner(process_state.inner(), &config) {
|
||||
eprintln!("自动启动 CPA 内核失败: {error}");
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ fn gui_config_defaults_are_stable() {
|
||||
assert!(content.contains("port = 8317"));
|
||||
assert!(content.contains("allow-lan = false"));
|
||||
assert!(content.contains("run-on-startup = false"));
|
||||
assert!(content.contains("start-core-on-launch = true"));
|
||||
assert!(content.contains("silent-start = false"));
|
||||
assert!(content.contains("close-behavior = \"ask\""));
|
||||
assert!(content.contains("window-width = 1531"));
|
||||
@@ -119,6 +120,7 @@ fn gui_window_size_round_trips_through_portable_config() {
|
||||
#[test]
|
||||
fn silent_start_defaults_off_and_requires_tray_support() {
|
||||
let legacy = toml::from_str::<GuiConfigFile>("port = 8317\n").unwrap();
|
||||
assert!(legacy.start_core_on_launch);
|
||||
assert!(!legacy.silent_start);
|
||||
assert!(!should_start_hidden(&legacy));
|
||||
|
||||
@@ -132,6 +134,18 @@ fn silent_start_defaults_off_and_requires_tray_support() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_start_on_launch_defaults_on_and_can_be_disabled() {
|
||||
let legacy = toml::from_str::<GuiConfigFile>("port = 8317\n").unwrap();
|
||||
assert!(should_start_core_on_launch(&legacy));
|
||||
|
||||
let disabled = GuiConfigFile {
|
||||
start_core_on_launch: false,
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
assert!(!should_start_core_on_launch(&disabled));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gui_window_size_is_clamped_and_requires_both_dimensions() {
|
||||
let mut config = GuiConfigFile {
|
||||
|
||||
@@ -767,6 +767,7 @@ fn startup_preserves_all_user_owned_yaml_and_only_applies_gui_managed_values() {
|
||||
allow_lan: true,
|
||||
host: "0.0.0.0".to_string(),
|
||||
run_on_startup: false,
|
||||
start_core_on_launch: true,
|
||||
silent_start: false,
|
||||
close_behavior: WindowsCloseBehavior::Ask,
|
||||
window_width: None,
|
||||
|
||||
@@ -3024,6 +3024,7 @@ mod tests {
|
||||
allow_lan: false,
|
||||
host: "127.0.0.1".to_string(),
|
||||
run_on_startup: false,
|
||||
start_core_on_launch: true,
|
||||
silent_start: false,
|
||||
close_behavior: crate::WindowsCloseBehavior::Ask,
|
||||
window_width: None,
|
||||
|
||||
@@ -365,6 +365,8 @@ export const jaOverrides = {
|
||||
'config.software.title': 'ソフトウェア設定',
|
||||
'config.software.autostart': 'システム起動時に開始',
|
||||
'config.software.autostartDescription': 'サインイン後に EasyCLIProxyAPI を自動的に起動します',
|
||||
'config.software.startCoreOnLaunch': '起動時にコアを開始',
|
||||
'config.software.startCoreOnLaunchDescription': 'EasyCLIProxyAPI の起動時に CPA コアを自動的に開始します',
|
||||
'config.software.silentStart': 'サイレント起動',
|
||||
'config.software.silentStartDescription': 'メインウィンドウを表示せず、システムトレイで直接起動します',
|
||||
'config.software.closeBehavior': '終了動作',
|
||||
|
||||
@@ -300,6 +300,8 @@ export const en: Record<MessageKey, string> = {
|
||||
'config.software.title': 'Software Settings',
|
||||
'config.software.autostart': 'Launch at startup',
|
||||
'config.software.autostartDescription': 'Automatically launch EasyCLIProxyAPI when you sign in',
|
||||
'config.software.startCoreOnLaunch': 'Start Core on Launch',
|
||||
'config.software.startCoreOnLaunchDescription': 'Automatically start the CPA core whenever EasyCLIProxyAPI launches',
|
||||
'config.software.silentStart': 'Silent Launch',
|
||||
'config.software.silentStartDescription': 'Start without showing the main window and run directly in the system tray',
|
||||
'config.software.closeBehavior': 'Close behavior',
|
||||
|
||||
@@ -298,6 +298,8 @@ export const zhCN = {
|
||||
'config.software.title': '软件设置',
|
||||
'config.software.autostart': '开机自启',
|
||||
'config.software.autostartDescription': '登录系统后自动启动 EasyCLIProxyAPI',
|
||||
'config.software.startCoreOnLaunch': '启动时启动内核',
|
||||
'config.software.startCoreOnLaunchDescription': '每次启动 EasyCLIProxyAPI 时自动启动 CPA 内核',
|
||||
'config.software.silentStart': '静默启动',
|
||||
'config.software.silentStartDescription': '启动应用时不显示主窗口,直接在系统托盘运行',
|
||||
'config.software.closeBehavior': '关闭行为',
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
Settings2,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Power,
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
@@ -75,6 +76,7 @@ type NetworkDraftDirty = Record<NetworkDraftField, boolean>;
|
||||
type SoftwareSettings = {
|
||||
closeBehavior: CloseBehavior;
|
||||
autostartEnabled: boolean;
|
||||
startCoreOnLaunch: boolean;
|
||||
silentStartEnabled: boolean;
|
||||
};
|
||||
|
||||
@@ -103,6 +105,7 @@ export function ConfigPanelPage() {
|
||||
const [softwareSettingsLoading, setSoftwareSettingsLoading] = useState(true);
|
||||
const [softwareCloseBehaviorDraft, setSoftwareCloseBehaviorDraft] = useState<CloseBehavior>('ask');
|
||||
const [softwareAutostartDraft, setSoftwareAutostartDraft] = useState(false);
|
||||
const [softwareStartCoreDraft, setSoftwareStartCoreDraft] = useState(true);
|
||||
const [softwareSilentStartDraft, setSoftwareSilentStartDraft] = useState(false);
|
||||
const [softwareSavedStatusVisible, setSoftwareSavedStatusVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -237,6 +240,7 @@ export function ConfigPanelPage() {
|
||||
setSoftwareSettings(result);
|
||||
setSoftwareCloseBehaviorDraft(result.closeBehavior);
|
||||
setSoftwareAutostartDraft(result.autostartEnabled);
|
||||
setSoftwareStartCoreDraft(result.startCoreOnLaunch);
|
||||
setSoftwareSilentStartDraft(result.silentStartEnabled);
|
||||
} catch (error) {
|
||||
setSoftwareSettings(null);
|
||||
@@ -526,6 +530,7 @@ export function ConfigPanelPage() {
|
||||
if (
|
||||
softwareCloseBehaviorDraft === softwareSettings.closeBehavior
|
||||
&& softwareAutostartDraft === softwareSettings.autostartEnabled
|
||||
&& softwareStartCoreDraft === softwareSettings.startCoreOnLaunch
|
||||
&& softwareSilentStartDraft === softwareSettings.silentStartEnabled
|
||||
) return;
|
||||
|
||||
@@ -535,18 +540,21 @@ export function ConfigPanelPage() {
|
||||
settings: {
|
||||
closeBehavior: softwareCloseBehaviorDraft,
|
||||
autostartEnabled: softwareAutostartDraft,
|
||||
startCoreOnLaunch: softwareStartCoreDraft,
|
||||
silentStartEnabled: softwareSilentStartDraft,
|
||||
},
|
||||
});
|
||||
setSoftwareSettings(result);
|
||||
setSoftwareCloseBehaviorDraft(result.closeBehavior);
|
||||
setSoftwareAutostartDraft(result.autostartEnabled);
|
||||
setSoftwareStartCoreDraft(result.startCoreOnLaunch);
|
||||
setSoftwareSilentStartDraft(result.silentStartEnabled);
|
||||
setSoftwareSavedStatusVisible(true);
|
||||
showNotice(t('config.notice.softwareUpdated'), 'success');
|
||||
} catch (error) {
|
||||
setSoftwareCloseBehaviorDraft(softwareSettings.closeBehavior);
|
||||
setSoftwareAutostartDraft(softwareSettings.autostartEnabled);
|
||||
setSoftwareStartCoreDraft(softwareSettings.startCoreOnLaunch);
|
||||
setSoftwareSilentStartDraft(softwareSettings.silentStartEnabled);
|
||||
setSoftwareSavedStatusVisible(false);
|
||||
showNotice(t('config.error.saveFailed', { error: String(error) }), 'error');
|
||||
@@ -585,8 +593,11 @@ export function ConfigPanelPage() {
|
||||
&& softwareAutostartDraft !== softwareSettings.autostartEnabled;
|
||||
const softwareSilentStartDirty = softwareSettings !== null
|
||||
&& softwareSilentStartDraft !== softwareSettings.silentStartEnabled;
|
||||
const softwareStartCoreDirty = softwareSettings !== null
|
||||
&& softwareStartCoreDraft !== softwareSettings.startCoreOnLaunch;
|
||||
const softwareSettingsDirty = softwareCloseBehaviorDirty
|
||||
|| softwareAutostartDirty
|
||||
|| softwareStartCoreDirty
|
||||
|| softwareSilentStartDirty;
|
||||
const softwareStatusLabel = softwareSettingsLoading
|
||||
? t('common.loading')
|
||||
@@ -1272,6 +1283,31 @@ export function ConfigPanelPage() {
|
||||
<span className="switch-track" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="config-software-setting-row">
|
||||
<div className="config-software-setting-copy">
|
||||
<span className="config-software-setting-icon" aria-hidden="true">
|
||||
<Power size={18} />
|
||||
</span>
|
||||
<div>
|
||||
<strong>{t('config.software.startCoreOnLaunch')}</strong>
|
||||
<small>{t('config.software.startCoreOnLaunchDescription')}</small>
|
||||
</div>
|
||||
</div>
|
||||
<label className="switch-control" title={t('config.software.startCoreOnLaunch')}>
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-label={t('config.software.startCoreOnLaunch')}
|
||||
checked={softwareStartCoreDraft}
|
||||
disabled={softwareSettingsLoading || softwareSettings === null || busyAction !== null}
|
||||
onChange={(event) => {
|
||||
setSoftwareSavedStatusVisible(false);
|
||||
setSoftwareStartCoreDraft(event.currentTarget.checked);
|
||||
}}
|
||||
/>
|
||||
<span className="switch-track" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="config-software-setting-row">
|
||||
<div className="config-software-setting-copy">
|
||||
<span className="config-software-setting-icon" aria-hidden="true">
|
||||
|
||||
Reference in new issue
Block a user