From a8a92da483a67276d85a8f93843cb96aa2200482 Mon Sep 17 00:00:00 2001 From: rowanchen-com <156078944+rowanchen-com@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:35:29 +0800 Subject: [PATCH] Added the ability to sort installed applications. (#11936) --- agent/app/api/v2/app_install.go | 12 +++ agent/app/dto/request/app.go | 9 ++ agent/app/dto/response/app.go | 1 + agent/app/model/app_install.go | 1 + agent/app/service/app_install.go | 14 +++ agent/app/service/app_utils.go | 1 + agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 7 ++ agent/router/ro_app.go | 1 + frontend/package.json | 1 + frontend/src/api/modules/app.ts | 4 + frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/es-es.ts | 1 + frontend/src/lang/modules/ja.ts | 1 + frontend/src/lang/modules/ko.ts | 1 + frontend/src/lang/modules/ms.ts | 1 + frontend/src/lang/modules/pt-br.ts | 1 + frontend/src/lang/modules/ru.ts | 1 + frontend/src/lang/modules/tr.ts | 1 + frontend/src/lang/modules/zh-Hant.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + frontend/src/views/app-store/index.scss | 4 + .../src/views/app-store/installed/index.vue | 86 +++++++++++++++++-- 23 files changed, 146 insertions(+), 6 deletions(-) diff --git a/agent/app/api/v2/app_install.go b/agent/app/api/v2/app_install.go index 4206154fe..cf456b58f 100644 --- a/agent/app/api/v2/app_install.go +++ b/agent/app/api/v2/app_install.go @@ -349,3 +349,15 @@ func (b *BaseApi) GetAppInstallInfo(c *gin.Context) { } helper.SuccessWithData(c, info) } + +func (b *BaseApi) UpdateAppInstallSort(c *gin.Context) { + var req request.AppInstallSort + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := appInstallService.UpdateSort(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/dto/request/app.go b/agent/app/dto/request/app.go index 145168ea9..89904f47f 100644 --- a/agent/app/dto/request/app.go +++ b/agent/app/dto/request/app.go @@ -138,3 +138,12 @@ type AppUpdateVersion struct { AppInstallID uint `json:"appInstallID" validate:"required"` UpdateVersion string `json:"updateVersion"` } + +type AppInstallSortItem struct { + InstallID uint `json:"installID"` + SortOrder int `json:"sortOrder"` +} + +type AppInstallSort struct { + Items []AppInstallSortItem `json:"items" validate:"required"` +} diff --git a/agent/app/dto/response/app.go b/agent/app/dto/response/app.go index 42fc3ba6c..dc5e66967 100644 --- a/agent/app/dto/response/app.go +++ b/agent/app/dto/response/app.go @@ -126,6 +126,7 @@ type AppInstallDTO struct { WebUI string `json:"webUI"` CreatedAt time.Time `json:"createdAt"` Favorite bool `json:"favorite"` + SortOrder int `json:"sortOrder"` App AppDetail `json:"app"` Container string `json:"container"` IsEdit bool `json:"isEdit"` diff --git a/agent/app/model/app_install.go b/agent/app/model/app_install.go index cd0904b56..5c94cbdf5 100644 --- a/agent/app/model/app_install.go +++ b/agent/app/model/app_install.go @@ -26,6 +26,7 @@ type AppInstall struct { HttpsPort int `json:"httpsPort"` WebUI string `json:"webUI"` Favorite bool `json:"favorite"` + SortOrder int `json:"sortOrder" gorm:"default:0"` App App `json:"app" gorm:"-:migration"` } diff --git a/agent/app/service/app_install.go b/agent/app/service/app_install.go index 61d9c1c30..0d053f483 100644 --- a/agent/app/service/app_install.go +++ b/agent/app/service/app_install.go @@ -57,6 +57,7 @@ type IAppInstallService interface { UpdateAppConfig(req request.AppConfigUpdate) error GetInstallList() ([]dto.AppInstallInfo, error) GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error) + UpdateSort(req request.AppInstallSort) error } func NewIAppInstalledService() IAppInstallService { @@ -83,6 +84,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo err error ) opts = append(opts, repo.WithOrderRuleBy("favorite", "descending")) + opts = append(opts, repo.WithOrderRuleBy("sort_order", "ascending")) if req.Name != "" { opts = append(opts, repo.WithByLikeName(req.Name)) @@ -300,6 +302,9 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { return opNginx(install.ContainerName, constant.NginxReload) case constant.Favorite: install.Favorite = req.Favorite + var maxSort int + global.DB.Model(&model.AppInstall{}).Where("favorite = ?", req.Favorite).Select("COALESCE(MAX(sort_order),0)").Scan(&maxSort) + install.SortOrder = maxSort + 1 return appInstallRepo.Save(context.Background(), &install) default: return errors.New("operate not support") @@ -318,6 +323,15 @@ func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error { return appInstallRepo.Save(context.Background(), &installed) } +func (a *AppInstallService) UpdateSort(req request.AppInstallSort) error { + for _, item := range req.Items { + if err := appInstallRepo.BatchUpdateBy(map[string]interface{}{"sort_order": item.SortOrder}, repo.WithByID(item.InstallID)); err != nil { + return err + } + } + return nil +} + func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallId)) if err != nil { diff --git a/agent/app/service/app_utils.go b/agent/app/service/app_utils.go index af0de83c5..f9144bc8a 100644 --- a/agent/app/service/app_utils.go +++ b/agent/app/service/app_utils.go @@ -1613,6 +1613,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda Document: installed.App.Document, }, Favorite: installed.Favorite, + SortOrder: installed.SortOrder, Container: installed.ContainerName, ServiceName: strings.ToLower(installed.ServiceName), } diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index e8bfe7f87..bc5c1d943 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -66,6 +66,7 @@ func InitAgentDB() { migrations.AddAgentTables, migrations.MigrateOpenclawAgents, migrations.AddAgentCustomModelFields, + migrations.AddAppInstallSortOrder, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index b340fd475..f71ec09dc 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -957,3 +957,10 @@ var AddAgentCustomModelFields = &gormigrate.Migration{ return nil }, } + +var AddAppInstallSortOrder = &gormigrate.Migration{ + ID: "20260222-add-app-install-sort-order", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.AppInstall{}) + }, +} diff --git a/agent/router/ro_app.go b/agent/router/ro_app.go index 3d9cc12f8..689323b76 100644 --- a/agent/router/ro_app.go +++ b/agent/router/ro_app.go @@ -40,6 +40,7 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) { appRouter.POST("/installed/params/update", baseApi.UpdateInstalled) appRouter.POST("/installed/update/versions", baseApi.GetUpdateVersions) appRouter.POST("/installed/config/update", baseApi.UpdateAppConfig) + appRouter.POST("/installed/sort/update", baseApi.UpdateAppInstallSort) appRouter.GET("/installed/info/:appInstallId", baseApi.GetAppInstallInfo) appRouter.POST("/installed/ignore", baseApi.IgnoreAppUpgrade) diff --git a/frontend/package.json b/frontend/package.json index a96b059b7..362f33db0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -51,6 +51,7 @@ "punycode": "^2.3.1", "qs": "^6.12.1", "screenfull": "^6.0.2", + "sortablejs": "^1.15.7", "uuid": "^10.0.0", "vue": "^3.4.27", "vue-clipboard3": "^2.0.0", diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index 85c978159..a595a27a7 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -123,6 +123,10 @@ export const updateInstallConfig = (req: App.AppConfigUpdate) => { return http.post(`apps/installed/config/update`, req); }; +export const updateAppInstallSort = (items: Array<{ installID: number; sortOrder: number }>) => { + return http.post(`apps/installed/sort/update`, { items }); +}; + export const syncCutomAppStore = (req: App.AppStoreSync) => { return http.post(`/custom/app/sync`, req); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 5c31f3514..efd4a38d5 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2395,6 +2395,7 @@ const message = { takeDown: 'TakeDown', allReadyInstalled: 'Installed', installHelper: 'If you have image pull issues, configure image acceleration.', + sortMode: 'Sort', installWarn: `The external access isn't checked, and it will make the application unable to access through external network. Do you want to continue?`, showIgnore: 'View ignored applications', cancelIgnore: 'Cancel ignore', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index a914abbfe..c86e88ba1 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -2409,6 +2409,7 @@ const message = { takeDown: 'Desinstalar', allReadyInstalled: 'Instaladas', installHelper: 'Si tiene problemas con el pull de imagen, configure aceleración.', + sortMode: 'Ordenar', installWarn: 'Si no habilita el acceso externo, la app no será accesible externamente. ¿Desea continuar?', showIgnore: 'Ver aplicaciones ignoradas', cancelIgnore: 'Cancelar ignoradas', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 4da8bcb3c..59eb94efe 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -2324,6 +2324,7 @@ const message = { takeDown: '降ろす', allReadyInstalled: 'インストール', installHelper: '画像プルの問題がある場合は、画像アクセラレーションを構成します。', + sortMode: '並べ替え', installWarn: `外部アクセスは有効になっていないため、アプリケーションが外部ネットワークを介してアクセスできるようになります。続けたいですか?`, showIgnore: '無視されたアプリケーションを表示します', cancelIgnore: 'キャンセルは無視します', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 456b6a592..6475584e1 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -2288,6 +2288,7 @@ const message = { takeDown: '내리기', allReadyInstalled: '설치됨', installHelper: '이미지 풀 문제 시 이미지 가속을 구성하세요.', + sortMode: '정렬', installWarn: '외부 접근이 활성화되지 않아 애플리케이션이 외부 네트워크에서 접근할 수 없습니다. 계속 하시겠습니까?', showIgnore: '무시된 애플리케이션 보기', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 42b249859..3276908ca 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -2384,6 +2384,7 @@ const message = { takeDown: 'Henti Operasi', allReadyInstalled: 'Telah Dipasang', installHelper: 'Jika terdapat isu tarikan imej, konfigurasikan pecutan imej.', + sortMode: 'Susun', installWarn: 'Akses luaran tidak diaktifkan, yang menghalang aplikasi daripada diakses melalui rangkaian luaran. Adakah anda mahu meneruskan?', showIgnore: 'Lihat aplikasi yang diabaikan', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index ce8b926b1..cbdf7c520 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -2378,6 +2378,7 @@ const message = { takeDown: 'Retirar', allReadyInstalled: 'Instalado', installHelper: 'Se houver problemas ao puxar a imagem, configure a aceleração da imagem.', + sortMode: 'Ordenar', upgradeHelper: 'Coloque aplicativos anormais de volta ao estado normal antes de atualizar. Se a atualização falhar, vá para "Logs > Logs do Sistema" para verificar a razão da falha.', installWarn: `O acesso externo não foi habilitado, o que impede que o aplicativo seja acessado via redes externas. Deseja continuar?`, diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 3f837f68e..b5a40317a 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -2376,6 +2376,7 @@ const message = { takeDown: 'Отключить', allReadyInstalled: 'Установлено', installHelper: 'Если есть проблемы с загрузкой образа, настройте ускорение образов.', + sortMode: 'Сортировка', installWarn: 'Внешний доступ не включен, что делает приложение недоступным через внешние сети. Хотите продолжить?', showIgnore: 'Просмотреть игнорируемые приложения', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 8c8b376b3..c89a000f1 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -2421,6 +2421,7 @@ const message = { takeDown: 'Kaldır', allReadyInstalled: 'Kurulu', installHelper: 'Görüntü çekme sorunlarınız varsa, görüntü hızlandırmasını yapılandırın.', + sortMode: 'Sırala', installWarn: 'Dış erişim işaretlenmedi, bu uygulama dış ağ üzerinden erişilemez hale getirecek. Devam etmek istiyor musunuz?', showIgnore: 'Yoksayılan uygulamaları görüntüle', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 4c9ccb57f..ced7dae75 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -2231,6 +2231,7 @@ const message = { takeDown: '已廢棄', allReadyInstalled: '已安裝', installHelper: '配置鏡像加速可以解決鏡像拉取失敗的問題', + sortMode: '排序', installWarn: '目前未勾選埠外部訪問,將無法透過外網IP:埠訪問,是否繼續? ', showIgnore: '查看忽略應用', cancelIgnore: '取消忽略', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 8f0018dbe..392198b79 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2228,6 +2228,7 @@ const message = { takeDown: '已废弃', allReadyInstalled: '已安装', installHelper: '配置镜像加速可以解决镜像拉取失败的问题', + sortMode: '排序', installWarn: '当前未勾选端口外部访问,将无法通过外网IP:端口访问,是否继续?', showIgnore: '查看忽略应用', cancelIgnore: '取消忽略', diff --git a/frontend/src/views/app-store/index.scss b/frontend/src/views/app-store/index.scss index 8c57c7200..fdc326dc0 100644 --- a/frontend/src/views/app-store/index.scss +++ b/frontend/src/views/app-store/index.scss @@ -1,3 +1,7 @@ +.sortable-ghost { + opacity: 0.4; +} + .install-card { margin-top: 10px; cursor: pointer; diff --git a/frontend/src/views/app-store/installed/index.vue b/frontend/src/views/app-store/installed/index.vue index 0f85cd191..a07e1e5fe 100644 --- a/frontend/src/views/app-store/installed/index.vue +++ b/frontend/src/views/app-store/installed/index.vue @@ -4,9 +4,23 @@