Feature/fix external subscription client expiry (#6333)

* fix(sub): honor client expiry for external links

* fix(ui): show client expiry on external links

* fix(sub): address external expiry review
This commit is contained in:
duqigit
2026-09-04 04:32:05 +08:00
committed by GitHub
parent 13e87a18c8
commit 2ddcf53020
16 changed files with 332 additions and 61 deletions
+10 -4
View File
@@ -56,8 +56,11 @@ _openapi:
- depth: 2
title: Replace a client's external links and external subscriptions. Sends the
full set; the server replaces all rows. Disabled rows stay saved for
editing but are not emitted in generated subscriptions.
url: '#replace-a-clients-external-links-and-external-subscriptions-sends-the-full-set-the-server-replaces-all-rows-disabled-rows-stay-saved-for-editing-but-are-not-emitted-in-generated-subscriptions'
editing but are not emitted in generated subscriptions. The owning
client's disabled or expired state also stops these rows from being
emitted on future subscription fetches; credentials already imported by
an app remain valid until the external provider revokes them.
url: '#replace-a-clients-external-links-and-external-subscriptions-sends-the-full-set-the-server-replaces-all-rows-disabled-rows-stay-saved-for-editing-but-are-not-emitted-in-generated-subscriptions-the-owning-clients-disabled-or-expired-state-also-stops-these-rows-from-being-emitted-on-future-subscription-fetches-credentials-already-imported-by-an-app-remain-valid-until-the-external-provider-revokes-them'
- depth: 2
title: Reset the up/down counters for every client globally. Quotas and expiry
are not affected. Triggers an Xray restart if any counter actually
@@ -318,8 +321,11 @@ _openapi:
id: detach-a-client-from-one-or-more-inbounds-without-deleting-the-client
- content: Replace a client's external links and external subscriptions. Sends the
full set; the server replaces all rows. Disabled rows stay saved for
editing but are not emitted in generated subscriptions.
id: replace-a-clients-external-links-and-external-subscriptions-sends-the-full-set-the-server-replaces-all-rows-disabled-rows-stay-saved-for-editing-but-are-not-emitted-in-generated-subscriptions
editing but are not emitted in generated subscriptions. The owning
client's disabled or expired state also stops these rows from being
emitted on future subscription fetches; credentials already imported
by an app remain valid until the external provider revokes them.
id: replace-a-clients-external-links-and-external-subscriptions-sends-the-full-set-the-server-replaces-all-rows-disabled-rows-stay-saved-for-editing-but-are-not-emitted-in-generated-subscriptions-the-owning-clients-disabled-or-expired-state-also-stops-these-rows-from-being-emitted-on-future-subscription-fetches-credentials-already-imported-by-an-app-remain-valid-until-the-external-provider-revokes-them
- content: Reset the up/down counters for every client globally. Quotas and expiry
are not affected. Triggers an Xray restart if any counter actually
moved.
+2 -2
View File
@@ -7290,7 +7290,7 @@
"tags": [
"Clients"
],
"summary": "Replace a client's external links and external subscriptions. Sends the full set; the server replaces all rows. Disabled rows stay saved for editing but are not emitted in generated subscriptions.",
"summary": "Replace a client's external links and external subscriptions. Sends the full set; the server replaces all rows. Disabled rows stay saved for editing but are not emitted in generated subscriptions. The owning client's disabled or expired state also stops these rows from being emitted on future subscription fetches; credentials already imported by an app remain valid until the external provider revokes them.",
"operationId": "post_panel_api_clients_email_externalLinks",
"parameters": [
{
@@ -7315,7 +7315,7 @@
"items": {
"type": "object"
},
"description": "Full replacement list; the server replaces all rows. Each row supports { kind, value, remark, enable, expiryTime, namePrefix }. kind=link: value must be a supported share link such as vless://, vmess://, trojan://, ss://, hysteria2://, or wireguard://, and remark overrides the exported node name. kind=subscription: value must be an http(s) subscription URL, and namePrefix is prepended to fetched node names. Omit enable to default true; enable=false or an expired expiryTime keeps the row saved but excludes it from generated subscriptions. expiryTime is a unix millisecond timestamp where 0 means never expire; a negative value is rejected. Rows are matched by kind+value across saves, so id is ignored on write. lastFetchAt and lastFetchError are read-only status fields returned by GET."
"description": "Full replacement list; the server replaces all rows. Each row supports { kind, value, remark, enable, expiryTime, namePrefix }. kind=link: value must be a supported share link such as vless://, vmess://, trojan://, ss://, hysteria2://, or wireguard://, and remark overrides the exported node name. kind=subscription: value must be an http(s) subscription URL, and namePrefix is prepended to fetched node names. Omit enable to default true; enable=false or an expired expiryTime keeps the row saved but excludes it from generated subscriptions. expiryTime is a unix millisecond timestamp where 0 means no link-specific expiry; the owning client's enabled state and expiry still apply. A negative value is rejected. Rows are matched by kind+value across saves, so id is ignored on write. lastFetchAt and lastFetchError are read-only status fields returned by GET."
}
},
"required": [