Merge pull request #13 from starkSV/feat/evalcenter-isos
feat: Enterprise & Server evaluation ISOs via Microsoft Eval Center
This commit is contained in:
commit
ddf96a873d
17 changed files with 917 additions and 21 deletions
48
README.md
48
README.md
|
|
@ -14,10 +14,11 @@
|
|||
MSDL replicates the session flow that Microsoft uses to serve ISO download links — identical to the approach used by [Rufus/Fido](https://github.com/pbatard/Fido). You get the exact same signed CDN URL Microsoft would give you, just without the browser requirement.
|
||||
|
||||
- ✅ Direct Microsoft CDN links — no proxying of actual file data
|
||||
- ✅ 38 languages per release
|
||||
- ✅ 38 languages per consumer release
|
||||
- ✅ ARM64 + x64 + x86 support
|
||||
- ✅ Windows Server 2016–2025 and Windows 11 Enterprise evaluation ISOs
|
||||
- ✅ No account, no browser lock, no ads, no tracking
|
||||
- ✅ Links expire in 24 hours (Microsoft's standard behaviour, not a limitation)
|
||||
- ✅ Consumer links expire in 24 hours (Microsoft's standard behaviour, not a limitation)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -133,10 +134,27 @@ Returns signed download links from Microsoft's CDN.
|
|||
}
|
||||
```
|
||||
|
||||
### `GET /evallinks?product=<slug>`
|
||||
|
||||
Returns direct CDN links for evaluation ISOs (Server/Enterprise). Links are resolved from Microsoft's Eval Center fwlink redirects and cached for 24 hours.
|
||||
|
||||
Valid slugs: `server-2025`, `server-2022`, `server-2019`, `server-2016`, `win11-ent`
|
||||
|
||||
```json
|
||||
{
|
||||
"links": [
|
||||
{ "arch": "x64", "lang": "en-us", "url": "https://software-static.download.prss.microsoft.com/..." },
|
||||
{ "arch": "x64", "lang": "fr-fr", "url": "https://software-static.download.prss.microsoft.com/..." }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Supported Products
|
||||
|
||||
### Consumer releases
|
||||
|
||||
| Product | ID | Architecture |
|
||||
|---|---|---|
|
||||
| Windows 11 25H2 | 3262 | x64 |
|
||||
|
|
@ -150,6 +168,18 @@ Returns signed download links from Microsoft's CDN.
|
|||
| Windows 8.1 | 52 | x64 / x86 |
|
||||
| Windows 8.1 Single Language | 48 | x64 / x86 |
|
||||
|
||||
### Evaluation editions (Server & Enterprise)
|
||||
|
||||
180-day trial ISOs sourced directly from Microsoft's Eval Center CDN. No registration required.
|
||||
|
||||
| Product | Slug | Architecture |
|
||||
|---|---|---|
|
||||
| Windows Server 2025 | `server-2025` | x64 |
|
||||
| Windows Server 2022 | `server-2022` | x64 |
|
||||
| Windows Server 2019 | `server-2019` | x64 |
|
||||
| Windows Server 2016 | `server-2016` | x64 |
|
||||
| Windows 11 Enterprise | `win11-ent` | x64 |
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
|
@ -195,12 +225,18 @@ Recommended setup:
|
|||
|
||||
## Contributing
|
||||
|
||||
Pull requests welcome. To add a new Windows release:
|
||||
Pull requests welcome. To add a new consumer Windows release:
|
||||
|
||||
1. Find the product ID on `www.microsoft.com/software-download-connector/api/`
|
||||
2. Add it to `frontend/public/data/products.json`
|
||||
3. Add related metadata in `ProductDetailPage.tsx` (`PRODUCT_META`, `RELATED_GROUPS`)
|
||||
4. Update the product table in this README
|
||||
2. Add it to `frontend/public/data/products.json` (name, archs, badge, related, active)
|
||||
3. Update the product table in this README
|
||||
|
||||
To add a new evaluation edition:
|
||||
|
||||
1. Find the fwlink URL on `microsoft.com/en-us/evalcenter/download-*`
|
||||
2. Add the slug and fwlink to the `evalProducts` map in `backend/main.go`
|
||||
3. Add the product config to `frontend/src/data/evalProducts.ts`
|
||||
4. Update the eval table in this README
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
208
backend/main.go
208
backend/main.go
|
|
@ -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 & → &)
|
||||
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"}`))
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 />} />
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ interface Row {
|
|||
}
|
||||
|
||||
const rows: Row[] = [
|
||||
{ feature: 'Direct CDN link', msdl: true, official: true, others: false },
|
||||
{ feature: 'No browser required', msdl: true, official: false, others: 'varies' },
|
||||
{ feature: 'ARM64 support', msdl: true, official: false, others: false },
|
||||
{ feature: 'No account needed', msdl: true, official: true, others: 'varies' },
|
||||
{ feature: 'No ads or tracking', msdl: true, official: true, others: false },
|
||||
{ feature: 'Direct CDN link', msdl: true, official: true, others: false },
|
||||
{ feature: 'No browser required', msdl: true, official: false, others: 'varies' },
|
||||
{ feature: 'ARM64 support', msdl: true, official: false, others: false },
|
||||
{ feature: 'Server & Enterprise ISOs', msdl: true, official: false, others: false },
|
||||
{ feature: 'No account needed', msdl: true, official: true, others: 'varies' },
|
||||
{ feature: 'No ads or tracking', msdl: true, official: true, others: false },
|
||||
]
|
||||
|
||||
function Cell({ value, highlight = false }: { value: CellValue; highlight?: boolean }) {
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ const faqs: FaqItem[] = [
|
|||
q: 'Can I use this with IDM or aria2?',
|
||||
a: 'Absolutely — and we strongly recommend it. Microsoft throttles single-threaded browser downloads. Tools like aria2 (aria2c -x 16 -s 16 --disable-ipv6=true "URL") or IDM use 16 parallel connections, which typically give you full line speed on their CDN.',
|
||||
},
|
||||
{
|
||||
q: 'Do you have Windows Server or Enterprise ISOs?',
|
||||
a: 'Yes — check the Enterprise & Server section (/eval). It includes Windows Server 2025, 2022, 2019, 2016, and Windows 11 Enterprise. These are official 180-day evaluation editions sourced directly from Microsoft\'s Eval Center CDN — no browser session, no registration required. They\'re fully functional for testing and lab use; a valid license is needed to activate beyond the trial period.',
|
||||
},
|
||||
{
|
||||
q: 'What\'s the difference between the various product IDs?',
|
||||
a: 'Each Windows release has a unique product ID on Microsoft\'s servers. For example, 3262 is Windows 11 25H2 (x64) and 3265 is Windows 11 25H2 (ARM64). MSDL exposes all known IDs so you can always find the exact build you need.',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const steps = [
|
|||
num: '1',
|
||||
icon: LayoutGrid,
|
||||
title: 'Pick a release',
|
||||
desc: 'Choose from Windows 11, 10, and 8.1 across all feature updates.',
|
||||
desc: 'Choose from Windows 11, 10, and 8.1 — or Server 2016–2025 and Enterprise evaluation editions.',
|
||||
},
|
||||
{
|
||||
num: '2',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
102
frontend/src/data/evalProducts.ts
Normal file
102
frontend/src/data/evalProducts.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
export interface EvalProductConfig {
|
||||
slug: string
|
||||
name: string
|
||||
version: string
|
||||
build: string
|
||||
description: string
|
||||
seoDesc: string // ≤155 chars, for <meta name="description"> and og:description
|
||||
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',
|
||||
seoDesc: 'Download Windows Server 2025 evaluation ISO from Microsoft. 180-day trial. Direct CDN link — no registration required.',
|
||||
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',
|
||||
seoDesc: 'Download Windows Server 2022 evaluation ISO from Microsoft. Long-term servicing channel. 180-day trial. Direct CDN link, no registration.',
|
||||
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',
|
||||
seoDesc: 'Download Windows Server 2019 evaluation ISO from Microsoft. Includes Defender ATP and Storage Migration Service. 180-day trial, direct CDN link.',
|
||||
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',
|
||||
seoDesc: 'Download Windows Server 2016 evaluation ISO from Microsoft. Supports Nano Server, Storage Spaces Direct, and Windows Containers. 180-day trial.',
|
||||
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',
|
||||
seoDesc: 'Download Windows 11 Enterprise evaluation ISO from Microsoft. Includes BitLocker, Defender for Endpoint, and Intune management. 180-day trial.',
|
||||
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))
|
||||
|
|
@ -20,6 +20,7 @@ export default function DisclaimerPage() {
|
|||
<meta property="og:title" content="Disclaimer | Windows ISO Downloader" />
|
||||
<meta property="og:description" content="Legal disclaimer for MSDL. Not affiliated with Microsoft Corporation." />
|
||||
<meta property="og:url" content={`${SITE_URL}/disclaimer`} />
|
||||
<meta name="robots" content="noindex, follow" />
|
||||
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
|
|
|
|||
337
frontend/src/pages/EvalDetailPage.tsx
Normal file
337
frontend/src/pages/EvalDetailPage.tsx
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
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} Evaluation ISO Download | Windows ISO Downloader`
|
||||
const pageDesc = product.seoDesc
|
||||
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 },
|
||||
],
|
||||
}
|
||||
|
||||
const softwareJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: `${product.name} Evaluation`,
|
||||
operatingSystem: 'Windows',
|
||||
applicationCategory: 'OperatingSystem',
|
||||
url: canonical,
|
||||
description: product.seoDesc,
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||
}
|
||||
|
||||
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) }} />
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(softwareJsonLd) }} />
|
||||
|
||||
<div className="max-w-2xl 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
135
frontend/src/pages/EvalPage.tsx
Normal file
135
frontend/src/pages/EvalPage.tsx
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
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 itemListJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ItemList',
|
||||
name: 'Windows Server & Enterprise Evaluation ISOs',
|
||||
description: 'Official evaluation ISOs for Windows Server and Enterprise editions, direct from Microsoft.',
|
||||
itemListElement: evalProducts.map((p, i) => ({
|
||||
'@type': 'ListItem',
|
||||
position: i + 1,
|
||||
name: p.name,
|
||||
url: `${SITE_URL}/product/${p.slug}`,
|
||||
})),
|
||||
}
|
||||
|
||||
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`} />
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(itemListJsonLd) }} />
|
||||
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
|
@ -66,7 +66,7 @@ const jsonLd = {
|
|||
applicationCategory: 'UtilitiesApplication',
|
||||
operatingSystem: 'Web',
|
||||
url: SITE_URL,
|
||||
description: 'Download official Microsoft Windows ISO files for Windows 11, 10, and 8.1. Direct links from Microsoft CDN. Free, no registration.',
|
||||
description: 'Download official Microsoft Windows ISO files for Windows 11, 10, 8.1, and Windows Server. Direct links from Microsoft CDN. Free, no registration.',
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||
}
|
||||
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default function PrivacyPolicyPage() {
|
|||
<meta property="og:title" content="Privacy Policy | Windows ISO Downloader" />
|
||||
<meta property="og:description" content="Privacy policy for MSDL. We log no data, use no trackers, and proxy no files." />
|
||||
<meta property="og:url" content={`${SITE_URL}/privacy-policy`} />
|
||||
<meta name="robots" content="noindex, follow" />
|
||||
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default function ProductDetailPage() {
|
|||
: hasCatalogError
|
||||
? 'Failed to load product catalog.'
|
||||
: productName
|
||||
? `Download official ISO image for ${productName}. Direct links pulled securely from Microsoft CDN servers.`
|
||||
? `Download the official ${productName} ISO directly from Microsoft's CDN. No registration, no proxy — real-time signed links.`
|
||||
: ''
|
||||
|
||||
const canonicalUrl = `${SITE_URL}/product/${productId}`
|
||||
|
|
@ -90,6 +90,17 @@ export default function ProductDetailPage() {
|
|||
],
|
||||
} : null
|
||||
|
||||
const softwareJsonLd = productName && !isNotFound && !hasCatalogError ? {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: productName,
|
||||
operatingSystem: 'Windows',
|
||||
applicationCategory: 'OperatingSystem',
|
||||
url: canonicalUrl,
|
||||
description: pageDescription,
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||
} : null
|
||||
|
||||
// Load product name + build string
|
||||
useEffect(() => {
|
||||
setBuildStr('')
|
||||
|
|
@ -209,8 +220,11 @@ export default function ProductDetailPage() {
|
|||
{breadcrumbJsonLd && (
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }} />
|
||||
)}
|
||||
{softwareJsonLd && (
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(softwareJsonLd) }} />
|
||||
)}
|
||||
|
||||
<div className="max-w-xl mx-auto px-5 pt-12 pb-10">
|
||||
<div className="max-w-2xl mx-auto px-5 pt-12 pb-10">
|
||||
{/* Back */}
|
||||
<button
|
||||
onClick={() => window.history.state?.idx ? navigate(-1) : navigate('/')}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function ProductsPage() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<title>All Windows Releases | Windows ISO Downloader</title>
|
||||
<title>All Windows ISO Releases | Windows ISO Downloader</title>
|
||||
<meta name="description" content="Browse all official Microsoft Windows ISO releases available for direct download. Windows 11, 10, and 8.1." />
|
||||
<link rel="canonical" href={`${SITE_URL}/products`} />
|
||||
<meta property="og:title" content="All Windows ISO Releases | Windows ISO Downloader" />
|
||||
<meta property="og:description" content="Browse all official Microsoft Windows ISO releases available for direct download." />
|
||||
<meta property="og:url" content={`${SITE_URL}/products`} />
|
||||
|
||||
<div className="max-w-3xl mx-auto px-4 pt-12 pb-10">
|
||||
<div className="max-w-4xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue