2026-04-14 18:05:32 +08:00

161 lines
5.7 KiB
TypeScript

import { getRequestOrigin } from "@/lib/request-origin";
import { buildSubscriptionUrl, getDefaultClashTemplate } from "@/lib/subscription";
import {
getAdminMirrorUser,
getSubscriptionSettings,
listSubscriptionNodes,
} from "@/lib/store";
import {
deleteProxyGroupAction,
saveProxyGroupAction,
saveSubscriptionSettingsAction,
} from "./actions";
export default async function AdminSubscriptionsPage() {
const [settings, adminUser, nodes, origin] = await Promise.all([
getSubscriptionSettings(),
getAdminMirrorUser(),
listSubscriptionNodes(),
getRequestOrigin(),
]);
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">
<h3>Proxy Groups</h3>
</div>
<div className="stack-lg">
{settings.proxyGroups.map((group) => (
<article key={group.id} className="instruction-block">
<form action={saveProxyGroupAction} className="stack-md">
<input type="hidden" name="id" value={group.id} />
<label className="field">
<span></span>
<input name="name" defaultValue={group.name} required />
</label>
<div className="checkbox-grid">
{nodes.map((node) => (
<label key={`${group.id}-${node.slug}`} className="checkbox">
<input
type="checkbox"
name="member_slugs"
value={node.slug}
defaultChecked={group.memberSlugs.includes(node.slug)}
/>
<span>{node.client_name || node.name}</span>
</label>
))}
</div>
<div className="action-row">
<button className="ghost-button" type="submit">
</button>
</div>
</form>
<form action={deleteProxyGroupAction}>
<input type="hidden" name="id" value={group.id} />
<button className="danger-button" type="submit">
</button>
</form>
</article>
))}
<article className="instruction-block">
<form action={saveProxyGroupAction} className="stack-md">
<label className="field">
<span></span>
<input name="name" placeholder="ChatGPT" required />
</label>
<div className="checkbox-grid">
{nodes.map((node) => (
<label key={`new-${node.slug}`} className="checkbox">
<input type="checkbox" name="member_slugs" value={node.slug} />
<span>{node.client_name || node.name}</span>
</label>
))}
</div>
<button className="primary-button" type="submit">
</button>
</form>
</article>
</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>
);
}