拆分服务器地址配置为独立字段,支持CDN优选IP故障切换,新增重置数据库按钮

- model: ServerURL → Protocol/Host/ServerIPs/Port/Path 五个字段
- db: 自动迁移旧 server_url 列到新表结构
- ui: 配置窗口改为横向分组布局,保存失败时不再关闭窗口
- transport: 支持 TLS SNI + Host 头覆盖以连接 CDN 边缘节点
- session: CDN IP 列表连接失败自动切换到下一个
- 新增重置数据库按钮,一键清空所有配置
This commit is contained in:
2026-07-06 20:58:11 +08:00
parent 32471e25b0
commit 96278fdf37
12 changed files with 469 additions and 72 deletions
+55
View File
@@ -1,6 +1,7 @@
package ui
import (
"os"
"sync"
"lmvpn/internal/config"
@@ -181,6 +182,60 @@ func (a *App) onDeleteProfile() {
}, a.window).Show()
}
// onResetDB deletes the SQLite database file after confirmation,
// then re-creates it. All profiles, credentials, and logs are lost.
func (a *App) onResetDB() {
dialog.NewCustomConfirm(i18n.T("DlgResetDBTitle"),
i18n.T("BtnDelete"), i18n.T("BtnCancel"),
widget.NewLabel(i18n.T("DlgResetDBMsg")),
func(ok bool) {
if !ok {
return
}
// Disconnect if connected.
a.mu.Lock()
client := a.ipcClient
a.mu.Unlock()
if client != nil {
_ = ipc.SendStop(client)
}
// Clear keychain entries for all profiles.
for _, p := range a.profiles {
_ = a.kc.DeleteAll(p.Name)
}
// Close and delete database.
if a.db != nil {
a.db.Close()
}
if err := os.Remove(paths.DBPath()); err != nil {
showError(i18n.T("DlgError"), err.Error(), a.window)
return
}
// Re-open (auto-creates new database).
store, err := db.Open()
if err != nil {
showError(i18n.T("DlgError"), err.Error(), a.window)
return
}
a.db = store
// Reset UI state.
a.currentProfile = nil
a.loadProfiles()
a.stateLabel.SetText(i18n.T("StateDisconnected"))
a.ipLabel.SetText(i18n.T("IpNone"))
a.uptimeLabel.SetText(i18n.T("UptimeNone"))
a.rxLabel.SetText(i18n.T("RxZero"))
a.txLabel.SetText(i18n.T("TxZero"))
a.connectBtn.Enable()
a.disconnectBtn.Disable()
}, a.window).Show()
}
// changeLanguage switches the active language, persists the choice to
// the config file, and rebuilds the UI so the new strings take effect
// immediately.