mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-15 10:25:00 +03:00
feat(sponsors): drop the sidebar slot and simplify the Sponsors page
The sidebar no longer carries a sponsor card. The backend stops serving the "sidebar" slot, so a sponsors.json entry that still lists it keeps only its other slots and one listing nothing else is dropped. Its rotation, three-sponsor cap and collapsed-rail logo rendering go with it. The Sponsors page now lists only real sponsors: the "Your brand here" card shows only as the empty state instead of sitting in the grid and repeating the header's Become a sponsor button, and the Available placements section and its slot-occupancy helper are removed. Cards are larger, two per row on wide screens. Unused i18n keys are dropped from every locale.
This commit is contained in:
@@ -45,7 +45,6 @@ const (
|
||||
sponsorsErrTTL = 10 * time.Minute
|
||||
maxSponsorsBytes = 256 << 10
|
||||
maxLogoBytes = 256 << 10
|
||||
maxSidebarSlots = 3
|
||||
localSponsorsDir = "../sponsors/3X"
|
||||
sponsorLogoPath = "/sponsors/logo/"
|
||||
)
|
||||
@@ -67,7 +66,7 @@ var (
|
||||
|
||||
// The panel proxies logos from sponsorLogoBase: CSP stays 'self' and no third party sees admin IPs.
|
||||
sponsorLogoRe = regexp.MustCompile(`^[A-Za-z0-9_-][A-Za-z0-9._-]*\.(png|webp|jpg)$`)
|
||||
sponsorSlots = map[string]bool{"dashboard": true, "sidebar": true, "page": true, "login": true}
|
||||
sponsorSlots = map[string]bool{"dashboard": true, "page": true, "login": true}
|
||||
|
||||
sponsorsMu sync.Mutex
|
||||
sponsorsRaw *SponsorList
|
||||
@@ -217,7 +216,6 @@ func activeSponsors(raw *SponsorList, now time.Time) *SponsorList {
|
||||
if strings.HasPrefix(raw.Contact, "https://") {
|
||||
out.Contact = raw.Contact
|
||||
}
|
||||
sidebarTaken := 0
|
||||
for _, sp := range raw.Sponsors {
|
||||
// A missing enable counts as on, so a forgotten field never hides a paid slot.
|
||||
disabled := sp.Enable != nil && !*sp.Enable
|
||||
@@ -233,18 +231,13 @@ func activeSponsors(raw *SponsorList, now time.Time) *SponsorList {
|
||||
}
|
||||
slots := make([]string, 0, len(sp.Slots))
|
||||
for _, slot := range sp.Slots {
|
||||
// The sidebar rotates, so capping it keeps each paid card on screen long enough.
|
||||
if !sponsorSlots[slot] || (slot == "sidebar" && sidebarTaken >= maxSidebarSlots) {
|
||||
continue
|
||||
if sponsorSlots[slot] {
|
||||
slots = append(slots, slot)
|
||||
}
|
||||
slots = append(slots, slot)
|
||||
}
|
||||
if len(slots) == 0 {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(slots, "sidebar") {
|
||||
sidebarTaken++
|
||||
}
|
||||
sp.Slots = slots
|
||||
out.Sponsors = append(out.Sponsors, sp)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -22,7 +21,7 @@ func validSponsor() Sponsor {
|
||||
return Sponsor{
|
||||
ID: "acme",
|
||||
Name: "Acme",
|
||||
Slots: []string{"dashboard", "sidebar"},
|
||||
Slots: []string{"dashboard"},
|
||||
Until: sponsorTestNow.Add(24 * time.Hour),
|
||||
Logo: "acme.png",
|
||||
Link: "https://acme.example/",
|
||||
@@ -59,34 +58,6 @@ func TestActiveSponsorsFilters(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestActiveSponsorsCapsSidebarAtThree(t *testing.T) {
|
||||
raw := &SponsorList{}
|
||||
for _, id := range []string{"expired", "a", "b", "c", "d", "e"} {
|
||||
sp := validSponsor()
|
||||
sp.ID = id
|
||||
sp.Slots = []string{"sidebar", "page"}
|
||||
if id == "expired" {
|
||||
sp.Until = sponsorTestNow
|
||||
}
|
||||
if id == "e" {
|
||||
sp.Slots = []string{"sidebar"}
|
||||
}
|
||||
raw.Sponsors = append(raw.Sponsors, sp)
|
||||
}
|
||||
got := activeSponsors(raw, sponsorTestNow)
|
||||
want := map[string][]string{
|
||||
"a": {"sidebar", "page"}, "b": {"sidebar", "page"}, "c": {"sidebar", "page"}, "d": {"page"},
|
||||
}
|
||||
if len(got.Sponsors) != len(want) {
|
||||
t.Fatalf("got %d sponsors, want %d (e has only sidebar and must drop)", len(got.Sponsors), len(want))
|
||||
}
|
||||
for _, sp := range got.Sponsors {
|
||||
if !slices.Equal(sp.Slots, want[sp.ID]) {
|
||||
t.Errorf("%s slots = %v, want %v", sp.ID, sp.Slots, want[sp.ID])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestActiveSponsorsLogoName(t *testing.T) {
|
||||
cases := []struct{ logo, want string }{
|
||||
{"acme.png", "/sponsors/logo/acme.png"},
|
||||
@@ -114,7 +85,7 @@ func TestActiveSponsorsLogoName(t *testing.T) {
|
||||
|
||||
func TestActiveSponsorsResolvesLogoAndSlots(t *testing.T) {
|
||||
sp := validSponsor()
|
||||
sp.Slots = []string{"subpage", "login"}
|
||||
sp.Slots = []string{"subpage", "sidebar", "login"}
|
||||
got := activeSponsors(&SponsorList{Contact: "javascript:x", Sponsors: []Sponsor{sp}}, sponsorTestNow)
|
||||
if len(got.Sponsors) != 1 {
|
||||
t.Fatalf("got %d sponsors, want 1", len(got.Sponsors))
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "راعٍ",
|
||||
"visit": "زيارة",
|
||||
"yourBrand": "علامتك التجارية هنا",
|
||||
"yourBrandText": "ضع شركتك أمام آلاف مسؤولي الخوادم الذين يفتحون 3X-UI كل يوم.",
|
||||
"placements": "المواضع المتاحة",
|
||||
"placementDashboard": "لافتة لوحة المعلومات",
|
||||
"placementDashboardDesc": "موضع مميز أعلى لوحة المعلومات، راعٍ واحد في كل مرة.",
|
||||
"placementSidebar": "بطاقة الشريط الجانبي",
|
||||
"placementSidebarDesc": "تظهر في كل صفحات اللوحة وتتناوب بين الرعاة.",
|
||||
"placementLogin": "صفحة تسجيل الدخول",
|
||||
"placementLoginDesc": "يراها المسؤول عند كل تسجيل دخول.",
|
||||
"placementPage": "صفحة الرعاة",
|
||||
"placementPageDesc": "مدرجة هنا مع الشعار والوصف والرابط.",
|
||||
"available": "متاح",
|
||||
"takenUntil": "محجوز حتى {date}",
|
||||
"activeCount": "{count} نشط"
|
||||
"yourBrandText": "ضع شركتك أمام آلاف مسؤولي الخوادم الذين يفتحون 3X-UI كل يوم."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Sponsor",
|
||||
"visit": "Visit",
|
||||
"yourBrand": "Your brand here",
|
||||
"yourBrandText": "Put your company in front of thousands of server admins who open 3X-UI every day.",
|
||||
"placements": "Available placements",
|
||||
"placementDashboard": "Overview banner",
|
||||
"placementDashboardDesc": "Premium spot at the top of the dashboard, one sponsor at a time.",
|
||||
"placementSidebar": "Sidebar card",
|
||||
"placementSidebarDesc": "Shown on every panel page, rotating between sponsors.",
|
||||
"placementLogin": "Login page",
|
||||
"placementLoginDesc": "Seen by the admin at every sign-in.",
|
||||
"placementPage": "Sponsors page",
|
||||
"placementPageDesc": "Listed here with logo, description and link.",
|
||||
"available": "Available",
|
||||
"takenUntil": "Taken until {date}",
|
||||
"activeCount": "{count} active"
|
||||
"yourBrandText": "Put your company in front of thousands of server admins who open 3X-UI every day."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Patrocinador",
|
||||
"visit": "Visitar",
|
||||
"yourBrand": "Tu marca aquí",
|
||||
"yourBrandText": "Muestra tu empresa a miles de administradores de servidores que abren 3X-UI cada día.",
|
||||
"placements": "Espacios disponibles",
|
||||
"placementDashboard": "Banner del panel",
|
||||
"placementDashboardDesc": "Espacio premium en la parte superior del panel, un patrocinador a la vez.",
|
||||
"placementSidebar": "Tarjeta lateral",
|
||||
"placementSidebarDesc": "Se muestra en todas las páginas del panel, rotando entre patrocinadores.",
|
||||
"placementLogin": "Página de inicio de sesión",
|
||||
"placementLoginDesc": "La ve el administrador en cada inicio de sesión.",
|
||||
"placementPage": "Página de patrocinadores",
|
||||
"placementPageDesc": "Aparece aquí con logo, descripción y enlace.",
|
||||
"available": "Disponible",
|
||||
"takenUntil": "Ocupado hasta {date}",
|
||||
"activeCount": "{count} activos"
|
||||
"yourBrandText": "Muestra tu empresa a miles de administradores de servidores que abren 3X-UI cada día."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "اسپانسر",
|
||||
"visit": "مشاهده",
|
||||
"yourBrand": "جای برند شما",
|
||||
"yourBrandText": "شرکت خود را جلوی چشم هزاران ادمین سروری بگذارید که هر روز 3X-UI را باز میکنند.",
|
||||
"placements": "جایگاههای موجود",
|
||||
"placementDashboard": "بنر داشبورد",
|
||||
"placementDashboardDesc": "جایگاه ویژه بالای صفحهی داشبورد، هر بار فقط یک اسپانسر.",
|
||||
"placementSidebar": "کارت سایدبار",
|
||||
"placementSidebarDesc": "در همهی صفحات پنل نمایش داده میشود و بین اسپانسرها چرخش دارد.",
|
||||
"placementLogin": "صفحهی ورود",
|
||||
"placementLoginDesc": "ادمین در هر بار ورود آن را میبیند.",
|
||||
"placementPage": "صفحهی اسپانسرها",
|
||||
"placementPageDesc": "همراه با لوگو، توضیحات و لینک در همین صفحه.",
|
||||
"available": "خالی",
|
||||
"takenUntil": "پر شده تا {date}",
|
||||
"activeCount": "{count} فعال"
|
||||
"yourBrandText": "شرکت خود را جلوی چشم هزاران ادمین سروری بگذارید که هر روز 3X-UI را باز میکنند."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Sponsor",
|
||||
"visit": "Kunjungi",
|
||||
"yourBrand": "Merek Anda di sini",
|
||||
"yourBrandText": "Tampilkan perusahaan Anda kepada ribuan admin server yang membuka 3X-UI setiap hari.",
|
||||
"placements": "Slot yang tersedia",
|
||||
"placementDashboard": "Banner dasbor",
|
||||
"placementDashboardDesc": "Tempat premium di atas dasbor, satu sponsor dalam satu waktu.",
|
||||
"placementSidebar": "Kartu sidebar",
|
||||
"placementSidebarDesc": "Tampil di setiap halaman panel, bergantian antar sponsor.",
|
||||
"placementLogin": "Halaman login",
|
||||
"placementLoginDesc": "Dilihat admin setiap kali masuk.",
|
||||
"placementPage": "Halaman sponsor",
|
||||
"placementPageDesc": "Tercantum di sini dengan logo, deskripsi, dan tautan.",
|
||||
"available": "Tersedia",
|
||||
"takenUntil": "Terisi hingga {date}",
|
||||
"activeCount": "{count} aktif"
|
||||
"yourBrandText": "Tampilkan perusahaan Anda kepada ribuan admin server yang membuka 3X-UI setiap hari."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "スポンサー",
|
||||
"visit": "訪問",
|
||||
"yourBrand": "ここにあなたのブランドを",
|
||||
"yourBrandText": "毎日 3X-UI を開く何千人ものサーバー管理者に御社をアピールできます。",
|
||||
"placements": "掲載枠",
|
||||
"placementDashboard": "ダッシュボードバナー",
|
||||
"placementDashboardDesc": "ダッシュボード上部のプレミアム枠。一度に 1 社のみ。",
|
||||
"placementSidebar": "サイドバーカード",
|
||||
"placementSidebarDesc": "パネルの全ページに表示され、スポンサー間でローテーションします。",
|
||||
"placementLogin": "ログインページ",
|
||||
"placementLoginDesc": "管理者がログインするたびに表示されます。",
|
||||
"placementPage": "スポンサーページ",
|
||||
"placementPageDesc": "ロゴ、説明、リンク付きでここに掲載されます。",
|
||||
"available": "空きあり",
|
||||
"takenUntil": "{date} まで予約済み",
|
||||
"activeCount": "{count} 件掲載中"
|
||||
"yourBrandText": "毎日 3X-UI を開く何千人ものサーバー管理者に御社をアピールできます。"
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Patrocinador",
|
||||
"visit": "Visitar",
|
||||
"yourBrand": "Sua marca aqui",
|
||||
"yourBrandText": "Coloque sua empresa diante de milhares de administradores de servidores que abrem o 3X-UI todos os dias.",
|
||||
"placements": "Espaços disponíveis",
|
||||
"placementDashboard": "Banner do painel",
|
||||
"placementDashboardDesc": "Espaço premium no topo do painel, um patrocinador por vez.",
|
||||
"placementSidebar": "Cartão lateral",
|
||||
"placementSidebarDesc": "Exibido em todas as páginas do painel, alternando entre patrocinadores.",
|
||||
"placementLogin": "Página de login",
|
||||
"placementLoginDesc": "Visto pelo administrador a cada login.",
|
||||
"placementPage": "Página de patrocinadores",
|
||||
"placementPageDesc": "Listado aqui com logo, descrição e link.",
|
||||
"available": "Disponível",
|
||||
"takenUntil": "Ocupado até {date}",
|
||||
"activeCount": "{count} ativos"
|
||||
"yourBrandText": "Coloque sua empresa diante de milhares de administradores de servidores que abrem o 3X-UI todos os dias."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Спонсор",
|
||||
"visit": "Перейти",
|
||||
"yourBrand": "Здесь может быть ваш бренд",
|
||||
"yourBrandText": "Покажите свою компанию тысячам администраторов серверов, которые каждый день открывают 3X-UI.",
|
||||
"placements": "Доступные места",
|
||||
"placementDashboard": "Баннер на главной",
|
||||
"placementDashboardDesc": "Премиум-место вверху панели, один спонсор за раз.",
|
||||
"placementSidebar": "Карточка в боковой панели",
|
||||
"placementSidebarDesc": "Показывается на всех страницах панели, спонсоры чередуются.",
|
||||
"placementLogin": "Страница входа",
|
||||
"placementLoginDesc": "Администратор видит её при каждом входе.",
|
||||
"placementPage": "Страница спонсоров",
|
||||
"placementPageDesc": "Размещение здесь с логотипом, описанием и ссылкой.",
|
||||
"available": "Свободно",
|
||||
"takenUntil": "Занято до {date}",
|
||||
"activeCount": "Активных: {count}"
|
||||
"yourBrandText": "Покажите свою компанию тысячам администраторов серверов, которые каждый день открывают 3X-UI."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Sponsor",
|
||||
"visit": "Ziyaret et",
|
||||
"yourBrand": "Markanız burada",
|
||||
"yourBrandText": "Şirketinizi her gün 3X-UI açan binlerce sunucu yöneticisinin önüne çıkarın.",
|
||||
"placements": "Mevcut yerleşimler",
|
||||
"placementDashboard": "Genel bakış afişi",
|
||||
"placementDashboardDesc": "Panonun üstünde premium alan, aynı anda tek sponsor.",
|
||||
"placementSidebar": "Kenar çubuğu kartı",
|
||||
"placementSidebarDesc": "Tüm panel sayfalarında gösterilir, sponsorlar arasında döner.",
|
||||
"placementLogin": "Giriş sayfası",
|
||||
"placementLoginDesc": "Yönetici her girişte görür.",
|
||||
"placementPage": "Sponsorlar sayfası",
|
||||
"placementPageDesc": "Logo, açıklama ve bağlantıyla burada listelenir.",
|
||||
"available": "Müsait",
|
||||
"takenUntil": "{date} tarihine kadar dolu",
|
||||
"activeCount": "{count} aktif"
|
||||
"yourBrandText": "Şirketinizi her gün 3X-UI açan binlerce sunucu yöneticisinin önüne çıkarın."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Спонсор",
|
||||
"visit": "Перейти",
|
||||
"yourBrand": "Тут може бути ваш бренд",
|
||||
"yourBrandText": "Покажіть свою компанію тисячам адміністраторів серверів, які щодня відкривають 3X-UI.",
|
||||
"placements": "Доступні місця",
|
||||
"placementDashboard": "Банер на головній",
|
||||
"placementDashboardDesc": "Преміум-місце вгорі панелі, один спонсор за раз.",
|
||||
"placementSidebar": "Картка в бічній панелі",
|
||||
"placementSidebarDesc": "Показується на всіх сторінках панелі, спонсори чергуються.",
|
||||
"placementLogin": "Сторінка входу",
|
||||
"placementLoginDesc": "Адміністратор бачить її під час кожного входу.",
|
||||
"placementPage": "Сторінка спонсорів",
|
||||
"placementPageDesc": "Розміщення тут із логотипом, описом і посиланням.",
|
||||
"available": "Вільно",
|
||||
"takenUntil": "Зайнято до {date}",
|
||||
"activeCount": "Активних: {count}"
|
||||
"yourBrandText": "Покажіть свою компанію тисячам адміністраторів серверів, які щодня відкривають 3X-UI."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "Tài trợ",
|
||||
"visit": "Truy cập",
|
||||
"yourBrand": "Thương hiệu của bạn ở đây",
|
||||
"yourBrandText": "Đưa công ty bạn đến hàng nghìn quản trị viên máy chủ mở 3X-UI mỗi ngày.",
|
||||
"placements": "Vị trí hiện có",
|
||||
"placementDashboard": "Banner tổng quan",
|
||||
"placementDashboardDesc": "Vị trí cao cấp ở đầu bảng điều khiển, mỗi lần một nhà tài trợ.",
|
||||
"placementSidebar": "Thẻ thanh bên",
|
||||
"placementSidebarDesc": "Hiển thị trên mọi trang của panel, luân phiên giữa các nhà tài trợ.",
|
||||
"placementLogin": "Trang đăng nhập",
|
||||
"placementLoginDesc": "Quản trị viên thấy mỗi lần đăng nhập.",
|
||||
"placementPage": "Trang nhà tài trợ",
|
||||
"placementPageDesc": "Được liệt kê tại đây với logo, mô tả và liên kết.",
|
||||
"available": "Còn trống",
|
||||
"takenUntil": "Đã đặt đến {date}",
|
||||
"activeCount": "{count} đang hoạt động"
|
||||
"yourBrandText": "Đưa công ty bạn đến hàng nghìn quản trị viên máy chủ mở 3X-UI mỗi ngày."
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "赞助",
|
||||
"visit": "访问",
|
||||
"yourBrand": "您的品牌展示位",
|
||||
"yourBrandText": "让每天打开 3X-UI 的数千名服务器管理员看到您的公司。",
|
||||
"placements": "可用展示位",
|
||||
"placementDashboard": "概览横幅",
|
||||
"placementDashboardDesc": "仪表盘顶部的高级位置,每次仅一家赞助商。",
|
||||
"placementSidebar": "侧边栏卡片",
|
||||
"placementSidebarDesc": "在面板所有页面显示,并在赞助商之间轮换。",
|
||||
"placementLogin": "登录页",
|
||||
"placementLoginDesc": "管理员每次登录时都能看到。",
|
||||
"placementPage": "赞助商页面",
|
||||
"placementPageDesc": "在此页面展示徽标、简介和链接。",
|
||||
"available": "可预订",
|
||||
"takenUntil": "已预订至 {date}",
|
||||
"activeCount": "{count} 个进行中"
|
||||
"yourBrandText": "让每天打开 3X-UI 的数千名服务器管理员看到您的公司。"
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
@@ -2423,19 +2423,7 @@
|
||||
"tag": "贊助",
|
||||
"visit": "造訪",
|
||||
"yourBrand": "您的品牌展示位",
|
||||
"yourBrandText": "讓每天打開 3X-UI 的數千名伺服器管理員看到您的公司。",
|
||||
"placements": "可用展示位",
|
||||
"placementDashboard": "總覽橫幅",
|
||||
"placementDashboardDesc": "儀表板頂部的高級位置,每次僅一家贊助商。",
|
||||
"placementSidebar": "側邊欄卡片",
|
||||
"placementSidebarDesc": "在面板所有頁面顯示,並在贊助商之間輪換。",
|
||||
"placementLogin": "登入頁",
|
||||
"placementLoginDesc": "管理員每次登入時都能看到。",
|
||||
"placementPage": "贊助商頁面",
|
||||
"placementPageDesc": "在此頁面展示標誌、簡介與連結。",
|
||||
"available": "可預訂",
|
||||
"takenUntil": "已預訂至 {date}",
|
||||
"activeCount": "{count} 個進行中"
|
||||
"yourBrandText": "讓每天打開 3X-UI 的數千名伺服器管理員看到您的公司。"
|
||||
}
|
||||
},
|
||||
"tgbot": {
|
||||
|
||||
Reference in New Issue
Block a user