mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-01 11:42:07 +03:00
feat(panel): add sponsor slots fed from sponsors.sanaei.dev
Monthly sponsor placements need to change without cutting a panel release. Panels now read 3X/sponsors.json from the MHSanaei/sponsors repo (GitHub Pages on sponsors.sanaei.dev) and show active sponsors in four slots: an overview banner, a rotating sidebar card (max three), the login page and a new Sponsors page that also lists open placements. An entry shows only while enable is not false and until is in the future; links must be https and logos are png/webp/jpg by name only. The list is cached for an hour and the last good copy survives upstream failures; logos are proxied through /sponsors/logo/:name with failures cached, so CSP stays 'self' and admin browsers never reach a third party. Admins can hide a slot for 24h. Under XUI_DEBUG the panel reads a sibling ../sponsors/3X checkout so edits can be previewed before push.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/entity"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/middleware"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/service/panel"
|
||||
@@ -28,6 +29,7 @@ type IndexController struct {
|
||||
|
||||
settingService service.SettingService
|
||||
userService panel.UserService
|
||||
panelService panel.PanelService
|
||||
tgbot tgbot.Tgbot
|
||||
}
|
||||
|
||||
@@ -42,12 +44,36 @@ func NewIndexController(g *gin.RouterGroup) *IndexController {
|
||||
func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
||||
g.GET("/", a.index)
|
||||
g.GET("/csrf-token", a.csrfToken)
|
||||
g.GET("/sponsors", a.sponsors)
|
||||
g.GET("/sponsors/logo/:name", a.sponsorLogo)
|
||||
|
||||
g.POST("/login", middleware.CSRFMiddleware(), a.login)
|
||||
g.POST("/logout", middleware.CSRFMiddleware(), a.logout)
|
||||
g.POST("/getTwoFactorEnable", middleware.CSRFMiddleware(), a.getTwoFactorEnable)
|
||||
}
|
||||
|
||||
// sponsors is public so the login page can render its slot; failures stay silent.
|
||||
func (a *IndexController) sponsors(c *gin.Context) {
|
||||
list, err := a.panelService.GetSponsors()
|
||||
if err != nil {
|
||||
logger.Debug("sponsors fetch failed:", err)
|
||||
c.JSON(http.StatusOK, entity.Msg{Success: false})
|
||||
return
|
||||
}
|
||||
jsonObj(c, list, nil)
|
||||
}
|
||||
|
||||
func (a *IndexController) sponsorLogo(c *gin.Context) {
|
||||
data, contentType, err := a.panelService.GetSponsorLogo(c.Param("name"))
|
||||
if err != nil {
|
||||
logger.Debug("sponsor logo failed:", err)
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.Data(http.StatusOK, contentType, data)
|
||||
}
|
||||
|
||||
// index handles the root route, redirecting logged-in users to the panel or showing the login page.
|
||||
func (a *IndexController) index(c *gin.Context) {
|
||||
if session.IsLogin(c) {
|
||||
|
||||
Reference in New Issue
Block a user