feat: add per-host subscription template overrides#278
feat: add per-host subscription template overrides#278ogerman wants to merge 2 commits intoPasarGuard:devfrom
Conversation
Allow hosts to override the Xray subscription template via a new `subscription_templates` field. Templates are loaded from CUSTOM_TEMPLATES_DIRECTORY and validated on create/update. Backend: - Add `subscription_templates` JSON column to ProxyHost (with migration) - Add SubscriptionTemplates Pydantic model - Add GET /api/host/subscription-templates endpoint - Add shared get_subscription_templates() helper (DRY listing + validation) - Exclude default templates from the list (respects env overrides) - Wire template override into Xray subscription rendering Frontend: - Add template select dropdown in host modal (hidden when no custom dir) - Add useGetSubscriptionTemplates hook and API types - Add i18n keys for en, ru, zh, fa Tests: - Add 9 API tests covering list, auth, CRUD with templates, validation Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis pull request adds per-host subscription template customization functionality. Users can now override default subscription templates (particularly for Xray) on a per-host basis. The implementation spans database schema changes, backend validation, template caching, API endpoints, and frontend UI components, with comprehensive test coverage. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/Frontend
participant API as API Endpoint
participant DB as Database
participant Validator as Host Validator
participant Templates as Template Manager
participant Xray as Xray Config
User->>API: POST /host (subscription_templates.xray = "custom.json")
API->>Validator: validate_subscription_templates()
Validator->>Templates: get_subscription_templates()
Templates->>DB: scan custom templates dir
DB-->>Templates: list of available templates
Templates-->>Validator: {xray: ["custom.json", ...]}
Validator->>Validator: verify "custom.json" in list
alt Template exists
Validator-->>API: ✓ validation passed
API->>DB: CREATE host + store subscription_templates
DB-->>API: host created
API-->>User: 201 Created
else Template not found
Validator-->>API: ✗ 400 Bad Request
API-->>User: Template not found error
end
User->>API: GET /subscription/xray?host_id=123
API->>Xray: build subscription
Xray->>DB: fetch host.subscription_templates
DB-->>Xray: {xray: "custom.json"}
Xray->>Xray: _get_template("custom.json")
Xray->>Templates: load custom.json from cache/disk
Templates-->>Xray: rendered template
Xray->>Xray: apply config with custom template
Xray-->>API: subscription data
API-->>User: subscription response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
dashboard/src/components/hosts/hosts-list.tsx (1)
359-502:⚠️ Potential issue | 🟠 Major
subscription_templatesis missing from drag-and-drop reorder payloads — reordering will silently drop template overrides.The
hostsToUpdatearray built inhandleDragEndconstructs fullCreateHostobjects for each host but never includessubscription_templates. When a user reorders hosts via drag-and-drop, themodifyHostscall will overwrite all hosts without their template settings, effectively clearing any per-host subscription template overrides.Proposed fix
Add
subscription_templatesto each host payload, e.g. after line 501:http_headers: host.http_headers || {}, + subscription_templates: (host as any).subscription_templates, }))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dashboard/src/components/hosts/hosts-list.tsx` around lines 359 - 502, The drag-and-drop reorder builds hostsToUpdate in handleDragEnd and omits subscription_templates, so calling modifyHosts overwrites per-host template overrides; update the hostsToUpdate mapping (the CreateHost objects constructed in the hostsToUpdate array inside handleDragEnd) to include subscription_templates: host.subscription_templates || undefined (or host.subscription_templates ?? undefined) when present so subscription template overrides are preserved before calling modifyHosts.
🧹 Nitpick comments (2)
dashboard/src/components/hosts/hosts-list.tsx (1)
90-90: Remove unnecessaryas anycasts —BaseHostalready includessubscription_templates.The
subscription_templatesproperty is defined in theBaseHostinterface (typeBaseHostSubscriptionTemplates), so the(host as any)casts on lines 90 and 276 are unnecessary. Replace(host as any).subscription_templateswithhost.subscription_templatesto preserve type safety.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dashboard/src/components/hosts/hosts-list.tsx` at line 90, The code is using unnecessary casts to any for the subscription_templates property; update the object construction in hosts-list.tsx (where subscription_templates is assigned) to use host.subscription_templates directly (remove the `(host as any)` cast) and do the same for the other occurrence later in the file (previously `(host as any).subscription_templates` around the second occurrence), relying on the BaseHost type for correct typing.tests/api/test_subscription_templates.py (1)
106-123: Host cleanup is not infinally— assertion failure leaks the host.If the assertion on line 119 fails,
client.delete(f"/api/host/{data['id']}")on line 121 is never reached. The core is cleaned up viafinally, but the host may remain orphaned (depending on cascade behavior). This pattern repeats in all CRUD tests.Consider restructuring so host deletion is also guaranteed:
Example
try: ... response = client.post("/api/host", headers=auth_headers(access_token), json=payload) assert response.status_code == status.HTTP_201_CREATED - data = response.json() - assert data["subscription_templates"] == {"xray": "xray/custom.json"} - - client.delete(f"/api/host/{data['id']}", headers=auth_headers(access_token)) + try: + assert data["subscription_templates"] == {"xray": "xray/custom.json"} + finally: + client.delete(f"/api/host/{data['id']}", headers=auth_headers(access_token)) finally: delete_core(access_token, core["id"])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/api/test_subscription_templates.py` around lines 106 - 123, The host deletion is inside the try block so a failing assertion can leak the created host; change the test to always delete the host in the finally block by capturing the created host id (e.g., set host_id = None before the try, assign host_id = data["id"] after successful creation from client.post) and then call client.delete(f"/api/host/{host_id}", headers=auth_headers(access_token)) in the finally before calling delete_core(access_token, core["id"]); update other CRUD tests with the same pattern (use client.post, assign data["id"] to host_id, and ensure client.delete runs in finally).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@app/db/migrations/versions/f1c2d3e4b5a6_add_subscription_templates_to_hosts.py`:
- Around line 1-7: The module-level docstring's "Revises: ee97c01bfbaf" is
inconsistent with the migration's down_revision variable; update the docstring
so the "Revises:" line matches down_revision = "20e2a5cf1e40" (i.e., change
ee97c01bfbaf to 20e2a5cf1e40) so the docstring and the migration's down_revision
are identical.
In `@app/routers/host.py`:
- Around line 17-23: The docstring on list_subscription_templates is inaccurate:
get_subscription_templates() only scans the CUSTOM_TEMPLATES_DIRECTORY and does
not include built-in templates. Update the docstring in the
list_subscription_templates handler to state that it returns subscription
templates found only in the custom templates directory (referencing
CUSTOM_TEMPLATES_DIRECTORY) and that built-in/default templates are excluded, or
alternatively modify get_subscription_templates to include built-ins if you
prefer that behavior; ensure the docstring and the behavior of
get_subscription_templates stay consistent.
In `@dashboard/public/statics/locales/fa.json`:
- Line 944: The Persian locale entry for the key "vlessRoute.info" is still in
English; replace the English string with an appropriate Persian translation for
"Optional route value for VLESS hosts. Must be exactly 4 hexadecimal characters
(0-9, a-f, A-F). Leave empty to use default routing." so the fa.json locale uses
Persian text for the vlessRoute.info key; ensure you preserve punctuation and
the requirement about 4 hexadecimal characters (0-9, a-f, A-F) in the translated
string.
In `@dashboard/public/statics/locales/ru.json`:
- Around line 1233-1238: The string for vlessRoute.info is still in English;
replace its value with a Russian translation while preserving the original
meaning and constraints. Update the vlessRoute.info entry so it reads something
like: "Необязательное значение route для хостов VLESS. Должно содержать ровно 4
шестнадцатеричных символа (0-9, a-f, A-F). Оставьте пустым, чтобы использовать
маршрутизацию по умолчанию." Ensure you only change the value for the
vlessRoute.info key and keep surrounding JSON structure intact.
In `@dashboard/public/statics/locales/zh.json`:
- Line 1050: The key "vlessRoute.info" currently contains an English message;
replace its value with a Chinese translation: "VLESS 主机的可选路由值。必须恰好为 4
个十六进制字符(0-9、a-f、A-F)。留空以使用默认路由。" so the Chinese locale entry matches the rest of
the file and conveys the same constraints and guidance.
In `@dashboard/src/service/api/index.ts`:
- Around line 1922-1925: The CreateHostSubscriptionTemplates type currently
allows arbitrary values via [key: string]: any; change that index signature to
[key: string]: string | null | undefined so additional template properties are
constrained to template filenames (strings) or nullable/undefined, keeping the
overall union with null intact; apply the same tightening to the analogous
subscription-template type declared around the other location (lines 2170-2173)
to ensure consistency with the Zod .passthrough() schema.
In `@tests/api/test_subscription_templates.py`:
- Around line 36-41: The test directly reassigns app.templates.env which isn't
restored; change that assignment to use monkeypatch.setattr so the original
Environment is auto-restored after the fixture. Specifically, replace the direct
write to app.templates.env with a monkeypatch.setattr call that sets
app.templates.env to a new app.templates.jinja2.Environment (with loader set to
app.templates.jinja2.FileSystemLoader([tmpdir, "app/templates"])) and ensure
filters are updated from app.templates.CUSTOM_FILTERS, so the monkeypatch
controls cleanup.
---
Outside diff comments:
In `@dashboard/src/components/hosts/hosts-list.tsx`:
- Around line 359-502: The drag-and-drop reorder builds hostsToUpdate in
handleDragEnd and omits subscription_templates, so calling modifyHosts
overwrites per-host template overrides; update the hostsToUpdate mapping (the
CreateHost objects constructed in the hostsToUpdate array inside handleDragEnd)
to include subscription_templates: host.subscription_templates || undefined (or
host.subscription_templates ?? undefined) when present so subscription template
overrides are preserved before calling modifyHosts.
---
Nitpick comments:
In `@dashboard/src/components/hosts/hosts-list.tsx`:
- Line 90: The code is using unnecessary casts to any for the
subscription_templates property; update the object construction in
hosts-list.tsx (where subscription_templates is assigned) to use
host.subscription_templates directly (remove the `(host as any)` cast) and do
the same for the other occurrence later in the file (previously `(host as
any).subscription_templates` around the second occurrence), relying on the
BaseHost type for correct typing.
In `@tests/api/test_subscription_templates.py`:
- Around line 106-123: The host deletion is inside the try block so a failing
assertion can leak the created host; change the test to always delete the host
in the finally block by capturing the created host id (e.g., set host_id = None
before the try, assign host_id = data["id"] after successful creation from
client.post) and then call client.delete(f"/api/host/{host_id}",
headers=auth_headers(access_token)) in the finally before calling
delete_core(access_token, core["id"]); update other CRUD tests with the same
pattern (use client.post, assign data["id"] to host_id, and ensure client.delete
runs in finally).
app/db/migrations/versions/f1c2d3e4b5a6_add_subscription_templates_to_hosts.py
Show resolved
Hide resolved
- Fix subscription_templates silently cleared on drag-and-drop reorder - Narrow index signature type from `any` to `string | null` - Fix migration docstring Revises field mismatch - Fix inaccurate router docstring for list endpoint - Translate vlessRoute.info for ru, zh, fa locales - Remove unnecessary `as any` casts in hosts-list - Move host cleanup into finally blocks in tests
|
this consepet is good but it not what i want to implement |
|
Looks like translation issues was from the original code, but I've fixed them anyway |
|
@ImMohammad20000 if we'll do so, how do we plan to migrate existing custom templates files (in current installations)? Import to db from JSON files via migrations? |
|
Yes migrate existing files to database |
Allow hosts to override the Xray subscription template via a new
subscription_templatesfield. Templates are loaded from CUSTOM_TEMPLATES_DIRECTORY and validated on create/update.Backend:
subscription_templatesJSON column to ProxyHost (with migration)Frontend:
Tests:
Summary by CodeRabbit
New Features
Localization