diff --git a/agent/app/service/device_clean.go b/agent/app/service/device_clean.go index 5d68c1e52bc3..8cb2065a4c27 100644 --- a/agent/app/service/device_clean.go +++ b/agent/app/service/device_clean.go @@ -28,10 +28,11 @@ import ( ) const ( - rollbackPath = "1panel/tmp" - upgradePath = "1panel/tmp/upgrade" - uploadPath = "1panel/uploads" - downloadPath = "1panel/download" + rollbackPath = "1panel/tmp" + communityRestorePath = "1panel/tmp/community-restore" + upgradePath = "1panel/tmp/upgrade" + uploadPath = "1panel/uploads" + downloadPath = "1panel/download" ) func (u *DeviceService) Scan() dto.CleanData { @@ -113,12 +114,15 @@ func (u *DeviceService) Clean(req []dto.Clean) { dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app")) dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "database")) dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "website")) + dropFileOrDir(path.Join(global.Dir.BaseDir, communityRestorePath)) case "rollback_app": dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app", item.Name)) case "rollback_database": dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "database", item.Name)) case "rollback_website": dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "website", item.Name)) + case "rollback_community_restore": + dropFileOrDir(path.Join(global.Dir.BaseDir, communityRestorePath, item.Name)) case "upload": dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, item.Name)) @@ -214,6 +218,7 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error { dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "app"), taskItem, &size, &fileCount) dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "website"), taskItem, &size, &fileCount) dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "database"), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.Dir.BaseDir, communityRestorePath), taskItem, &size, &fileCount) upgrades := path.Join(global.Dir.BaseDir, upgradePath) oldUpgradeFiles, _ := os.ReadDir(upgrades) @@ -611,6 +616,7 @@ func loadRollBackTree(fileOp fileUtils.FileOp) []dto.CleanTree { treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "app"), "rollback_app", fileOp) treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "website"), "rollback_website", fileOp) treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "database"), "rollback_database", fileOp) + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, communityRestorePath), "rollback_community_restore", fileOp) return treeData } diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index 75984f9e6460..0a078db1cc3f 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -307,6 +307,13 @@ export namespace Setting { status: string; message: string; } + export interface CommunityRestoreStatus { + state: 'Ready' | 'Running' | 'Failed'; + message: string; + packageExist: boolean; + packageDirectory: string; + } + export type CommunityRestoreMode = 'online' | 'offline'; export interface NodeItem { id: number; groupID?: number; diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index 7541b81d2702..17adbb8be0a9 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -65,6 +65,12 @@ export const getEnterpriseLicense = () => { export const getEnterpriseLicenseStatus = () => { return http.get(`/core/enterprise/licenses/status`); }; +export const getCommunityRestoreStatus = () => { + return http.get(`/core/enterprise/licenses/community-restore/status`); +}; +export const restoreCommunity = (mode: Setting.CommunityRestoreMode) => { + return http.post(`/core/enterprise/licenses/community-restore`, { mode }); +}; // agent export const loadBaseDir = (node?: string) => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index cc6b31486c1c..fb23afc82d05 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -3175,6 +3175,20 @@ const message = { 'Please copy the device ID first to obtain a license. You can log in after importing the license.', licenseRequiredUserTip: 'The Enterprise Edition license is not bound. Contact a super administrator to import the license.', + restoreCommunity: 'Restore Community Edition', + restoreCommunityOnline: 'Online restore', + restoreCommunityOffline: 'Offline restore', + restoreCommunityPackageFound: + 'A usable offline Community Edition package was detected. Offline restore is available.', + restoreCommunityPackageMissing: + 'No usable offline Community Edition package was detected. Offline restore is currently unavailable.', + restoreCommunityOnlineHelper: + 'The Community Edition package for the current version will be downloaded automatically, and Enterprise-only data will be cleaned up.', + restoreCommunityOfflineHelper: + 'Place an official package no older than the current Enterprise version in {path}. Switching is enabled after a usable package is detected.', + restoreCommunityConfirm: + 'After switching, you can sign in to Community Edition with the current Enterprise super administrator account. Enterprise-only databases will be deleted during the switch.', + restoreCommunityStarting: 'Preparing Community Edition. The panel will restart automatically when ready.', levelUpPro: 'Upgrade to Commercial Edition', licenseSync: 'License Sync', knowMorePro: 'Learn More', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 4738b95b3c04..71763c1497a0 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -3239,6 +3239,21 @@ const message = { 'Primero copie el ID del dispositivo para obtener una licencia. Podrá iniciar sesión después de importarla.', licenseRequiredUserTip: 'La licencia Enterprise Edition no está vinculada. Contacte con un superadministrador para importar la licencia.', + restoreCommunity: 'Restaurar Community Edition', + restoreCommunityOnline: 'Restauración en línea', + restoreCommunityOffline: 'Restauración sin conexión', + restoreCommunityPackageFound: + 'Se detectó un paquete sin conexión de Community Edition válido. La restauración sin conexión está disponible.', + restoreCommunityPackageMissing: + 'No se detectó ningún paquete sin conexión de Community Edition válido. La restauración sin conexión no está disponible actualmente.', + restoreCommunityOnlineHelper: + 'Se descargará automáticamente el paquete Community Edition de la versión actual y se limpiarán los datos exclusivos de Enterprise.', + restoreCommunityOfflineHelper: + 'Coloque en {path} un paquete oficial cuya versión no sea anterior a la versión Enterprise actual. El cambio se habilitará al detectar un paquete utilizable.', + restoreCommunityConfirm: + 'Después del cambio, podrá iniciar sesión en Community Edition con la cuenta actual de superadministrador de Enterprise. Las bases de datos exclusivas de Enterprise se eliminarán durante el cambio.', + restoreCommunityStarting: + 'Preparando Community Edition. El panel se reiniciará automáticamente cuando termine.', levelUpPro: 'Actualizar a la edición comercial', licenseSync: 'Sincronizar licencia', knowMorePro: 'Ver más', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 51d19175818d..7893b6a207bf 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -3147,6 +3147,21 @@ const message = { licenseRequiredShortTip: 'لطفاً ابتدا شناسه دستگاه را کپی کنید تا مجوز دریافت کنید. پس از وارد کردن مجوز می‌توانید وارد شوید.', licenseRequiredUserTip: 'مجوز نسخه سازمانی متصل نیست. برای وارد کردن مجوز با ابرمدیر تماس بگیرید.', + restoreCommunity: 'بازیابی نسخه Community', + restoreCommunityOnline: 'بازیابی آنلاین', + restoreCommunityOffline: 'بازیابی آفلاین', + restoreCommunityPackageFound: + 'یک بسته آفلاین قابل استفاده Community Edition شناسایی شد. بازیابی آفلاین در دسترس است.', + restoreCommunityPackageMissing: + 'هیچ بسته آفلاین قابل استفاده Community Edition شناسایی نشد. بازیابی آفلاین در حال حاضر در دسترس نیست.', + restoreCommunityOnlineHelper: + 'بسته نسخه Community برای نسخه فعلی به‌طور خودکار دانلود و داده‌های ویژه Enterprise پاک‌سازی می‌شوند.', + restoreCommunityOfflineHelper: + 'یک بسته رسمی با نسخه‌ای قدیمی‌تر نبودن از نسخه فعلی Enterprise را در {path} قرار دهید. پس از شناسایی بسته قابل استفاده، تغییر نسخه فعال می‌شود.', + restoreCommunityConfirm: + 'پس از تغییر، می‌توانید با حساب ابرمدیر فعلی Enterprise وارد نسخه Community شوید. پایگاه‌های داده ویژه Enterprise در طول تغییر حذف می‌شوند.', + restoreCommunityStarting: + 'نسخه Community در حال آماده‌سازی است. پس از تکمیل، پنل به‌طور خودکار راه‌اندازی مجدد می‌شود.', levelUpPro: 'ارتقاء به نسخه تجاری', licenseSync: 'همگام‌سازی مجوز', knowMorePro: 'بیشتر بدانید', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 6ca5604ea9ac..fdbb1ce10080 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -3181,6 +3181,20 @@ const message = { 'まずデバイス ID をコピーしてライセンスを取得し、ライセンスをインポートするとログインできます。', licenseRequiredUserTip: 'Enterprise Edition のライセンスがバインドされていません。スーパー管理者に連絡してライセンスをインポートしてください。', + restoreCommunity: 'コミュニティ版に戻す', + restoreCommunityOnline: 'オンライン復元', + restoreCommunityOffline: 'オフライン復元', + restoreCommunityPackageFound: + '使用可能なコミュニティ版オフラインパッケージが検出されました。オフライン復元を実行できます。', + restoreCommunityPackageMissing: + '使用可能なコミュニティ版オフラインパッケージが検出されていないため、現在オフライン復元を実行できません。', + restoreCommunityOnlineHelper: + '現在のバージョンのコミュニティ版パッケージを自動的にダウンロードし、Enterprise 専用データを削除します。', + restoreCommunityOfflineHelper: + '現在の Enterprise バージョン以上の公式パッケージを {path} に配置してください。使用可能なパッケージが検出されると切り替えが有効になります。', + restoreCommunityConfirm: + '切り替え後は、現在の Enterprise スーパー管理者アカウントでコミュニティ版にログインできます。切り替え中に Enterprise 専用データベースが削除されます。', + restoreCommunityStarting: 'コミュニティ版を準備しています。完了後、パネルは自動的に再起動します。', technicalAdvice: '技術的な相談', advice: '相談', levelUpPro: '商用版にアップグレード', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 34b019e2b2d4..c3d67ccb7f88 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -3112,6 +3112,20 @@ const message = { licenseRequiredShortTip: '먼저 장치 ID를 복사해 라이선스를 받은 뒤, 라이선스를 가져오면 로그인할 수 있습니다.', licenseRequiredUserTip: 'Enterprise Edition 라이선스가 바인딩되지 않았습니다. 슈퍼 관리자에게 라이선스 가져오기를 요청하세요.', + restoreCommunity: '커뮤니티 버전 복원', + restoreCommunityOnline: '온라인 복원', + restoreCommunityOffline: '오프라인 복원', + restoreCommunityPackageFound: + '사용 가능한 Community Edition 오프라인 설치 패키지가 감지되었습니다. 오프라인 복원을 실행할 수 있습니다.', + restoreCommunityPackageMissing: + '사용 가능한 Community Edition 오프라인 설치 패키지가 감지되지 않아 현재 오프라인 복원을 실행할 수 없습니다.', + restoreCommunityOnlineHelper: + '현재 버전의 커뮤니티 버전 패키지를 자동으로 다운로드하고 Enterprise 전용 데이터를 정리합니다.', + restoreCommunityOfflineHelper: + '현재 Enterprise 버전 이상인 공식 패키지를 {path}에 저장하세요. 사용 가능한 패키지가 감지되면 전환이 활성화됩니다.', + restoreCommunityConfirm: + '전환 후 현재 Enterprise 슈퍼 관리자 계정으로 커뮤니티 버전에 로그인할 수 있습니다. 전환 과정에서 Enterprise 전용 데이터베이스가 삭제됩니다.', + restoreCommunityStarting: '커뮤니티 버전을 준비 중입니다. 완료되면 패널이 자동으로 다시 시작됩니다.', technicalAdvice: '기술 상담', advice: '상담', levelUpPro: '상용 버전으로 업그레이드', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 5191f603da3d..bcff0e42b1cb 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -3101,6 +3101,20 @@ const message = { licenseRequiredShortTip: 'ກະລຸນາຄັດລອກ ID ອຸປະກອນກ່ອນເພື່ອຮັບລິຂະສິດ. ທ່ານສາມາດເຂົ້າລະບົບໄດ້ຫຼັງຈາກນຳເຂົ້າລິຂະສິດແລ້ວ.', licenseRequiredUserTip: 'ລິຂະສິດເວີຊັນ Enterprise ບໍ່ໄດ້ຖືກຜູກ. ຕິດຕໍ່ຜູ້ດູແລລະບົບສູງສຸດເພື່ອນຳເຂົ້າລິຂະສິດ.', + restoreCommunity: 'ກູ້ຄືນ Community Edition', + restoreCommunityOnline: 'ກູ້ຄືນອອນລາຍ', + restoreCommunityOffline: 'ກູ້ຄືນອອບລາຍ', + restoreCommunityPackageFound: + 'ກວດພົບແພັກເກດ Community Edition ອອບລາຍທີ່ໃຊ້ໄດ້ ແລະສາມາດດຳເນີນການກູ້ຄືນອອບລາຍໄດ້.', + restoreCommunityPackageMissing: + 'ບໍ່ພົບແພັກເກດ Community Edition ອອບລາຍທີ່ໃຊ້ໄດ້ ຈຶ່ງຍັງບໍ່ສາມາດກູ້ຄືນອອບລາຍໄດ້.', + restoreCommunityOnlineHelper: + 'ລະບົບຈະດາວໂຫຼດແພັກເກດ Community Edition ສຳລັບເວີຊັນປັດຈຸບັນອັດຕະໂນມັດ ແລະ ລ້າງຂໍ້ມູນສະເພາະ Enterprise.', + restoreCommunityOfflineHelper: + 'ວາງແພັກເກດທາງການທີ່ມີເວີຊັນບໍ່ຕ່ຳກວ່າ Enterprise ປັດຈຸບັນໄວ້ໃນ {path}. ການສະຫຼັບຈະເປີດໃຊ້ເມື່ອກວດພົບແພັກເກດທີ່ໃຊ້ໄດ້.', + restoreCommunityConfirm: + 'ຫຼັງຈາກສະຫຼັບ ສາມາດໃຊ້ບັນຊີຜູ້ດູແລສູງສຸດ Enterprise ປັດຈຸບັນເຂົ້າ Community Edition. ຖານຂໍ້ມູນສະເພາະ Enterprise ຈະຖືກລຶບໃນລະຫວ່າງການສະຫຼັບ.', + restoreCommunityStarting: 'ກຳລັງກະກຽມ Community Edition. ແຜງຄວບຄຸມຈະເລີ່ມໃໝ່ອັດຕະໂນມັດເມື່ອສຳເລັດ.', levelUpPro: 'ອັບເກຣດເປັນເວີຊັນ Commercial', licenseSync: 'ຊິງຄ໌ລິຂະສິດ', knowMorePro: 'ຮຽນຮູ້ເພີ່ມເຕີມ', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index d86b9d599940..c744bc2f25a0 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -3237,6 +3237,21 @@ const message = { 'Sila salin ID peranti terlebih dahulu untuk mendapatkan lesen. Anda boleh log masuk selepas mengimport lesen.', licenseRequiredUserTip: 'Lesen Enterprise Edition belum diikat. Hubungi pentadbir super untuk mengimport lesen.', + restoreCommunity: 'Pulihkan Community Edition', + restoreCommunityOnline: 'Pemulihan dalam talian', + restoreCommunityOffline: 'Pemulihan luar talian', + restoreCommunityPackageFound: + 'Pakej luar talian Community Edition yang boleh digunakan telah dikesan. Pemulihan luar talian tersedia.', + restoreCommunityPackageMissing: + 'Tiada pakej luar talian Community Edition yang boleh digunakan dikesan. Pemulihan luar talian tidak tersedia pada masa ini.', + restoreCommunityOnlineHelper: + 'Pakej Community Edition untuk versi semasa akan dimuat turun secara automatik dan data khusus Enterprise akan dibersihkan.', + restoreCommunityOfflineHelper: + 'Letakkan pakej rasmi yang tidak lebih lama daripada versi Enterprise semasa dalam {path}. Pertukaran akan didayakan selepas pakej yang boleh digunakan dikesan.', + restoreCommunityConfirm: + 'Selepas pertukaran, anda boleh log masuk ke Community Edition menggunakan akaun pentadbir super Enterprise semasa. Pangkalan data khusus Enterprise akan dipadamkan semasa pertukaran.', + restoreCommunityStarting: + 'Community Edition sedang disediakan. Panel akan dimulakan semula secara automatik apabila selesai.', technicalAdvice: 'Konsultasi Teknikal', advice: 'Konsultasi', levelUpPro: 'Naik taraf ke Edisi Komersial', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index fe9ae35992d9..4401a1ec890c 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -3358,6 +3358,21 @@ const message = { 'Copie primeiro o ID do dispositivo para obter a licença. Você poderá entrar depois de importá-la.', licenseRequiredUserTip: 'A licença Enterprise Edition não está vinculada. Entre em contato com o superadministrador para importar a licença.', + restoreCommunity: 'Restaurar Community Edition', + restoreCommunityOnline: 'Restauração online', + restoreCommunityOffline: 'Restauração offline', + restoreCommunityPackageFound: + 'Um pacote offline utilizável da Community Edition foi detectado. A restauração offline está disponível.', + restoreCommunityPackageMissing: + 'Nenhum pacote offline utilizável da Community Edition foi detectado. A restauração offline não está disponível no momento.', + restoreCommunityOnlineHelper: + 'O pacote Community Edition da versão atual será baixado automaticamente e os dados exclusivos da Enterprise serão limpos.', + restoreCommunityOfflineHelper: + 'Coloque em {path} um pacote oficial cuja versão não seja anterior à versão Enterprise atual. A troca será habilitada quando um pacote utilizável for detectado.', + restoreCommunityConfirm: + 'Após a troca, você poderá entrar na Community Edition com a conta atual de superadministrador da Enterprise. Os bancos de dados exclusivos da Enterprise serão excluídos durante a troca.', + restoreCommunityStarting: + 'Preparando a Community Edition. O painel será reiniciado automaticamente quando estiver pronto.', technicalAdvice: 'Consultoria técnica', advice: 'Consultoria', levelUpPro: 'Fazer upgrade para a edição comercial', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index e08c3413042f..65f0808f2991 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -3215,6 +3215,21 @@ const message = { 'Сначала скопируйте ID устройства, чтобы получить лицензию. После импорта лицензии вы сможете войти.', licenseRequiredUserTip: 'Лицензия Enterprise Edition не привязана. Обратитесь к суперадминистратору для импорта лицензии.', + restoreCommunity: 'Восстановить Community Edition', + restoreCommunityOnline: 'Онлайн-восстановление', + restoreCommunityOffline: 'Офлайн-восстановление', + restoreCommunityPackageFound: + 'Обнаружен подходящий офлайн-пакет Community Edition. Офлайн-восстановление доступно.', + restoreCommunityPackageMissing: + 'Подходящий офлайн-пакет Community Edition не обнаружен. Офлайн-восстановление сейчас недоступно.', + restoreCommunityOnlineHelper: + 'Пакет Community Edition текущей версии будет загружен автоматически, а данные, предназначенные только для Enterprise, будут очищены.', + restoreCommunityOfflineHelper: + 'Поместите официальный пакет версии не ниже текущей Enterprise в {path}. Переключение станет доступно после обнаружения подходящего пакета.', + restoreCommunityConfirm: + 'После переключения можно войти в Community Edition с текущей учётной записью суперадминистратора Enterprise. Базы данных Enterprise будут удалены во время переключения.', + restoreCommunityStarting: + 'Подготавливается Community Edition. После завершения панель автоматически перезапустится.', technicalAdvice: 'Техническая консультация', advice: 'Консультация', levelUpPro: 'Обновить до коммерческой версии', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 9a4daf853910..807ff28d502c 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -3219,6 +3219,21 @@ const message = { 'Lütfen önce cihaz kimliğini kopyalayarak lisansı alın. Lisansı içe aktardıktan sonra giriş yapabilirsiniz.', licenseRequiredUserTip: 'Enterprise Edition lisansı bağlı değil. Lisansı içe aktarması için süper yöneticiyle iletişime geçin.', + restoreCommunity: 'Community Edition sürümünü geri yükle', + restoreCommunityOnline: 'Çevrimiçi geri yükleme', + restoreCommunityOffline: 'Çevrimdışı geri yükleme', + restoreCommunityPackageFound: + 'Kullanılabilir bir çevrimdışı Community Edition paketi algılandı. Çevrimdışı geri yükleme kullanılabilir.', + restoreCommunityPackageMissing: + 'Kullanılabilir bir çevrimdışı Community Edition paketi algılanmadı. Çevrimdışı geri yükleme şu anda kullanılamıyor.', + restoreCommunityOnlineHelper: + 'Geçerli sürümün Community Edition paketi otomatik olarak indirilecek ve Enterprise sürümüne özel veriler temizlenecektir.', + restoreCommunityOfflineHelper: + 'Geçerli Enterprise sürümünden eski olmayan resmi bir paketi {path} dizinine yerleştirin. Kullanılabilir bir paket algılandığında geçiş etkinleştirilir.', + restoreCommunityConfirm: + 'Geçişten sonra mevcut Enterprise süper yönetici hesabıyla Community Edition sürümüne giriş yapabilirsiniz. Enterprise sürümüne özel veritabanları geçiş sırasında silinecektir.', + restoreCommunityStarting: + 'Community Edition hazırlanıyor. Hazır olduğunda panel otomatik olarak yeniden başlatılacaktır.', levelUpPro: 'Ticari sürüme yükselt', licenseSync: 'Lisans Senkronizasyonu', knowMorePro: 'Daha Fazla Bilgi Edinin', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index ffb5f4ff024f..4351b133eb92 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -2949,6 +2949,16 @@ const message = { licenseRequiredTip: '目前尚未匯入許可證,複製裝置 ID 後取得許可證並匯入後才能登入使用。', licenseRequiredShortTip: '請先複製裝置 ID 取得許可證,匯入許可證後即可登入。', licenseRequiredUserTip: '企業版許可證未綁定,請聯絡超級管理員匯入許可證。', + restoreCommunity: '恢復社群版', + restoreCommunityOnline: '線上恢復', + restoreCommunityOffline: '離線恢復', + restoreCommunityPackageFound: '已偵測到可用的社群版離線安裝包,可以執行離線恢復。', + restoreCommunityPackageMissing: '未偵測到可用的社群版離線安裝包,暫時無法執行離線恢復。', + restoreCommunityOnlineHelper: '切換後將自動下載目前版本的社群版安裝包,並清理企業版專屬資料。', + restoreCommunityOfflineHelper: + '請先將版本不低於目前企業版的官方安裝包放入 {path},偵測到可用安裝包後即可執行切換。', + restoreCommunityConfirm: '切換後可使用目前企業版超級管理員帳號登入社群版,切換過程中將刪除企業版專屬資料庫。', + restoreCommunityStarting: '正在準備社群版,完成後面板將自動重新啟動。', levelUpPro: '升級商業版', licenseSync: '許可證同步', knowMorePro: '了解更多', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 800479afd48e..bd18e5aada31 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2944,6 +2944,15 @@ const message = { licenseRequiredTip: '当前尚未导入许可证,复制设备 ID 后获取许可证并导入后才能登录使用。', licenseRequiredShortTip: '请先复制设备 ID 获取许可证,导入许可证后即可登录。', licenseRequiredUserTip: '企业版许可证未绑定,请联系超级管理员导入许可证。', + restoreCommunity: '恢复社区版', + restoreCommunityOnline: '在线恢复', + restoreCommunityOffline: '离线恢复', + restoreCommunityPackageFound: '已检测到可用的社区版离线安装包,可以执行离线恢复。', + restoreCommunityPackageMissing: '未检测到可用的社区版离线安装包,暂时无法执行离线恢复。', + restoreCommunityOnlineHelper: '将自动下载当前版本的社区版安装包,完成切换。', + restoreCommunityOfflineHelper: '请先将版本不低于当前企业版的官方安装包放入 {path} 后执行切换。', + restoreCommunityConfirm: '切换后可使用当前企业版超级管理员账号登录社区版,切换过程中将删除企业版专属数据库。', + restoreCommunityStarting: '正在准备社区版,完成后面板将自动重启。', levelUpPro: '升级商业版', licenseSync: '许可证同步', knowMorePro: '了解商业版', diff --git a/frontend/src/views/setting/license-required/community-restore/index.vue b/frontend/src/views/setting/license-required/community-restore/index.vue new file mode 100644 index 000000000000..1f7e696a4989 --- /dev/null +++ b/frontend/src/views/setting/license-required/community-restore/index.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/frontend/src/views/setting/license-required/index.vue b/frontend/src/views/setting/license-required/index.vue index 0079d8bfb19a..e18a75798578 100644 --- a/frontend/src/views/setting/license-required/index.vue +++ b/frontend/src/views/setting/license-required/index.vue @@ -1,11 +1,26 @@ @@ -93,37 +110,43 @@ import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'; import { useRouter } from 'vue-router'; import type { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile } from 'element-plus'; import { genFileId } from 'element-plus'; -import { getEnterpriseLicense, uploadEnterpriseLicense } from '@/api/modules/setting'; +import { getCommunityRestoreStatus, getEnterpriseLicense, uploadEnterpriseLicense } from '@/api/modules/setting'; import { getLoginSetting } from '@/api/modules/auth'; import { useGlobalStore } from '@/composables/useGlobalStore'; -import { MsgSuccess } from '@/utils/message'; +import { MsgError, MsgSuccess } from '@/utils/message'; import { preloadImage } from '@/utils/browser'; import { adjustColorToRGBA } from '@/utils/color'; import { getXpackSettingForTheme } from '@/utils/xpack'; import { copyText } from '@/utils/clipboard'; import i18n from '@/lang'; +import CommunityRestoreDialog from './community-restore/index.vue'; const router = useRouter(); const { + globalStore, entrance, isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isFxplay, isIntl, + isLoading: restoring, isLogin, isMasterProductPro, isOffline, + isOnRestart, menuAccordion, openMenuTabs, themeConfig, } = useGlobalStore(); const loading = ref(false); +const communityRestoreRef = ref>(); const uploadRef = ref(); const uploaderFiles = ref([]); const licenseInfo = reactive({ deviceID: '', }); +let restoreTimer: number | undefined; const defaultLoginImage = new URL('@/assets/images/1panel-login-enterprise.png', import.meta.url).href; const defaultLoginBgImage = new URL('@/assets/images/1panel-login-bg.jpg', import.meta.url).href; const loadedLoginImage = ref(null); @@ -133,6 +156,7 @@ const imgLoaded = ref(false); const FIXED_WIDTH = 1000; const FIXED_HEIGHT = 415; +const RESTORE_POLL_INTERVAL = 10_000; const containerWidth = computed(() => `${FIXED_WIDTH}px`); const containerHeight = computed(() => `${FIXED_HEIGHT}px`); const width = ref(window.innerWidth); @@ -183,7 +207,7 @@ const applyLoginThemeColor = () => { ); document.documentElement.style.setProperty( '--login-loading-mask-color', - adjustColorToRGBA(loginBtnLinkColor, 30, 15), + adjustColorToRGBA(loginBtnLinkColor, 0, 60), ); }; @@ -259,6 +283,72 @@ const loadLicenseInfo = async () => { isEnterpriseLicensed.value = false; }; +const loadRestoreInfo = async () => { + const res = await getCommunityRestoreStatus(); + restoring.value = res.data.state === 'Running'; + isOnRestart.value = restoring.value; +}; + +const scheduleRestorePoll = () => { + window.clearTimeout(restoreTimer); + restoreTimer = window.setTimeout(pollRestoreStatus, RESTORE_POLL_INTERVAL); +}; + +const redirectToCommunityLogin = () => { + restoring.value = false; + isOnRestart.value = false; + globalStore.setLogStatus(false); + globalStore.clearAuthInfo(); + isEnterprise.value = false; + isEnterpriseLicensed.value = false; + isEnterpriseLicenseLoaded.value = true; + window.location.replace(entrance.value ? `/${entrance.value}` : '/'); +}; + +const waitForCommunityService = async () => { + try { + const response = await fetch(`${import.meta.env.VITE_API_URL}/core/auth/setting`, { + credentials: 'include', + cache: 'no-store', + }); + if (response.ok) { + const result = await response.json(); + if (result?.data?.isEnterprise === false) { + redirectToCommunityLogin(); + return; + } + } + } catch { + // The core service is expected to be temporarily unavailable while binaries are switched. + } + scheduleRestorePoll(); +}; + +const pollRestoreStatus = async () => { + try { + const res = await getCommunityRestoreStatus(); + if (res.data.state === 'Failed') { + restoring.value = false; + isOnRestart.value = false; + MsgError(res.data.message); + return; + } + restoring.value = res.data.state === 'Running'; + isOnRestart.value = restoring.value; + if (restoring.value) { + scheduleRestorePoll(); + } + } catch { + await waitForCommunityService(); + } +}; + +const handleCommunityRestoreStarted = () => { + restoring.value = true; + isOnRestart.value = true; + scheduleRestorePoll(); +}; + const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => { uploaderFiles.value = uploadFiles; }; @@ -317,6 +407,12 @@ onMounted(async () => { await loadXpackLoginTheme(); await loadBackground(); await loadLicenseInfo(); + if (!isEnterpriseLicensed.value) { + await loadRestoreInfo(); + if (restoring.value) { + scheduleRestorePoll(); + } + } } finally { loading.value = false; } @@ -324,6 +420,7 @@ onMounted(async () => { onUnmounted(() => { window.removeEventListener('resize', updateSize); + window.clearTimeout(restoreTimer); }); @@ -332,6 +429,18 @@ onUnmounted(() => { border-radius: 0; } +.community-restore-link { + position: absolute; + top: 16px; + right: 20px; + z-index: 1; + color: var(--login-btn-link-color, #005eeb); + + &:hover { + color: var(--login-btn-link-hover-color, #0054d3); + } +} + .login-form { max-width: 560px; gap: 16px; @@ -449,7 +558,11 @@ onUnmounted(() => { background-color: var(--login-loading-mask-color) !important; .el-loading-spinner .path { - stroke: var(--login-btn-link-color); + stroke: #ffffff; + } + + .el-loading-text { + color: #ffffff; } } diff --git a/frontend/src/views/toolbox/clean/index.vue b/frontend/src/views/toolbox/clean/index.vue index 1ed1c22f5512..9003c7e9de9a 100644 --- a/frontend/src/views/toolbox/clean/index.vue +++ b/frontend/src/views/toolbox/clean/index.vue @@ -510,6 +510,8 @@ function load18n(label: string) { return i18n.global.t('clean.snapshot'); case 'rollback': return i18n.global.t('clean.rollback'); + case 'rollback_community_restore': + return i18n.global.t('license.restoreCommunity'); case 'tmp_backup': return i18n.global.t('clean.tmpBackup'); case 'unknown_app':