mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-18 16:17:16 +00:00
feat(sub): redesign the subscription page around usage, tabs and app imports
The info page was a long key/value table followed by every link and two app dropdowns, and it rendered left-to-right even for Persian and Arabic. It now leads with a usage ring, the remaining quota and a stats grid, and splits the rest into Subscription / Apps / Configs tabs. - Status tells expired, data-used-up and disabled apart instead of one "Inactive", replacing the hard-coded English expiry chip. - The Apps tab keeps every Android and iOS app with its existing deep link, preselects the visitor's platform and adds Windows: Hiddify and Clash Verge Rev import directly, v2rayN copies the link. - fa-IR and ar-EG render right-to-left; URLs, IDs and sizes stay LTR. - The footer shows the support link and the client refresh interval, so subPageContext now carries subUpdates (also in ?format=info). - Status, days-left and app deep-link logic lives in subPageModel.ts, with unit tests pinning the deep links the page already shipped.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -660,6 +661,8 @@ func (a *SUBController) subPageContext(page PageData) map[string]any {
|
||||
if datepicker == "" {
|
||||
datepicker = "gregorian"
|
||||
}
|
||||
subUpdates, _ := a.settingService.GetSubUpdates()
|
||||
updateHours, _ := strconv.Atoi(subUpdates)
|
||||
|
||||
return map[string]any{
|
||||
"sId": page.SId,
|
||||
@@ -680,6 +683,7 @@ func (a *SUBController) subPageContext(page PageData) map[string]any {
|
||||
"subClashUrl": page.SubClashUrl,
|
||||
"subTitle": page.SubTitle,
|
||||
"subSupportUrl": page.SubSupportUrl,
|
||||
"subUpdates": updateHours,
|
||||
"links": page.Result,
|
||||
"emails": page.Emails,
|
||||
"datepicker": datepicker,
|
||||
|
||||
@@ -76,6 +76,9 @@ func TestSubInfoEndpoint_ServesStatusJSONEvenForBrowsers(t *testing.T) {
|
||||
t.Fatalf("info payload missing %q; body=%s", key, w.Body.String())
|
||||
}
|
||||
}
|
||||
if info["subUpdates"] != float64(12) {
|
||||
t.Fatalf("subUpdates = %v, want the default 12-hour interval", info["subUpdates"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubInfoEndpoint_UnknownSubIs404(t *testing.T) {
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "حدث خطأ ما",
|
||||
"subscription": {
|
||||
"title": "معلومات الاشتراك",
|
||||
"subId": "معرّف الاشتراك",
|
||||
"status": "الحالة",
|
||||
"downloaded": "التنزيل",
|
||||
"uploaded": "الرفع",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "بدون انتهاء",
|
||||
"copyAllConfigs": "نسخ جميع الإعدادات",
|
||||
"copyAllConfigsCopied": "تم نسخ جميع الإعدادات",
|
||||
"email": "البريد"
|
||||
"daysLeft": "الأيام المتبقية",
|
||||
"expired": "منتهي الصلاحية",
|
||||
"depleted": "نفدت البيانات",
|
||||
"ofTotal": "من {total}",
|
||||
"tabLinks": "الاشتراك",
|
||||
"tabApps": "التطبيقات",
|
||||
"tabConfigs": "الكونفيجات",
|
||||
"scanTitle": "امسح بهاتفك",
|
||||
"scanHint": "امسح هذا الرمز في تطبيق VPN لإضافة الاشتراك دون نسخ الرابط.",
|
||||
"updateInterval": "تحديث تلقائي كل {hours} ساعة",
|
||||
"support": "الدعم",
|
||||
"qrTitle": "امسح. استورد. اتصل.",
|
||||
"qrHint": "امسح الرمز بتطبيق العميل. لا تشارك هذا الرمز مع أحد.",
|
||||
"saveQr": "حفظ QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "الثيم",
|
||||
|
||||
@@ -80,8 +80,6 @@
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"subscription": {
|
||||
"title": "Subscription info",
|
||||
"subId": "Subscription ID",
|
||||
"email": "Email",
|
||||
"status": "Status",
|
||||
"downloaded": "Downloaded",
|
||||
"uploaded": "Uploaded",
|
||||
@@ -93,7 +91,21 @@
|
||||
"unlimited": "Unlimited",
|
||||
"noExpiry": "No expiry",
|
||||
"copyAllConfigs": "Copy All Configs",
|
||||
"copyAllConfigsCopied": "All configs copied"
|
||||
"copyAllConfigsCopied": "All configs copied",
|
||||
"daysLeft": "Days left",
|
||||
"expired": "Expired",
|
||||
"depleted": "Data used up",
|
||||
"ofTotal": "of {total}",
|
||||
"tabLinks": "Subscription",
|
||||
"tabApps": "Apps",
|
||||
"tabConfigs": "Configs",
|
||||
"scanTitle": "Scan with your phone",
|
||||
"scanHint": "Scan this code in your VPN app to add the subscription without copying the link.",
|
||||
"updateInterval": "Auto-updates every {hours} hours",
|
||||
"support": "Support",
|
||||
"qrTitle": "Scan. Import. Connect.",
|
||||
"qrHint": "Scan with your client app. Keep this code private.",
|
||||
"saveQr": "Save QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Theme",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "Algo salió mal",
|
||||
"subscription": {
|
||||
"title": "Información de suscripción",
|
||||
"subId": "ID de suscripción",
|
||||
"status": "Estado",
|
||||
"downloaded": "Descargado",
|
||||
"uploaded": "Subido",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "Sin caducidad",
|
||||
"copyAllConfigs": "Copiar Todas las Configuraciones",
|
||||
"copyAllConfigsCopied": "Todas las configuraciones copiadas",
|
||||
"email": "Email"
|
||||
"daysLeft": "Días restantes",
|
||||
"expired": "Caducada",
|
||||
"depleted": "Datos agotados",
|
||||
"ofTotal": "de {total}",
|
||||
"tabLinks": "Suscripción",
|
||||
"tabApps": "Aplicaciones",
|
||||
"tabConfigs": "Configuraciones",
|
||||
"scanTitle": "Escanea con tu teléfono",
|
||||
"scanHint": "Escanea este código en tu aplicación VPN para añadir la suscripción sin copiar el enlace.",
|
||||
"updateInterval": "Se actualiza automáticamente cada {hours} horas",
|
||||
"support": "Soporte",
|
||||
"qrTitle": "Escanea. Importa. Conéctate.",
|
||||
"qrHint": "Escanéalo con tu aplicación cliente. Mantén este código en privado.",
|
||||
"saveQr": "Guardar QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Tema",
|
||||
|
||||
@@ -80,8 +80,6 @@
|
||||
"somethingWentWrong": "مشکلی پیش آمد",
|
||||
"subscription": {
|
||||
"title": "اطلاعات سابسکریپشن",
|
||||
"subId": "شناسه اشتراک",
|
||||
"email": "ایمیل",
|
||||
"status": "وضعیت",
|
||||
"downloaded": "دانلود",
|
||||
"uploaded": "آپلود",
|
||||
@@ -93,7 +91,21 @@
|
||||
"unlimited": "نامحدود",
|
||||
"noExpiry": "بدون انقضا",
|
||||
"copyAllConfigs": "کپی همه کانفیگها",
|
||||
"copyAllConfigsCopied": "همه کانفیگها کپی شدند"
|
||||
"copyAllConfigsCopied": "همه کانفیگها کپی شدند",
|
||||
"daysLeft": "روز باقیمانده",
|
||||
"expired": "منقضی شده",
|
||||
"depleted": "حجم تمام شده",
|
||||
"ofTotal": "از {total}",
|
||||
"tabLinks": "اشتراک",
|
||||
"tabApps": "اپلیکیشنها",
|
||||
"tabConfigs": "کانفیگها",
|
||||
"scanTitle": "اسکن با گوشی",
|
||||
"scanHint": "این کد را در اپلیکیشن VPN اسکن کنید تا اشتراک بدون کپیکردن لینک اضافه شود.",
|
||||
"updateInterval": "بهروزرسانی خودکار هر {hours} ساعت",
|
||||
"support": "پشتیبانی",
|
||||
"qrTitle": "اسکن. افزودن. اتصال.",
|
||||
"qrHint": "با اپلیکیشن کلاینت اسکن کنید. این کد را به کسی ندهید.",
|
||||
"saveQr": "ذخیره QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "تم",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "Terjadi kesalahan",
|
||||
"subscription": {
|
||||
"title": "Info langganan",
|
||||
"subId": "ID langganan",
|
||||
"status": "Status",
|
||||
"downloaded": "Diunduh",
|
||||
"uploaded": "Diunggah",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "Tanpa kedaluwarsa",
|
||||
"copyAllConfigs": "Salin Semua Konfigurasi",
|
||||
"copyAllConfigsCopied": "Semua konfigurasi tersalin",
|
||||
"email": "Email"
|
||||
"daysLeft": "Sisa hari",
|
||||
"expired": "Kedaluwarsa",
|
||||
"depleted": "Kuota habis",
|
||||
"ofTotal": "dari {total}",
|
||||
"tabLinks": "Langganan",
|
||||
"tabApps": "Aplikasi",
|
||||
"tabConfigs": "Konfigurasi",
|
||||
"scanTitle": "Pindai dengan ponsel",
|
||||
"scanHint": "Pindai kode ini di aplikasi VPN untuk menambahkan langganan tanpa menyalin tautan.",
|
||||
"updateInterval": "Diperbarui otomatis setiap {hours} jam",
|
||||
"support": "Dukungan",
|
||||
"qrTitle": "Pindai. Impor. Terhubung.",
|
||||
"qrHint": "Pindai dengan aplikasi klien Anda. Jaga kerahasiaan kode ini.",
|
||||
"saveQr": "Simpan QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Tema",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "エラーが発生しました",
|
||||
"subscription": {
|
||||
"title": "サブスクリプション情報",
|
||||
"subId": "サブスクリプションID",
|
||||
"status": "ステータス",
|
||||
"downloaded": "ダウンロード",
|
||||
"uploaded": "アップロード",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "期限なし",
|
||||
"copyAllConfigs": "すべての設定をコピー",
|
||||
"copyAllConfigsCopied": "すべての設定をコピーしました",
|
||||
"email": "メール"
|
||||
"daysLeft": "残り日数",
|
||||
"expired": "期限切れ",
|
||||
"depleted": "データ上限到達",
|
||||
"ofTotal": "/ {total}",
|
||||
"tabLinks": "サブスクリプション",
|
||||
"tabApps": "アプリ",
|
||||
"tabConfigs": "設定",
|
||||
"scanTitle": "スマートフォンでスキャン",
|
||||
"scanHint": "VPN アプリでこのコードをスキャンすると、リンクをコピーせずにサブスクリプションを追加できます。",
|
||||
"updateInterval": "{hours} 時間ごとに自動更新",
|
||||
"support": "サポート",
|
||||
"qrTitle": "スキャン。インポート。接続。",
|
||||
"qrHint": "クライアントアプリでスキャンしてください。このコードは他人に見せないでください。",
|
||||
"saveQr": "QR を保存"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "テーマ",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "Algo deu errado",
|
||||
"subscription": {
|
||||
"title": "Informações da assinatura",
|
||||
"subId": "ID da assinatura",
|
||||
"status": "Status",
|
||||
"downloaded": "Baixado",
|
||||
"uploaded": "Enviado",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "Sem validade",
|
||||
"copyAllConfigs": "Copiar Todas as Configurações",
|
||||
"copyAllConfigsCopied": "Todas as configurações copiadas",
|
||||
"email": "Email"
|
||||
"daysLeft": "Dias restantes",
|
||||
"expired": "Expirada",
|
||||
"depleted": "Dados esgotados",
|
||||
"ofTotal": "de {total}",
|
||||
"tabLinks": "Assinatura",
|
||||
"tabApps": "Aplicativos",
|
||||
"tabConfigs": "Configurações",
|
||||
"scanTitle": "Escaneie com o celular",
|
||||
"scanHint": "Escaneie este código no seu aplicativo de VPN para adicionar a assinatura sem copiar o link.",
|
||||
"updateInterval": "Atualiza automaticamente a cada {hours} horas",
|
||||
"support": "Suporte",
|
||||
"qrTitle": "Escaneie. Importe. Conecte.",
|
||||
"qrHint": "Escaneie com seu aplicativo cliente. Mantenha este código em sigilo.",
|
||||
"saveQr": "Salvar QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Tema",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "Что-то пошло не так",
|
||||
"subscription": {
|
||||
"title": "Информация о подписке",
|
||||
"subId": "ID подписки",
|
||||
"status": "Статус",
|
||||
"downloaded": "Загружено",
|
||||
"uploaded": "Отправлено",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "Бессрочно",
|
||||
"copyAllConfigs": "Копировать все конфигурации",
|
||||
"copyAllConfigsCopied": "Все конфигурации скопированы",
|
||||
"email": "Email"
|
||||
"daysLeft": "Осталось дней",
|
||||
"expired": "Истёк",
|
||||
"depleted": "Трафик исчерпан",
|
||||
"ofTotal": "из {total}",
|
||||
"tabLinks": "Подписка",
|
||||
"tabApps": "Приложения",
|
||||
"tabConfigs": "Конфиги",
|
||||
"scanTitle": "Сканируйте телефоном",
|
||||
"scanHint": "Отсканируйте этот код в VPN-приложении, чтобы добавить подписку без копирования ссылки.",
|
||||
"updateInterval": "Автообновление каждые {hours} ч",
|
||||
"support": "Поддержка",
|
||||
"qrTitle": "Сканируйте. Импортируйте. Подключайтесь.",
|
||||
"qrHint": "Отсканируйте в клиентском приложении. Никому не показывайте этот код.",
|
||||
"saveQr": "Сохранить QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Тема",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "Bir hata oluştu",
|
||||
"subscription": {
|
||||
"title": "Abonelik Bilgisi",
|
||||
"subId": "Abonelik Kimliği",
|
||||
"status": "Durum",
|
||||
"downloaded": "İndirilen",
|
||||
"uploaded": "Yüklenen",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "Süresiz",
|
||||
"copyAllConfigs": "Tüm Yapılandırmaları Kopyala",
|
||||
"copyAllConfigsCopied": "Tüm yapılandırmalar kopyalandı",
|
||||
"email": "E-posta"
|
||||
"daysLeft": "Kalan gün",
|
||||
"expired": "Süresi doldu",
|
||||
"depleted": "Kota doldu",
|
||||
"ofTotal": "/ {total}",
|
||||
"tabLinks": "Abonelik",
|
||||
"tabApps": "Uygulamalar",
|
||||
"tabConfigs": "Yapılandırmalar",
|
||||
"scanTitle": "Telefonunuzla tarayın",
|
||||
"scanHint": "Bağlantıyı kopyalamadan aboneliği eklemek için bu kodu VPN uygulamanızda tarayın.",
|
||||
"updateInterval": "Her {hours} saatte bir otomatik güncellenir",
|
||||
"support": "Destek",
|
||||
"qrTitle": "Tara. İçe aktar. Bağlan.",
|
||||
"qrHint": "İstemci uygulamanızla tarayın. Bu kodu kimseyle paylaşmayın.",
|
||||
"saveQr": "QR'ı kaydet"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Tema",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "Щось пішло не так",
|
||||
"subscription": {
|
||||
"title": "Інформація про підписку",
|
||||
"subId": "ID підписки",
|
||||
"status": "Статус",
|
||||
"downloaded": "Завантажено",
|
||||
"uploaded": "Відвантажено",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "Без строку",
|
||||
"copyAllConfigs": "Копіювати всі конфігурації",
|
||||
"copyAllConfigsCopied": "Всі конфігурації скопійовано",
|
||||
"email": "Email"
|
||||
"daysLeft": "Залишилось днів",
|
||||
"expired": "Термін дії минув",
|
||||
"depleted": "Трафік вичерпано",
|
||||
"ofTotal": "з {total}",
|
||||
"tabLinks": "Підписка",
|
||||
"tabApps": "Застосунки",
|
||||
"tabConfigs": "Конфіги",
|
||||
"scanTitle": "Скануйте телефоном",
|
||||
"scanHint": "Відскануйте цей код у VPN-застосунку, щоб додати підписку без копіювання посилання.",
|
||||
"updateInterval": "Автооновлення кожні {hours} год",
|
||||
"support": "Підтримка",
|
||||
"qrTitle": "Скануйте. Імпортуйте. Підключайтеся.",
|
||||
"qrHint": "Відскануйте в клієнтському застосунку. Нікому не показуйте цей код.",
|
||||
"saveQr": "Зберегти QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Тема",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "Đã xảy ra lỗi",
|
||||
"subscription": {
|
||||
"title": "Thông tin đăng ký",
|
||||
"subId": "ID đăng ký",
|
||||
"status": "Trạng thái",
|
||||
"downloaded": "Đã tải xuống",
|
||||
"uploaded": "Đã tải lên",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "Không hết hạn",
|
||||
"copyAllConfigs": "Sao chép tất cả cấu hình",
|
||||
"copyAllConfigsCopied": "Đã sao chép tất cả cấu hình",
|
||||
"email": "Email"
|
||||
"daysLeft": "Số ngày còn lại",
|
||||
"expired": "Đã hết hạn",
|
||||
"depleted": "Hết dung lượng",
|
||||
"ofTotal": "trên {total}",
|
||||
"tabLinks": "Gói đăng ký",
|
||||
"tabApps": "Ứng dụng",
|
||||
"tabConfigs": "Cấu hình",
|
||||
"scanTitle": "Quét bằng điện thoại",
|
||||
"scanHint": "Quét mã này trong ứng dụng VPN để thêm gói đăng ký mà không cần sao chép liên kết.",
|
||||
"updateInterval": "Tự động cập nhật mỗi {hours} giờ",
|
||||
"support": "Hỗ trợ",
|
||||
"qrTitle": "Quét. Nhập. Kết nối.",
|
||||
"qrHint": "Quét bằng ứng dụng khách. Không chia sẻ mã này cho người khác.",
|
||||
"saveQr": "Lưu QR"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "Chủ đề",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "出了点问题",
|
||||
"subscription": {
|
||||
"title": "订阅信息",
|
||||
"subId": "订阅 ID",
|
||||
"status": "状态",
|
||||
"downloaded": "已下载",
|
||||
"uploaded": "已上传",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "无到期",
|
||||
"copyAllConfigs": "复制全部配置",
|
||||
"copyAllConfigsCopied": "已复制全部配置",
|
||||
"email": "邮箱"
|
||||
"daysLeft": "剩余天数",
|
||||
"expired": "已过期",
|
||||
"depleted": "流量已用完",
|
||||
"ofTotal": "共 {total}",
|
||||
"tabLinks": "订阅",
|
||||
"tabApps": "应用",
|
||||
"tabConfigs": "配置",
|
||||
"scanTitle": "用手机扫码",
|
||||
"scanHint": "在 VPN 应用中扫描此二维码,无需复制链接即可添加订阅。",
|
||||
"updateInterval": "每 {hours} 小时自动更新",
|
||||
"support": "支持",
|
||||
"qrTitle": "扫码。导入。连接。",
|
||||
"qrHint": "使用客户端应用扫描。请勿泄露此二维码。",
|
||||
"saveQr": "保存二维码"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "主题",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"somethingWentWrong": "發生錯誤",
|
||||
"subscription": {
|
||||
"title": "訂閱資訊",
|
||||
"subId": "訂閱 ID",
|
||||
"status": "狀態",
|
||||
"downloaded": "已下載",
|
||||
"uploaded": "已上傳",
|
||||
@@ -93,7 +92,20 @@
|
||||
"noExpiry": "無到期",
|
||||
"copyAllConfigs": "複製全部配置",
|
||||
"copyAllConfigsCopied": "已複製全部配置",
|
||||
"email": "電子郵件"
|
||||
"daysLeft": "剩餘天數",
|
||||
"expired": "已過期",
|
||||
"depleted": "流量已用完",
|
||||
"ofTotal": "共 {total}",
|
||||
"tabLinks": "訂閱",
|
||||
"tabApps": "應用程式",
|
||||
"tabConfigs": "設定檔",
|
||||
"scanTitle": "用手機掃描",
|
||||
"scanHint": "在 VPN 應用程式中掃描此 QR 碼,無需複製連結即可新增訂閱。",
|
||||
"updateInterval": "每 {hours} 小時自動更新",
|
||||
"support": "支援",
|
||||
"qrTitle": "掃描。匯入。連線。",
|
||||
"qrHint": "使用用戶端應用程式掃描。請勿外洩此 QR 碼。",
|
||||
"saveQr": "儲存 QR 碼"
|
||||
},
|
||||
"menu": {
|
||||
"theme": "主題",
|
||||
|
||||
Reference in New Issue
Block a user