119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import { getRequestOrigin } from "@/lib/request-origin";
|
||
import { buildSubscriptionUrl, getDefaultClashTemplate } from "@/lib/subscription";
|
||
import {
|
||
getAdminMirrorUser,
|
||
getSubscriptionSettings,
|
||
listSubscriptionNodes,
|
||
} from "@/lib/store";
|
||
|
||
import { saveSubscriptionSettingsAction } from "./actions";
|
||
import { ProxyGroupEditor } from "./proxy-group-editor";
|
||
|
||
export default async function AdminSubscriptionsPage() {
|
||
const [settings, adminUser, nodes, origin] = await Promise.all([
|
||
getSubscriptionSettings(),
|
||
getAdminMirrorUser(),
|
||
listSubscriptionNodes(),
|
||
getRequestOrigin(),
|
||
]);
|
||
const proxyGroupNodes = nodes.map((node) => ({
|
||
slug: node.slug,
|
||
label: node.client_name || node.name,
|
||
meta:
|
||
node.client_name && node.client_name !== node.name
|
||
? `${node.name} / ${node.slug}`
|
||
: node.slug,
|
||
}));
|
||
|
||
return (
|
||
<main className="page-stack">
|
||
<section className="hero-panel">
|
||
<div className="hero-copy">
|
||
<span className="eyebrow">SUBSCRIPTIONS</span>
|
||
<h2>订阅与模板</h2>
|
||
<p className="page-lede">
|
||
管理 Clash 基础模板、订阅文件名、代理分组以及管理员默认不限量订阅链接。
|
||
</p>
|
||
<div className="badge-row">
|
||
<span className="status-pill status-pill--ok">动态 proxies</span>
|
||
<span className="status-pill status-pill--ink">分组 {settings.proxyGroups.length}</span>
|
||
</div>
|
||
</div>
|
||
<div className="hero-aside">
|
||
<div className="stat-tile">
|
||
<span>可用节点</span>
|
||
<strong>{nodes.length}</strong>
|
||
</div>
|
||
<div className="stat-tile">
|
||
<span>默认主组</span>
|
||
<strong>{settings.profileName}</strong>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="panel-card">
|
||
<div className="section-title">
|
||
<h3>管理员订阅</h3>
|
||
</div>
|
||
{adminUser?.subscription_token ? (
|
||
<div className="instruction-block no-margin">
|
||
<p className="muted mono break-all">
|
||
{buildSubscriptionUrl(adminUser.subscription_token, origin)}
|
||
</p>
|
||
</div>
|
||
) : (
|
||
<p className="muted">管理员订阅链接暂不可用。</p>
|
||
)}
|
||
</section>
|
||
|
||
<section className="panel-card">
|
||
<div className="section-title section-title--top">
|
||
<div className="stack-tight">
|
||
<h3>Proxy Groups</h3>
|
||
<p className="muted">已选列表从上到下的顺序,就是写入 Clash 配置时的节点顺序。</p>
|
||
</div>
|
||
</div>
|
||
<div className="stack-lg">
|
||
{settings.proxyGroups.map((group) => (
|
||
<ProxyGroupEditor key={group.id} group={group} nodes={proxyGroupNodes} />
|
||
))}
|
||
|
||
<ProxyGroupEditor key={`new-${settings.proxyGroups.length}`} nodes={proxyGroupNodes} />
|
||
</div>
|
||
</section>
|
||
|
||
<section className="panel-card">
|
||
<div className="section-title">
|
||
<h3>Clash 模板</h3>
|
||
</div>
|
||
<form action={saveSubscriptionSettingsAction} className="stack-lg">
|
||
<label className="field">
|
||
<span>订阅文件名 / 默认主组名</span>
|
||
<input name="profile_name" defaultValue={settings.profileName} required />
|
||
</label>
|
||
<label className="field">
|
||
<span>基础模板</span>
|
||
<textarea
|
||
name="template"
|
||
rows={18}
|
||
defaultValue={settings.template || getDefaultClashTemplate()}
|
||
/>
|
||
</label>
|
||
<label className="field">
|
||
<span>附加 proxy-groups YAML</span>
|
||
<textarea
|
||
name="extra_proxy_groups"
|
||
rows={12}
|
||
defaultValue={settings.extraProxyGroups}
|
||
placeholder={` - name: ChatGPT\n type: select\n proxies: ["US 01 - hy2", "JP 01 - hy2"]`}
|
||
/>
|
||
</label>
|
||
<button className="primary-button" type="submit">
|
||
保存订阅模板
|
||
</button>
|
||
</form>
|
||
</section>
|
||
</main>
|
||
);
|
||
}
|