feat: v2 supports international version (#11939)

This commit is contained in:
ssongliu
2026-02-25 10:56:06 +08:00
committed by GitHub
parent a8a92da483
commit c3f45d4787
47 changed files with 223 additions and 92 deletions

View File

@@ -19,6 +19,7 @@ type NodeInfo struct {
Scope string `json:"scope"`
BaseDir string `json:"baseDir"`
NodePort uint `json:"nodePort"`
Edition string `json:"edition"`
Version string `json:"version"`
ServerCrt string `json:"serverCrt"`
ServerKey string `json:"serverKey"`

View File

@@ -416,7 +416,7 @@ func (a AppService) Install(req request.AppInstallCreate, executeScript bool) (a
}
} else {
if appDetail.DockerCompose == "" {
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode, app.Key, appDetail.Version)
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
_, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return
@@ -817,7 +817,7 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
return res, nil
}
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.AppRepoURL(), global.CONF.Base.Mode)
_, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return nil, err
@@ -889,7 +889,7 @@ func getAppFromRepo(downloadPath string) error {
func getAppList() (*dto.AppList, error) {
list := &dto.AppList{}
if err := getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)); err != nil {
if err := getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.AppRepoURL(), global.CONF.Base.Mode)); err != nil {
return nil, err
}
listFile := filepath.Join(global.Dir.ResourceDir, "1panel.json")

View File

@@ -81,7 +81,7 @@ func (a AppService) createSyncAppStoreTask(sharedCtx **appSyncContext) func(t *t
ctx := &appSyncContext{
task: t,
httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.LoadRequestTransport()},
baseRemoteUrl: fmt.Sprintf("%s/%s/1panel", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode),
baseRemoteUrl: fmt.Sprintf("%s/%s/1panel", global.AppRepoURL(), global.CONF.Base.Mode),
systemVersion: setting.SystemVersion,
settingService: settingService,
list: list,

View File

@@ -4,11 +4,6 @@ base:
is_demo: false
is_offline: false
remote_url:
app_repo: https://apps-assets.fit2cloud.com
repo_url: https://resource.fit2cloud.com/1panel/package/v2
resource_url: https://resource.fit2cloud.com/1panel/resource/v2
log:
level: debug
time_zone: Asia/Shanghai

View File

@@ -2,13 +2,13 @@ package global
type ServerConfig struct {
Base Base `mapstructure:"base"`
RemoteURL RemoteURL `mapstructure:"remote_url"`
Log LogConfig `mapstructure:"log"`
DockerConfig DockerConfig
}
type Base struct {
Port string `mapstructure:"port"`
Edition string `mapstructure:"edition"` // [ cn / intl]
Version string `mapstructure:"version"`
EncryptKey string `mapstructure:"encrypt_key"`
Mode string `mapstructure:"mode"` // xpack [ Enable / Disable ]
@@ -17,12 +17,6 @@ type Base struct {
IsOffLine bool `mapstructure:"is_offline"`
}
type RemoteURL struct {
AppRepo string `mapstructure:"app_repo"`
RepoUrl string `mapstructure:"repo_url"`
ResourceUrl string `mapstructure:"resource_url"`
}
type SystemDir struct {
BaseDir string
DbDir string

View File

@@ -40,3 +40,25 @@ var (
TaskCtxMap = make(map[string]context.CancelFunc)
)
func RepoURL() string {
if CONF.Base.Edition == "cn" {
return "https://resource.fit2cloud.com/1panel/package/v2"
} else {
return "https://resource.1panel.pro"
}
}
func ResourceURL() string {
if CONF.Base.Edition == "cn" {
return "https://resource.fit2cloud.com/1panel/resource/v2"
} else {
return "https://resource.1panel.pro"
}
}
func AppRepoURL() string {
if CONF.Base.Edition == "cn" {
return "https://apps-assets.fit2cloud.com"
} else {
return "https://apps.1panel.pro"
}
}

View File

@@ -42,6 +42,7 @@ func initGlobalData() {
_ = settingRepo.Update("SystemVersion", node.Version)
}
global.CONF.Base.Version = node.Version
global.CONF.Base.Edition = node.Edition
global.CONF.Base.EncryptKey, _ = settingRepo.GetValueByKey("EncryptKey")
}

View File

@@ -105,7 +105,7 @@ func loadRestorePath(upgradeDir string) (string, error) {
}
func downloadLangFromRemote(fileOp files.FileOp) {
path := fmt.Sprintf("%s/language/lang.tar.gz", global.CONF.RemoteURL.RepoUrl)
path := fmt.Sprintf("%s/language/lang.tar.gz", global.RepoURL())
if err := fileOp.DownloadFile(path, "/usr/local/bin/lang.tar.gz"); err != nil {
global.LOG.Errorf("download lang.tar.gz failed, err: %v", err)
return
@@ -123,7 +123,7 @@ func downloadLangFromRemote(fileOp files.FileOp) {
}
func downloadGeoFromRemote(fileOp files.FileOp, targetPath string) {
_ = os.MkdirAll(path.Dir(targetPath), os.ModePerm)
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.CONF.RemoteURL.RepoUrl)
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.RepoURL())
if err := fileOp.DownloadFile(pathItem, targetPath); err != nil {
global.LOG.Errorf("download geo ip failed, err: %v", err)
return

View File

@@ -419,3 +419,22 @@ func GetDockerComposeCommand() string {
}
return ""
}
func LoadParams(param string) string {
stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param)
if err != nil {
panic(err)
}
info := strings.ReplaceAll(stdout, "\n", "")
if len(info) == 0 || info == `""` {
panic(fmt.Sprintf("error `%s` find in /usr/local/bin/1pctl", param))
}
return info
}
func LoadParamsWithoutPanic(param string) string {
stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param)
if err != nil {
return ""
}
return strings.ReplaceAll(stdout, "\n", "")
}

View File

@@ -51,7 +51,7 @@ func GetUpgradeVersionInfo() (*dto.UpgradeInfo, error) {
if strings.Contains(itemVersion, "beta") {
mode = "beta"
}
notes, err := loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, itemVersion, itemVersion))
notes, err := loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.RepoURL(), mode, itemVersion, itemVersion))
if err != nil {
return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err)
}
@@ -101,9 +101,9 @@ func loadVersionByMode(developer, currentVersion string) (string, string, string
}
func loadVersion(isLatest bool, currentVersion, mode string) string {
path := fmt.Sprintf("%s/%s/latest", global.CONF.RemoteURL.RepoUrl, mode)
path := fmt.Sprintf("%s/%s/latest", global.RepoURL(), mode)
if !isLatest {
path = fmt.Sprintf("%s/%s/latest.current", global.CONF.RemoteURL.RepoUrl, mode)
path = fmt.Sprintf("%s/%s/latest.current", global.RepoURL(), mode)
}
_, latestVersionRes, err := HandleRequest(path, http.MethodGet, constant.TimeOut20s)
if err != nil {

View File

@@ -4,17 +4,15 @@ package xpack
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/gin-gonic/gin"
)
@@ -26,25 +24,14 @@ func StartClam(startClam *model.Clam, isUpdate bool) (int, error) {
func LoadNodeInfo(isBase bool) (model.NodeInfo, error) {
var info model.NodeInfo
info.BaseDir = loadParams("BASE_DIR")
info.Version = loadParams("ORIGINAL_VERSION")
info.BaseDir = common.LoadParams("BASE_DIR")
info.Version = common.LoadParams("ORIGINAL_VERSION")
info.Edition = common.LoadParamsWithoutPanic("EDITION")
info.Scope = "master"
global.IsMaster = true
return info, nil
}
func loadParams(param string) string {
stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param)
if err != nil {
panic(err)
}
info := strings.ReplaceAll(stdout, "\n", "")
if len(info) == 0 || info == `""` {
panic(fmt.Sprintf("error `%s` find in /usr/local/bin/1pctl", param))
}
return info
}
func GetImagePrefix() string {
return ""
}

View File

@@ -211,7 +211,7 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
needCaptcha := global.IPTracker.NeedCaptcha(ip)
res := &dto.LoginSetting{
IsDemo: global.CONF.Base.IsDemo,
IsIntl: global.CONF.Base.IsIntl,
IsIntl: global.CONF.Base.Edition == "intl",
IsFxplay: global.CONF.Base.IsFxplay,
IsOffLine: global.CONF.Base.IsOffLine,
Language: settingInfo.Language,

View File

@@ -15,6 +15,7 @@ type SettingInfo struct {
Ipv6 string `json:"ipv6"`
BindAddress string `json:"bindAddress"`
PanelName string `json:"panelName"`
Edition string `json:"edition"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
Language string `json:"language"`

View File

@@ -201,7 +201,7 @@ func (u *ScriptService) Sync(req dto.OperateByTaskID) error {
}
syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("RemoteScriptLibrary"), task.TaskSync, task.TaskScopeScript), func(t *task.Task) (err error) {
versionUrl := fmt.Sprintf("%s/scripts/version.txt", global.CONF.RemoteURL.ResourceURL)
versionUrl := fmt.Sprintf("%s/scripts/version.txt", global.ResourceURL())
_, versionRes, err := req_helper.HandleRequestWithProxy(versionUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return fmt.Errorf("load scripts version from remote failed, err: %v", err)
@@ -216,7 +216,7 @@ func (u *ScriptService) Sync(req dto.OperateByTaskID) error {
return nil
}
dataUrl := fmt.Sprintf("%s/scripts/data.yaml", global.CONF.RemoteURL.ResourceURL)
dataUrl := fmt.Sprintf("%s/scripts/data.yaml", global.ResourceURL())
_, dataRes, err := req_helper.HandleRequestWithProxy(dataUrl, http.MethodGet, constant.TimeOut20s)
syncTask.LogWithStatus(i18n.GetMsgByKey("DownloadData"), err)
if err != nil {
@@ -232,7 +232,7 @@ func (u *ScriptService) Sync(req dto.OperateByTaskID) error {
if _, err := os.Stat(tmpDir); err != nil {
_ = os.MkdirAll(tmpDir, 0755)
}
scriptsUrl := fmt.Sprintf("%s/scripts/scripts.tar.gz", global.CONF.RemoteURL.ResourceURL)
scriptsUrl := fmt.Sprintf("%s/scripts/scripts.tar.gz", global.ResourceURL())
err = files.DownloadFileWithProxyStream(scriptsUrl, tmpDir+"/scripts.tar.gz")
syncTask.LogWithStatus(i18n.GetMsgByKey("DownloadPackage"), err)
if err != nil {

View File

@@ -61,8 +61,6 @@ type ISettingService interface {
UpdateSystemSSL() error
GenerateRSAKey() error
GetLoginSetting() (*dto.SystemSetting, error)
UpdateAppstoreConfig(req dto.AppstoreUpdate) error
GetAppstoreConfig() (*dto.AppstoreConfig, error)
DefaultMenu() error
@@ -92,6 +90,10 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
if err := json.Unmarshal(arr, &info); err != nil {
return nil, err
}
if info.Edition == "" {
info.Edition = "cn"
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
}
if info.ProxyPasswdKeep != constant.StatusEnable {
info.ProxyPasswd = ""
} else {
@@ -168,6 +170,8 @@ func (u *SettingService) Update(key, value string) error {
} else {
global.Cron.Remove(global.ScriptSyncJobID)
}
case "Edition":
global.CONF.Base.Edition = value
}
return nil
@@ -679,19 +683,6 @@ func (u *SettingService) GenerateRSAKey() error {
return nil
}
func (u *SettingService) GetLoginSetting() (*dto.SystemSetting, error) {
settingInfo, err := u.GetSettingInfo()
if err != nil {
return nil, err
}
res := &dto.SystemSetting{
Language: settingInfo.Language,
IsDemo: global.CONF.Base.IsDemo,
IsIntl: global.CONF.Base.IsIntl,
}
return res, nil
}
func (u *SettingService) UpdateAppstoreConfig(req dto.AppstoreUpdate) error {
return settingRepo.UpdateOrCreate(req.Scope, req.Status)
}

View File

@@ -117,7 +117,7 @@ func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
if strings.HasPrefix(upgrade.TestVersion, upgrade.LatestVersion+"-beta") {
upgrade.TestVersion = ""
}
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, itemVersion, itemVersion))
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.RepoURL(), mode, itemVersion, itemVersion))
if err != nil {
return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err)
}
@@ -130,7 +130,7 @@ func (u *UpgradeService) LoadNotes(req dto.Upgrade) (string, error) {
if strings.Contains(req.Version, "beta") {
mode = "beta"
}
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, req.Version, req.Version))
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.RepoURL(), mode, req.Version, req.Version))
if err != nil {
return "", fmt.Errorf("load releases-notes of version %s failed, err: %v", req.Version, err)
}
@@ -162,7 +162,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
if strings.Contains(req.Version, "beta") {
mode = "beta"
}
downloadPath := fmt.Sprintf("%s/%s/%s/release", global.CONF.RemoteURL.RepoUrl, mode, req.Version)
downloadPath := fmt.Sprintf("%s/%s/%s/release", global.RepoURL(), mode, req.Version)
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch)
_ = settingRepo.Update("SystemStatus", "Upgrading")
go func() {
@@ -442,9 +442,9 @@ func (u *UpgradeService) loadVersionByMode(developer, currentVersion string) (st
}
func (u *UpgradeService) loadVersion(isLatest bool, currentVersion, mode string) string {
path := fmt.Sprintf("%s/%s/latest", global.CONF.RemoteURL.RepoUrl, mode)
path := fmt.Sprintf("%s/%s/latest", global.RepoURL(), mode)
if !isLatest {
path = fmt.Sprintf("%s/%s/latest.current", global.CONF.RemoteURL.RepoUrl, mode)
path = fmt.Sprintf("%s/%s/latest.current", global.RepoURL(), mode)
}
_, latestVersionRes, err := req_helper.HandleRequestWithProxy(path, http.MethodGet, constant.TimeOut20s)
if err != nil {

View File

@@ -10,11 +10,6 @@ base:
password: admin123
version: v2.0.0
remote_url:
app_repo: https://apps-assets.fit2cloud.com
repo_url: https://resource.fit2cloud.com/1panel/package/v2
resource_url: https://resource.fit2cloud.com/1panel/resource/v2
log:
level: debug
time_zone: Asia/Shanghai

View File

@@ -3,7 +3,6 @@ package global
type ServerConfig struct {
Base Base `mapstructure:"base"`
Conn Conn `mapstructure:"conn"`
RemoteURL RemoteURL `mapstructure:"remote_url"`
LogConfig LogConfig `mapstructure:"log"`
}
@@ -13,9 +12,9 @@ type Base struct {
Password string `mapstructure:"password"`
Language string `mapstructure:"language"`
IsDemo bool `mapstructure:"is_demo"`
IsIntl bool `mapstructure:"is_intl"`
IsOffLine bool `mapstructure:"is_offline"`
IsFxplay bool `mapstructure:"is_fxplay"`
Edition string `mapstructure:"edition"`
Version string `mapstructure:"version"`
InstallDir string `mapstructure:"install_dir"`
ChangeUserInfo string `mapstructure:"change_user_info"`
@@ -37,11 +36,6 @@ type ApiInterface struct {
ApiKeyValidityTime string `mapstructure:"api_key_validity_time"`
}
type RemoteURL struct {
RepoUrl string `mapstructure:"repo_url"`
ResourceURL string `mapstructure:"resource_url"`
}
type LogConfig struct {
Level string `mapstructure:"level"`
TimeZone string `mapstructure:"timeZone"`

View File

@@ -34,3 +34,25 @@ var (
)
type DBOption func(*gorm.DB) *gorm.DB
func RepoURL() string {
if CONF.Base.Edition == "cn" {
return "https://resource.fit2cloud.com/1panel/package/v2"
} else {
return "https://resource.1panel.pro"
}
}
func ResourceURL() string {
if CONF.Base.Edition == "cn" {
return "https://resource.fit2cloud.com/1panel/resource/v2"
} else {
return "https://resource.1panel.pro"
}
}
func AppRepoURL() string {
if CONF.Base.Edition == "cn" {
return "https://apps-assets.fit2cloud.com"
} else {
return "https://apps.1panel.pro"
}
}

View File

@@ -86,6 +86,7 @@ ErrNoSuchNode: "Node information not found, please check and retry!"
ErrNodeUnbind: "This node is not within the license binding range, please check and retry!"
ErrNodeBind: "This node is already bound to a license, please check and retry!"
ErrNodeLocalRollback: "The primary node does not support direct rollback. Please manually execute the '1pctl restore' command to rollback!"
ErrIntlLicense: "The current version does not support importing international licenses yet. Stay tuned!"
InvalidRequestBodyType: "Invalid request body format, please check and ensure the content meets the required format before retrying!"
InvalidLicenseCodeType: "Invalid license code format provided, please check and try again!"

View File

@@ -86,6 +86,7 @@ ErrNoSuchNode: "Información del nodo no encontrada, ¡verifique e intente de nu
ErrNodeUnbind: "Este nodo no está dentro del rango de vinculación de la licencia, ¡verifique e intente de nuevo!"
ErrNodeBind: "Este nodo ya está vinculado a una licencia, ¡verifique e intente de nuevo!"
ErrNodeLocalRollback: "El nodo principal no admite la reversión directa. ¡Ejecute manualmente el comando '1pctl restore' para revertir!"
ErrIntlLicense: "La versión actual aún no admite importar licencias de la edición internacional. ¡Próximamente!"
InvalidRequestBodyType: "Formato del cuerpo de la solicitud no válido, por favor revisa y asegúrate de que el contenido cumpla con el formato requerido antes de reintentar."
InvalidLicenseCodeType: "Formato de código de licencia no válido, por favor revisa e inténtalo de nuevo."
LicenseNotFoundType: "Licencia no encontrada, no existe ningún registro coincidente en el sistema para la licencia proporcionada. Por favor revisa e inténtalo de nuevo."

View File

@@ -81,6 +81,7 @@ ErrNoSuchNode: "そのノード情報が見つかりませんでした、確認
ErrNodeUnbind: "そのノードはライセンスのバインド範囲内ではありません、確認して再試行してください!"
ErrNodeBind: "そのノードはライセンスにバインドされています、確認して再試行してください!"
ErrNodeLocalRollback: "マスターードは直接ロールバックをサポートしていません。手動で「1pctl restore」コマンドを実行してロールバックしてください"
ErrIntlLicense: "現在のバージョンでは国際版ライセンスのインポートはまだサポートされていません。しばらくお待ちください!"
InvalidRequestBodyType: "リクエストボディの形式が無効です。内容が要求された形式に準拠しているか確認してから再試行してください!"
InvalidLicenseCodeType: "提供されたライセンスコードの形式が無効です。確認してから再試行してください!"

View File

@@ -80,6 +80,7 @@ ErrNoSuchNode: "노드 정보를 찾을 수 없습니다. 다시 확인하고
ErrNodeUnbind: "이 노드가 라이선스 바인딩 범위에 있지 않음을 감지하였습니다. 다시 확인하고 시도해 주세요!"
ErrNodeBind: "이 노드가 이미 라이선스에 바인딩되어 있음을 감지하였습니다. 다시 확인하고 시도해 주세요!"
ErrNodeLocalRollback: "마스터 노드는 직접 롤백을 지원하지 않습니다. 수동으로 '1pctl restore' 명령어를 실행하여 롤백하세요!"
ErrIntlLicense: "현재 버전에서는 국제판 라이선스 가져오기를 아직 지원하지 않습니다. 곧 제공될 예정입니다!"
InvalidRequestBodyType: "요청 본문 형식이 잘못되었습니다. 내용이 형식 요구 사항을 충족하는지 확인한 후 다시 시도하세요!"
InvalidLicenseCodeType: "제공된 라이선스 코드 형식이 잘못되었습니다. 확인 후 다시 시도하세요!"

View File

@@ -75,6 +75,7 @@ ErrNoSuchNode: "Maklumat nod tidak ditemui, sila semak dan cuba lagi!"
ErrNodeUnbind: "Nod di luar skop lesen dikesan, sila semak dan cuba lagi!"
ErrNodeBind: "Nod ini telah diikat dengan lesen, sila semak dan cuba lagi!"
ErrNodeLocalRollback: "Nod utama tidak menyokong rollback secara langsung. Sila laksanakan arahan '1pctl restore' secara manual untuk rollback!"
ErrIntlLicense: "Versi semasa belum menyokong import lesen edisi antarabangsa. Nantikan!"
InvalidRequestBodyType: "Format badan permintaan tidak sah, sila periksa dan pastikan kandungan memenuhi format yang diperlukan sebelum mencuba semula!"
InvalidLicenseCodeType: "Format kod lesen yang diberikan tidak sah, sila periksa dan cuba lagi!"

View File

@@ -80,6 +80,7 @@ ErrNoSuchNode: "As informações deste nó não foram encontradas, por favor ver
ErrNodeUnbind: "Nó fora do escopo da licença detectado, por favor verifique e tente novamente!"
ErrNodeBind: "Este nó já está vinculado a uma licença, por favor verifique e tente novamente!"
ErrNodeLocalRollback: "O nó principal não suporta rollback direto. Por favor, execute manualmente o comando '1pctl restore' para fazer o rollback!"
ErrIntlLicense: "A versão atual ainda não oferece suporte à importação de licença da edição internacional. Em breve!"
InvalidRequestBodyType: "Formato do corpo da requisição inválido. Verifique se o conteúdo está no formato correto e tente novamente!"
InvalidLicenseCodeType: "Formato do código de licença inválido. Verifique e tente novamente!"

View File

@@ -80,6 +80,7 @@ ErrNoSuchNode: "Информация об узле не найдена, пров
ErrNodeUnbind: "Обнаружен узел вне области действия лицензии, проверьте и повторите попытку!"
ErrNodeBind: "Этот узел уже связан с лицензией, проверьте и повторите попытку!"
ErrNodeLocalRollback: "Основной узел не поддерживает прямой откат. Пожалуйста, вручную выполните команду '1pctl restore' для отката!"
ErrIntlLicense: "Текущая версия пока не поддерживает импорт международной лицензии. Скоро будет доступно!"
InvalidRequestBodyType: "Неверный формат тела запроса. Проверьте, соответствует ли содержимое требуемому формату, и повторите попытку!"
InvalidLicenseCodeType: "Указан неверный формат лицензионного кода. Проверьте и повторите попытку!"

View File

@@ -79,6 +79,7 @@ ErrNoSuchNode: "Düğüm bilgisi bulunamadı, lütfen kontrol edip tekrar deneyi
ErrNodeUnbind: "Bu düğüm lisans bağlama aralığında değil, lütfen kontrol edip tekrar deneyin!"
ErrNodeBind: "Bu düğüm zaten bir lisansa bağlı, lütfen kontrol edip tekrar deneyin!"
ErrNodeLocalRollback: "Ana düğüm doğrudan geri alma desteklemiyor. Lütfen geri almak için '1pctl restore' komutunu manuel olarak çalıştırın!"
ErrIntlLicense: "Mevcut sürüm uluslararası sürüm lisansının içe aktarılmasını henüz desteklemiyor. Yakında!"
InvalidRequestBodyType: "Geçersiz istek gövdesi formatı, lütfen içeriğin format gereksinimlerine uygun olduğunu kontrol edip tekrar deneyin!"
InvalidLicenseCodeType: "Sağlanan lisans kodu formatı geçersiz, lütfen kontrol edip tekrar deneyin!"

View File

@@ -80,6 +80,7 @@ ErrNoSuchNode: "未能找到該節點資訊,請檢查後重試!"
ErrNodeUnbind: "檢測到該節點未在許可證綁定範圍內,請檢查後重試!"
ErrNodeBind: "檢測到該節點已綁定許可證,請檢查後重試!"
ErrNodeLocalRollback: "主節點暫不支援直接回滾請手動執行「1pctl restore」指令回滾"
ErrIntlLicense: "當前版本暫不支援匯入國際版許可證,敬請期待!"
InvalidRequestBodyType: "請求體格式錯誤,請檢查請求內容是否符合格式要求後重試!"
InvalidLicenseCodeType: "提供的許可證格式錯誤,請檢查後重試!"

View File

@@ -86,6 +86,7 @@ ErrNoSuchNode: "未能找到该节点信息,请检查后重试!"
ErrNodeUnbind: "检测到该节点未在许可证绑定范围内,请检查后重试!"
ErrNodeBind: "检测到该节点已绑定许可证,请检查后重试!"
ErrNodeLocalRollback: "主节点暂不支持直接回滚,请手动执行 1pctl restore 命令回滚!"
ErrIntlLicense: "当前版本暂不支持导入国际版许可证,敬请期待!"
InvalidRequestBodyType: "请求体格式错误,请检查请求内容是否符合格式要求后重试!"
InvalidLicenseCodeType: "提供的许可证格式错误,请检查后重试!"

View File

@@ -104,7 +104,7 @@ func loadRestorePath(upgradeDir string) (string, error) {
}
func downloadLangFromRemote() {
path := fmt.Sprintf("%s/language/lang.tar.gz", global.CONF.RemoteURL.RepoUrl)
path := fmt.Sprintf("%s/language/lang.tar.gz", global.RepoURL())
if err := fileUtils.DownloadFile(path, "/usr/local/bin/lang.tar.gz"); err != nil {
global.LOG.Errorf("download lang.tar.gz failed, err: %v", err)
return
@@ -122,7 +122,7 @@ func downloadLangFromRemote() {
}
func downloadGeoFromRemote(targetPath string) {
_ = os.MkdirAll(path.Dir(targetPath), os.ModePerm)
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.CONF.RemoteURL.RepoUrl)
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.RepoURL())
if err := fileUtils.DownloadFile(pathItem, targetPath); err != nil {
global.LOG.Errorf("download geo ip failed, err: %v", err)
return

View File

@@ -16,6 +16,7 @@ func Init() {
settingRepo := repo.NewISettingRepo()
global.CONF.Conn.Port, _ = settingRepo.GetValueByKey("ServerPort")
global.CONF.Conn.Ipv6, _ = settingRepo.GetValueByKey("Ipv6")
global.CONF.Base.Edition, _ = settingRepo.GetValueByKey("Edition")
global.Api.ApiInterfaceStatus, _ = settingRepo.GetValueByKey("ApiInterfaceStatus")
if global.Api.ApiInterfaceStatus == constant.StatusEnable {
global.Api.ApiKey, _ = settingRepo.GetValueByKey("ApiKey")

View File

@@ -32,6 +32,7 @@ func Init() {
migrations.AdjustXpackNode,
migrations.UpdateAiAgentsMenu,
migrations.AddDashboardCarouselSetting,
migrations.AddEditionSetting,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@@ -64,6 +64,9 @@ var InitSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "PanelName", Value: "1Panel"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Edition", Value: global.CONF.Base.Edition}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Language", Value: language}).Error; err != nil {
return err
}
@@ -814,3 +817,20 @@ var UpdateAiAgentsMenu = &gormigrate.Migration{
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var AddEditionSetting = &gormigrate.Migration{
ID: "20260224-add-edition-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "Edition").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "Edition", Value: global.CONF.Base.Edition}).Error
}
return err
}
if setting.Value == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "Edition").Update("value", global.CONF.Base.Edition).Error
}
return nil
},
}

View File

@@ -21,7 +21,7 @@ func Init() {
port := "9999"
mode := ""
version := "v2.0.0"
username, password, entrance, language := "", "", "", "zh"
username, password, entrance, language, edition := "", "", "", "zh", ""
v := viper.NewWithOptions()
v.SetConfigType("yaml")
@@ -37,7 +37,7 @@ func Init() {
v.SetConfigName("app")
v.AddConfigPath(path.Join("/opt/1panel/conf"))
if err := v.ReadInConfig(); err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
panic(fmt.Errorf("fatal error config file: %s", err))
}
} else {
baseDir = common.LoadParams("BASE_DIR")
@@ -47,10 +47,11 @@ func Init() {
password = common.LoadParams("ORIGINAL_PASSWORD")
entrance = common.LoadParams("ORIGINAL_ENTRANCE")
language = common.LoadParams("LANGUAGE")
edition = common.LoadParamsWithoutPanic("EDITION")
reader := bytes.NewReader(conf.AppYaml)
if err := v.ReadConfig(reader); err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
panic(fmt.Errorf("fatal error config file: %s", err))
}
}
v.OnConfigChange(func(e fsnotify.Event) {
@@ -82,17 +83,18 @@ func Init() {
if serverConfig.Conn.Entrance != "" {
entrance = serverConfig.Conn.Entrance
}
if serverConfig.Base.IsIntl {
language = "en"
}
}
global.CONF = serverConfig
global.CONF.Base.InstallDir = baseDir
global.CONF.Base.IsDemo = v.GetBool("base.is_demo")
global.CONF.Base.IsIntl = v.GetBool("base.is_intl")
global.CONF.Base.IsFxplay = v.GetBool("base.is_fxplay")
global.CONF.Base.IsOffLine = v.GetBool("base.is_offline")
if edition == "intl" {
global.CONF.Base.Edition = "intl"
} else {
global.CONF.Base.Edition = "cn"
}
global.CONF.Base.Version = version
global.CONF.Base.Username = username
global.CONF.Base.Password = password

View File

@@ -245,6 +245,13 @@ func LoadParams(param string) string {
}
return info
}
func LoadParamsWithoutPanic(param string) string {
stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param)
if err != nil {
return ""
}
return strings.ReplaceAll(stdout, "\n", "")
}
func GetRealClientIP(c *gin.Context) string {
addr := c.Request.RemoteAddr

View File

@@ -17,6 +17,7 @@ export namespace Setting {
ntpSite: string;
panelName: string;
edition: string;
theme: string;
menuTabs: string;
language: string;

View File

@@ -1812,6 +1812,11 @@ const message = {
light: 'Light',
auto: 'Follow System',
language: 'Language',
region: 'Region',
regionHelper:
'Different regions use different download sources and related documentation links for app store and script library. Choose as needed.',
cn: 'China Mainland',
intl: 'Overseas',
languageHelper:
'By default, it follows the browser language. This parameter takes effect only on the current browser',
sessionTimeout: 'Session timeout',

View File

@@ -1818,6 +1818,11 @@ const message = {
light: 'Claro',
auto: 'Seguir sistema',
language: 'Idioma',
region: 'Región de operación',
regionHelper:
'Diferentes regiones tienen distintas direcciones de descarga y enlaces de documentación para la tienda de aplicaciones y la biblioteca de scripts.',
cn: 'China continental',
intl: 'Internacional',
languageHelper:
'Por defecto sigue el idioma del navegador. Este parámetro solo tiene efecto en el navegador actual',
sessionTimeout: 'Tiempo de espera de sesión',

View File

@@ -1770,6 +1770,11 @@ const message = {
light: 'ライト',
auto: 'システムをフォローします',
language: '言語',
region: '運用リージョン',
regionHelper:
'運用リージョンにより、アプリストアやスクリプトライブラリのダウンロード先および関連ドキュメントのリンクが異なります。',
cn: '中国本土',
intl: '海外',
languageHelper:
'デフォルトでは、ブラウザ言語に従います。このパラメーターは、現在のブラウザでのみ有効になります',
sessionTimeout: 'セッションタイムアウト',

View File

@@ -1749,6 +1749,11 @@ const message = {
light: '라이트',
auto: '시스템 따라가기',
language: '언어',
region: '운영 지역',
regionHelper:
'운영 지역에 따라 앱 스토어, 스크립트 라이브러리의 다운로드 주소 및 관련 문서 링크가 다를 수 있습니다.',
cn: '중국 본토',
intl: '해외',
languageHelper: '기본적으로 브라우저 언어를 따릅니다. 이 설정은 현재 브라우저에서만 적용됩니다.',
sessionTimeout: '세션 타임아웃',
sessionTimeoutError: '최소 세션 타임아웃은 300초입니다.',

View File

@@ -1816,6 +1816,11 @@ const message = {
light: 'Terang',
auto: 'Ikut Sistem',
language: 'Bahasa',
region: 'Wilayah operasi',
regionHelper:
'Wilayah operasi yang berbeza mempunyai alamat muat turun dan pautan dokumentasi berbeza untuk gedung aplikasi serta pustaka skrip.',
cn: 'Tanah Besar China',
intl: 'Antarabangsa',
languageHelper:
'Secara lalai, ia mengikuti bahasa penyemak imbas. Parameter ini hanya berkuat kuasa pada penyemak imbas semasa',
sessionTimeout: 'Tempoh tamat sesi',

View File

@@ -1805,6 +1805,11 @@ const message = {
light: 'Claro',
auto: 'Seguir o sistema',
language: 'Idioma',
region: 'Região de operação',
regionHelper:
'Regiões de operação diferentes usam endereços de download e links de documentação distintos para a loja de aplicativos e a biblioteca de scripts.',
cn: 'China continental',
intl: 'Internacional',
languageHelper: 'Por padrão, segue o idioma do navegador. Este parâmetro tem efeito apenas no navegador atual',
sessionTimeout: 'Tempo limite de sessão',
sessionTimeoutError: 'O tempo mínimo de sessão é de 300 segundos',

View File

@@ -1805,6 +1805,11 @@ const message = {
light: 'Светлая',
auto: 'Как в системе',
language: 'Язык',
region: 'Регион работы',
regionHelper:
'Для разных регионов используются разные адреса загрузки и ссылки на документацию для магазина приложений и библиотеки скриптов.',
cn: 'Материковый Китай',
intl: 'Международный',
languageHelper: 'По умолчанию следует языку браузера. Этот параметр действует только в текущем браузере',
sessionTimeout: 'Время сессии',
sessionTimeoutError: 'Минимальное время сессии 300 секунд',

View File

@@ -1831,6 +1831,11 @@ const message = {
light: 'Açık',
auto: 'Sistemi takip et',
language: 'Dil',
region: 'Çalışma bölgesi',
regionHelper:
'Farklı çalışma bölgelerinde uygulama mağazası ve betik kütüphanesi için indirme adresleri ile ilgili dokümantasyon bağlantıları farklı olabilir.',
cn: 'Çin anakarası',
intl: 'Uluslararası',
languageHelper:
'Varsayılan olarak tarayıcı dilini takip eder. Bu parametre yalnızca geçerli tarayıcıda etkilidir',
sessionTimeout: 'Oturum zaman aşımı',

View File

@@ -1716,6 +1716,10 @@ const message = {
light: '亮色',
auto: '跟隨系統',
language: '系統語言',
region: '運行區域',
regionHelper: '不同運行區域的應用商店腳本庫下載地址及相關文件連結有所差異請按需選擇',
cn: '中國大陸',
intl: '海外',
languageHelper: '預設跟隨瀏覽器語言設定後只對目前瀏覽器生效更換瀏覽器後需要重新設定',
sessionTimeout: '超時時間',
sessionTimeoutError: '最小超時時間為 300 ',

View File

@@ -1720,6 +1720,10 @@ const message = {
light: '亮色',
auto: '跟随系统',
language: '系统语言',
region: '运行区域',
regionHelper: '不同运行区域应用商店脚本库等下载地址及相关文档链接有所区别请按需选择',
cn: '中国大陆',
intl: '海外',
languageHelper: '默认跟随浏览器语言设置后只对当前浏览器生效更换浏览器后需要重新设置',
sessionTimeout: '超时时间',
sessionTimeoutError: '最小超时时间为 300 ',

View File

@@ -201,6 +201,18 @@
{{ $t('commons.button.set') }}
</el-button>
</el-form-item>
<el-form-item :label="$t('setting.region')" prop="edition">
<el-radio-group @change="onSave('Edition', form.edition)" v-model="form.edition">
<el-radio value="cn">
<span>{{ $t('setting.cn') }}</span>
</el-radio>
<el-radio value="intl">
<span>{{ $t('setting.intl') }}</span>
</el-radio>
</el-radio-group>
<span class="input-help">{{ $t('setting.regionHelper') }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
@@ -278,6 +290,7 @@ const form = reactive({
themeColor: {} as ThemeColor,
menuTabs: '',
language: '',
edition: '',
complexityVerification: '',
developerMode: '',
systemIP: '',
@@ -341,6 +354,7 @@ const search = async () => {
form.menuTabs = res.data.menuTabs;
form.panelName = res.data.panelName;
form.language = res.data.language;
form.edition = res.data.edition;
form.sessionTimeout = Number(res.data.sessionTimeout);
form.proxyUrl = res.data.proxyUrl;
@@ -511,15 +525,20 @@ const onSave = async (key: string, val: any) => {
};
try {
await updateSetting(param);
if (key === 'Language') {
await globalStore.updateLanguage(val);
location.reload();
}
if (key === 'Theme') {
handleThemeChange(val);
}
if (key === 'MenuTabs') {
globalStore.setOpenMenuTabs(val === 'Enable');
switch (key) {
case 'Theme':
handleThemeChange(val);
break;
case 'MenuTabs':
globalStore.setOpenMenuTabs(val === 'Enable');
break;
case 'Language':
await globalStore.updateLanguage(val);
location.reload();
break;
case 'Edition':
globalStore.isIntl = val === 'intl';
break;
}
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();