feat: establish NekoNest Cloud control and relay

This commit is contained in:
2026-08-12 23:25:43 +08:00
commit f27606b709
222 changed files with 71456 additions and 0 deletions
@@ -0,0 +1,65 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
export function PairingCancelButton({
pairingId,
onCancelled,
}: {
pairingId: string;
onCancelled?: () => void;
}) {
const router = useRouter();
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
async function cancel() {
if (
!window.confirm(
"取消后这枚配对凭证立即失效并释放占位。若 daemon 正在认领,最终状态以主机列表为准。确定取消?",
)
) {
return;
}
setBusy(true);
setError("");
try {
const response = await fetch("/api/hosts/pairing/cancel", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ pairingId }),
});
const body = (await response.json()) as { message?: string };
if (!response.ok) {
throw new Error(body.message || "取消配对请求失败");
}
onCancelled?.();
router.refresh();
} catch (cancelError) {
setError(
cancelError instanceof Error ? cancelError.message : "取消配对请求失败",
);
} finally {
setBusy(false);
}
}
return (
<div className="pairing-cancel-action">
<button
className="button button-secondary"
type="button"
onClick={cancel}
disabled={busy}
>
{busy ? "正在取消…" : "取消配对"}
</button>
{error && (
<small className="form-error" role="alert">
{error}
</small>
)}
</div>
);
}