mirror of
https://github.com/koho/frpmgr.git
synced 2025-10-20 16:03:47 +08:00
Allow setting default configuration format (#127)
* Allow setting default configuration format * Fix spaces
This commit is contained in:
@ -1 +0,0 @@
|
||||
[defaults]
|
@ -1,19 +0,0 @@
|
||||
# Language Setting File
|
||||
#
|
||||
# Specify your preferred Language ID on this setting file.
|
||||
# The text messages will be displayed in the specified language.
|
||||
#
|
||||
# Please note that you must restart program to apply the modification of
|
||||
# this setting file.
|
||||
#
|
||||
# Only one line is acceptable. Any other lines are ignored.
|
||||
#
|
||||
# Available Language IDs are:
|
||||
# zh-CN: Simplified Chinese (简体中文)
|
||||
# zh-TW: Traditional Chinese (繁體中文)
|
||||
# en-US: English (English)
|
||||
# ja-JP: Japanese (日本語)
|
||||
# ko-KR: Korean (한국어)
|
||||
# es-ES: Spanish (Español)
|
||||
|
||||
# Specify a Language ID here.
|
23
i18n/text.go
23
i18n/text.go
@ -3,16 +3,15 @@ package i18n
|
||||
//go:generate go run golang.org/x/text/cmd/gotext -srclang=en-US update -out=catalog.go -lang=en-US,zh-CN,zh-TW,ja-JP,ko-KR,es-ES ../cmd/frpmgr
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
)
|
||||
|
||||
const LangFile = "lang.config"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
)
|
||||
|
||||
var (
|
||||
printer *message.Printer
|
||||
@ -43,19 +42,17 @@ func GetLanguage() string {
|
||||
|
||||
// langInConfig returns the UI language code in config file
|
||||
func langInConfig() string {
|
||||
langFile, err := os.Open(LangFile)
|
||||
b, err := os.ReadFile(config.DefaultAppFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer langFile.Close()
|
||||
|
||||
scanner := bufio.NewScanner(langFile)
|
||||
for scanner.Scan() {
|
||||
if text := strings.TrimSpace(scanner.Text()); text != "" && !strings.HasPrefix(text, "#") {
|
||||
return text
|
||||
}
|
||||
var s struct {
|
||||
Lang string `json:"lang"`
|
||||
}
|
||||
return ""
|
||||
if err = json.Unmarshal(b, &s); err != nil {
|
||||
return ""
|
||||
}
|
||||
return s.Lang
|
||||
}
|
||||
|
||||
// lang returns the user preferred UI language.
|
||||
|
@ -166,6 +166,14 @@ namespace actions
|
||||
{
|
||||
ForceDeleteDirectory(Path.Combine(installPath, "profiles"));
|
||||
ForceDeleteDirectory(Path.Combine(installPath, "logs"));
|
||||
try
|
||||
{
|
||||
File.Delete(Path.Combine(installPath, "app.json"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
session.Log(e.Message);
|
||||
}
|
||||
}
|
||||
return ActionResult.Success;
|
||||
}
|
||||
@ -210,8 +218,8 @@ namespace actions
|
||||
public static ActionResult SetLangConfig(Session session)
|
||||
{
|
||||
session.Log("Set language config");
|
||||
string langPath = session["CustomActionData"];
|
||||
if (string.IsNullOrEmpty(langPath))
|
||||
string installPath = session["CustomActionData"];
|
||||
if (string.IsNullOrEmpty(installPath))
|
||||
{
|
||||
return ActionResult.Failure;
|
||||
}
|
||||
@ -220,7 +228,18 @@ namespace actions
|
||||
{
|
||||
return ActionResult.Failure;
|
||||
}
|
||||
File.AppendAllText(langPath, name.ToString() + Environment.NewLine, Encoding.UTF8);
|
||||
string cfgPath = Path.Combine(installPath, "app.json");
|
||||
if (!File.Exists(cfgPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(cfgPath, "{\n \"lang\": \"" + name.ToString() + "\"\n}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
session.Log(e.Message);
|
||||
}
|
||||
}
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
|
@ -82,12 +82,6 @@
|
||||
<!-- A dummy to make WiX create ServiceControl table for us. -->
|
||||
<ServiceControl Id="DummyService.E3F2D6BE_38C7_4654_9C1B_C667A1F9040A" Name="DummyService.E3F2D6BE_38C7_4654_9C1B_C667A1F9040A" />
|
||||
</Component>
|
||||
<Component Guid="{4D11B79E-74E2-4F35-81EA-8B5CC7225A78}" Id="LangConfig" Win64="$(var.Win64)">
|
||||
<File Id="LangConfig" Source="..\cmd\frpmgr\lang.config" KeyPath="yes"/>
|
||||
</Component>
|
||||
<Component Guid="{B73203B2-5C06-4C45-BC6C-1C98CE11A292}" Id="AppConfig" Win64="$(var.Win64)">
|
||||
<File Id="AppConfig" Source="..\cmd\frpmgr\app.config" KeyPath="yes"/>
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
@ -99,7 +93,7 @@
|
||||
<CustomAction Id="KillProcesses.SetProperty" Return="check" Property="KillProcesses" Value="[#MainApplication]" />
|
||||
<CustomAction Id="EvaluateFrpServices.SetProperty" Return="check" Property="EvaluateFrpServices" Value="[#MainApplication]" />
|
||||
<CustomAction Id="RemoveFrpFiles.SetProperty" Return="check" Property="RemoveFrpFiles" Value="[INSTALLFOLDER]" />
|
||||
<CustomAction Id="SetLangConfig.SetProperty" Return="check" Property="SetLangConfig" Value="[#LangConfig]" />
|
||||
<CustomAction Id="SetLangConfig.SetProperty" Return="check" Property="SetLangConfig" Value="[INSTALLFOLDER]" />
|
||||
<CustomAction Id="MoveFrpProfiles.SetProperty" Return="check" Property="MoveFrpProfiles" Value="[INSTALLFOLDER]" />
|
||||
<CustomAction Id="RemoveOldFrpServices.SetProperty" Return="check" Property="RemoveOldFrpServices" Value="[#MainApplication]" />
|
||||
|
||||
|
@ -48,23 +48,40 @@ static int Cleanup(void) {
|
||||
|
||||
static Language *GetPreferredLang(TCHAR *folder) {
|
||||
TCHAR langPath[MAX_PATH];
|
||||
if (PathCombine(langPath, folder, L"lang.config") == NULL) {
|
||||
if (PathCombine(langPath, folder, L"app.json") == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
FILE *file;
|
||||
if (_wfopen_s(&file, langPath, L"r") != 0) {
|
||||
if (_wfopen_s(&file, langPath, L"rb") != 0) {
|
||||
return NULL;
|
||||
}
|
||||
char lang[sizeof(languages[0].code)];
|
||||
while (fgets(lang, sizeof(lang), file) != NULL) {
|
||||
for (int i = 0; i < sizeof(languages) / sizeof(languages[0]); i++) {
|
||||
if (strncmp(lang, languages[i].code, strlen(languages[i].code)) == 0) {
|
||||
fclose(file);
|
||||
return &languages[i];
|
||||
}
|
||||
fseek(file, 0L, SEEK_END);
|
||||
long fileSize = ftell(file);
|
||||
fseek(file, 0L, SEEK_SET);
|
||||
char *buf = malloc(fileSize + 1);
|
||||
size_t size = fread(buf, 1, fileSize, file);
|
||||
buf[size] = 0;
|
||||
fclose(file);
|
||||
const char *p1 = strstr(buf, "\"lang\"");
|
||||
if (p1 == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
const char *p2 = strstr(p1, ":");
|
||||
if (p2 == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
const char *p3 = strstr(p2, "\"");
|
||||
if (p3 == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
for (int i = 0; i < sizeof(languages) / sizeof(languages[0]); i++) {
|
||||
if (strncmp(p3 + 1, languages[i].code, strlen(languages[i].code)) == 0) {
|
||||
free(buf);
|
||||
return &languages[i];
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
cleanup:
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1,34 +1,72 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"gopkg.in/ini.v1"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
)
|
||||
|
||||
const DefaultAppFile = "app.config"
|
||||
const DefaultAppFile = "app.json"
|
||||
|
||||
type App struct {
|
||||
Password string `ini:"password,omitempty"`
|
||||
Defaults ClientCommon `ini:"defaults"`
|
||||
Lang string `json:"lang,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Defaults DefaultValue `json:"defaults"`
|
||||
}
|
||||
|
||||
func UnmarshalAppConfFromIni(source any, dst *App) error {
|
||||
cfg, err := ini.LoadSources(ini.LoadOptions{
|
||||
IgnoreInlineComment: true,
|
||||
AllowBooleanKeys: true,
|
||||
}, source)
|
||||
type DefaultValue struct {
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
LogMaxDays int64 `json:"logMaxDays"`
|
||||
DeleteAfterDays int64 `json:"deleteAfterDays,omitempty"`
|
||||
DNSServer string `json:"dnsServer,omitempty"`
|
||||
NatHoleSTUNServer string `json:"natHoleStunServer,omitempty"`
|
||||
ConnectServerLocalIP string `json:"connectServerLocalIP,omitempty"`
|
||||
TCPMux bool `json:"tcpMux"`
|
||||
TLSEnable bool `json:"tls"`
|
||||
ManualStart bool `json:"manualStart,omitempty"`
|
||||
LegacyFormat bool `json:"legacyFormat,omitempty"`
|
||||
}
|
||||
|
||||
func (dv *DefaultValue) AsClientConfig() ClientCommon {
|
||||
conf := ClientCommon{
|
||||
ServerPort: consts.DefaultServerPort,
|
||||
Protocol: dv.Protocol,
|
||||
User: dv.User,
|
||||
LogLevel: dv.LogLevel,
|
||||
LogMaxDays: dv.LogMaxDays,
|
||||
DNSServer: dv.DNSServer,
|
||||
NatHoleSTUNServer: dv.NatHoleSTUNServer,
|
||||
ConnectServerLocalIP: dv.ConnectServerLocalIP,
|
||||
TCPMux: dv.TCPMux,
|
||||
TLSEnable: dv.TLSEnable,
|
||||
ManualStart: dv.ManualStart,
|
||||
LegacyFormat: dv.LegacyFormat,
|
||||
DisableCustomTLSFirstByte: true,
|
||||
}
|
||||
if dv.DeleteAfterDays > 0 {
|
||||
conf.AutoDelete = AutoDelete{
|
||||
DeleteMethod: consts.DeleteRelative,
|
||||
DeleteAfterDays: dv.DeleteAfterDays,
|
||||
}
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func UnmarshalAppConf(path string, dst *App) error {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = cfg.MapTo(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return json.Unmarshal(b, dst)
|
||||
}
|
||||
|
||||
func (conf *App) Save(path string) error {
|
||||
cfg := ini.Empty()
|
||||
if err := cfg.ReflectFrom(conf); err != nil {
|
||||
b, err := json.MarshalIndent(conf, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cfg.SaveTo(path)
|
||||
return os.WriteFile(path, b, 0666)
|
||||
}
|
||||
|
@ -1,43 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalAppConfFromIni(t *testing.T) {
|
||||
input := `
|
||||
password = abcde
|
||||
|
||||
[defaults]
|
||||
server_port = 7000
|
||||
log_level = info
|
||||
log_max_days = 5
|
||||
protocol = kcp
|
||||
login_fail_exit = false
|
||||
user = user
|
||||
tcp_mux = true
|
||||
frpmgr_manual_start = true
|
||||
frpmgr_delete_after_days = 1
|
||||
input := `{
|
||||
"password": "abcde",
|
||||
"defaults": {
|
||||
"logLevel": "info",
|
||||
"logMaxDays": 5,
|
||||
"protocol": "kcp",
|
||||
"user": "user",
|
||||
"tcpMux": true,
|
||||
"manualStart": true,
|
||||
"deleteAfterDays": 1,
|
||||
"legacyFormat": true
|
||||
}
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(DefaultAppFile, []byte(input), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := App{
|
||||
Password: "abcde",
|
||||
Defaults: ClientCommon{
|
||||
ServerPort: 7000,
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 5,
|
||||
Protocol: "kcp",
|
||||
LoginFailExit: false,
|
||||
User: "user",
|
||||
TCPMux: true,
|
||||
ManualStart: true,
|
||||
AutoDelete: AutoDelete{
|
||||
DeleteAfterDays: 1,
|
||||
},
|
||||
Defaults: DefaultValue{
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 5,
|
||||
Protocol: "kcp",
|
||||
User: "user",
|
||||
TCPMux: true,
|
||||
ManualStart: true,
|
||||
DeleteAfterDays: 1,
|
||||
LegacyFormat: true,
|
||||
},
|
||||
}
|
||||
var actual App
|
||||
if err := UnmarshalAppConfFromIni([]byte(input), &actual); err != nil {
|
||||
if err := UnmarshalAppConf(DefaultAppFile, &actual); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
|
@ -428,7 +428,19 @@ func (conf *ClientConfig) saveTOML(path string) error {
|
||||
|
||||
func (conf *ClientConfig) Complete(read bool) {
|
||||
// Common config
|
||||
if conf.AuthMethod == "" {
|
||||
conf.AuthMethod = consts.AuthToken
|
||||
}
|
||||
conf.ClientAuth = conf.ClientAuth.Complete()
|
||||
if conf.ServerPort == 0 {
|
||||
conf.ServerPort = consts.DefaultServerPort
|
||||
}
|
||||
if conf.LogLevel == "" {
|
||||
conf.LogLevel = consts.LogLevelInfo
|
||||
}
|
||||
if conf.LogMaxDays == 0 {
|
||||
conf.LogMaxDays = consts.DefaultLogMaxDays
|
||||
}
|
||||
if conf.AdminPort == 0 {
|
||||
conf.AdminUser = ""
|
||||
conf.AdminPwd = ""
|
||||
@ -436,6 +448,9 @@ func (conf *ClientConfig) Complete(read bool) {
|
||||
conf.AdminTLS = v1.TLSConfig{}
|
||||
conf.PprofEnable = false
|
||||
}
|
||||
if conf.DeleteMethod == "" {
|
||||
conf.DeleteMethod = consts.DeleteRelative
|
||||
}
|
||||
conf.AutoDelete = conf.AutoDelete.Complete()
|
||||
if !conf.TCPMux {
|
||||
conf.TCPMuxKeepaliveInterval = 0
|
||||
@ -603,11 +618,11 @@ func UnmarshalClientConf(source interface{}) (*ClientConfig, error) {
|
||||
if err = config.LoadConfigure(b, &cfg, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var r ClientConfig
|
||||
r.ClientCommon = ClientCommonFromV1(&cfg.ClientCommonConfig)
|
||||
r.ManualStart = cfg.Mgr.ManualStart
|
||||
r.SVCBEnable = cfg.Mgr.SVCBEnable
|
||||
r.AutoDelete = cfg.Mgr.AutoDelete
|
||||
var conf ClientConfig
|
||||
conf.ClientCommon = ClientCommonFromV1(&cfg.ClientCommonConfig)
|
||||
conf.ManualStart = cfg.Mgr.ManualStart
|
||||
conf.SVCBEnable = cfg.Mgr.SVCBEnable
|
||||
conf.AutoDelete = cfg.Mgr.AutoDelete
|
||||
// Proxies
|
||||
ignore := make(map[string]struct{})
|
||||
proxies := make([]*Proxy, len(cfg.Proxies))
|
||||
@ -622,26 +637,29 @@ func UnmarshalClientConf(source interface{}) (*ClientConfig, error) {
|
||||
}
|
||||
proxies[i] = p
|
||||
}
|
||||
r.Proxies = lo.Filter(proxies, func(item *Proxy, index int) bool {
|
||||
conf.Proxies = lo.Filter(proxies, func(item *Proxy, index int) bool {
|
||||
_, ok := ignore[item.Name]
|
||||
return !ok
|
||||
})
|
||||
// Visitors
|
||||
for _, v := range cfg.Visitors {
|
||||
r.Proxies = append(r.Proxies, ClientVisitorFromV1(v))
|
||||
conf.Proxies = append(conf.Proxies, ClientVisitorFromV1(v))
|
||||
}
|
||||
return &r, nil
|
||||
conf.Complete(true)
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
func NewDefaultClientConfig() *ClientConfig {
|
||||
return &ClientConfig{
|
||||
ClientCommon: ClientCommon{
|
||||
ClientAuth: ClientAuth{AuthMethod: consts.AuthToken},
|
||||
ServerPort: 7000,
|
||||
LogLevel: "info",
|
||||
ServerPort: consts.DefaultServerPort,
|
||||
LogLevel: consts.LogLevelInfo,
|
||||
LogMaxDays: consts.DefaultLogMaxDays,
|
||||
TCPMux: true,
|
||||
TLSEnable: true,
|
||||
DisableCustomTLSFirstByte: true,
|
||||
LoginFailExit: true,
|
||||
AutoDelete: AutoDelete{DeleteMethod: consts.DeleteRelative},
|
||||
},
|
||||
Proxies: make([]*Proxy, 0),
|
||||
|
@ -6,12 +6,19 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExpiry(t *testing.T) {
|
||||
func init() {
|
||||
if err := os.MkdirAll("testdata", 0750); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.Chdir("testdata"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpiry(t *testing.T) {
|
||||
if err := os.WriteFile("example.ini", []byte("test"), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Chdir("testdata")
|
||||
os.WriteFile("example.ini", []byte("test"), 0666)
|
||||
tests := []struct {
|
||||
input AutoDelete
|
||||
expected time.Duration
|
||||
|
@ -3,6 +3,7 @@ package consts
|
||||
const (
|
||||
RangePrefix = "range:"
|
||||
DefaultSTUNServer = "stun.easyvoip.com:3478"
|
||||
DefaultServerPort = 7000
|
||||
)
|
||||
|
||||
// Protocols
|
||||
@ -83,3 +84,5 @@ const (
|
||||
)
|
||||
|
||||
var LogLevels = []string{LogLevelTrace, LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError}
|
||||
|
||||
const DefaultLogMaxDays = 3
|
||||
|
@ -1,4 +1,4 @@
|
||||
package consts
|
||||
package res
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -10,7 +10,7 @@ import (
|
||||
. "github.com/lxn/walk/declarative"
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/version"
|
||||
)
|
||||
|
||||
@ -37,7 +37,7 @@ type aboutViewModel struct {
|
||||
func NewAboutPage() *AboutPage {
|
||||
ap := new(AboutPage)
|
||||
ap.viewModel.TabIcon = loadShieldIcon(16)
|
||||
ap.viewModel.UpdateIcon = loadIcon(consts.IconUpdate, 32)
|
||||
ap.viewModel.UpdateIcon = loadIcon(res.IconUpdate, 32)
|
||||
return ap
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ func (ap *AboutPage) Page() TabPage {
|
||||
Composite{
|
||||
Layout: VBox{Margins: Margins{Left: 12}},
|
||||
Children: []Widget{
|
||||
Label{Text: AppName, Font: consts.TextLarge, TextColor: consts.ColorDarkBlue},
|
||||
Label{Text: AppName, Font: res.TextLarge, TextColor: res.ColorDarkBlue},
|
||||
Label{Text: i18n.Sprintf("Version: %s", version.Number)},
|
||||
Label{Text: i18n.Sprintf("FRP version: %s", version.FRPVersion)},
|
||||
Label{Text: i18n.Sprintf("Built on: %s", version.BuildDate)},
|
||||
@ -67,7 +67,7 @@ func (ap *AboutPage) Page() TabPage {
|
||||
i18n.Sprintf("Download updates"), i18n.Sprintf("Checking for updates"),
|
||||
i18n.Sprintf("Check for updates"),
|
||||
)),
|
||||
Font: consts.TextMedium,
|
||||
Font: res.TextMedium,
|
||||
OnClicked: func() {
|
||||
if ap.viewModel.NewVersion {
|
||||
openPath(ap.viewModel.HtmlUrl)
|
||||
@ -90,7 +90,7 @@ func (ap *AboutPage) Page() TabPage {
|
||||
Label{Text: i18n.Sprintf("For comments or to report bugs, please visit the project page:")},
|
||||
LinkLabel{
|
||||
Alignment: AlignHNearVCenter,
|
||||
Text: fmt.Sprintf(`<a id="home" href="%s">%s</a>`, consts.ProjectURL, consts.ProjectURL),
|
||||
Text: fmt.Sprintf(`<a id="home" href="%s">%s</a>`, res.ProjectURL, res.ProjectURL),
|
||||
OnLinkActivated: func(link *walk.LinkLabelLink) {
|
||||
openPath(link.URL())
|
||||
},
|
||||
@ -99,7 +99,7 @@ func (ap *AboutPage) Page() TabPage {
|
||||
Label{Text: i18n.Sprintf("For FRP configuration documentation, please visit the FRP project page:")},
|
||||
LinkLabel{
|
||||
Alignment: AlignHNearVCenter,
|
||||
Text: fmt.Sprintf(`<a id="frp" href="%s">%s</a>`, consts.FRPProjectURL, consts.FRPProjectURL),
|
||||
Text: fmt.Sprintf(`<a id="frp" href="%s">%s</a>`, res.FRPProjectURL, res.FRPProjectURL),
|
||||
OnLinkActivated: func(link *walk.LinkLabelLink) {
|
||||
openPath(link.URL())
|
||||
},
|
||||
@ -121,7 +121,7 @@ func (ap *AboutPage) checkUpdate(showErr bool) {
|
||||
ap.db.Reset()
|
||||
go func() {
|
||||
var body []byte
|
||||
resp, err := http.Get(consts.UpdateURL)
|
||||
resp, err := http.Get(res.UpdateURL)
|
||||
if err != nil {
|
||||
goto Fin
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
. "github.com/lxn/walk/declarative"
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
)
|
||||
|
||||
// NewBrowseLineEdit places a tool button at the tail of a LineEdit, and opens a file dialog when the button is clicked
|
||||
@ -49,7 +49,7 @@ func NewBasicDialog(assignTo **walk.Dialog, title string, icon Property, db Data
|
||||
Icon: icon,
|
||||
Title: title,
|
||||
Layout: VBox{},
|
||||
Font: consts.TextRegular,
|
||||
Font: res.TextRegular,
|
||||
DefaultButton: &acceptPB,
|
||||
CancelButton: &cancelPB,
|
||||
DataBinder: db,
|
||||
@ -144,7 +144,7 @@ func NewAttributeTable(m *AttributeModel, nameWidth, valueWidth int) Composite {
|
||||
func NewAttributeDialog(title string, data *map[string]string) Dialog {
|
||||
var p *walk.Dialog
|
||||
m := NewAttributeModel(*data)
|
||||
dlg := NewBasicDialog(&p, title, loadIcon(consts.IconFile, 32), DataBinder{}, func() {
|
||||
dlg := NewBasicDialog(&p, title, loadIcon(res.IconFile, 32), DataBinder{}, func() {
|
||||
*data = m.AsMap()
|
||||
p.Accept()
|
||||
},
|
||||
|
16
ui/conf.go
16
ui/conf.go
@ -78,13 +78,11 @@ func (conf *Conf) Save() error {
|
||||
}
|
||||
|
||||
var (
|
||||
appConf = config.App{Defaults: config.ClientCommon{
|
||||
ServerPort: 7000,
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
TCPMux: true,
|
||||
TLSEnable: true,
|
||||
DisableCustomTLSFirstByte: true,
|
||||
appConf = config.App{Defaults: config.DefaultValue{
|
||||
LogLevel: consts.LogLevelInfo,
|
||||
LogMaxDays: consts.DefaultLogMaxDays,
|
||||
TCPMux: true,
|
||||
TLSEnable: true,
|
||||
}}
|
||||
// The config list contains all the loaded configs
|
||||
confList []*Conf
|
||||
@ -93,7 +91,7 @@ var (
|
||||
)
|
||||
|
||||
func loadAllConfs() error {
|
||||
_ = config.UnmarshalAppConfFromIni(config.DefaultAppFile, &appConf)
|
||||
_ = config.UnmarshalAppConf(config.DefaultAppFile, &appConf)
|
||||
// Find all config files in `profiles` directory
|
||||
files, err := filepath.Glob(PathOfConf("*.conf"))
|
||||
if err != nil {
|
||||
@ -191,7 +189,7 @@ func commitConf(conf *Conf, flag runFlag) {
|
||||
|
||||
func newDefaultClientConfig() *config.ClientConfig {
|
||||
return &config.ClientConfig{
|
||||
ClientCommon: appConf.Defaults,
|
||||
ClientCommon: appConf.Defaults.AsClientConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/layout"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/util"
|
||||
)
|
||||
|
||||
@ -119,7 +120,7 @@ func (cv *ConfView) View() Widget {
|
||||
}
|
||||
conf := cv.model.items[row]
|
||||
if !conf.Data.AutoStart() {
|
||||
style.TextColor = consts.ColorBlue
|
||||
style.TextColor = res.ColorBlue
|
||||
}
|
||||
margin := cv.listView.IntFrom96DPI(1)
|
||||
bitmapWidth := cv.listView.IntFrom96DPI(16)
|
||||
@ -161,27 +162,27 @@ func (cv *ConfView) View() Widget {
|
||||
Menu{
|
||||
OnTriggered: cv.editNew,
|
||||
Text: i18n.Sprintf("New Config"),
|
||||
Image: loadIcon(consts.IconNewConf, 16),
|
||||
Image: loadIcon(res.IconNewConf, 16),
|
||||
Items: []MenuItem{
|
||||
Action{
|
||||
AssignTo: &cv.tbAddAction,
|
||||
Text: i18n.Sprintf("Manual Settings"),
|
||||
Image: loadIcon(consts.IconCreate, 16),
|
||||
Image: loadIcon(res.IconCreate, 16),
|
||||
OnTriggered: cv.editNew,
|
||||
},
|
||||
Action{
|
||||
Text: i18n.SprintfEllipsis("Import from File"),
|
||||
Image: loadIcon(consts.IconFileImport, 16),
|
||||
Image: loadIcon(res.IconFileImport, 16),
|
||||
OnTriggered: cv.onFileImport,
|
||||
},
|
||||
Action{
|
||||
Text: i18n.SprintfEllipsis("Import from URL"),
|
||||
Image: loadIcon(consts.IconURLImport, 16),
|
||||
Image: loadIcon(res.IconURLImport, 16),
|
||||
OnTriggered: cv.onURLImport,
|
||||
},
|
||||
Action{
|
||||
Text: i18n.Sprintf("Import from Clipboard"),
|
||||
Image: loadIcon(consts.IconClipboard, 16),
|
||||
Image: loadIcon(res.IconClipboard, 16),
|
||||
OnTriggered: cv.onClipboardImport,
|
||||
},
|
||||
},
|
||||
@ -190,14 +191,14 @@ func (cv *ConfView) View() Widget {
|
||||
Action{
|
||||
Enabled: Bind("conf.Selected"),
|
||||
AssignTo: &cv.tbDeleteAction,
|
||||
Image: loadIcon(consts.IconDelete, 16),
|
||||
Image: loadIcon(res.IconDelete, 16),
|
||||
OnTriggered: cv.onDelete,
|
||||
},
|
||||
Separator{},
|
||||
Action{
|
||||
Enabled: Bind("conf.Selected"),
|
||||
AssignTo: &cv.tbExportAction,
|
||||
Image: loadIcon(consts.IconExport, 16),
|
||||
Image: loadIcon(res.IconExport, 16),
|
||||
OnTriggered: cv.onExport,
|
||||
},
|
||||
},
|
||||
@ -321,7 +322,7 @@ checkName:
|
||||
|
||||
func (cv *ConfView) onFileImport() {
|
||||
dlg := walk.FileDialog{
|
||||
Filter: consts.FilterConfig + consts.FilterAllFiles,
|
||||
Filter: res.FilterConfig + res.FilterAllFiles,
|
||||
Title: i18n.Sprintf("Import from File"),
|
||||
}
|
||||
|
||||
@ -356,7 +357,7 @@ func (cv *ConfView) ImportFiles(files []string) {
|
||||
subTotal, subImported := cv.importZip(path, nil, false)
|
||||
total += subTotal
|
||||
imported += subImported
|
||||
} else if slices.Contains(consts.SupportedConfigFormats, ext) {
|
||||
} else if slices.Contains(res.SupportedConfigFormats, ext) {
|
||||
total++
|
||||
newPath, ok := cv.checkConfName(path, false)
|
||||
if !ok {
|
||||
@ -431,7 +432,7 @@ func (cv *ConfView) importZip(path string, data []byte, rename bool) (total, imp
|
||||
if file.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(consts.SupportedConfigFormats, strings.ToLower(filepath.Ext(file.Name))) {
|
||||
if !slices.Contains(res.SupportedConfigFormats, strings.ToLower(filepath.Ext(file.Name))) {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
@ -454,8 +455,8 @@ func (cv *ConfView) onClipboardImport() {
|
||||
}
|
||||
var name string
|
||||
// Check for a share link
|
||||
if strings.HasPrefix(text, consts.ShareLinkScheme) {
|
||||
text = strings.TrimPrefix(text, consts.ShareLinkScheme)
|
||||
if strings.HasPrefix(text, res.ShareLinkScheme) {
|
||||
text = strings.TrimPrefix(text, res.ShareLinkScheme)
|
||||
content, err := base64.StdEncoding.DecodeString(text)
|
||||
if err != nil {
|
||||
showError(err, cv.Form())
|
||||
@ -484,7 +485,7 @@ func (cv *ConfView) onCopyShareLink() {
|
||||
}
|
||||
// Insert the config name in the first line
|
||||
content = append([]byte("# "+conf.Name+"\n"), content...)
|
||||
walk.Clipboard().SetText(consts.ShareLinkScheme + base64.StdEncoding.EncodeToString(content))
|
||||
walk.Clipboard().SetText(res.ShareLinkScheme + base64.StdEncoding.EncodeToString(content))
|
||||
}
|
||||
}
|
||||
|
||||
@ -522,7 +523,7 @@ func (cv *ConfView) onDelete() {
|
||||
|
||||
func (cv *ConfView) onExport() {
|
||||
dlg := walk.FileDialog{
|
||||
Filter: consts.FilterZip,
|
||||
Filter: res.FilterZip,
|
||||
Title: i18n.Sprintf("Export All Configs to ZIP"),
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/util"
|
||||
"github.com/koho/frpmgr/services"
|
||||
)
|
||||
@ -86,7 +87,7 @@ func (cd *EditClientDialog) View() Dialog {
|
||||
if cd.Conf.Name != "" {
|
||||
title = i18n.Sprintf("Edit Client - %s", cd.Conf.Name)
|
||||
}
|
||||
dlg := NewBasicDialog(&cd.Dialog, title, loadIcon(consts.IconEditDialog, 32), DataBinder{
|
||||
dlg := NewBasicDialog(&cd.Dialog, title, loadIcon(res.IconEditDialog, 32), DataBinder{
|
||||
AssignTo: &cd.db,
|
||||
Name: "common",
|
||||
DataSource: cd.binder,
|
||||
@ -109,7 +110,7 @@ func (cd *EditClientDialog) basicConfPage() TabPage {
|
||||
Layout: Grid{Columns: 2},
|
||||
Children: []Widget{
|
||||
Label{Text: i18n.SprintfColon("Name")},
|
||||
LineEdit{AssignTo: &cd.nameView, Text: Bind("Name", consts.ValidateNonEmpty), OnTextChanged: func() {
|
||||
LineEdit{AssignTo: &cd.nameView, Text: Bind("Name", res.ValidateNonEmpty), OnTextChanged: func() {
|
||||
if name := cd.nameView.Text(); name != "" {
|
||||
curLog := strings.TrimSpace(cd.logFileView.Text())
|
||||
// Automatically change the log file if it's empty or using the default log directory
|
||||
@ -119,7 +120,7 @@ func (cd *EditClientDialog) basicConfPage() TabPage {
|
||||
}
|
||||
}},
|
||||
Label{Text: i18n.SprintfColon("Server Address")},
|
||||
LineEdit{Text: Bind("ServerAddress", consts.ValidateNonEmpty)},
|
||||
LineEdit{Text: Bind("ServerAddress", res.ValidateNonEmpty)},
|
||||
Label{Text: i18n.SprintfColon("Server Port")},
|
||||
Composite{
|
||||
Layout: HBox{MarginsZero: true},
|
||||
@ -197,7 +198,7 @@ func (cd *EditClientDialog) logConfPage() TabPage {
|
||||
VSpacer{Size: 2, ColumnSpan: 2},
|
||||
Label{Text: i18n.SprintfColon("Log File")},
|
||||
NewBrowseLineEdit(&cd.logFileView, true, true, Bind("LogFile"),
|
||||
i18n.Sprintf("Select Log File"), consts.FilterLog, true),
|
||||
i18n.Sprintf("Select Log File"), res.FilterLog, true),
|
||||
Label{Text: i18n.SprintfColon("Level")},
|
||||
ComboBox{
|
||||
Value: Bind("LogLevel"),
|
||||
@ -231,7 +232,7 @@ func (cd *EditClientDialog) adminConfPage() TabPage {
|
||||
},
|
||||
ToolButton{
|
||||
Enabled: Bind("adminPort.Value > 0 && !legacyFormat.Checked"),
|
||||
Image: loadIcon(consts.IconLock, 16),
|
||||
Image: loadIcon(res.IconLock, 16),
|
||||
ToolTipText: "TLS", OnClicked: func() {
|
||||
cd.adminTLSDialog().Run(cd.Form())
|
||||
},
|
||||
@ -332,13 +333,13 @@ func (cd *EditClientDialog) tlsConfPage() TabPage {
|
||||
LineEdit{Visible: Bind("tlsCheck.Checked"), Text: Bind("TLSServerName")},
|
||||
Label{Visible: Bind("tlsCheck.Checked"), Text: i18n.SprintfColon("Certificate")},
|
||||
NewBrowseLineEdit(nil, Bind("tlsCheck.Checked"), true, Bind("TLSCertFile"),
|
||||
i18n.Sprintf("Select Certificate File"), consts.FilterCert, true),
|
||||
i18n.Sprintf("Select Certificate File"), res.FilterCert, true),
|
||||
Label{Visible: Bind("tlsCheck.Checked"), Text: i18n.SprintfColon("Certificate Key"), AlwaysConsumeSpace: true},
|
||||
NewBrowseLineEdit(nil, Bind("tlsCheck.Checked"), true, Bind("TLSKeyFile"),
|
||||
i18n.Sprintf("Select Certificate Key File"), consts.FilterKey, true),
|
||||
i18n.Sprintf("Select Certificate Key File"), res.FilterKey, true),
|
||||
Label{Visible: Bind("tlsCheck.Checked"), Text: i18n.SprintfColon("Trusted CA"), AlwaysConsumeSpace: true},
|
||||
NewBrowseLineEdit(nil, Bind("tlsCheck.Checked"), true, Bind("TLSTrustedCaFile"),
|
||||
i18n.Sprintf("Select Trusted CA File"), consts.FilterCert, true),
|
||||
i18n.Sprintf("Select Trusted CA File"), res.FilterCert, true),
|
||||
Label{Visible: Bind("tlsCheck.Checked"), Text: i18n.SprintfColon("Other Options")},
|
||||
CheckBox{Visible: Bind("tlsCheck.Checked"), Text: i18n.Sprintf("Disable custom first byte"), Checked: Bind("DisableCustomTLSFirstByte")},
|
||||
},
|
||||
@ -423,7 +424,7 @@ func (cd *EditClientDialog) advancedConfPage() TabPage {
|
||||
|
||||
func (cd *EditClientDialog) experimentDialog() Dialog {
|
||||
dlg := NewBasicDialog(nil, i18n.Sprintf("Experimental Features"),
|
||||
loadIcon(consts.IconExperiment, 32), DataBinder{DataSource: cd.binder}, nil,
|
||||
loadIcon(res.IconExperiment, 32), DataBinder{DataSource: cd.binder}, nil,
|
||||
Label{Text: i18n.Sprintf("* The following features may affect the stability of the service.")},
|
||||
CheckBox{Checked: Bind("SVCBEnable"), Text: i18n.Sprintf("Use server SVCB records"), Alignment: AlignHNearVNear},
|
||||
VSpacer{},
|
||||
@ -436,19 +437,19 @@ func (cd *EditClientDialog) experimentDialog() Dialog {
|
||||
func (cd *EditClientDialog) adminTLSDialog() Dialog {
|
||||
var widgets [4]*walk.LineEdit
|
||||
dlg := NewBasicDialog(nil, "TLS",
|
||||
loadIcon(consts.IconLock, 32),
|
||||
loadIcon(res.IconLock, 32),
|
||||
DataBinder{DataSource: &cd.binder.AdminTLS}, nil,
|
||||
Label{Text: i18n.SprintfColon("Host Name")},
|
||||
LineEdit{AssignTo: &widgets[0], Text: Bind("ServerName")},
|
||||
Label{Text: i18n.SprintfColon("Certificate")},
|
||||
NewBrowseLineEdit(&widgets[1], true, true, Bind("CertFile"),
|
||||
i18n.Sprintf("Select Certificate File"), consts.FilterCert, true),
|
||||
i18n.Sprintf("Select Certificate File"), res.FilterCert, true),
|
||||
Label{Text: i18n.SprintfColon("Certificate Key")},
|
||||
NewBrowseLineEdit(&widgets[2], true, true, Bind("KeyFile"),
|
||||
i18n.Sprintf("Select Certificate Key File"), consts.FilterKey, true),
|
||||
i18n.Sprintf("Select Certificate Key File"), res.FilterKey, true),
|
||||
Label{Text: i18n.SprintfColon("Trusted CA")},
|
||||
NewBrowseLineEdit(&widgets[3], true, true, Bind("TrustedCaFile"),
|
||||
i18n.Sprintf("Select Trusted CA File"), consts.FilterCert, true),
|
||||
i18n.Sprintf("Select Trusted CA File"), res.FilterCert, true),
|
||||
VSpacer{Size: 4},
|
||||
)
|
||||
dlg.MinSize = Size{Width: 350}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
)
|
||||
|
||||
type EditProxyDialog struct {
|
||||
@ -106,7 +107,7 @@ func (pd *EditProxyDialog) View() Dialog {
|
||||
if pd.exist && pd.Proxy.Name != "" {
|
||||
title = i18n.Sprintf("Edit Proxy - %s", pd.Proxy.Name)
|
||||
}
|
||||
dlg := NewBasicDialog(&pd.Dialog, title, loadIcon(consts.IconEditDialog, 32), DataBinder{
|
||||
dlg := NewBasicDialog(&pd.Dialog, title, loadIcon(res.IconEditDialog, 32), DataBinder{
|
||||
AssignTo: &pd.vmDB,
|
||||
Name: "vm",
|
||||
DataSource: &pd.viewModel,
|
||||
@ -125,8 +126,8 @@ func (pd *EditProxyDialog) View() Dialog {
|
||||
Composite{
|
||||
Layout: HBox{MarginsZero: true},
|
||||
Children: []Widget{
|
||||
LineEdit{AssignTo: &pd.nameView, Text: Bind("Name", consts.ValidateNonEmpty)},
|
||||
PushButton{Text: i18n.SprintfLSpace("Random"), Image: loadIcon(consts.IconRandom, 16), OnClicked: func() {
|
||||
LineEdit{AssignTo: &pd.nameView, Text: Bind("Name", res.ValidateNonEmpty)},
|
||||
PushButton{Text: i18n.SprintfLSpace("Random"), Image: loadIcon(res.IconRandom, 16), OnClicked: func() {
|
||||
pd.nameView.SetText(lo.RandomString(8, lo.AlphanumericCharset))
|
||||
}},
|
||||
},
|
||||
@ -348,10 +349,10 @@ func (pd *EditProxyDialog) pluginProxyPage() TabPage {
|
||||
},
|
||||
Label{Visible: Bind("vm.PluginCertVisible"), Text: i18n.SprintfColon("Certificate")},
|
||||
NewBrowseLineEdit(nil, Bind("vm.PluginCertVisible"), true, Bind("PluginCrtPath"),
|
||||
i18n.Sprintf("Select Certificate File"), consts.FilterCert, true),
|
||||
i18n.Sprintf("Select Certificate File"), res.FilterCert, true),
|
||||
Label{Visible: Bind("vm.PluginCertVisible"), Text: i18n.SprintfColon("Certificate Key")},
|
||||
NewBrowseLineEdit(nil, Bind("vm.PluginCertVisible"), true, Bind("PluginKeyPath"),
|
||||
i18n.Sprintf("Select Certificate Key File"), consts.FilterKey, true),
|
||||
i18n.Sprintf("Select Certificate Key File"), res.FilterKey, true),
|
||||
Label{Visible: Bind("vm.PluginHTTPFwdVisible"), Text: i18n.SprintfColon("Host Rewrite")},
|
||||
LineEdit{Visible: Bind("vm.PluginHTTPFwdVisible"), Text: Bind("PluginHostHeaderRewrite")},
|
||||
},
|
||||
|
17
ui/icon.go
17
ui/icon.go
@ -4,11 +4,12 @@ import (
|
||||
"github.com/lxn/walk"
|
||||
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
)
|
||||
|
||||
var cachedIconsForWidthAndId = make(map[widthAndId]*walk.Icon)
|
||||
|
||||
func loadIcon(id consts.Icon, size int) (icon *walk.Icon) {
|
||||
func loadIcon(id res.Icon, size int) (icon *walk.Icon) {
|
||||
icon = cachedIconsForWidthAndId[widthAndId{size, id}]
|
||||
if icon != nil {
|
||||
return
|
||||
@ -27,7 +28,7 @@ func loadIcon(id consts.Icon, size int) (icon *walk.Icon) {
|
||||
|
||||
type widthAndId struct {
|
||||
width int
|
||||
icon consts.Icon
|
||||
icon res.Icon
|
||||
}
|
||||
|
||||
type widthAndState struct {
|
||||
@ -44,24 +45,24 @@ func iconForState(state consts.ServiceState, size int) (icon *walk.Icon) {
|
||||
}
|
||||
switch state {
|
||||
case consts.StateStarted:
|
||||
icon = loadIcon(consts.IconStateRunning, size)
|
||||
icon = loadIcon(res.IconStateRunning, size)
|
||||
case consts.StateStopped, consts.StateUnknown:
|
||||
icon = loadIcon(consts.IconStateStopped, size)
|
||||
icon = loadIcon(res.IconStateStopped, size)
|
||||
default:
|
||||
icon = loadIcon(consts.IconStateWorking, size)
|
||||
icon = loadIcon(res.IconStateWorking, size)
|
||||
}
|
||||
cachedIconsForWidthAndState[widthAndState{size, state}] = icon
|
||||
return
|
||||
}
|
||||
|
||||
func loadLogoIcon(size int) *walk.Icon {
|
||||
return loadIcon(consts.IconLogo, size)
|
||||
return loadIcon(res.IconLogo, size)
|
||||
}
|
||||
|
||||
func loadShieldIcon(size int) (icon *walk.Icon) {
|
||||
icon = loadIcon(consts.IconNewVersion1, size)
|
||||
icon = loadIcon(res.IconNewVersion1, size)
|
||||
if icon == nil {
|
||||
icon = loadIcon(consts.IconNewVersion2, size)
|
||||
icon = loadIcon(res.IconNewVersion2, size)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
. "github.com/lxn/walk/declarative"
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
)
|
||||
|
||||
type NATDiscoveryDialog struct {
|
||||
@ -27,7 +27,7 @@ func NewNATDiscoveryDialog(serverAddr string) *NATDiscoveryDialog {
|
||||
}
|
||||
|
||||
func (nd *NATDiscoveryDialog) Run(owner walk.Form) (int, error) {
|
||||
dlg := NewBasicDialog(&nd.Dialog, i18n.Sprintf("NAT Discovery"), loadIcon(consts.IconNat, 32),
|
||||
dlg := NewBasicDialog(&nd.Dialog, i18n.Sprintf("NAT Discovery"), loadIcon(res.IconNat, 32),
|
||||
DataBinder{}, nil,
|
||||
VSpacer{Size: 1},
|
||||
Composite{
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/services"
|
||||
)
|
||||
|
||||
@ -37,7 +38,7 @@ func NewPanelView() *PanelView {
|
||||
|
||||
func (pv *PanelView) View() Widget {
|
||||
var cpIcon *walk.CustomWidget
|
||||
cpIconColor := consts.ColorDarkGray
|
||||
cpIconColor := res.ColorDarkGray
|
||||
setCopyIconColor := func(button walk.MouseButton, color walk.Color) {
|
||||
if button == walk.LeftButton {
|
||||
cpIconColor = color
|
||||
@ -79,10 +80,10 @@ func (pv *PanelView) View() Widget {
|
||||
return drawCopyIcon(canvas, cpIconColor)
|
||||
},
|
||||
OnMouseDown: func(x, y int, button walk.MouseButton) {
|
||||
setCopyIconColor(button, consts.ColorLightBlue)
|
||||
setCopyIconColor(button, res.ColorLightBlue)
|
||||
},
|
||||
OnMouseUp: func(x, y int, button walk.MouseButton) {
|
||||
setCopyIconColor(button, consts.ColorDarkGray)
|
||||
setCopyIconColor(button, res.ColorDarkGray)
|
||||
bounds := cpIcon.ClientBoundsPixels()
|
||||
if x >= 0 && x <= bounds.Right() && y >= 0 && y <= bounds.Bottom() {
|
||||
walk.Clipboard().SetText(pv.addressText.Text())
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/validators"
|
||||
)
|
||||
|
||||
@ -38,7 +39,7 @@ func NewPluginProxyDialog(title string, icon *walk.Icon, plugin string) *PluginP
|
||||
func (pp *PluginProxyDialog) Run(owner walk.Form) (int, error) {
|
||||
widgets := []Widget{
|
||||
Label{Text: i18n.SprintfColon("Remote Port")},
|
||||
LineEdit{Text: Bind("RemotePort", consts.ValidatePortRange...), MinSize: Size{Width: 280}},
|
||||
LineEdit{Text: Bind("RemotePort", res.ValidatePortRange...), MinSize: Size{Width: 280}},
|
||||
}
|
||||
switch pp.plugin {
|
||||
case consts.PluginHttpProxy, consts.PluginSocks5:
|
||||
@ -53,7 +54,7 @@ func (pp *PluginProxyDialog) Run(owner walk.Form) (int, error) {
|
||||
case consts.PluginStaticFile:
|
||||
widgets = append(widgets,
|
||||
Label{Text: i18n.SprintfColon("Local Directory")},
|
||||
NewBrowseLineEdit(nil, true, true, Bind("Dir", consts.ValidateNonEmpty),
|
||||
NewBrowseLineEdit(nil, true, true, Bind("Dir", res.ValidateNonEmpty),
|
||||
i18n.Sprintf("Select a folder for directory listing."), "", false),
|
||||
)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/validators"
|
||||
)
|
||||
|
||||
@ -44,7 +45,7 @@ func (pp *PortProxyDialog) Run(owner walk.Form) (int, error) {
|
||||
Label{Text: i18n.SprintfColon("Name"), ColumnSpan: 2},
|
||||
LineEdit{Text: Bind("Name"), CueBanner: "open_xxx", ColumnSpan: 2},
|
||||
Label{Text: i18n.SprintfColon("Remote Port"), ColumnSpan: 2},
|
||||
LineEdit{Text: Bind("RemotePort", consts.ValidatePortRange...), ColumnSpan: 2},
|
||||
LineEdit{Text: Bind("RemotePort", res.ValidatePortRange...), ColumnSpan: 2},
|
||||
Label{Text: i18n.SprintfColon("Protocol"), ColumnSpan: 2},
|
||||
Composite{
|
||||
Layout: HBox{MarginsZero: true},
|
||||
@ -56,10 +57,10 @@ func (pp *PortProxyDialog) Run(owner walk.Form) (int, error) {
|
||||
},
|
||||
Label{Text: i18n.SprintfColon("Local Address")},
|
||||
Label{Text: i18n.SprintfColon("Port")},
|
||||
LineEdit{Text: Bind("LocalAddr", consts.ValidateNonEmpty), StretchFactor: 2},
|
||||
LineEdit{Text: Bind("LocalPort", consts.ValidatePortRange...), StretchFactor: 1},
|
||||
LineEdit{Text: Bind("LocalAddr", res.ValidateNonEmpty), StretchFactor: 2},
|
||||
LineEdit{Text: Bind("LocalPort", res.ValidatePortRange...), StretchFactor: 1},
|
||||
}
|
||||
return NewBasicDialog(&pp.Dialog, i18n.Sprintf("Open Port"), loadIcon(consts.IconOpenPort, 32), DataBinder{
|
||||
return NewBasicDialog(&pp.Dialog, i18n.Sprintf("Open Port"), loadIcon(res.IconOpenPort, 32), DataBinder{
|
||||
AssignTo: &pp.db,
|
||||
DataSource: pp.binder,
|
||||
ErrorPresenter: validators.SilentToolTipErrorPresenter{},
|
||||
|
@ -1,7 +1,6 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
@ -10,6 +9,7 @@ import (
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/sec"
|
||||
"github.com/koho/frpmgr/pkg/validators"
|
||||
)
|
||||
@ -47,7 +47,7 @@ func (pp *PrefPage) passwordSection() GroupBox {
|
||||
Title: i18n.Sprintf("Master password"),
|
||||
Layout: Grid{Alignment: AlignHNearVCenter, Columns: 2},
|
||||
Children: []Widget{
|
||||
ImageView{Image: loadIcon(consts.IconKey, 32)},
|
||||
ImageView{Image: loadIcon(res.IconKey, 32)},
|
||||
Label{Text: i18n.Sprintf("You can set a password to restrict access to this program.\nYou will be asked to enter it the next time you use this program.")},
|
||||
CheckBox{
|
||||
AssignTo: &pp.usePassword,
|
||||
@ -93,7 +93,7 @@ func (pp *PrefPage) languageSection() GroupBox {
|
||||
Title: i18n.Sprintf("Languages"),
|
||||
Layout: Grid{Alignment: AlignHNearVCenter, Columns: 2},
|
||||
Children: []Widget{
|
||||
ImageView{Image: loadIcon(consts.IconLanguage, 32)},
|
||||
ImageView{Image: loadIcon(res.IconLanguage, 32)},
|
||||
Composite{
|
||||
Layout: VBox{MarginsZero: true},
|
||||
Children: []Widget{
|
||||
@ -136,7 +136,7 @@ func (pp *PrefPage) defaultSection() GroupBox {
|
||||
Title: i18n.Sprintf("Defaults"),
|
||||
Layout: Grid{Alignment: AlignHNearVCenter, Columns: 2, Spacing: 10, Margins: Margins{Left: 9, Top: 9, Right: 9, Bottom: 16}},
|
||||
Children: []Widget{
|
||||
ImageView{Image: loadIcon(consts.IconDefaults, 32)},
|
||||
ImageView{Image: loadIcon(res.IconDefaults, 32)},
|
||||
Label{Text: i18n.Sprintf("Define the default value when creating a new configuration.\nThe value here will not affect the existing configuration.")},
|
||||
Composite{
|
||||
Row: 1, Column: 1,
|
||||
@ -179,7 +179,7 @@ func (pp *PrefPage) changePassword() string {
|
||||
var vm struct {
|
||||
Password string
|
||||
}
|
||||
NewBasicDialog(nil, i18n.Sprintf("Master password"), loadIcon(consts.IconKey, 32),
|
||||
NewBasicDialog(nil, i18n.Sprintf("Master password"), loadIcon(res.IconKey, 32),
|
||||
DataBinder{
|
||||
AssignTo: &db,
|
||||
DataSource: &vm,
|
||||
@ -189,7 +189,7 @@ func (pp *PrefPage) changePassword() string {
|
||||
MinSize: Size{Width: 280},
|
||||
Children: []Widget{
|
||||
Label{Text: i18n.SprintfColon("New master password")},
|
||||
LineEdit{AssignTo: &pwdEdit, Text: Bind("Password", consts.ValidateNonEmpty), PasswordMode: true},
|
||||
LineEdit{AssignTo: &pwdEdit, Text: Bind("Password", res.ValidateNonEmpty), PasswordMode: true},
|
||||
Label{Text: i18n.SprintfColon("Re-enter password")},
|
||||
LineEdit{Text: Bind("", validators.ConfirmPassword{Password: &pwdEdit}), PasswordMode: true},
|
||||
},
|
||||
@ -208,14 +208,15 @@ func (pp *PrefPage) changePassword() string {
|
||||
}
|
||||
|
||||
func (pp *PrefPage) switchLanguage(lc string) {
|
||||
if err := os.WriteFile(i18n.LangFile, []byte(lc), 0660); err != nil {
|
||||
appConf.Lang = lc
|
||||
if err := saveAppConfig(); err != nil {
|
||||
showError(err, pp.Form())
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *PrefPage) setDefaultValue() (int, error) {
|
||||
dlg := NewBasicDialog(nil, i18n.Sprintf("Defaults"),
|
||||
loadIcon(consts.IconDefaults, 32),
|
||||
loadIcon(res.IconDefaults, 32),
|
||||
DataBinder{DataSource: &appConf.Defaults}, nil, Composite{
|
||||
Layout: Grid{Columns: 2, MarginsZero: true},
|
||||
Children: []Widget{
|
||||
@ -254,7 +255,8 @@ func (pp *PrefPage) setDefaultValue() (int, error) {
|
||||
Children: []Widget{
|
||||
CheckBox{Text: i18n.Sprintf("TCP Mux"), Checked: Bind("TCPMux")},
|
||||
CheckBox{Text: "TLS", Checked: Bind("TLSEnable")},
|
||||
CheckBox{Text: i18n.Sprintf("Disable auto-start at boot"), Checked: Bind("ManualStart")},
|
||||
CheckBox{Text: i18n.Sprintf("Disable auto-start at boot"), Checked: Bind("ManualStart"), ColumnSpan: 2},
|
||||
CheckBox{Text: i18n.Sprintf("Use legacy format config file"), Checked: Bind("LegacyFormat"), ColumnSpan: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/util"
|
||||
)
|
||||
|
||||
@ -102,19 +103,19 @@ func (pv *ProxyView) createToolbar() ToolBar {
|
||||
Action{
|
||||
AssignTo: &pv.newAction,
|
||||
Text: i18n.Sprintf("Add"),
|
||||
Image: loadIcon(consts.IconCreate, 16),
|
||||
Image: loadIcon(res.IconCreate, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onEdit(false, nil)
|
||||
},
|
||||
},
|
||||
Menu{
|
||||
Text: i18n.Sprintf("Quick Add"),
|
||||
Image: loadIcon(consts.IconQuickAdd, 16),
|
||||
Image: loadIcon(res.IconQuickAdd, 16),
|
||||
Items: []MenuItem{
|
||||
Action{
|
||||
AssignTo: &pv.portAction,
|
||||
Text: i18n.Sprintf("Open Port"),
|
||||
Image: loadIcon(consts.IconOpenPort, 16),
|
||||
Image: loadIcon(res.IconOpenPort, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewPortProxyDialog())
|
||||
},
|
||||
@ -122,85 +123,85 @@ func (pv *ProxyView) createToolbar() ToolBar {
|
||||
Action{
|
||||
AssignTo: &pv.rdAction,
|
||||
Text: i18n.Sprintf("Remote Desktop"),
|
||||
Image: loadIcon(consts.IconRemote, 16),
|
||||
Image: loadIcon(res.IconRemote, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewSimpleProxyDialog(i18n.Sprintf("Remote Desktop"), loadIcon(consts.IconRemote, 32),
|
||||
pv.onQuickAdd(NewSimpleProxyDialog(i18n.Sprintf("Remote Desktop"), loadIcon(res.IconRemote, 32),
|
||||
"rdp", []string{consts.ProxyTypeTCP, consts.ProxyTypeUDP}, ":3389"))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.vncAction,
|
||||
Text: "VNC",
|
||||
Image: loadIcon(consts.IconVNC, 16),
|
||||
Image: loadIcon(res.IconVNC, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("VNC", loadIcon(consts.IconVNC, 32),
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("VNC", loadIcon(res.IconVNC, 32),
|
||||
"vnc", []string{consts.ProxyTypeTCP, consts.ProxyTypeUDP}, ":5900"))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.sshAction,
|
||||
Text: "SSH",
|
||||
Image: loadIcon(consts.IconSSH, 16),
|
||||
Image: loadIcon(res.IconSSH, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("SSH", loadIcon(consts.IconSSH, 32),
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("SSH", loadIcon(res.IconSSH, 32),
|
||||
"ssh", []string{consts.ProxyTypeTCP}, ":22"))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.webAction,
|
||||
Text: "Web",
|
||||
Image: loadIcon(consts.IconWeb, 16),
|
||||
Image: loadIcon(res.IconWeb, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("Web", loadIcon(consts.IconWeb, 32),
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("Web", loadIcon(res.IconWeb, 32),
|
||||
"web", []string{consts.ProxyTypeTCP}, ":80"))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.dnsAction,
|
||||
Text: "DNS",
|
||||
Image: loadIcon(consts.IconDns, 16),
|
||||
Image: loadIcon(res.IconDns, 16),
|
||||
OnTriggered: func() {
|
||||
systemDns := util.GetSystemDnsServer()
|
||||
if systemDns == "" {
|
||||
systemDns = "114.114.114.114"
|
||||
}
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("DNS", loadIcon(consts.IconDns, 32),
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("DNS", loadIcon(res.IconDns, 32),
|
||||
"dns", []string{consts.ProxyTypeUDP}, systemDns+":53"))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.vpnAction,
|
||||
Text: "OpenVPN",
|
||||
Image: loadIcon(consts.IconLock, 16),
|
||||
Image: loadIcon(res.IconLock, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("OpenVPN", loadIcon(consts.IconLock, 32),
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("OpenVPN", loadIcon(res.IconLock, 32),
|
||||
"openvpn", []string{consts.ProxyTypeTCP, consts.ProxyTypeUDP}, ":1194"))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.ftpAction,
|
||||
Text: "FTP",
|
||||
Image: loadIcon(consts.IconFtp, 16),
|
||||
Image: loadIcon(res.IconFtp, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("FTP", loadIcon(consts.IconFtp, 32),
|
||||
pv.onQuickAdd(NewSimpleProxyDialog("FTP", loadIcon(res.IconFtp, 32),
|
||||
"ftp", []string{consts.ProxyTypeTCP}, ":21"))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.httpFileAction,
|
||||
Text: i18n.Sprintf("HTTP File Server"),
|
||||
Image: loadIcon(consts.IconHttpFile, 16),
|
||||
Image: loadIcon(res.IconHttpFile, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewPluginProxyDialog(i18n.Sprintf("HTTP File Server"), loadIcon(consts.IconHttpFile, 32),
|
||||
pv.onQuickAdd(NewPluginProxyDialog(i18n.Sprintf("HTTP File Server"), loadIcon(res.IconHttpFile, 32),
|
||||
consts.PluginStaticFile))
|
||||
},
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.httpProxyAction,
|
||||
Text: i18n.Sprintf("HTTP Proxy"),
|
||||
Image: loadIcon(consts.IconHttpProxy, 16),
|
||||
Image: loadIcon(res.IconHttpProxy, 16),
|
||||
OnTriggered: func() {
|
||||
pv.onQuickAdd(NewPluginProxyDialog(i18n.Sprintf("HTTP Proxy"), loadIcon(consts.IconHttpProxy, 32),
|
||||
pv.onQuickAdd(NewPluginProxyDialog(i18n.Sprintf("HTTP Proxy"), loadIcon(res.IconHttpProxy, 32),
|
||||
consts.PluginHttpProxy))
|
||||
},
|
||||
},
|
||||
@ -208,7 +209,7 @@ func (pv *ProxyView) createToolbar() ToolBar {
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.editAction,
|
||||
Image: loadIcon(consts.IconEdit, 16),
|
||||
Image: loadIcon(res.IconEdit, 16),
|
||||
Text: i18n.Sprintf("Edit"),
|
||||
Enabled: Bind("proxy.CurrentIndex >= 0"),
|
||||
OnTriggered: func() {
|
||||
@ -217,21 +218,21 @@ func (pv *ProxyView) createToolbar() ToolBar {
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.toggleAction,
|
||||
Image: loadIcon(consts.IconDisable, 16),
|
||||
Image: loadIcon(res.IconDisable, 16),
|
||||
Text: i18n.Sprintf("Disable"),
|
||||
Enabled: Bind("proxy.CurrentIndex >= 0 && switchable(proxy.CurrentIndex)"),
|
||||
OnTriggered: pv.onToggleProxy,
|
||||
},
|
||||
Action{
|
||||
AssignTo: &pv.deleteAction,
|
||||
Image: loadIcon(consts.IconDelete, 16),
|
||||
Image: loadIcon(res.IconDelete, 16),
|
||||
Text: i18n.Sprintf("Delete"),
|
||||
Enabled: Bind("proxy.CurrentIndex >= 0"),
|
||||
OnTriggered: pv.onDelete,
|
||||
},
|
||||
Menu{
|
||||
Text: i18n.Sprintf("Open Config"),
|
||||
Image: loadIcon(consts.IconOpen, 16),
|
||||
Image: loadIcon(res.IconOpen, 16),
|
||||
Items: []MenuItem{
|
||||
Action{
|
||||
AssignTo: &pv.openConfAction,
|
||||
@ -273,7 +274,7 @@ func (pv *ProxyView) createProxyTable() TableView {
|
||||
ActionRef{Action: &pv.newAction},
|
||||
Menu{
|
||||
Text: i18n.Sprintf("Quick Add"),
|
||||
Image: loadIcon(consts.IconQuickAdd, 16),
|
||||
Image: loadIcon(res.IconQuickAdd, 16),
|
||||
Items: []MenuItem{
|
||||
ActionRef{Action: &pv.portAction},
|
||||
ActionRef{Action: &pv.rdAction},
|
||||
@ -289,19 +290,19 @@ func (pv *ProxyView) createProxyTable() TableView {
|
||||
},
|
||||
Action{
|
||||
Text: i18n.Sprintf("Import from Clipboard"),
|
||||
Image: loadIcon(consts.IconClipboard, 16),
|
||||
Image: loadIcon(res.IconClipboard, 16),
|
||||
OnTriggered: pv.onClipboardImport,
|
||||
},
|
||||
Separator{},
|
||||
Action{
|
||||
Enabled: Bind("proxy.CurrentIndex >= 0"),
|
||||
Text: i18n.Sprintf("Copy Access Address"),
|
||||
Image: loadIcon(consts.IconSysCopy, 16),
|
||||
Image: loadIcon(res.IconSysCopy, 16),
|
||||
OnTriggered: pv.onCopyAccessAddr,
|
||||
},
|
||||
Menu{
|
||||
Text: i18n.Sprintf("Open Config"),
|
||||
Image: loadIcon(consts.IconOpen, 16),
|
||||
Image: loadIcon(res.IconOpen, 16),
|
||||
Items: []MenuItem{
|
||||
ActionRef{Action: &pv.openConfAction},
|
||||
ActionRef{Action: &pv.showConfAction},
|
||||
@ -317,11 +318,11 @@ func (pv *ProxyView) createProxyTable() TableView {
|
||||
if _, proxy := pv.getConfigProxy(style.Row()); proxy != nil {
|
||||
if proxy.Disabled {
|
||||
// Disabled proxy
|
||||
style.TextColor = consts.ColorGray
|
||||
style.BackgroundColor = consts.ColorGrayBG
|
||||
style.TextColor = res.ColorGray
|
||||
style.BackgroundColor = res.ColorGrayBG
|
||||
} else if proxy.IsVisitor() {
|
||||
// Visitor proxy
|
||||
style.TextColor = consts.ColorBlue
|
||||
style.TextColor = res.ColorBlue
|
||||
}
|
||||
// Normal proxy is default black text
|
||||
}
|
||||
@ -532,10 +533,10 @@ func (pv *ProxyView) switchToggleAction() {
|
||||
}
|
||||
if proxy.Disabled {
|
||||
pv.toggleAction.SetText(i18n.Sprintf("Enable"))
|
||||
pv.toggleAction.SetImage(loadIcon(consts.IconEnable, 16))
|
||||
pv.toggleAction.SetImage(loadIcon(res.IconEnable, 16))
|
||||
} else {
|
||||
pv.toggleAction.SetText(i18n.Sprintf("Disable"))
|
||||
pv.toggleAction.SetImage(loadIcon(consts.IconDisable, 16))
|
||||
pv.toggleAction.SetImage(loadIcon(res.IconDisable, 16))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/util"
|
||||
"github.com/koho/frpmgr/pkg/validators"
|
||||
)
|
||||
@ -48,11 +48,11 @@ func NewSimpleProxyDialog(title string, icon *walk.Icon, service string, types [
|
||||
func (sp *SimpleProxyDialog) Run(owner walk.Form) (int, error) {
|
||||
widgets := []Widget{
|
||||
Label{Text: i18n.SprintfColon("Remote Port"), ColumnSpan: 2},
|
||||
LineEdit{Text: Bind("RemotePort", consts.ValidatePortRange...), ColumnSpan: 2},
|
||||
LineEdit{Text: Bind("RemotePort", res.ValidatePortRange...), ColumnSpan: 2},
|
||||
Label{Text: i18n.SprintfColon("Local Address")},
|
||||
Label{Text: i18n.SprintfColon("Port")},
|
||||
LineEdit{Text: Bind("LocalAddr", consts.ValidateNonEmpty), StretchFactor: 2},
|
||||
LineEdit{Text: Bind("LocalPort", consts.ValidatePortRange...), StretchFactor: 1},
|
||||
LineEdit{Text: Bind("LocalAddr", res.ValidateNonEmpty), StretchFactor: 2},
|
||||
LineEdit{Text: Bind("LocalPort", res.ValidatePortRange...), StretchFactor: 1},
|
||||
}
|
||||
switch sp.service {
|
||||
case "ftp":
|
||||
@ -60,9 +60,9 @@ func (sp *SimpleProxyDialog) Run(owner walk.Form) (int, error) {
|
||||
widgets = append(widgets, Label{Text: i18n.SprintfColon("Passive Port Range"), ColumnSpan: 2}, Composite{
|
||||
Layout: HBox{MarginsZero: true},
|
||||
Children: []Widget{
|
||||
LineEdit{AssignTo: &lPortMinEdit, Text: Bind("LocalPortMin", consts.ValidatePortRange...)},
|
||||
LineEdit{AssignTo: &lPortMinEdit, Text: Bind("LocalPortMin", res.ValidatePortRange...)},
|
||||
Label{Text: "-"},
|
||||
LineEdit{Text: Bind("LocalPortMax", append(consts.ValidatePortRange, validators.GTE{Value: &lPortMinEdit})...)},
|
||||
LineEdit{Text: Bind("LocalPortMax", append(res.ValidatePortRange, validators.GTE{Value: &lPortMinEdit})...)},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
6
ui/ui.go
6
ui/ui.go
@ -12,7 +12,7 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/util"
|
||||
"github.com/koho/frpmgr/services"
|
||||
)
|
||||
@ -77,7 +77,7 @@ func RunUI() error {
|
||||
Persistent: true,
|
||||
Visible: false,
|
||||
Layout: VBox{Margins: Margins{Left: 5, Top: 5, Right: 5, Bottom: 5}},
|
||||
Font: consts.TextRegular,
|
||||
Font: res.TextRegular,
|
||||
Children: []Widget{
|
||||
TabWidget{
|
||||
AssignTo: &fm.tabs,
|
||||
@ -158,7 +158,7 @@ func openFolder(path string) {
|
||||
// openFileDialog shows a file dialog to choose file or directory and sends the selected path to the LineEdit view
|
||||
func openFileDialog(receiver *walk.LineEdit, title string, filter string, file bool) error {
|
||||
dlg := walk.FileDialog{
|
||||
Filter: filter + consts.FilterAllFiles,
|
||||
Filter: filter + res.FilterAllFiles,
|
||||
Title: title,
|
||||
}
|
||||
var ok bool
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/util"
|
||||
)
|
||||
|
||||
@ -54,12 +54,12 @@ func NewURLImportDialog() *URLImportDialog {
|
||||
}
|
||||
|
||||
func (ud *URLImportDialog) Run(owner walk.Form) (int, error) {
|
||||
return NewBasicDialog(&ud.Dialog, i18n.Sprintf("Import from URL"), loadIcon(consts.IconURLImport, 32),
|
||||
return NewBasicDialog(&ud.Dialog, i18n.Sprintf("Import from URL"), loadIcon(res.IconURLImport, 32),
|
||||
DataBinder{AssignTo: &ud.db, DataSource: &ud.viewModel, Name: "vm"}, ud.onImport,
|
||||
Label{Text: i18n.Sprintf("* Support batch import, one link per line.")},
|
||||
TextEdit{
|
||||
Enabled: Bind("!vm.Working"),
|
||||
Text: Bind("URLs", consts.ValidateNonEmpty),
|
||||
Text: Bind("URLs", res.ValidateNonEmpty),
|
||||
VScroll: true,
|
||||
MinSize: Size{Width: 430, Height: 130},
|
||||
},
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/res"
|
||||
"github.com/koho/frpmgr/pkg/sec"
|
||||
)
|
||||
|
||||
@ -27,7 +27,7 @@ func NewValidateDialog() *ValidateDialog {
|
||||
}
|
||||
|
||||
func (vd *ValidateDialog) Run() (int, error) {
|
||||
name, err := syscall.UTF16PtrFromString(consts.DialogValidate)
|
||||
name, err := syscall.UTF16PtrFromString(res.DialogValidate)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@ -44,9 +44,9 @@ func (vd *ValidateDialog) proc(h win.HWND, msg uint32, wp, lp uintptr) uintptr {
|
||||
switch msg {
|
||||
case win.WM_INITDIALOG:
|
||||
SetWindowText(h, fmt.Sprintf("%s - %s", i18n.Sprintf("Enter Password"), AppLocalName))
|
||||
SetWindowText(win.GetDlgItem(h, consts.DialogTitle), i18n.Sprintf("You must enter an administration password to operate the %s.", AppLocalName))
|
||||
SetWindowText(win.GetDlgItem(h, consts.DialogStatic1), i18n.Sprintf("Enter Administration Password"))
|
||||
SetWindowText(win.GetDlgItem(h, consts.DialogStatic2), i18n.SprintfColon("Password"))
|
||||
SetWindowText(win.GetDlgItem(h, res.DialogTitle), i18n.Sprintf("You must enter an administration password to operate the %s.", AppLocalName))
|
||||
SetWindowText(win.GetDlgItem(h, res.DialogStatic1), i18n.Sprintf("Enter Administration Password"))
|
||||
SetWindowText(win.GetDlgItem(h, res.DialogStatic2), i18n.SprintfColon("Password"))
|
||||
SetWindowText(win.GetDlgItem(h, win.IDOK), i18n.Sprintf("OK"))
|
||||
SetWindowText(win.GetDlgItem(h, win.IDCANCEL), i18n.Sprintf("Cancel"))
|
||||
vd.setIcon(h, int(win.GetDpiForWindow(h)))
|
||||
@ -54,11 +54,11 @@ func (vd *ValidateDialog) proc(h win.HWND, msg uint32, wp, lp uintptr) uintptr {
|
||||
case win.WM_COMMAND:
|
||||
switch win.LOWORD(uint32(wp)) {
|
||||
case win.IDOK:
|
||||
passwd := GetWindowText(win.GetDlgItem(h, consts.DialogEdit))
|
||||
passwd := GetWindowText(win.GetDlgItem(h, res.DialogEdit))
|
||||
if sec.EncryptPassword(passwd) != appConf.Password {
|
||||
win.MessageBox(h, windows.StringToUTF16Ptr(i18n.Sprintf("The password is incorrect. Re-enter password.")),
|
||||
windows.StringToUTF16Ptr(AppLocalName), windows.MB_ICONERROR)
|
||||
win.SetFocus(win.GetDlgItem(h, consts.DialogEdit))
|
||||
win.SetFocus(win.GetDlgItem(h, res.DialogEdit))
|
||||
} else {
|
||||
win.EndDialog(h, win.IDOK)
|
||||
}
|
||||
@ -80,7 +80,7 @@ func (vd *ValidateDialog) setIcon(h win.HWND, dpi int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iconFile, err := syscall.UTF16PtrFromString(filepath.Join(system32, consts.IconKey.Dll+".dll"))
|
||||
iconFile, err := syscall.UTF16PtrFromString(filepath.Join(system32, res.IconKey.Dll+".dll"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -89,10 +89,10 @@ func (vd *ValidateDialog) setIcon(h win.HWND, dpi int) error {
|
||||
vd.hIcon = 0
|
||||
}
|
||||
size := walk.SizeFrom96DPI(walk.Size{Width: 32, Height: 32}, dpi)
|
||||
win.SHDefExtractIcon(iconFile, int32(consts.IconKey.Index),
|
||||
win.SHDefExtractIcon(iconFile, int32(res.IconKey.Index),
|
||||
0, nil, &vd.hIcon, win.MAKELONG(0, uint16(size.Width)))
|
||||
if vd.hIcon != 0 {
|
||||
win.SendDlgItemMessage(h, consts.DialogIcon, stmSetIcon, uintptr(vd.hIcon), 0)
|
||||
win.SendDlgItemMessage(h, res.DialogIcon, stmSetIcon, uintptr(vd.hIcon), 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user