feat: implement mirror download for CA certificates

This commit is contained in:
dyzulk
2025-12-31 09:40:14 +07:00
parent 0290333e97
commit 8e0f97dbc2
2 changed files with 110 additions and 8 deletions

View File

@@ -248,7 +248,7 @@ export default function HomeClient() {
<div className="space-y-3"> <div className="space-y-3">
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download`} href={`/download/ca-certificate?serial=${cert.serial}`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 rounded-xl border border-gray-200 dark:border-gray-700 font-medium text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 rounded-xl border border-gray-200 dark:border-gray-700 font-medium text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
title={t('download_standard_title')} title={t('download_standard_title')}
> >
@@ -258,7 +258,7 @@ export default function HomeClient() {
{t('download_standard')} {t('download_standard')}
</a> </a>
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download?format=der`} href={`/download/ca-certificate?serial=${cert.serial}&format=der`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400 rounded-xl border border-green-200 dark:border-green-500/20 font-medium text-sm hover:bg-green-100 dark:hover:bg-green-500/20 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400 rounded-xl border border-green-200 dark:border-green-500/20 font-medium text-sm hover:bg-green-100 dark:hover:bg-green-500/20 transition-colors"
title={t('download_android_title')} title={t('download_android_title')}
> >
@@ -270,7 +270,7 @@ export default function HomeClient() {
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download/windows`} href={`/download/ca-certificate?serial=${cert.serial}&target=windows`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-50 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 rounded-xl border border-blue-200 dark:border-blue-500/20 font-medium text-sm hover:bg-blue-100 dark:hover:bg-blue-500/20 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-50 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 rounded-xl border border-blue-200 dark:border-blue-500/20 font-medium text-sm hover:bg-blue-100 dark:hover:bg-blue-500/20 transition-colors"
title={t('download_windows_title')} title={t('download_windows_title')}
> >
@@ -280,7 +280,7 @@ export default function HomeClient() {
{t('download_windows')} {t('download_windows')}
</a> </a>
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download/mac`} href={`/download/ca-certificate?serial=${cert.serial}&target=mac`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 rounded-xl border border-gray-200 dark:border-gray-600 font-medium text-sm hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 rounded-xl border border-gray-200 dark:border-gray-600 font-medium text-sm hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
title={t('download_macos_title')} title={t('download_macos_title')}
> >
@@ -317,7 +317,7 @@ export default function HomeClient() {
<div className="space-y-3"> <div className="space-y-3">
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download`} href={`/download/ca-certificate?serial=${cert.serial}`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 rounded-xl border border-gray-200 dark:border-gray-700 font-medium text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 rounded-xl border border-gray-200 dark:border-gray-700 font-medium text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
title={t('download_standard_title')} title={t('download_standard_title')}
> >
@@ -327,7 +327,7 @@ export default function HomeClient() {
{t('download_standard')} {t('download_standard')}
</a> </a>
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download?format=der`} href={`/download/ca-certificate?serial=${cert.serial}&format=der`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400 rounded-xl border border-green-200 dark:border-green-500/20 font-medium text-sm hover:bg-green-100 dark:hover:bg-green-500/20 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400 rounded-xl border border-green-200 dark:border-green-500/20 font-medium text-sm hover:bg-green-100 dark:hover:bg-green-500/20 transition-colors"
title={t('download_android_title')} title={t('download_android_title')}
> >
@@ -339,7 +339,7 @@ export default function HomeClient() {
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download/windows`} href={`/download/ca-certificate?serial=${cert.serial}&target=windows`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-50 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 rounded-xl border border-blue-200 dark:border-blue-500/20 font-medium text-sm hover:bg-blue-100 dark:hover:bg-blue-500/20 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-50 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 rounded-xl border border-blue-200 dark:border-blue-500/20 font-medium text-sm hover:bg-blue-100 dark:hover:bg-blue-500/20 transition-colors"
title={t('download_windows_title')} title={t('download_windows_title')}
> >
@@ -349,7 +349,7 @@ export default function HomeClient() {
{t('download_windows')} {t('download_windows')}
</a> </a>
<a <a
href={`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/public/ca-certificates/${cert.serial}/download/mac`} href={`/download/ca-certificate?serial=${cert.serial}&target=mac`}
className="flex items-center justify-center gap-2 px-4 py-3 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 rounded-xl border border-gray-200 dark:border-gray-600 font-medium text-sm hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors" className="flex items-center justify-center gap-2 px-4 py-3 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 rounded-xl border border-gray-200 dark:border-gray-600 font-medium text-sm hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
title={t('download_macos_title')} title={t('download_macos_title')}
> >

View File

@@ -0,0 +1,102 @@
"use client";
import { Suspense, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import CommonGridShape from "@/components/common/CommonGridShape";
function DownloadMirrorContent() {
const searchParams = useSearchParams();
const [status, setStatus] = useState("Preparing your download...");
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const serial = searchParams.get("serial");
const format = searchParams.get("format");
const target = searchParams.get("target");
if (!serial) {
setError("Missing certificate serial.");
setStatus("");
return;
}
// Construct backend URL
const baseUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
let downloadUrl = `${baseUrl}/api/public/ca-certificates/${serial}/download`;
// Append target if specified (windows/mac)
if (target) {
downloadUrl += `/${target}`;
}
// Append format if specified (der)
if (format) {
downloadUrl += `?format=${format}`;
}
// Mirroring: Redirect to the backend download URL
setStatus("Redirecting to download...");
window.location.href = downloadUrl;
// Optional: Auto close or go back after some time
// But usually the download happens in the background and the page stays
const timeout = setTimeout(() => {
setStatus("If your download didn't start automatically, please refresh this page.");
}, 5000);
return () => clearTimeout(timeout);
}, [searchParams]);
return (
<div className="relative z-1 bg-white p-6 sm:p-0 dark:bg-gray-900 flex-grow flex items-center justify-center min-h-screen">
<div className="relative z-10 w-full max-w-md p-8 text-center">
{error ? (
<div className="space-y-6">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/20">
<svg className="h-8 w-8 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div>
<h2 className="text-xl font-bold text-gray-900 dark:text-white">Download Error</h2>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">{error}</p>
</div>
<button
onClick={() => window.history.back()}
className="inline-flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-brand-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-600"
>
Go Back
</button>
</div>
) : (
<div className="space-y-6">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-brand-100 dark:bg-brand-900/20">
<svg className="animate-spin h-8 w-8 text-brand-600 dark:text-brand-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<h2 className="text-xl font-bold text-gray-900 dark:text-white">{status}</h2>
<p className="text-sm text-gray-500 dark:text-gray-400">Mirroring Download from TrustLab Trust Store</p>
</div>
)}
</div>
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<CommonGridShape />
</div>
</div>
);
}
export default function DownloadMirror() {
return (
<Suspense fallback={
<div className="flex h-screen items-center justify-center bg-white dark:bg-gray-900">
<div className="h-10 w-10 animate-spin rounded-full border-4 border-brand-500 border-t-transparent"></div>
</div>
}>
<DownloadMirrorContent />
</Suspense>
);
}