feat: add Enterprise & Server eval ISOs via Microsoft evalcenter

Adds /eval listing page and /product/<slug> detail pages for Windows
Server 2025/2022/2019/2016 and Windows 11 Enterprise evaluation ISOs,
sourced from Microsoft evalcenter without any session flow.

Backend: new /evallinks endpoint fetches evalcenter page, extracts
fwlinks, follows all redirects in parallel, detects arch + locale.
24h cache per product with startup warming so first hit is instant.

Frontend: EvalPage listing at /eval, EvalDetailPage at /product/<slug>
matching consumer structure with downloads, aria2 template, system
requirements, and also-available section. Smart ProductRouter in
App.tsx dispatches slug vs numeric productId. Dock highlights Products
tab for /product/* and /eval routes. CTA banner on home page and
footer link added. sitemap.xml updated with 6 new URLs.

Closes #13

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Shekhar Vaidya 2026-05-18 21:56:57 +05:30
parent d6d782bc49
commit a78059187f
9 changed files with 811 additions and 4 deletions

View file

@ -4,13 +4,16 @@ import (
"bytes"
"encoding/json"
"fmt"
"html"
"io"
"log"
"net/http"
"net/url"
"os"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
@ -39,6 +42,146 @@ var (
validSkuID = regexp.MustCompile(`^[a-zA-Z0-9_\-]+$`)
)
// --- Evalcenter (Enterprise / Server eval ISOs) ---
type EvalProduct struct {
Name string
EvalURL string
}
type EvalLink struct {
Arch string `json:"arch"`
Lang string `json:"lang"`
URL string `json:"url"`
}
type EvalLinksResponse struct {
Product string `json:"product"`
Name string `json:"name"`
Links []EvalLink `json:"links"`
}
type evalCacheEntry struct {
Links []EvalLink
CachedAt time.Time
}
var (
evalProductMap = map[string]EvalProduct{
"server-2025": {Name: "Windows Server 2025", EvalURL: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2025"},
"server-2022": {Name: "Windows Server 2022", EvalURL: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022"},
"server-2019": {Name: "Windows Server 2019", EvalURL: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2019"},
"server-2016": {Name: "Windows Server 2016", EvalURL: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2016"},
"win11-ent": {Name: "Windows 11 Enterprise", EvalURL: "https://www.microsoft.com/en-us/evalcenter/download-windows-11-enterprise"},
}
evalCache = make(map[string]evalCacheEntry)
evalCacheMu sync.RWMutex
evalCacheTTL = 24 * time.Hour
fwlinkRe = regexp.MustCompile(`https://go\.microsoft\.com/fwlink/[^"'\s<>]+`)
isoLangRe = regexp.MustCompile(`_([a-z]{2}-[a-z]{2})\.iso$`)
)
func detectArch(rawURL string) string {
lower := strings.ToLower(rawURL)
if strings.Contains(lower, "arm64") {
return "ARM64"
}
if strings.Contains(lower, "x64") {
return "x64"
}
if strings.Contains(lower, "x86") {
return "x86"
}
return "ISO"
}
func detectLang(rawURL string) string {
m := isoLangRe.FindStringSubmatch(strings.ToLower(rawURL))
if len(m) > 1 {
return m[1]
}
return ""
}
func fetchEvalLinks(evalURL string) ([]EvalLink, error) {
client := &http.Client{Timeout: 15 * time.Second}
// Step 1: Fetch the evalcenter page to extract fwlinks
req, _ := http.NewRequest("GET", evalURL, nil)
req.Header.Set("User-Agent", UA)
req.Header.Set("Accept", "text/html,application/xhtml+xml")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("fetching evalcenter page: %w", err)
}
defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
// Step 2: Extract all fwlink URLs (unescape HTML entities like &amp; → &)
rawMatches := fwlinkRe.FindAllString(string(bodyBytes), -1)
// Deduplicate fwlinks
seen := map[string]bool{}
var fwlinks []string
for _, raw := range rawMatches {
fwlink := html.UnescapeString(raw)
if !seen[fwlink] {
seen[fwlink] = true
fwlinks = append(fwlinks, fwlink)
}
}
// Step 3: Follow all redirects in parallel
type result struct {
link EvalLink
ok bool
}
results := make([]result, len(fwlinks))
var wg sync.WaitGroup
for i, fwlink := range fwlinks {
wg.Add(1)
go func(idx int, fw string) {
defer wg.Done()
fwReq, _ := http.NewRequest("GET", fw, nil)
fwReq.Header.Set("User-Agent", UA)
fwResp, err := client.Do(fwReq)
if err != nil {
return
}
fwResp.Body.Close()
finalURL := fwResp.Request.URL.String()
if !strings.Contains(strings.ToLower(finalURL), ".iso") {
return
}
results[idx] = result{
link: EvalLink{Arch: detectArch(finalURL), Lang: detectLang(finalURL), URL: finalURL},
ok: true,
}
}(i, fwlink)
}
wg.Wait()
var links []EvalLink
for _, r := range results {
if r.ok {
links = append(links, r.link)
}
}
// Sort: en-us first, then alphabetically by lang
sort.Slice(links, func(i, j int) bool {
if links[i].Lang == "en-us" {
return true
}
if links[j].Lang == "en-us" {
return false
}
return links[i].Lang < links[j].Lang
})
return links, nil
}
func setWorkerSecret(req *http.Request) {
if workerSecret != "" {
req.Header.Set("X-Worker-Secret", workerSecret)
@ -178,7 +321,7 @@ func enableCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
// Allowed origins
if origin == "https://msdl.tech-latest.com" || origin == "http://localhost:5173" || origin == "http://localhost:3000" {
if origin == "https://msdl.tech-latest.com" || strings.HasPrefix(origin, "http://localhost:") {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
@ -440,14 +583,77 @@ func handleProxy(w http.ResponseWriter, r *http.Request) {
w.Write(buf.Bytes())
}
// --- /evallinks endpoint ---
func handleEvalLinks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
product := r.URL.Query().Get("product")
if product == "" {
respondJSONError(w, http.StatusBadRequest, "product query parameter is required")
return
}
evalProduct, ok := evalProductMap[product]
if !ok {
respondJSONError(w, http.StatusNotFound, "Unknown eval product: "+product)
return
}
// Check cache
evalCacheMu.RLock()
cached, exists := evalCache[product]
evalCacheMu.RUnlock()
if exists && time.Since(cached.CachedAt) < evalCacheTTL {
log.Printf("/evallinks: product=%s -> cache hit (%d links)\n", product, len(cached.Links))
json.NewEncoder(w).Encode(EvalLinksResponse{Product: product, Name: evalProduct.Name, Links: cached.Links})
return
}
links, err := fetchEvalLinks(evalProduct.EvalURL)
if err != nil {
respondJSONError(w, http.StatusBadGateway, "Failed to fetch eval links: "+err.Error())
return
}
if len(links) == 0 {
respondJSONError(w, http.StatusNotFound, "No download links found for this eval product")
return
}
evalCacheMu.Lock()
evalCache[product] = evalCacheEntry{Links: links, CachedAt: time.Now()}
evalCacheMu.Unlock()
log.Printf("/evallinks: product=%s -> %d links\n", product, len(links))
json.NewEncoder(w).Encode(EvalLinksResponse{Product: product, Name: evalProduct.Name, Links: links})
}
// warmEvalCache pre-fetches all eval product links in the background at startup.
func warmEvalCache() {
for slug, product := range evalProductMap {
go func(s string, p EvalProduct) {
links, err := fetchEvalLinks(p.EvalURL)
if err != nil || len(links) == 0 {
log.Printf("eval warm: %s failed: %v\n", s, err)
return
}
evalCacheMu.Lock()
evalCache[s] = evalCacheEntry{Links: links, CachedAt: time.Now()}
evalCacheMu.Unlock()
log.Printf("eval warm: %s -> %d links cached\n", s, len(links))
}(slug, product)
}
}
func main() {
go cleanupSessions()
go warmEvalCache()
mux := http.NewServeMux()
// API Routing
mux.HandleFunc("/skuinfo", handleSkuInfo)
mux.HandleFunc("/proxy", handleProxy)
mux.HandleFunc("/evallinks", handleEvalLinks)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ok"}`))

View file

@ -32,6 +32,19 @@
<changefreq>yearly</changefreq>
</url>
<!-- Enterprise & Server -->
<url>
<loc>https://msdl.tech-latest.com/eval</loc>
<lastmod>2026-05-18</lastmod>
<priority>0.8</priority>
<changefreq>monthly</changefreq>
</url>
<url><loc>https://msdl.tech-latest.com/product/server-2025</loc><lastmod>2026-05-18</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/server-2022</loc><lastmod>2026-05-18</lastmod><priority>0.9</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/server-2019</loc><lastmod>2026-05-18</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/server-2016</loc><lastmod>2026-05-18</lastmod><priority>0.7</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/win11-ent</loc><lastmod>2026-05-18</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<!-- Product Pages -->
<url><loc>https://msdl.tech-latest.com/product/48</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/52</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>

View file

@ -8,7 +8,17 @@ import ProductDetailPage from './pages/ProductDetailPage'
import AboutPage from './pages/AboutPage'
import PrivacyPolicyPage from './pages/PrivacyPolicyPage'
import DisclaimerPage from './pages/DisclaimerPage'
import EvalPage from './pages/EvalPage'
import EvalDetailPage from './pages/EvalDetailPage'
import NotFoundPage from './pages/NotFoundPage'
import { evalSlugSet } from './data/evalProducts'
import { useParams } from 'react-router-dom'
function ProductRouter() {
const { productId } = useParams<{ productId: string }>()
if (productId && evalSlugSet.has(productId)) return <EvalDetailPage />
return <ProductDetailPage />
}
import ScrollToTop from './components/ScrollToTop'
export default function App() {
@ -19,7 +29,8 @@ export default function App() {
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<ProductsPage />} />
<Route path="/product/:productId" element={<ProductDetailPage />} />
<Route path="/product/:productId" element={<ProductRouter />} />
<Route path="/eval" element={<EvalPage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/privacy-policy" element={<PrivacyPolicyPage />} />
<Route path="/disclaimer" element={<DisclaimerPage />} />

View file

@ -24,7 +24,12 @@ export default function Dock() {
const [activeIndex, setActiveIndex] = useState(0)
useEffect(() => {
const idx = items.findIndex(item => item.path && item.path === location.pathname)
// Exact match first
let idx = items.findIndex(item => item.path && item.path === location.pathname)
// /product/* and /eval* → highlight Products tab
if (idx === -1 && (location.pathname.startsWith('/product/') || location.pathname.startsWith('/eval'))) {
idx = items.findIndex(item => item.path === '/products')
}
if (idx !== -1) setActiveIndex(idx)
}, [location.pathname])

View file

@ -9,6 +9,8 @@ export default function SiteFooter() {
All ISO files are hosted on Microsoft's official CDN.
</p>
<div className="flex items-center justify-center gap-4 mt-2 text-[11px] text-zinc-700">
<Link to="/eval" className="hover:text-zinc-500 transition-colors">Enterprise & Server</Link>
<span>·</span>
<Link to="/privacy-policy" className="hover:text-zinc-500 transition-colors">Privacy Policy</Link>
<span>·</span>
<Link to="/disclaimer" className="hover:text-zinc-500 transition-colors">Disclaimer</Link>

View file

@ -0,0 +1,96 @@
export interface EvalProductConfig {
slug: string
name: string
version: string
build: string
description: string
archs: string[]
type: 'server' | 'enterprise'
requirements: {
cpu: string
ram: string
disk: string
note?: string
}
}
export const evalProducts: EvalProductConfig[] = [
{
slug: 'server-2025',
name: 'Windows Server 2025',
version: 'Standard / Datacenter',
build: 'Latest evaluation build',
description:
'The latest Windows Server release featuring enhanced security, hybrid cloud capabilities, and improved performance. Includes SMB over QUIC, credential guard improvements, and delegated managed service accounts.',
archs: ['x64'],
type: 'server',
requirements: {
cpu: '1.4 GHz 64-bit',
ram: '512 MB / 2 GB GUI',
disk: '32 GB',
},
},
{
slug: 'server-2022',
name: 'Windows Server 2022',
version: 'Standard / Datacenter',
build: 'Latest evaluation build',
description:
'Long-term servicing channel release with Secured-core server support, TLS 1.3 by default, and DNS-over-HTTPS. The recommended choice for new server infrastructure evaluation.',
archs: ['x64'],
type: 'server',
requirements: {
cpu: '1.4 GHz 64-bit',
ram: '512 MB / 2 GB GUI',
disk: '32 GB',
},
},
{
slug: 'server-2019',
name: 'Windows Server 2019',
version: 'Standard / Datacenter',
build: '17763',
description:
'Stable and widely deployed. Includes Windows Defender Advanced Threat Protection, Storage Migration Service, and System Insights. Ideal for existing infrastructure evaluation.',
archs: ['x64'],
type: 'server',
requirements: {
cpu: '1.4 GHz 64-bit',
ram: '512 MB / 2 GB GUI',
disk: '32 GB',
},
},
{
slug: 'server-2016',
name: 'Windows Server 2016',
version: 'Standard / Datacenter',
build: '14393',
description:
'Legacy server release with Nano Server, Storage Spaces Direct, and Windows Containers support. Suitable for older environment compatibility testing.',
archs: ['x64'],
type: 'server',
requirements: {
cpu: '1.4 GHz 64-bit',
ram: '512 MB / 2 GB GUI',
disk: '32 GB',
},
},
{
slug: 'win11-ent',
name: 'Windows 11 Enterprise',
version: 'Evaluation',
build: 'Latest evaluation build',
description:
'Full enterprise feature set including Windows Hello for Business, BitLocker, Microsoft Defender for Endpoint integration, and advanced management via Intune and Group Policy.',
archs: ['x64'],
type: 'enterprise',
requirements: {
cpu: '1 GHz, 2+ cores 64-bit',
ram: '4 GB',
disk: '64 GB',
note: 'TPM 2.0 required',
},
},
]
export const evalSlugSet = new Set(evalProducts.map(p => p.slug))

View file

@ -0,0 +1,325 @@
import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { motion, AnimatePresence } from 'motion/react'
import { ArrowLeft, Download, ExternalLink, AlertTriangle, Copy, Check, Server, ChevronDown } from 'lucide-react'
import { toast } from 'sonner'
import { evalProducts } from '../data/evalProducts'
import Aria2Tip from '../components/Aria2Tip'
import RelatedReleases from '../components/RelatedReleases'
import NotFoundPage from './NotFoundPage'
const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:3002'
const SITE_URL = 'https://msdl.tech-latest.com'
interface EvalLink {
arch: string
lang: string
url: string
}
const langNames: Record<string, string> = {
'en-us': 'English',
'zh-cn': 'Chinese (Simplified)',
'zh-tw': 'Chinese (Traditional)',
'fr-fr': 'French',
'de-de': 'German',
'it-it': 'Italian',
'ja-jp': 'Japanese',
'ko-kr': 'Korean',
'pt-br': 'Portuguese (Brazil)',
'ru-ru': 'Russian',
'es-es': 'Spanish',
'pl-pl': 'Polish',
'nl-nl': 'Dutch',
'sv-se': 'Swedish',
'tr-tr': 'Turkish',
}
function WinLogo({ className }: { className?: string }) {
return (
<svg viewBox="0 0 88 88" className={className} fill="currentColor" aria-hidden="true">
<path d="M0 12.402l35.687-4.86.016 34.423-35.67.203zm35.67 33.529l.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z" />
</svg>
)
}
export default function EvalDetailPage() {
const { productId } = useParams<{ productId: string }>()
const navigate = useNavigate()
const product = evalProducts.find(p => p.slug === productId)
const [links, setLinks] = useState<EvalLink[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [copiedUrl, setCopiedUrl] = useState<string | null>(null)
const [moreOpen, setMoreOpen] = useState(false)
useEffect(() => {
if (!product) return
setIsLoading(true)
setError(null)
fetch(`${API_BASE}/evallinks?product=${product.slug}`)
.then(r => r.json())
.then(data => {
if (data.error) throw new Error(data.error)
setLinks(data.links ?? [])
})
.catch(err => setError(err.message))
.finally(() => setIsLoading(false))
}, [product?.slug])
async function handleCopy(url: string) {
try {
await navigator.clipboard.writeText(url)
setCopiedUrl(url)
toast.success('Link copied to clipboard')
setTimeout(() => setCopiedUrl(null), 2000)
} catch {
toast.error('Failed to copy link')
}
}
if (!product) return <NotFoundPage />
const primaryLink = links[0] ?? null
const otherLinks = links.slice(1)
const pageTitle = `${product.name} ISO Download | Windows ISO Downloader`
const pageDesc = `Download ${product.name} evaluation ISO directly from Microsoft. ${product.description}`
const canonical = `${SITE_URL}/product/${product.slug}`
const breadcrumbJsonLd = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL },
{ '@type': 'ListItem', position: 2, name: 'Enterprise & Server', item: `${SITE_URL}/eval` },
{ '@type': 'ListItem', position: 3, name: product.name, item: canonical },
],
}
return (
<>
<title>{pageTitle}</title>
<meta name="description" content={pageDesc} />
<link rel="canonical" href={canonical} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={pageDesc} />
<meta property="og:url" content={canonical} />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }} />
<div className="max-w-xl mx-auto px-5 pt-12 pb-10">
{/* Back */}
<button
onClick={() => window.history.state?.idx ? navigate(-1) : navigate('/eval')}
className="flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-300 mb-8 transition-colors"
>
<ArrowLeft size={15} />
Enterprise & Server
</button>
<motion.div
initial={{ opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35 }}
>
{/* Header */}
<div className="flex items-start gap-4 mb-6">
<div className="w-12 h-12 rounded-xl bg-white/5 border border-white/8 flex items-center justify-center flex-shrink-0">
{product.type === 'server'
? <Server className="w-6 h-6 text-white/50" />
: <WinLogo className="w-6 h-6 text-white/50" />
}
</div>
<div className="min-w-0">
<h1 className="text-2xl font-bold text-white leading-tight tracking-tight">
{product.name}
</h1>
<p className="text-[12px] font-mono text-zinc-500 mt-0.5">{product.version}</p>
<div className="flex gap-1.5 mt-2">
{product.archs.map(arch => (
<span key={arch} className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded bg-white/5 border border-white/8 text-zinc-500">
{arch}
</span>
))}
<span className="text-[10px] font-mono font-semibold px-2 py-0.5 rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-400">
EVAL
</span>
</div>
</div>
</div>
{/* Eval notice */}
<div className="flex items-start gap-2.5 p-3.5 rounded-xl border border-amber-500/15 bg-amber-500/6 mb-5">
<AlertTriangle size={14} className="text-amber-400/80 mt-0.5 flex-shrink-0" />
<p className="text-[11px] text-amber-400/70 leading-relaxed">
<strong className="text-amber-400">180-day evaluation only.</strong> Not for production use. A valid license is required for activation beyond the trial period.
</p>
</div>
{/* Download card */}
<div className="rounded-2xl border border-white/7 bg-[#111113] p-6 mb-4">
<p className="text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-4">
Download
</p>
{isLoading ? (
<div className="space-y-2.5">
<div className="h-11 bg-white/5 rounded-xl animate-pulse" />
<div className="h-8 w-36 bg-white/4 rounded-lg animate-pulse" />
</div>
) : error ? (
<div className="py-4 text-center">
<p className="text-sm text-red-400 mb-1">Failed to load download links</p>
<p className="text-xs text-zinc-600">{error}</p>
</div>
) : primaryLink ? (
<div className="space-y-3">
{/* Primary link */}
<div className="flex items-center gap-2">
<a
href={primaryLink.url}
target="_blank"
rel="noopener noreferrer"
className="flex-1 flex items-center gap-2 px-4 py-3 rounded-xl bg-blue-500/10 border border-blue-500/20 text-blue-400 hover:bg-blue-500/15 hover:border-blue-500/30 transition-all text-sm font-medium"
>
<Download size={14} />
Download {primaryLink.arch}
{primaryLink.lang && (
<span className="text-blue-400/60 font-normal">
· {langNames[primaryLink.lang] ?? primaryLink.lang}
</span>
)}
<ExternalLink size={12} className="ml-auto opacity-60" />
</a>
<button
onClick={() => handleCopy(primaryLink.url)}
className={`p-3 rounded-xl border transition-all flex-shrink-0 ${
copiedUrl === primaryLink.url
? 'border-green-500/30 bg-green-500/10 text-green-400'
: 'border-white/8 bg-white/4 text-zinc-500 hover:text-white hover:border-white/15'
}`}
title="Copy download link"
>
{copiedUrl === primaryLink.url ? <Check size={14} /> : <Copy size={14} />}
</button>
</div>
{/* More languages */}
{otherLinks.length > 0 && (
<div>
<button
onClick={() => setMoreOpen(o => !o)}
className="flex items-center gap-1.5 text-xs text-zinc-600 hover:text-zinc-400 transition-colors"
>
<ChevronDown
size={12}
className={`transition-transform duration-200 ${moreOpen ? 'rotate-180' : ''}`}
/>
{otherLinks.length} more language{otherLinks.length > 1 ? 's' : ''}
</button>
<AnimatePresence>
{moreOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden"
>
<div className="space-y-1.5 mt-2.5">
{otherLinks.map(link => (
<div key={link.url} className="flex items-center gap-2">
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="flex-1 flex items-center gap-2 px-3 py-2 rounded-lg bg-white/4 border border-white/6 text-zinc-400 hover:bg-white/7 hover:border-white/10 hover:text-white transition-all text-xs"
>
<Download size={11} />
{link.arch}
{link.lang && (
<span className="text-zinc-500">
· {langNames[link.lang] ?? link.lang}
</span>
)}
<ExternalLink size={10} className="ml-auto opacity-50" />
</a>
<button
onClick={() => handleCopy(link.url)}
className={`px-2.5 py-2 rounded-lg border text-[10px] font-mono transition-all flex-shrink-0 ${
copiedUrl === link.url
? 'border-green-500/30 bg-green-500/10 text-green-400'
: 'border-white/8 bg-white/4 text-zinc-500 hover:text-white hover:border-white/15'
}`}
>
{copiedUrl === link.url ? '✓' : 'Copy'}
</button>
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)}
<p className="text-[10px] text-zinc-600 pt-1">
Direct from Microsoft's CDN · Evaluation only requires activation after 180 days
</p>
</div>
) : (
<p className="text-sm text-zinc-500 text-center py-4">No download links available.</p>
)}
</div>
{/* System requirements */}
<div className="rounded-xl border border-white/7 bg-[#111113] p-4 mb-4">
<p className="text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-3">
System Requirements
</p>
<div className="grid grid-cols-3 gap-3">
<div className="flex flex-col gap-1">
<span className="text-[11px] text-zinc-600">Processor</span>
<span className="text-sm font-semibold text-white">{product.requirements.cpu}</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-[11px] text-zinc-600">RAM</span>
<span className="text-sm font-semibold text-white">{product.requirements.ram}</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-[11px] text-zinc-600">Storage</span>
<span className="text-sm font-semibold text-white">{product.requirements.disk}</span>
</div>
</div>
{product.requirements.note && (
<div className="mt-3 pt-3 border-t border-white/5 flex items-center gap-2">
<AlertTriangle size={12} className="text-amber-500 flex-shrink-0" />
<span className="text-[11px] text-amber-500/80">{product.requirements.note}</span>
</div>
)}
</div>
{/* About */}
<div className="rounded-xl border border-white/7 bg-[#111113] p-4">
<p className="text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-2">
About this release
</p>
<p className="text-sm text-zinc-400 leading-relaxed">{product.description}</p>
</div>
{/* Aria2 tip — outside box, same as consumer page */}
<Aria2Tip />
{/* Also available — outside box, same as consumer page */}
<RelatedReleases
current={product.slug}
items={evalProducts.map(p => ({ id: p.slug, label: p.name }))}
/>
</motion.div>
</div>
</>
)
}

View file

@ -0,0 +1,121 @@
import { Link } from 'react-router-dom'
import { motion } from 'motion/react'
import { ArrowRight, AlertTriangle, Server } from 'lucide-react'
import { evalProducts } from '../data/evalProducts'
const SITE_URL = 'https://msdl.tech-latest.com'
const typeColors = {
server: 'text-violet-400 bg-violet-500/10 border-violet-500/20',
enterprise: 'text-blue-400 bg-blue-500/10 border-blue-500/20',
} as const
const typeLabel = {
server: 'Server',
enterprise: 'Enterprise',
} as const
const container = {
hidden: {},
show: { transition: { staggerChildren: 0.06 } },
}
const cardVariant = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { type: 'spring' as const, stiffness: 280, damping: 28 } },
}
export default function EvalPage() {
return (
<>
<title>Enterprise & Server ISOs | Windows ISO Downloader</title>
<meta name="description" content="Download Windows Server 2025, 2022, 2019, 2016 and Windows 11 Enterprise evaluation ISOs directly from Microsoft's CDN." />
<link rel="canonical" href={`${SITE_URL}/eval`} />
<meta property="og:title" content="Enterprise & Server ISOs | Windows ISO Downloader" />
<meta property="og:description" content="Download Windows Server and Enterprise evaluation ISOs directly from Microsoft's CDN." />
<meta property="og:url" content={`${SITE_URL}/eval`} />
<div className="max-w-4xl mx-auto px-5 pt-12 pb-10">
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
{/* Header */}
<div className="mb-6">
<p className="text-[11px] font-mono font-semibold tracking-[0.2em] uppercase text-violet-400 mb-3">
Enterprise & Server
</p>
<h1 className="text-3xl font-bold text-white mb-2">Evaluation ISOs</h1>
<p className="text-zinc-400 text-sm leading-relaxed max-w-lg">
Official evaluation editions from Microsoft's evalcenter. Direct links from Microsoft's CDN no session, no registration.
</p>
</div>
{/* Eval notice */}
<div className="flex items-start gap-3 p-4 rounded-xl border border-amber-500/15 bg-amber-500/6 mb-8">
<AlertTriangle size={15} className="text-amber-400/80 mt-0.5 flex-shrink-0" />
<p className="text-xs text-amber-400/70 leading-relaxed">
<strong className="text-amber-400">Evaluation editions only.</strong> These ISOs are time-limited 180-day trials intended for testing and evaluation not for production use. A valid license is required for activation beyond the trial period.
</p>
</div>
{/* Product grid */}
<motion.div
className="grid grid-cols-1 sm:grid-cols-2 gap-3"
variants={container}
initial="hidden"
animate="show"
>
{evalProducts.map(product => (
<motion.div key={product.slug} variants={cardVariant}>
<Link to={`/product/${product.slug}`} className="block outline-none group">
<motion.div
whileHover={{ y: -3, scale: 1.005 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
className="relative rounded-2xl border border-white/7 bg-[#111113] p-6
hover:border-white/13 hover:shadow-2xl hover:shadow-black/40
transition-colors duration-300"
>
{/* Header row */}
<div className="flex items-start justify-between mb-4">
<div className="w-9 h-9 rounded-lg bg-white/5 border border-white/8 flex items-center justify-center">
<Server className="w-4 h-4 text-white/50" />
</div>
<span className={`text-[10px] font-semibold px-2 py-0.5 rounded-full border ${typeColors[product.type]}`}>
{typeLabel[product.type]}
</span>
</div>
{/* Title */}
<h2 className="text-base font-semibold text-white leading-tight">{product.name}</h2>
<p className="text-[11px] font-mono text-zinc-500 mt-0.5 mb-3">{product.version}</p>
<p className="text-sm text-zinc-400 leading-relaxed mb-4 line-clamp-2">{product.description}</p>
{/* Footer */}
<div className="flex items-center justify-between">
<div className="flex gap-1.5">
{product.archs.map(arch => (
<span key={arch} className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded bg-white/5 border border-white/8 text-zinc-500">
{arch}
</span>
))}
<span className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded bg-amber-500/8 border border-amber-500/20 text-amber-500/70">
EVAL
</span>
</div>
<div className="flex items-center gap-1 text-xs text-zinc-600 group-hover:text-zinc-400 transition-colors">
<span>Get links</span>
<ArrowRight size={11} className="group-hover:translate-x-0.5 transition-transform" />
</div>
</div>
</motion.div>
</Link>
</motion.div>
))}
</motion.div>
</motion.div>
</div>
</>
)
}

View file

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { motion } from 'motion/react'
import { ArrowRight } from 'lucide-react'
import { ArrowRight, Server } from 'lucide-react'
import ProductCard from '../components/ProductCard'
import StatsBar from '../components/StatsBar'
import HowItWorks from '../components/HowItWorks'
@ -161,6 +161,34 @@ export default function HomePage() {
))}
</motion.div>
{/* Enterprise & Server CTA */}
<motion.div
className="mb-4"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.45 }}
>
<Link
to="/eval"
className="group flex items-center justify-between w-full px-5 py-3.5 rounded-xl border border-violet-500/15 bg-violet-500/5 hover:border-violet-500/25 hover:bg-violet-500/8 transition-all duration-200"
>
<div className="flex items-center gap-3">
<div className="w-7 h-7 rounded-lg bg-violet-500/10 border border-violet-500/20 flex items-center justify-center flex-shrink-0">
<Server size={14} className="text-violet-400" />
</div>
<div>
<p className="text-sm font-medium text-white/80 group-hover:text-white transition-colors leading-tight">
Enterprise & Server ISOs
</p>
<p className="text-[11px] text-zinc-600 mt-0.5">
Server 2025, 2022, 2019, 2016 · Win 11 Enterprise · Eval editions
</p>
</div>
</div>
<ArrowRight size={14} className="text-zinc-600 group-hover:text-violet-400 group-hover:translate-x-0.5 transition-all" />
</Link>
</motion.div>
{/* Browse all CTA */}
<motion.div
className="text-center"