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:
MHSanaei
2026-09-26 03:31:51 +02:00
parent 89e200ead4
commit fd7b3559bc
47 changed files with 2323 additions and 10 deletions
+78
View File
@@ -4109,6 +4109,84 @@ export const SCHEMAS: Record<string, unknown> = {
],
"type": "object"
},
"Sponsor": {
"description": "Sponsor is one paid placement published in the repo's sponsors.json.",
"properties": {
"enable": {
"example": true,
"nullable": true,
"type": "boolean"
},
"id": {
"example": "acme-2026-10",
"type": "string"
},
"link": {
"example": "https://acme.example/?utm_source=3x-ui",
"type": "string"
},
"logo": {
"example": "/sponsors/logo/acme.png",
"type": "string"
},
"name": {
"example": "Acme VPS",
"type": "string"
},
"slots": {
"items": {
"type": "string"
},
"type": "array"
},
"text": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"title": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"until": {
"example": "2026-11-01T00:00:00Z",
"format": "date-time",
"type": "string"
}
},
"required": [
"id",
"link",
"name",
"slots",
"text",
"title",
"until"
],
"type": "object"
},
"SponsorList": {
"description": "SponsorList is the active sponsor set plus the contact link for new sponsors.",
"properties": {
"contact": {
"example": "https://t.me/example",
"type": "string"
},
"sponsors": {
"items": {
"$ref": "#/components/schemas/Sponsor"
},
"type": "array"
}
},
"required": [
"sponsors"
],
"type": "object"
},
"SubBalancer": {
"description": "SubBalancer is one extra JSON-subscription config document whose members are\nthe selected inbounds' proxy outbounds. SortOrder shares SubSortIndex semantics.",
"properties": {