Files
frpmgr/ui/confpage.go
2025-06-17 17:02:11 +08:00

218 lines
5.2 KiB
Go

package ui
import (
"strings"
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
"github.com/samber/lo"
"github.com/koho/frpmgr/i18n"
"github.com/koho/frpmgr/pkg/consts"
"github.com/koho/frpmgr/services"
)
type ConfPage struct {
*walk.TabPage
// Views
confView *ConfView
detailView *DetailView
welcomeView *walk.Composite
svcCleanup func() error
}
func NewConfPage(cfgList []*Conf) *ConfPage {
v := new(ConfPage)
v.confView = NewConfView(cfgList)
v.detailView = NewDetailView()
return v
}
func (cp *ConfPage) Page() TabPage {
return TabPage{
AssignTo: &cp.TabPage,
Title: i18n.Sprintf("Configuration"),
Layout: HBox{},
DataBinder: DataBinder{
AssignTo: &confDB,
DataSource: &ConfBinder{
List: cp.confView.model.List,
SetState: cp.confView.model.SetStateByConf,
Commit: func(conf *Conf, flag runFlag) {
if conf != nil {
if err := conf.Save(); err != nil {
showError(err, cp.Form())
return
}
if flag == runFlagForceStart {
// The service of config is stopped by other code, but it should be restarted
} else if conf.State == consts.ConfigStateStarted {
// Hot-Reloading frp configuration
if flag == runFlagReload {
if err := services.ReloadService(conf.Path); err != nil {
showError(err, cp.Form())
}
return
}
// The service is running, we should stop it and restart it later
if err := cp.detailView.panelView.StopService(conf); err != nil {
showError(err, cp.Form())
return
}
} else {
// The service is stopped all the time, there's nothing to do about it
return
}
if err := cp.detailView.panelView.StartService(conf); err != nil {
showError(err, cp.Form())
return
}
}
},
},
Name: "conf",
},
Children: []Widget{
cp.confView.View(),
cp.detailView.View(),
cp.createWelcomeView(),
cp.createMultiSelectionView(),
},
}
}
func (cp *ConfPage) createWelcomeView() Composite {
return Composite{
AssignTo: &cp.welcomeView,
Visible: Bind("confView.SelectedCount == 0"),
Layout: HBox{},
Children: []Widget{
HSpacer{},
Composite{
Layout: VBox{Spacing: 20},
Children: []Widget{
VSpacer{},
PushButton{
Text: i18n.Sprintf("New Configuration"),
MinSize: Size{Width: 200},
OnClicked: cp.confView.editNew,
},
PushButton{
Text: i18n.Sprintf("Import from File"),
MinSize: Size{Width: 200},
OnClicked: cp.confView.onFileImport,
},
VSpacer{},
},
},
HSpacer{},
},
}
}
func (cp *ConfPage) createMultiSelectionView() Composite {
count := "{Count}"
text := i18n.Sprintf("Delete %s configs", count)
expr := "confView.SelectedCount"
if i := strings.Index(text, count); i >= 0 {
if left := text[:i]; left != "" {
expr = "'" + left + "' + " + expr
}
if right := text[i+len(count):]; right != "" {
expr += " + '" + right + "'"
}
}
return Composite{
Visible: Bind("confView.SelectedCount > 1"),
Layout: HBox{},
Children: []Widget{
HSpacer{},
PushButton{
Text: Bind(expr),
MinSize: Size{Width: 200},
OnClicked: cp.confView.onDelete,
},
HSpacer{},
},
}
}
func (cp *ConfPage) OnCreate() {
// Create all child views
cp.confView.OnCreate()
cp.detailView.OnCreate()
// Select the first config
if cp.confView.model.RowCount() > 0 {
cp.confView.listView.SetCurrentIndex(0)
}
cp.confView.model.RowEdited().Attach(func(i int) {
cp.detailView.panelView.Invalidate(false)
})
cp.addVisibleChangedListener()
cleanup, err := services.WatchConfigServices(func() []string {
return lo.Map(getConfList(), func(item *Conf, index int) string {
return item.Path
})
}, func(path string, state consts.ConfigState) {
cp.Synchronize(func() {
if cp.confView.model.SetStateByPath(path, state) {
if conf := getCurrentConf(); conf != nil && conf.Path == path {
cp.detailView.panelView.setState(state)
if !cp.Visible() {
return
}
if state == consts.ConfigStateStarted {
cp.detailView.proxyView.startTracker(true)
} else {
if cp.detailView.proxyView.stopTracker() {
cp.detailView.proxyView.resetProxyState(-1)
}
}
}
}
})
})
if err != nil {
showError(err, cp.Form())
return
}
cp.svcCleanup = cleanup
}
func (cp *ConfPage) addVisibleChangedListener() {
var oldState consts.ConfigState
cp.VisibleChanged().Attach(func() {
if cp.Visible() {
defer func() {
oldState = consts.ConfigStateUnknown
}()
if conf := getCurrentConf(); conf != nil {
if conf.State == consts.ConfigStateStarted {
cp.detailView.proxyView.startTracker(true)
} else if oldState == consts.ConfigStateStarted {
cp.detailView.proxyView.resetProxyState(-1)
}
}
} else {
cp.detailView.proxyView.stopTracker()
if conf := getCurrentConf(); conf != nil {
oldState = conf.State
}
}
})
}
func (cp *ConfPage) Close() error {
if cp.svcCleanup != nil {
return cp.svcCleanup()
}
cp.detailView.proxyView.stopTracker()
return nil
}
func warnConfigRemoved(owner walk.Form, name string) {
showWarningMessage(owner, i18n.Sprintf("Config already removed"), i18n.Sprintf("The config \"%s\" already removed.", name))
}