feat: Optimize the application upgrade logic. (#13415)

This commit is contained in:
CityFun
2026-07-29 17:19:52 +08:00
committed by GitHub
parent 563df3da71
commit a5fbbfc460
29 changed files with 236 additions and 463 deletions
+8 -412
View File
@@ -9,7 +9,6 @@ import (
"math"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
@@ -783,416 +782,6 @@ func buildNginx(parentTask *task.Task, nginxInstall model.AppInstall, catalogPat
return commitNginxModuleBuilds(nginxInstall, previousModules, modules, false, catalogPath)
}
func upgradeInstall(req request.AppInstallUpgrade) error {
install, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
if err != nil {
return err
}
originalInstall := install
oldVersion := install.Version
detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID))
if err != nil {
return err
}
if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) {
return errors.New("vLLM can only upgrade within the same image type")
}
if install.Version == detail.Version {
return errors.New("two version is same")
}
upgradeTask, err := task.NewTaskWithOps(install.Name, task.TaskUpgrade, task.TaskScopeApp, req.TaskID, install.ID)
if err != nil {
return err
}
install.Status = constant.StatusUpgrading
var (
upErr error
backupFile string
nginxUpgradeSnapshot *openrestyUpgradeSnapshot
)
backUpApp := func(t *task.Task) error {
backupService := NewIBackupService()
backupRecordService := NewIBackupRecordService()
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
backupRecord, err := backupService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
if err == nil {
backups, _ := backupRecordService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
if len(backups) > 3 {
backupsToDelete := backups[:len(backups)-3]
var deleteIDs []uint
for _, backup := range backupsToDelete {
deleteIDs = append(deleteIDs, backup.ID)
}
_ = backupRecordService.BatchDeleteRecord(deleteIDs)
}
backupFile = path.Join(global.Dir.LocalBackupDir, backupRecord.FileDir, backupRecord.FileName)
} else {
return buserr.WithNameAndErr("ErrAppBackup", install.Name, err)
}
return nil
}
if req.Backup {
upgradeTask.AddSubTask(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeApp), backUpApp, nil)
}
upgradeApp := func(t *task.Task) error {
fileOp := files.NewFileOp()
detailDir := path.Join(global.Dir.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version)
if install.App.Resource == constant.AppResourceRemote {
if err = downloadApp(install.App, detail, &install, t.Logger); err != nil {
return err
}
if detail.DockerCompose == "" {
composeDetail, err := fileOp.GetContent(path.Join(detailDir, "docker-compose.yml"))
if err != nil {
return err
}
detail.DockerCompose = string(composeDetail)
_ = appDetailRepo.Update(context.Background(), detail)
}
go func() {
RequestDownloadCallBack(detail.DownloadCallBackUrl)
}()
}
if install.App.Resource == constant.AppResourceLocal {
detailDir = path.Join(global.Dir.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
}
content, err := fileOp.GetContent(install.GetEnvPath())
if err != nil {
return err
}
oldEnvContent := append([]byte(nil), content...)
oldDockerCompose := install.DockerCompose
targetNginxCatalogPath := ""
if install.App.Key == constant.AppOpenresty {
nginxUpgradeSnapshot, err = createOpenrestyUpgradeSnapshot(install.GetPath())
if err != nil {
return err
}
}
if install.App.Key == vllmAppKeyForUpgrade {
envs := make(map[string]interface{})
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
return err
}
image := buildVllmUpgradeImage(loadVllmImageFromEnv(install.Env), oldVersion, detail.Version)
envs[vllmImageEnvKey] = image
paramByte, err := json.Marshal(envs)
if err != nil {
return err
}
install.Env = string(paramByte)
content = setVllmImageInEnvContent(content, image)
}
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
if install.App.Key == constant.AppOpenresty {
installBuildDir := path.Join(install.GetPath(), nginxModuleBuildDir)
detailBuildDir := path.Join(detailDir, nginxModuleBuildDir)
if !fileOp.Stat(installBuildDir) {
if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil {
return err
}
}
if err := fileOp.DeleteDir(path.Join(installBuildDir, nginxModuleTmpDir)); err != nil {
return err
}
if err := fileOp.CopyDir(path.Join(detailBuildDir, nginxModuleTmpDir), installBuildDir); err != nil {
return err
}
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
return err
}
if err := syncNginxModuleBuilder(detailBuildDir, installBuildDir); err != nil {
return err
}
targetCatalogSource := path.Join(detailBuildDir, nginxModuleCatalogFile)
if !fileOp.Stat(targetCatalogSource) {
return fmt.Errorf("target OpenResty module catalog not found: %s", targetCatalogSource)
}
targetNginxCatalogPath = path.Join(installBuildDir, nginxModuleCatalogPendingFile)
if err := stageNginxModuleCatalog(targetCatalogSource, targetNginxCatalogPath); err != nil {
return err
}
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
return err
}
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.vh.default.conf"), installBuildDir); err != nil {
return err
}
}
sourceScripts := path.Join(detailDir, "scripts")
if fileOp.Stat(sourceScripts) {
dstScripts := path.Join(install.GetPath(), "scripts")
_ = fileOp.DeleteDir(dstScripts)
_ = fileOp.CreateDir(dstScripts, constant.DirPerm)
scriptCmd := exec.Command("cp", "-rf", sourceScripts+"/.", dstScripts+"/")
_, _ = scriptCmd.CombinedOutput()
}
var newCompose string
if err = migrateOpenclawProtocolUpgrade(&install, oldVersion, detail.Version); err != nil {
return err
}
if req.DockerCompose == "" {
if install.App.Key == vllmAppKeyForUpgrade {
newCompose = install.DockerCompose
} else {
newCompose, err = getUpgradeCompose(install, detail)
if err != nil {
return err
}
}
} else {
newCompose = req.DockerCompose
}
install.DockerCompose = newCompose
install.Version = detail.Version
install.AppDetailId = req.DetailID
var oldImageIDs []appImageID
if req.DeleteImage {
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
oldImageIDs, err = getAppImageIDsByCompose(dockerCLi, oldEnvContent, []byte(oldDockerCompose))
dockerCLi.Close()
if err != nil {
return err
}
}
if req.PullImage {
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
defer dockerCLi.Close()
for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image))
if pullErr := dockerCLi.PullImageWithProcess(t, image); pullErr != nil {
if exist, _ := dockerCLi.ImageExists(image); exist {
t.Log(i18n.GetMsgByKey("UseExistImage"))
continue
}
return buserr.WithNameAndErr("ErrDockerPullImage", "", pullErr)
}
exist, err := dockerCLi.ImageExists(image)
if err != nil || !exist {
return buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s does not exist after pull: %v", image, err))
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
if install.App.Key == constant.AppOpenresty {
modules, moduleErr := loadNginxModulesWithCatalog(install, targetNginxCatalogPath)
if moduleErr != nil {
return moduleErr
}
// Build dynamic modules for the target version before stopping the
// current container. Static modules retain the full rebuild path.
if !hasEnabledStaticNginxModules(modules) {
previousModules := cloneNginxModules(modules)
modules, moduleErr = buildDynamicNginxModules(install, modules, nil, false, "", targetNginxCatalogPath, t)
if moduleErr != nil {
return moduleErr
}
if moduleErr = saveNginxModulesWithCatalog(install, modules, targetNginxCatalogPath); moduleErr != nil {
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
return moduleErr
}
}
}
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
return upErr
}
return err
}
envs := make(map[string]interface{})
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
return err
}
envParams := make(map[string]string, len(envs))
if install.App.Key == constant.AppOpenresty {
packageUrl, _ := env.GetEnvValueByKey(install.GetEnvPath(), "CONTAINER_PACKAGE_URL")
addPackage, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_ADD_PACKAGE_BUILDDEPS")
options, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_CONFIG_OPTIONS_MORE")
envParams["CONTAINER_PACKAGE_URL"] = packageUrl
envParams["RESTY_ADD_PACKAGE_BUILDDEPS"] = addPackage
envParams["RESTY_CONFIG_OPTIONS_MORE"] = options
}
handleMap(envs, envParams)
if err = env.Write(envParams, install.GetEnvPath()); err != nil {
return err
}
if err = runScript(t, &install, "upgrade"); err != nil {
return err
}
if err = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), constant.FilePerm); err != nil {
return err
}
if install.App.Key == constant.AppOpenresty {
if err = buildNginx(t, install, targetNginxCatalogPath); err != nil {
t.Log(err.Error())
return err
}
}
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
t.Log(logStr)
if out, err := compose.Up(install.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
}
return err
}
t.LogSuccess(logStr)
install.Status = constant.StatusRunning
if install.App.Key == constant.AppOpenresty {
if err = commitStaticNginxModuleBuilds(install, targetNginxCatalogPath, t); err != nil {
return err
}
activeCatalogPath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleCatalogFile)
if err = activateNginxModuleCatalogAndCommit(targetNginxCatalogPath, activeCatalogPath, func() error {
return appInstallRepo.Save(context.Background(), &install)
}); err != nil {
return err
}
} else {
if err = appInstallRepo.Save(context.Background(), &install); err != nil {
return err
}
}
if nginxUpgradeSnapshot != nil {
nginxUpgradeSnapshot.Cleanup()
nginxUpgradeSnapshot = nil
}
if req.DeleteImage {
newEnvContent, err := fileOp.GetContent(install.GetEnvPath())
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
return nil
}
excludeImages, err := docker.GetImagesFromDockerCompose(newEnvContent, []byte(install.DockerCompose))
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
return nil
}
dockerCLi, err := docker.NewClient()
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
return nil
}
defer dockerCLi.Close()
if err = deleteAppImagesByIDs(t, dockerCLi, oldImageIDs, excludeImages); err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
}
}
return nil
}
rollBackApp := func(t *task.Task) {
if req.Backup {
t.Log(i18n.GetWithName("AppRecover", install.Name))
recoverErr := NewIBackupService().AppRecover(dto.CommonRecover{
Name: install.App.Key, DetailName: install.Name, Type: "app", DownloadAccountID: 1, File: backupFile,
})
if recoverErr == nil {
if nginxUpgradeSnapshot != nil {
nginxUpgradeSnapshot.Cleanup()
nginxUpgradeSnapshot = nil
}
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
return
}
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), recoverErr)
if install.App.Key != constant.AppOpenresty {
return
}
}
if install.App.Key == constant.AppOpenresty && nginxUpgradeSnapshot != nil {
if out, rollbackErr := compose.Down(install.GetComposePath()); rollbackErr != nil {
if out != "" {
rollbackErr = fmt.Errorf("%s: %w", out, rollbackErr)
}
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
}
if rollbackErr := nginxUpgradeSnapshot.Restore(); rollbackErr != nil {
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
return
}
nginxUpgradeSnapshot.Cleanup()
nginxUpgradeSnapshot = nil
if out, rollbackErr := compose.Up(originalInstall.GetComposePath()); rollbackErr != nil {
if out != "" {
rollbackErr = fmt.Errorf("%s: %w", out, rollbackErr)
}
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
return
}
originalInstall.Status = constant.StatusRunning
originalInstall.Message = ""
if rollbackErr := appInstallRepo.Save(context.Background(), &originalInstall); rollbackErr != nil {
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
return
}
install = originalInstall
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
return
}
if install.App.Key == constant.AppOpenresty {
if rollbackErr := appInstallRepo.Save(context.Background(), &originalInstall); rollbackErr != nil {
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
return
}
install = originalInstall
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
}
}
upgradeTimeout := 1 * time.Hour
if install.App.Key == constant.AppOpenresty {
// Dynamic modules are built serially and each Docker build has its own
// timeout. An outer deadline would start rollback while upgradeApp is
// still mutating the installation because SubTask does not stop its
// action goroutine on timeout.
upgradeTimeout = 0
}
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp), upgradeApp, rollBackApp, 0, upgradeTimeout)
upgradingInstall := install
if err = appInstallRepo.Save(context.Background(), &upgradingInstall); err != nil {
return err
}
go func() {
if taskErr := upgradeTask.Execute(); taskErr != nil {
existInstall, _ := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
if existInstall.ID > 0 && existInstall.Status != constant.StatusRunning {
existInstall.Status = constant.StatusUpgradeErr
existInstall.Message = taskErr.Error()
_ = appInstallRepo.Save(context.Background(), &existInstall)
}
}
}()
return nil
}
func skipCheckStatus(service types.ServiceConfig) bool {
for key := range service.Labels {
if key == "skipStatusCheck" {
@@ -2233,6 +1822,10 @@ func isHostModel(dockerCompose string) bool {
}
func copyAppDetailMissing(fileOp files.FileOp, srcDir, dstDir string) error {
return copyAppDetailMissingTracked(fileOp, srcDir, dstDir, nil)
}
func copyAppDetailMissingTracked(fileOp files.FileOp, srcDir, dstDir string, createdPaths *[]string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return err
@@ -2244,6 +1837,9 @@ func copyAppDetailMissing(fileOp files.FileOp, srcDir, dstDir string) error {
srcPath := path.Join(srcDir, entry.Name())
dstPath := path.Join(dstDir, entry.Name())
if !fileOp.Stat(dstPath) {
if createdPaths != nil {
*createdPaths = append(*createdPaths, dstPath)
}
if entry.IsDir() {
if err := fileOp.CopyDir(srcPath, dstDir); err != nil {
return err
@@ -2258,7 +1854,7 @@ func copyAppDetailMissing(fileOp files.FileOp, srcDir, dstDir string) error {
if !entry.IsDir() {
continue
}
if err := copyAppDetailMissing(fileOp, srcPath, dstPath); err != nil {
if err := copyAppDetailMissingTracked(fileOp, srcPath, dstPath, createdPaths); err != nil {
return err
}
}
+33 -1
View File
@@ -90,6 +90,34 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
return record, nil
}
func backupAppWithParentTask(install *model.AppInstall, parentTask *task.Task, fileName string) (*model.BackupRecord, error) {
itemDir := fmt.Sprintf("app/%s/%s", install.App.Key, install.Name)
backupDir := path.Join(global.Dir.LocalBackupDir, itemDir)
record := &model.BackupRecord{
Type: "app",
Name: install.App.Key,
DetailName: install.Name,
SourceAccountIDs: "1",
DownloadAccountID: 1,
FileDir: itemDir,
FileName: fileName,
TaskID: parentTask.TaskID,
Status: constant.StatusWaiting,
}
if err := backupRepo.CreateRecord(record); err != nil {
return nil, err
}
if err := handleAppBackup(install, parentTask, record.ID, backupDir, fileName, "", "", parentTask.TaskID); err != nil {
markBackupFailed(record.ID, err)
record.Status = constant.StatusFailed
record.Message = err.Error()
return record, err
}
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusSuccess})
record.Status = constant.StatusSuccess
return record, nil
}
func (u *BackupService) AppRecover(req dto.CommonRecover) error {
app, err := appRepo.GetFirst(appRepo.WithKey(req.Name))
if err != nil {
@@ -203,7 +231,11 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
return err
}
defer func() {
_, _ = compose.Up(install.GetComposePath())
if isRollback {
_, _ = compose.UpWithoutPull(install.GetComposePath())
} else {
_, _ = compose.Up(install.GetComposePath())
}
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
}()
+16 -10
View File
@@ -170,6 +170,10 @@ type containerSwitchClient interface {
NetworkDisconnect(context.Context, string, string, bool) error
}
type containerInspectClient interface {
ContainerInspect(context.Context, string) (container.InspectResponse, error)
}
type containerOperationMutex struct {
mutex sync.Mutex
locks map[string]*containerOperationLockEntry
@@ -335,7 +339,7 @@ const (
containerHealthCheckMaxWait = 10 * time.Minute
)
func waitContainerReady(ctx context.Context, cli containerSwitchClient, containerID string) error {
func waitContainerReady(ctx context.Context, cli containerInspectClient, containerID string) error {
info, err := cli.ContainerInspect(ctx, containerID)
if err != nil {
return err
@@ -347,14 +351,15 @@ func waitContainerReady(ctx context.Context, cli containerSwitchClient, containe
return waitContainerStable(ctx, cli, containerID, info)
}
initialRestartCount := info.RestartCount
timeout := containerHealthCheckTimeout(info.Config)
deadline := time.NewTimer(timeout)
ticker := time.NewTicker(time.Second)
defer deadline.Stop()
defer ticker.Stop()
for {
if info.State.Restarting || info.RestartCount != 0 {
return fmt.Errorf("container restarted %d times during startup", info.RestartCount)
if info.State.Restarting || info.RestartCount != initialRestartCount {
return fmt.Errorf("container restart count changed from %d to %d during startup", initialRestartCount, info.RestartCount)
}
if info.State.Health == nil {
return fmt.Errorf("container health status is unavailable")
@@ -382,9 +387,10 @@ func waitContainerReady(ctx context.Context, cli containerSwitchClient, containe
}
}
func waitContainerStable(ctx context.Context, cli containerSwitchClient, containerID string, initial container.InspectResponse) error {
func waitContainerStable(ctx context.Context, cli containerInspectClient, containerID string, initial container.InspectResponse) error {
startedAt := initial.State.StartedAt
if err := checkContainerStableState(initial, startedAt); err != nil {
restartCount := initial.RestartCount
if err := checkContainerStableState(initial, startedAt, restartCount); err != nil {
return err
}
deadline := time.NewTimer(containerStartStabilization)
@@ -400,25 +406,25 @@ func waitContainerStable(ctx context.Context, cli containerSwitchClient, contain
if err != nil {
return err
}
return checkContainerStableState(info, startedAt)
return checkContainerStableState(info, startedAt, restartCount)
case <-ticker.C:
info, err := cli.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
if err := checkContainerStableState(info, startedAt); err != nil {
if err := checkContainerStableState(info, startedAt, restartCount); err != nil {
return err
}
}
}
}
func checkContainerStableState(info container.InspectResponse, startedAt string) error {
func checkContainerStableState(info container.InspectResponse, startedAt string, restartCount int) error {
if err := checkContainerRunningState(info); err != nil {
return err
}
if info.State.Restarting || info.RestartCount != 0 {
return fmt.Errorf("container restarted %d times during startup", info.RestartCount)
if info.State.Restarting || info.RestartCount != restartCount {
return fmt.Errorf("container restart count changed from %d to %d during startup", restartCount, info.RestartCount)
}
if startedAt != "" && info.State.StartedAt != startedAt {
return fmt.Errorf("container start time changed during startup")
+1
View File
@@ -515,6 +515,7 @@ type openrestyUpgradeSnapshot struct {
var openrestyUpgradeSnapshotPaths = []string{
nginxModuleBuildDir,
nginxModuleModulesDir,
"scripts",
path.Join(nginxModuleConfDir, nginxModuleEnabledConfDir),
path.Join(nginxModuleConfDir, "nginx.conf"),
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: "The {{ .name }} application is not compatible with the
ErrDockerFailed: 'Docker is abnormal; check service status'
ErrDockerComposeCmdNotFound: 'Docker Compose not found on host'
UseExistImage: 'Image exists; using existing image'
UpgradePrepare: 'Prepare application upgrade'
UpgradeStop: 'Stop original application'
UpgradeWaitReady: 'Wait for application readiness'
UpgradeBackupDisabled: 'Upgrade backup is disabled; data changes made by upgrade scripts cannot be fully rolled back'
UpgradeRollbackFailed: 'Rollback failed'
ErrDatabaseNotFound: 'Database not found: {{ .name }}'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: 'La aplicación {{ .name }} no es compatible con la ver
ErrDockerFailed: 'El estado de Docker es anómalo, revise el servicio'
ErrDockerComposeCmdNotFound: 'El comando Docker Compose no existe instálelo primero en el host'
UseExistImage: 'La imagen ya existe, usando imagen existente'
UpgradePrepare: 'Preparar actualización de la aplicación'
UpgradeStop: 'Detener la aplicación original'
UpgradeWaitReady: 'Esperar a que la aplicación esté lista'
UpgradeBackupDisabled: 'La copia de actualización está desactivada; los cambios de datos de los scripts no se pueden revertir por completo'
UpgradeRollbackFailed: 'Error al revertir'
ErrDatabaseNotFound: 'La base de datos {{ .name }} no existe'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: "برنامه {{ .name }} با نسخه فعلی 1Panel
ErrDockerFailed: 'Docker غیرعادی است؛ وضعیت سرویس را بررسی کنید'
ErrDockerComposeCmdNotFound: 'Docker Compose روی میزبان یافت نشد'
UseExistImage: 'تصویر وجود دارد؛ استفاده از تصویر موجود'
UpgradePrepare: 'آماده‌سازی ارتقاء برنامه'
UpgradeStop: 'توقف برنامه اصلی'
UpgradeWaitReady: 'انتظار برای آماده شدن برنامه'
UpgradeBackupDisabled: 'پشتیبان ارتقاء غیرفعال است؛ تغییرات داده اسکریپت‌های ارتقاء کاملاً قابل بازگشت نیست'
UpgradeRollbackFailed: 'بازگشت ناموفق بود'
ErrDatabaseNotFound: 'پایگاه داده یافت نشد: {{ .name }}'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: '{{ .name }} アプリケーションは現在の 1Pane
ErrDockerFailed: 'Docker の状態が異常です。サービス状態を確認してください'
ErrDockerComposeCmdNotFound: 'Docker Compose コマンドは存在しません。ホストマシンにこのコマンドを先にインストールしてください'
UseExistImage: 'イメージは既に存在します。既存のイメージを使用します'
UpgradePrepare: 'アプリケーションのアップグレードを準備'
UpgradeStop: '元のアプリケーションを停止'
UpgradeWaitReady: 'アプリケーションの準備完了を待機'
UpgradeBackupDisabled: 'アップグレードバックアップが無効なため、アップグレードスクリプトによるデータ変更を完全にはロールバックできません'
UpgradeRollbackFailed: 'ロールバックに失敗しました'
ErrDatabaseNotFound: 'データベース {{ .name }} は存在しません'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: '{{ .name }} 응용 프로그램은 현재 1Panel 버
ErrDockerFailed: 'Docker의 상태가 비정상입니다. 서비스 상태를 확인하세요'
ErrDockerComposeCmdNotFound: 'Docker Compose 명령이 없습니다. 호스트 머신에 먼저 이 명령을 설치하세요'
UseExistImage: '이미지가 이미 존재하여 기존 이미지를 사용합니다'
UpgradePrepare: '애플리케이션 업그레이드 준비'
UpgradeStop: '기존 애플리케이션 중지'
UpgradeWaitReady: '애플리케이션 준비 상태 대기'
UpgradeBackupDisabled: '업그레이드 백업이 비활성화되어 업그레이드 스크립트의 데이터 변경을 완전히 롤백할 수 없습니다'
UpgradeRollbackFailed: '롤백 실패'
ErrDatabaseNotFound: '데이터베이스 {{ .name }} 이(가) 존재하지 않습니다'
# SSH
+5
View File
@@ -144,6 +144,11 @@ ErrAppVersionDeprecated: "ແອັບພລິເຄຊັນ {{ .name }} ບ
ErrDockerFailed: 'Docker ຜິດປົກກະຕິ; ກະລຸນາກວດສອບສະຖານະການບໍລິການ'
ErrDockerComposeCmdNotFound: 'ບໍ່ພົບຄຳສັ່ງ Docker Compose ໃນເຄື່ອງ'
UseExistImage: 'ມີຮູບພາບຢູ່ແລ້ວ; ກຳລັງໃຊ້ຮູບພາບທີ່ມີຢູ່'
UpgradePrepare: 'ກະກຽມອັບເກຣດແອັບ'
UpgradeStop: 'ຢຸດແອັບເດີມ'
UpgradeWaitReady: 'ລໍຖ້າແອັບພ້ອມ'
UpgradeBackupDisabled: 'ປິດການສຳຮອງກ່ອນອັບເກຣດ; ການປ່ຽນແປງຂໍ້ມູນຈາກສະຄຣິບບໍ່ສາມາດຍ້ອນກັບໄດ້ທັງໝົດ'
UpgradeRollbackFailed: 'ຍ້ອນກັບລົ້ມເຫຼວ'
ErrDatabaseNotFound: 'ບໍ່ພົບຖານຂໍ້ມູນ: {{ .name }}'
#ssh
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: 'Aplikasi {{ .name }} tidak sesuai dengan versi 1Panel
ErrDockerFailed: 'Keadaan Docker tidak normal, sila periksa status perkhidmatan'
ErrDockerComposeCmdNotFound: 'Perintah Docker Compose tidak wujud, sila pasang perintah ini di mesin tuan terlebih dahulu'
UseExistImage: 'Imej sudah wujud, menggunakan imej sedia ada'
UpgradePrepare: 'Sediakan naik taraf aplikasi'
UpgradeStop: 'Hentikan aplikasi asal'
UpgradeWaitReady: 'Tunggu aplikasi sedia'
UpgradeBackupDisabled: 'Sandaran naik taraf dilumpuhkan; perubahan data oleh skrip naik taraf tidak dapat diundur sepenuhnya'
UpgradeRollbackFailed: 'Undur gagal'
ErrDatabaseNotFound: 'Pangkalan data {{ .name }} tidak wujud'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: 'O aplicativo {{ .name }} não é compatível com a ver
ErrDockerFailed: 'O estado do Docker está anormal, verifique o status do serviço'
ErrDockerComposeCmdNotFound: 'O comando Docker Compose não existe, instale este comando na máquina host primeiro'
UseExistImage: 'A imagem já existe, usando imagem existente'
UpgradePrepare: 'Preparar atualização do aplicativo'
UpgradeStop: 'Parar aplicativo original'
UpgradeWaitReady: 'Aguardar o aplicativo ficar pronto'
UpgradeBackupDisabled: 'O backup de atualização está desativado; alterações de dados feitas pelos scripts não podem ser totalmente revertidas'
UpgradeRollbackFailed: 'Falha na reversão'
ErrDatabaseNotFound: 'O banco de dados {{ .name }} não existe'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: 'Приложение {{ .name }} несовмести
ErrDockerFailed: 'Состояние Docker аномально, проверьте состояние сервиса'
ErrDockerComposeCmdNotFound: 'Команда Docker Compose отсутствует, пожалуйста, установите эту команду на хост-машине сначала'
UseExistImage: 'Образ уже существует, используется существующий образ'
UpgradePrepare: 'Подготовка обновления приложения'
UpgradeStop: 'Остановка исходного приложения'
UpgradeWaitReady: 'Ожидание готовности приложения'
UpgradeBackupDisabled: 'Резервная копия обновления отключена; изменения данных из сценариев обновления нельзя полностью откатить'
UpgradeRollbackFailed: 'Ошибка отката'
ErrDatabaseNotFound: 'База данных {{ .name }} не существует'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: '{{ .name }} uygulaması mevcut 1Panel sürümü ile uy
ErrDockerFailed: 'Docker durumu anormal, lütfen servis durumunu kontrol edin'
ErrDockerComposeCmdNotFound: 'Docker Compose komutu mevcut değil, lütfen önce bu komutu host makinesine yükleyin'
UseExistImage: 'Görüntü zaten mevcut, mevcut görüntü kullanılıyor'
UpgradePrepare: 'Uygulama yükseltmesini hazırla'
UpgradeStop: 'Özgün uygulamayı durdur'
UpgradeWaitReady: 'Uygulamanın hazır olmasını bekle'
UpgradeBackupDisabled: 'Yükseltme yedeği devre dışı; yükseltme betiklerinin veri değişiklikleri tamamen geri alınamaz'
UpgradeRollbackFailed: 'Geri alma başarısız'
ErrDatabaseNotFound: '{{ .name }} veritabanı mevcut değil'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: '{{ .name }} 應用不適配目前 1Panel 版本,跳
ErrDockerFailed: 'Docker 狀態異常,請檢查服務狀態'
ErrDockerComposeCmdNotFound: 'Docker Compose 指令不存在,請先在宿主機安裝此指令'
UseExistImage: '映像已存在,使用現有映像'
UpgradePrepare: '準備應用程式升級'
UpgradeStop: '停止原應用程式'
UpgradeWaitReady: '等待應用程式就緒'
UpgradeBackupDisabled: '未啟用升級備份,升級腳本產生的資料變更無法完整回滾'
UpgradeRollbackFailed: '回滾失敗'
ErrDatabaseNotFound: '資料庫 {{ .name }} 不存在'
# SSH
+5
View File
@@ -154,6 +154,11 @@ ErrAppVersionDeprecated: "{{ .name }} 应用不适配当前 1Panel 版本,跳
ErrDockerFailed: "Docker 状态异常,请检查服务状态"
ErrDockerComposeCmdNotFound: "Docker Compose 命令不存在,请先在宿主机安装"
UseExistImage: "镜像已存在,使用存量镜像"
UpgradePrepare: "准备应用升级"
UpgradeStop: "停止原应用"
UpgradeWaitReady: "等待应用就绪"
UpgradeBackupDisabled: "未启用升级备份,升级脚本产生的数据变更无法完整回滚"
UpgradeRollbackFailed: "回滚失败"
ErrDatabaseNotFound: "数据库 {{ .name }} 不存在"
# SSH
+34 -4
View File
@@ -43,21 +43,51 @@ func Up(filePath string) (string, error) {
return "", err
}
base, extra := getComposeBaseCmd()
args := append(extra, loadFiles(filePath)...)
args = append(args, "up", "-d")
args := append(extra, upArgs(filePath, false)...)
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(base, args...)
}
func UpWithoutPull(filePath string) (string, error) {
if err := checkCmd(); err != nil {
return "", err
}
base, extra := getComposeBaseCmd()
args := append(extra, upArgs(filePath, true)...)
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(base, args...)
}
func upArgs(filePath string, withoutPull bool) []string {
args := loadFiles(filePath)
args = append(args, "up", "-d")
if withoutPull {
args = append(args, "--pull", "never", "--no-build")
}
return args
}
func UpWithTask(filePath string, task *task.Task, forcePull bool) error {
if err := PullComposeImages(filePath, forcePull, task); err != nil {
return err
}
base, extra := getComposeBaseCmd()
args := append(extra, loadFiles(filePath)...)
args = append(args, "up", "-d")
args := append(extra, upArgs(filePath, false)...)
return cmd.NewCommandMgr(cmd.WithTask(*task), cmd.WithTimeout(20*time.Minute)).Run(base, args...)
}
func BuildWithTask(filePath, projectName string, task *task.Task) error {
if err := checkCmd(); err != nil {
return err
}
base, extra := getComposeBaseCmd()
args := append([]string(nil), extra...)
if projectName != "" {
args = append(args, "--project-name", projectName)
}
args = append(args, loadFiles(filePath)...)
args = append(args, "build")
return cmd.NewCommandMgr(cmd.WithTask(*task), cmd.WithTimeout(120*time.Minute)).Run(base, args...)
}
func PullComposeImages(filePath string, forcePull bool, task *task.Task) error {
return pullComposeImages(filePath, forcePull, task)
}
+7 -3
View File
@@ -1013,6 +1013,9 @@ const message = {
performanceSetting: 'Performance Settings',
logSetting: 'Log Settings',
lbPolicy: 'Load Balancing Policy',
protocolConversion: 'Protocol Conversion',
protocolConversionDesc:
'Allow supported direct conversion between Chat Completions, Responses, and Anthropic Messages. Native protocols always take priority.',
lbPolicyRoundRobin: 'Round Robin',
lbPolicyRoundRobinDesc:
'Select available model accounts in order. Use this when backends have similar capacity.',
@@ -1045,11 +1048,9 @@ const message = {
requestBody: 'Request Body',
requestBodySize: 'Request Body Size',
requestBodyTruncated: 'Truncated',
requestBodyHash: 'Request Body Hash',
responseBody: 'Response Body',
responseBodySize: 'Response Body Size',
responseBodyTruncated: 'Response Body Truncated',
responseBodyHash: 'Response Body Hash',
readableContent: 'Readable Content',
rawResponse: 'Raw Response',
replyContent: 'Reply Content',
@@ -1095,7 +1096,10 @@ const message = {
usageKeywordPlaceholder: 'Search Request ID / model / upstream model / error message',
attemptChain: 'Attempt Chain',
attemptIndex: 'Call Order',
noAttemptDetails: 'This request hit once; no fallback details',
clientProtocol: 'Client API',
upstreamProtocol: 'Upstream API',
conversionPath: 'Conversion Path',
noAttemptDetails: 'No attempt chain records',
input: 'Input',
output: 'Output',
total: 'Total',
+7 -3
View File
@@ -1023,6 +1023,9 @@ const message = {
performanceSetting: 'Configuración de rendimiento',
logSetting: 'Configuración de registros',
lbPolicy: 'Estrategia de balanceo',
protocolConversion: 'Conversión de protocolo',
protocolConversionDesc:
'Permite la conversión directa compatible entre Chat Completions, Responses y Anthropic Messages. Los protocolos nativos siempre tienen prioridad.',
lbPolicyRoundRobin: 'Round Robin',
lbPolicyRoundRobinDesc:
'Selecciona las cuentas de modelo disponibles en orden. Úselo cuando los backends tengan capacidad similar.',
@@ -1055,11 +1058,9 @@ const message = {
requestBody: 'Cuerpo de solicitud',
requestBodySize: 'Tamaño del cuerpo de solicitud',
requestBodyTruncated: 'Truncado',
requestBodyHash: 'Hash del cuerpo de solicitud',
responseBody: 'Cuerpo de respuesta',
responseBodySize: 'Tamaño del cuerpo de respuesta',
responseBodyTruncated: 'Cuerpo de respuesta truncado',
responseBodyHash: 'Hash del cuerpo de respuesta',
readableContent: 'Contenido legible',
rawResponse: 'Respuesta original',
replyContent: 'Contenido de respuesta',
@@ -1105,7 +1106,10 @@ const message = {
usageKeywordPlaceholder: 'Buscar Request ID / modelo / modelo upstream / mensaje de error',
attemptChain: 'Cadena de intentos',
attemptIndex: 'Orden de llamada',
noAttemptDetails: 'Esta solicitud acertó en el primer intento; no hay detalles de fallback',
clientProtocol: 'API del cliente',
upstreamProtocol: 'API upstream',
conversionPath: 'Ruta de conversión',
noAttemptDetails: 'No hay registros de la cadena de intentos',
input: 'Entrada',
output: 'Salida',
total: 'Total',
+7 -3
View File
@@ -997,6 +997,9 @@ const message = {
performanceSetting: 'تنظیمات عملکرد',
logSetting: 'تنظیمات لاگ',
lbPolicy: 'خط مشی توزیع بار',
protocolConversion: 'تبدیل پروتکل',
protocolConversionDesc:
'تبدیل مستقیم پشتیبانی‌شده بین Chat Completions، Responses و Anthropic Messages را فعال می‌کند. پروتکل بومی همیشه اولویت دارد.',
lbPolicyRoundRobin: 'چرخه‌ای',
lbPolicyRoundRobinDesc:
'حساب‌های مدل موجود را به ترتیب انتخاب کنید. زمانی استفاده شود که بک‌اندها ظرفیت مشابهی دارند.',
@@ -1029,11 +1032,9 @@ const message = {
requestBody: 'بدنه درخواست',
requestBodySize: 'اندازه بدنه درخواست',
requestBodyTruncated: 'کوتاه شده',
requestBodyHash: 'هش بدنه درخواست',
responseBody: 'بدنه پاسخ',
responseBodySize: 'اندازه بدنه پاسخ',
responseBodyTruncated: 'کوتاه شده',
responseBodyHash: 'هش بدنه پاسخ',
readableContent: 'محتوا قابل خواندن',
rawResponse: 'پاسخ خام',
replyContent: 'محتوا پاسخ',
@@ -1079,7 +1080,10 @@ const message = {
usageKeywordPlaceholder: 'جستجوی شناسه درخواست / مدل / مدل بالادست / پیام خطا',
attemptChain: 'زنجیره تلاش',
attemptIndex: 'ترتیب فراخوانی',
noAttemptDetails: 'این درخواست یک بار انجام شده است؛ جزئیات خرابی وجود ندارد',
clientProtocol: 'API کاربر',
upstreamProtocol: 'API بالادست',
conversionPath: 'مسیر تبدیل',
noAttemptDetails: 'هیچ سابقه‌ای از زنجیره تلاش وجود ندارد',
input: 'ورودی',
output: 'خروجی',
total: 'مجموع',
+7 -3
View File
@@ -1010,6 +1010,9 @@ const message = {
performanceSetting: 'パフォーマンス設定',
logSetting: 'ログ設定',
lbPolicy: '負荷分散ポリシー',
protocolConversion: 'プロトコル変換',
protocolConversionDesc:
'Chat CompletionsResponsesAnthropic Messages 間でサポートされる直接変換を有効にしますネイティブプロトコルが常に優先されます',
lbPolicyRoundRobin: 'ラウンドロビン',
lbPolicyRoundRobinDesc:
'利用可能なモデルアカウントを順番に選択しますバックエンドの性能が近い場合に適しています',
@@ -1042,11 +1045,9 @@ const message = {
requestBody: 'リクエスト本文',
requestBodySize: 'リクエスト本文サイズ',
requestBodyTruncated: '切り詰め',
requestBodyHash: 'リクエスト本文 Hash',
responseBody: 'レスポンス本文',
responseBodySize: 'レスポンス本文サイズ',
responseBodyTruncated: 'レスポンス本文の切り詰め',
responseBodyHash: 'レスポンス本文 Hash',
readableContent: '読みやすい内容',
rawResponse: '生レスポンス',
replyContent: '返信内容',
@@ -1092,7 +1093,10 @@ const message = {
usageKeywordPlaceholder: 'Request ID / モデル / 上流モデル / エラーメッセージを検索',
attemptChain: '試行チェーン',
attemptIndex: '呼び出し順',
noAttemptDetails: 'このリクエストは 1 回で命中しましたfallback 明細はありません',
clientProtocol: 'クライアント API',
upstreamProtocol: 'アップストリーム API',
conversionPath: '変換パス',
noAttemptDetails: '呼び出しチェーンの記録はありません',
input: '入力',
output: '出力',
total: '合計',
+7 -3
View File
@@ -994,6 +994,9 @@ const message = {
performanceSetting: '성능 설정',
logSetting: '로그 설정',
lbPolicy: '부하 분산 정책',
protocolConversion: '프로토콜 변환',
protocolConversionDesc:
'Chat Completions, Responses Anthropic Messages 간에 지원되는 직접 변환을 허용합니다. 네이티브 프로토콜이 항상 우선합니다.',
lbPolicyRoundRobin: '라운드 로빈',
lbPolicyRoundRobinDesc:
'사용 가능한 모델 계정을 순서대로 선택합니다. 백엔드 성능이 비슷한 경우에 적합합니다.',
@@ -1026,11 +1029,9 @@ const message = {
requestBody: '요청 본문',
requestBodySize: '요청 본문 크기',
requestBodyTruncated: '잘림 여부',
requestBodyHash: '요청 본문 Hash',
responseBody: '응답 본문',
responseBodySize: '응답 본문 크기',
responseBodyTruncated: '응답 본문 잘림 여부',
responseBodyHash: '응답 본문 Hash',
readableContent: '읽기 쉬운 내용',
rawResponse: '원본 응답',
replyContent: '응답 내용',
@@ -1076,7 +1077,10 @@ const message = {
usageKeywordPlaceholder: 'Request ID / 모델 / 업스트림 모델 / 오류 메시지 검색',
attemptChain: '시도 체인',
attemptIndex: '호출 순서',
noAttemptDetails: ' 요청은 번에 적중했으며 fallback 상세가 없습니다',
clientProtocol: '클라이언트 API',
upstreamProtocol: '업스트림 API',
conversionPath: '변환 경로',
noAttemptDetails: '호출 체인 기록이 없습니다',
input: '입력',
output: '출력',
total: '합계',
+7 -3
View File
@@ -1004,6 +1004,9 @@ const message = {
performanceSetting: 'ຕັ້ງຄ່າປະສິດທິພາບ',
logSetting: 'ຕັ້ງຄ່າລັອກ',
lbPolicy: 'ນະໂຍບາຍການກະຈາຍພາລະ (Load Balancing)',
protocolConversion: 'ການປ່ຽນໂປຣໂຕຄອນ',
protocolConversionDesc:
'ອະນຸຍາດການປ່ຽນໂດຍກົງທີ່ຮອງຮັບລະຫວ່າງ Chat Completions, Responses ແລະ Anthropic Messages. ໂປຣໂຕຄອນເດີມຈະຖືກໃຫ້ສິດກ່ອນສະເໝີ.',
lbPolicyRoundRobin: 'Round Robin',
lbPolicyRoundRobinDesc: 'ເລືອກບັນຊີໂມເດວຕາມລຳດັບ. ໃຊ້ເມື່ອລະບົບຫຼັງບ້ານມີຄວາມສາມາດໃກ້ຄຽງກັນ.',
lbPolicyWeightedRoundRobin: 'Weighted Round Robin',
@@ -1035,11 +1038,9 @@ const message = {
requestBody: 'Request Body',
requestBodySize: 'ຂະໜາດ Request Body',
requestBodyTruncated: 'ຖືກຕັດບາງສ່ວນ',
requestBodyHash: 'Hash ຂອງ Request Body',
responseBody: 'Response Body',
responseBodySize: 'ຂະໜາດ Response Body',
responseBodyTruncated: 'Response Body ຖືກຕັດບາງສ່ວນ',
responseBodyHash: 'Hash ຂອງ Response Body',
readableContent: 'ເນື້ອໃນທີ່ອ່ານໄດ້',
rawResponse: 'Response ດິບ',
replyContent: 'ເນື້ອໃນການຕອບກັບ',
@@ -1085,7 +1086,10 @@ const message = {
usageKeywordPlaceholder: 'ຄົ້ນຫາ Request ID / ໂມເດວ / ໂມເດວຕົ້ນທາງ / ຂໍ້ຄວາມຜິດພາດ',
attemptChain: 'ລຳດັບການລອງໃໝ່',
attemptIndex: 'ລຳດັບການເອີ້ນ',
noAttemptDetails: 'ຄຳຂໍນີ້ສຳເລັດໃນຄັ້ງດຽວ; ບໍ່ມີລາຍລະອຽດການສຳຮອງ',
clientProtocol: 'API ລູກຄ້າ',
upstreamProtocol: 'API ຕົ້ນທາງ',
conversionPath: 'ເສັ້ນທາງການປ່ຽນ',
noAttemptDetails: 'ບໍ່ມີບັນທຶກລຳດັບການລອງ',
input: 'ຂາເຂົ້າ',
output: 'ຂາອອກ',
total: 'ທັງໝົດ',
+7 -3
View File
@@ -1020,6 +1020,9 @@ const message = {
performanceSetting: 'Tetapan Prestasi',
logSetting: 'Tetapan Log',
lbPolicy: 'Strategi imbangan beban',
protocolConversion: 'Penukaran protokol',
protocolConversionDesc:
'Benarkan penukaran terus yang disokong antara Chat Completions, Responses dan Anthropic Messages. Protokol asli sentiasa diutamakan.',
lbPolicyRoundRobin: 'Round Robin',
lbPolicyRoundRobinDesc:
'Pilih akaun model tersedia mengikut turutan. Sesuai apabila kapasiti backend hampir sama.',
@@ -1052,11 +1055,9 @@ const message = {
requestBody: 'Badan Permintaan',
requestBodySize: 'Saiz Badan Permintaan',
requestBodyTruncated: 'Dipotong',
requestBodyHash: 'Hash Badan Permintaan',
responseBody: 'Badan Respons',
responseBodySize: 'Saiz Badan Respons',
responseBodyTruncated: 'Badan Respons Dipotong',
responseBodyHash: 'Hash Badan Respons',
readableContent: 'Kandungan Boleh Dibaca',
rawResponse: 'Respons Asal',
replyContent: 'Kandungan Balasan',
@@ -1102,7 +1103,10 @@ const message = {
usageKeywordPlaceholder: 'Cari Request ID / model / model upstream / mesej ralat',
attemptChain: 'Rantaian percubaan',
attemptIndex: 'Urutan panggilan',
noAttemptDetails: 'Permintaan ini berjaya sekali percubaan; tiada butiran fallback',
clientProtocol: 'API klien',
upstreamProtocol: 'API upstream',
conversionPath: 'Laluan penukaran',
noAttemptDetails: 'Tiada rekod rantaian percubaan',
input: 'Input',
output: 'Output',
total: 'Jumlah',
+7 -3
View File
@@ -1016,6 +1016,9 @@ const message = {
performanceSetting: 'Configurações de desempenho',
logSetting: 'Configurações de log',
lbPolicy: 'Política de balanceamento',
protocolConversion: 'Conversão de protocolo',
protocolConversionDesc:
'Permite conversão direta compatível entre Chat Completions, Responses e Anthropic Messages. Protocolos nativos sempre têm prioridade.',
lbPolicyRoundRobin: 'Round Robin',
lbPolicyRoundRobinDesc:
'Seleciona contas de modelo disponíveis em ordem. Use quando os backends tiverem capacidade semelhante.',
@@ -1048,11 +1051,9 @@ const message = {
requestBody: 'Corpo da requisição',
requestBodySize: 'Tamanho do corpo da requisição',
requestBodyTruncated: 'Truncado',
requestBodyHash: 'Hash do corpo da requisição',
responseBody: 'Corpo da resposta',
responseBodySize: 'Tamanho do corpo da resposta',
responseBodyTruncated: 'Corpo da resposta truncado',
responseBodyHash: 'Hash do corpo da resposta',
readableContent: 'Conteúdo legível',
rawResponse: 'Resposta bruta',
replyContent: 'Conteúdo da resposta',
@@ -1098,7 +1099,10 @@ const message = {
usageKeywordPlaceholder: 'Pesquisar Request ID / modelo / modelo upstream / mensagem de erro',
attemptChain: 'Cadeia de tentativas',
attemptIndex: 'Ordem de chamada',
noAttemptDetails: 'Esta requisição acertou na primeira tentativa; sem detalhes de fallback',
clientProtocol: 'API do cliente',
upstreamProtocol: 'API upstream',
conversionPath: 'Caminho de conversão',
noAttemptDetails: 'Nenhum registro da cadeia de tentativas',
input: 'Entrada',
output: 'Saída',
total: 'Total',
+7 -3
View File
@@ -1009,6 +1009,9 @@ const message = {
performanceSetting: 'Настройки производительности',
logSetting: 'Настройки журналов',
lbPolicy: 'Политика балансировки',
protocolConversion: 'Преобразование протокола',
protocolConversionDesc:
'Разрешает поддерживаемое прямое преобразование между Chat Completions, Responses и Anthropic Messages. Нативный протокол всегда имеет приоритет.',
lbPolicyRoundRobin: 'Round Robin',
lbPolicyRoundRobinDesc:
'Выбирает доступные аккаунты моделей по очереди. Подходит, когда бэкенды имеют схожую производительность.',
@@ -1041,11 +1044,9 @@ const message = {
requestBody: 'Тело запроса',
requestBodySize: 'Размер тела запроса',
requestBodyTruncated: 'Обрезано',
requestBodyHash: 'Hash тела запроса',
responseBody: 'Тело ответа',
responseBodySize: 'Размер тела ответа',
responseBodyTruncated: 'Тело ответа обрезано',
responseBodyHash: 'Hash тела ответа',
readableContent: 'Читаемое содержимое',
rawResponse: 'Исходный ответ',
replyContent: 'Содержимое ответа',
@@ -1091,7 +1092,10 @@ const message = {
usageKeywordPlaceholder: 'Поиск по Request ID / модели / upstream-модели / сообщению об ошибке',
attemptChain: 'Цепочка попыток',
attemptIndex: 'Порядок вызова',
noAttemptDetails: 'Запрос выполнен с первой попытки, деталей fallback нет',
clientProtocol: 'API клиента',
upstreamProtocol: 'Вышестоящий API',
conversionPath: 'Путь преобразования',
noAttemptDetails: 'Нет записей цепочки попыток',
input: 'Вход',
output: 'Выход',
total: 'Итого',
+7 -3
View File
@@ -1019,6 +1019,9 @@ const message = {
performanceSetting: 'Performans Ayarları',
logSetting: 'Günlük Ayarları',
lbPolicy: 'Yük dengeleme politikası',
protocolConversion: 'Protokol dönüştürme',
protocolConversionDesc:
'Chat Completions, Responses ve Anthropic Messages arasında desteklenen doğrudan dönüşüme izin verir. Yerel protokol her zaman önceliklidir.',
lbPolicyRoundRobin: 'Round Robin',
lbPolicyRoundRobinDesc:
'Kullanılabilir model hesaplarını sırayla seçer. Backend kapasiteleri benzer olduğunda kullanın.',
@@ -1051,11 +1054,9 @@ const message = {
requestBody: 'İstek Gövdesi',
requestBodySize: 'İstek Gövdesi Boyutu',
requestBodyTruncated: 'Kısaltıldı',
requestBodyHash: 'İstek Gövdesi Hash',
responseBody: 'Yanıt Gövdesi',
responseBodySize: 'Yanıt Gövdesi Boyutu',
responseBodyTruncated: 'Yanıt Gövdesi Kısaltıldı',
responseBodyHash: 'Yanıt Gövdesi Hash',
readableContent: 'Okunabilir İçerik',
rawResponse: 'Ham Yanıt',
replyContent: 'Yanıt İçeriği',
@@ -1101,7 +1102,10 @@ const message = {
usageKeywordPlaceholder: 'Request ID / model / upstream model / hata mesajı ara',
attemptChain: 'Deneme zinciri',
attemptIndex: 'Çağrı sırası',
noAttemptDetails: 'Bu istek tek denemede isabet etti; fallback ayrıntısı yok',
clientProtocol: 'İstemci API',
upstreamProtocol: 'Üst API',
conversionPath: 'Dönüşüm yolu',
noAttemptDetails: 'Deneme zinciri kaydı yok',
input: 'Girdi',
output: 'Çıktı',
total: 'Toplam',
+7 -3
View File
@@ -955,6 +955,9 @@ const message = {
performanceSetting: '效能設定',
logSetting: '日誌設定',
lbPolicy: '負載策略',
protocolConversion: '協議轉換',
protocolConversionDesc:
'啟用後文字請求可在 Chat CompletionsResponses Anthropic Messages 的支援協議間直接轉換原生協議始終優先',
lbPolicyRoundRobin: '輪詢',
lbPolicyRoundRobinDesc: '按順序依次選擇可用模型帳號適合後端能力接近的場景',
lbPolicyWeightedRoundRobin: '加權輪詢',
@@ -983,11 +986,9 @@ const message = {
requestBody: '請求體',
requestBodySize: '請求體大小',
requestBodyTruncated: '是否截斷',
requestBodyHash: '請求體 Hash',
responseBody: '響應體',
responseBodySize: '響應體大小',
responseBodyTruncated: '響應體是否截斷',
responseBodyHash: '響應體 Hash',
readableContent: '可讀內容',
rawResponse: '原始響應',
replyContent: '回覆內容',
@@ -1033,7 +1034,10 @@ const message = {
usageKeywordPlaceholder: '搜尋 Request ID / 模型 / 上游模型 / 錯誤資訊',
attemptChain: '呼叫鏈路',
attemptIndex: '調用順序',
noAttemptDetails: '本次請求一次命中 fallback 明細',
clientProtocol: '用戶端 API',
upstreamProtocol: '上游 API',
conversionPath: '轉換路徑',
noAttemptDetails: '暫無呼叫鏈路記錄',
input: '輸入',
output: '輸出',
total: '總計',
+7 -3
View File
@@ -952,6 +952,9 @@ const message = {
performanceSetting: '性能设置',
logSetting: '日志设置',
lbPolicy: '负载策略',
protocolConversion: '协议转换',
protocolConversionDesc:
'开启后文本请求可在 Chat CompletionsResponses Anthropic Messages 的受支持协议间直接转换原生协议始终优先',
lbPolicyRoundRobin: '轮询',
lbPolicyRoundRobinDesc: '按顺序依次选择可用模型账号适合后端能力接近的场景',
lbPolicyWeightedRoundRobin: '加权轮询',
@@ -980,11 +983,9 @@ const message = {
requestBody: '请求体',
requestBodySize: '请求体大小',
requestBodyTruncated: '是否截断',
requestBodyHash: '请求体 Hash',
responseBody: '响应体',
responseBodySize: '响应体大小',
responseBodyTruncated: '响应体是否截断',
responseBodyHash: '响应体 Hash',
readableContent: '可读内容',
rawResponse: '原始响应',
replyContent: '回复内容',
@@ -1030,7 +1031,10 @@ const message = {
usageKeywordPlaceholder: '搜索 Request ID / 模型 / 上游模型 / 错误信息',
attemptChain: '调用链路',
attemptIndex: '调用顺序',
noAttemptDetails: '本次请求一次命中 fallback 明细',
clientProtocol: '客户端 API',
upstreamProtocol: '上游 API',
conversionPath: '转换路径',
noAttemptDetails: '暂无调用链路记录',
input: '输入',
output: '输出',
total: '总计',