Implement inactive product state, restore backend tracker limits, and add dedicated catalog error UI panel

This commit is contained in:
Shekhar Vaidya 2026-04-11 13:01:56 +05:30
parent 1910ce621a
commit 2d688ae7e8
7 changed files with 278 additions and 125 deletions

View file

@ -53,7 +53,7 @@ func getReferer(productId string) string {
// Replicate Fido session tracking
func setupSession() (string, error) {
sessionID := uuid.New().String()
client := &http.Client{Timeout: 3 * time.Second}
client := &http.Client{Timeout: 10 * time.Second}
// Step 1: Register session
req1, _ := http.NewRequest("GET", fmt.Sprintf("https://vlscppe.microsoft.com/tags?org_id=%s&session_id=%s", ORG_ID, sessionID), nil)

View file

@ -1,110 +1,207 @@
{
"48": {
"name": "Windows 8.1 Single Language (9600.17415)",
"badge": "LEGACY",
"archs": ["x64", "x86"],
"related": ["52"]
},
"52": {
"name": "Windows 8.1 (9600.17415)",
"badge": "LEGACY",
"archs": ["x64", "x86"],
"related": ["48", "2618"]
},
"2378": {
"name": "Windows 10 22H2 Home China (19045.2006)",
"badge": "EOL SOON",
"archs": ["x64"],
"related": ["2618"]
},
"2618": {
"name": "Windows 10 22H2 v1 (19045.2965)",
"badge": "EOL SOON",
"archs": ["x64", "x86"],
"related": ["3113", "3262", "52"]
},
"3113": {
"name": "Windows 11 24H2 (26100.1742)",
"badge": "STABLE",
"archs": ["x64"],
"related": ["3262", "3131", "2618"]
},
"3114": {
"name": "Windows 11 24H2 Home China (26100.1742)",
"badge": "STABLE",
"archs": ["x64"],
"related": ["3113", "3115"]
},
"3115": {
"name": "Windows 11 24H2 Pro China (26100.1742)",
"badge": "STABLE",
"archs": ["x64"],
"related": ["3113", "3114"]
},
"3131": {
"name": "Windows 11 Arm64 24H2 (26100.1742)",
"badge": "STABLE",
"archs": ["ARM64"],
"related": ["3265", "3113"]
},
"3132": {
"name": "Windows 11 Arm64 24H2 Home China (26100.1742)",
"badge": "STABLE",
"archs": ["ARM64"],
"related": ["3131", "3133"]
},
"3133": {
"name": "Windows 11 Arm64 24H2 Pro China (26100.1742)",
"badge": "STABLE",
"archs": ["ARM64"],
"related": ["3131", "3132"]
},
"3262": {
"name": "Windows 11 25H2 (26200.6584)",
"badge": "LATEST",
"archs": ["x64"],
"related": ["3265", "3321", "3113"]
},
"3263": {
"name": "Windows 11 25H2 Home China (26200.6584)",
"badge": "LATEST",
"archs": ["x64"],
"related": ["3262", "3264"]
},
"3264": {
"name": "Windows 11 25H2 Pro China (26200.6584)",
"badge": "LATEST",
"archs": ["x64"],
"related": ["3262", "3263"]
},
"3265": {
"name": "Windows 11 Arm64 25H2 (26200.6584)",
"badge": "LATEST",
"archs": ["ARM64"],
"related": ["3262", "3321", "3131"]
},
"3266": {
"name": "Windows 11 Arm64 25H2 Home China (26200.6584)",
"badge": "LATEST",
"archs": ["ARM64"],
"related": ["3265", "3267"]
},
"3267": {
"name": "Windows 11 Arm64 25H2 Pro China (26200.6584)",
"badge": "LATEST",
"archs": ["ARM64"],
"related": ["3265", "3266"]
},
"3321": {
"name": "Windows 11 25H2 (Updated Oct)",
"badge": "LATEST",
"archs": ["x64"],
"related": ["3262", "3324", "3113"]
},
"3324": {
"name": "Windows 11 Arm64 25H2 (Updated Oct)",
"badge": "LATEST",
"archs": ["ARM64"],
"related": ["3321", "3262", "3265"]
}
"48": {
"name": "Windows 8.1 Single Language (9600.17415)",
"badge": "LEGACY",
"archs": [
"x64",
"x86"
],
"related": [
"52"
]
},
"52": {
"name": "Windows 8.1 (9600.17415)",
"badge": "LEGACY",
"archs": [
"x64",
"x86"
],
"related": [
"48",
"2618"
]
},
"2378": {
"name": "Windows 10 22H2 Home China (19045.2006)",
"badge": "EOL SOON",
"archs": [
"x64"
],
"related": [
"2618"
]
},
"2618": {
"name": "Windows 10 22H2 v1 (19045.2965)",
"badge": "EOL SOON",
"archs": [
"x64",
"x86"
],
"related": [
"3113",
"3262",
"52"
]
},
"3113": {
"name": "Windows 11 24H2 (26100.1742)",
"badge": "STABLE",
"archs": [
"x64"
],
"related": [
"3262",
"3131",
"2618"
]
},
"3114": {
"name": "Windows 11 24H2 Home China (26100.1742)",
"badge": "STABLE",
"archs": [
"x64"
],
"related": [
"3113",
"3115"
]
},
"3115": {
"name": "Windows 11 24H2 Pro China (26100.1742)",
"badge": "STABLE",
"archs": [
"x64"
],
"related": [
"3113",
"3114"
]
},
"3131": {
"name": "Windows 11 Arm64 24H2 (26100.1742)",
"badge": "STABLE",
"archs": [
"ARM64"
],
"related": [
"3265",
"3113"
]
},
"3132": {
"name": "Windows 11 Arm64 24H2 Home China (26100.1742)",
"badge": "STABLE",
"archs": [
"ARM64"
],
"related": [
"3131",
"3133"
]
},
"3133": {
"name": "Windows 11 Arm64 24H2 Pro China (26100.1742)",
"badge": "STABLE",
"archs": [
"ARM64"
],
"related": [
"3131",
"3132"
]
},
"3262": {
"name": "Windows 11 25H2 (26200.6584)",
"badge": "LATEST",
"archs": [
"x64"
],
"related": [
"3265",
"3321",
"3113"
]
},
"3263": {
"name": "Windows 11 25H2 Home China (26200.6584)",
"badge": "LATEST",
"archs": [
"x64"
],
"related": [
"3262",
"3264"
]
},
"3264": {
"name": "Windows 11 25H2 Pro China (26200.6584)",
"badge": "LATEST",
"archs": [
"x64"
],
"related": [
"3262",
"3263"
]
},
"3265": {
"name": "Windows 11 Arm64 25H2 (26200.6584)",
"badge": "LATEST",
"archs": [
"ARM64"
],
"related": [
"3262",
"3321",
"3131"
]
},
"3266": {
"name": "Windows 11 Arm64 25H2 Home China (26200.6584)",
"badge": "LATEST",
"archs": [
"ARM64"
],
"related": [
"3265",
"3267"
]
},
"3267": {
"name": "Windows 11 Arm64 25H2 Pro China (26200.6584)",
"badge": "LATEST",
"archs": [
"ARM64"
],
"related": [
"3265",
"3266"
]
},
"3321": {
"name": "Windows 11 25H2 (Updated Oct)",
"badge": "LATEST",
"archs": [
"x64"
],
"related": [
"3262",
"3324",
"3113"
]
},
"3324": {
"name": "Windows 11 Arm64 25H2 (Updated Oct)",
"badge": "LATEST",
"archs": [
"ARM64"
],
"related": [
"3321",
"3262",
"3265"
]
}
}

View file

@ -12,7 +12,10 @@ export default function StatsBar() {
useEffect(() => {
fetch('/data/products.json')
.then(r => r.json())
.then(data => setTotalReleases(Object.keys(data).length.toString()))
.then((data: Record<string, { active?: boolean }>) => {
const activeCount = Object.values(data).filter(p => p.active !== false).length
setTotalReleases(activeCount.toString())
})
.catch(() => {})
}, [])

View file

@ -69,7 +69,10 @@ export default function HomePage() {
fetch('/data/products.json')
.then(r => r.json())
.then(data => setTotalReleases(Object.keys(data).length))
.then((data: Record<string, { active?: boolean }>) => {
const activeCount = Object.values(data).filter(p => p.active !== false).length
setTotalReleases(activeCount)
})
.catch(() => {})
}, [])

View file

@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom'
import { motion, AnimatePresence } from 'motion/react'
import * as Select from '@radix-ui/react-select'
import { ArrowLeft, ChevronDown, Download, AlertTriangle, Check, ExternalLink } from 'lucide-react'
import { ArrowLeft, ChevronDown, Download, AlertTriangle, Check, ExternalLink, WifiOff } from 'lucide-react'
import { toast } from 'sonner'
import type { Sku, DownloadOption } from '../types'
import SystemRequirements from '../components/SystemRequirements'
@ -16,6 +16,7 @@ interface ProductCatalogEntry {
badge: string
archs: string[]
related: string[]
active?: boolean
}
function WinLogo({ className }: { className?: string }) {
@ -54,8 +55,9 @@ export default function ProductDetailPage() {
const [copiedUri, setCopiedUri] = useState<string | null>(null)
const [isNotFound, setIsNotFound] = useState(false)
const [isValidated, setIsValidated] = useState(false)
const [hasCatalogError, setHasCatalogError] = useState(false)
const [meta, setMeta] = useState<{ badge: string; archs: string[] }>({ badge: '', archs: [] })
const [meta, setMeta] = useState<{ badge: string; archs: string[]; active: boolean }>({ badge: '', archs: [], active: true })
const [related, setRelated] = useState<{ id: string; label: string }[]>([])
// Load product name + build string
@ -63,12 +65,16 @@ export default function ProductDetailPage() {
setBuildStr('')
setIsNotFound(false)
setIsValidated(false)
setHasCatalogError(false)
setProductName('')
setMeta({ badge: '', archs: [] })
setMeta({ badge: '', archs: [], active: true })
setRelated([])
fetch('/data/products.json')
.then(r => r.json())
.then(r => {
if (!r.ok) throw new Error('Failed to fetch catalog')
return r.json()
})
.then((data: Record<string, ProductCatalogEntry>) => {
const product = data[productId!]
@ -83,7 +89,7 @@ export default function ProductDetailPage() {
const name = product.name
setProductName(name)
setMeta({ badge: product.badge || '', archs: product.archs || [] })
setMeta({ badge: product.badge || '', archs: product.archs || [], active: product.active !== false })
setRelated((product.related || []).map(rId => ({
id: rId,
@ -104,8 +110,8 @@ export default function ProductDetailPage() {
if (match) setBuildStr(match[1])
})
.catch(() => {
setIsNotFound(true)
setProductName('Error Loading Product')
setHasCatalogError(true)
setProductName('Error Loading Catalog')
})
}, [productId])
@ -211,7 +217,18 @@ export default function ProductDetailPage() {
{/* Main card */}
<div className="rounded-2xl border border-white/7 bg-[#111113] p-6 space-y-5">
{isNotFound ? (
{hasCatalogError ? (
<div className="flex flex-col items-center justify-center py-6 text-center">
<WifiOff size={32} className="text-zinc-600 mb-4" />
<h2 className="text-lg font-semibold text-white mb-2">Network Error</h2>
<p className="text-zinc-500 text-sm max-w-xs px-4 leading-relaxed mb-6">
Failed to load the product catalog. Please check your internet connection or try refreshing the page.
</p>
<button onClick={() => window.location.reload()} className="px-4 py-2 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
Retry
</button>
</div>
) : isNotFound ? (
<div className="flex flex-col items-center justify-center py-6 text-center">
<AlertTriangle size={32} className="text-zinc-600 mb-4" />
<h2 className="text-lg font-semibold text-white mb-2">Product Not Found</h2>
@ -227,6 +244,17 @@ export default function ProductDetailPage() {
</Link>
</div>
</div>
) : !meta.active ? (
<div className="flex flex-col items-center justify-center py-6 text-center">
<AlertTriangle size={32} className="text-amber-500/50 mb-4" />
<h2 className="text-lg font-semibold text-white mb-2">Product Discontinued</h2>
<p className="text-zinc-500 text-sm max-w-sm px-4 leading-relaxed mb-6">
This release is no longer hosted on Microsoft's official CDN and cannot be downloaded.
</p>
<Link to="/products" className="px-4 py-2.5 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
Browse other releases
</Link>
</div>
) : isLoadingLangs ? (
<div className="space-y-4">
<div className="flex items-center gap-2.5 text-xs text-zinc-400 font-medium tracking-wide">
@ -403,13 +431,13 @@ export default function ProductDetailPage() {
</AnimatePresence>
</div>
{!isNotFound && (
{(!isNotFound && !hasCatalogError) && (
<>
{/* System requirements */}
<SystemRequirements isWin11={isWin11(productId!)} />
{/* aria2 tip */}
<Aria2Tip downloadUrl={firstUri} />
{meta.active && <Aria2Tip downloadUrl={firstUri} />}
{/* Related releases */}
{related.length > 0 && (

View file

@ -22,8 +22,11 @@ export default function ProductsPage() {
fetch('/data/products.json')
.then(r => r.json())
.then((data: Record<string, { name: string }>) => {
setProducts(Object.entries(data).map(([id, product]) => ({ id, name: product.name })))
.then((data: Record<string, { name: string; active?: boolean }>) => {
const activeProducts = Object.entries(data)
.filter(([_, product]) => product.active !== false)
.map(([id, product]) => ({ id, name: product.name }))
setProducts(activeProducts)
})
.finally(() => setIsLoading(false))
}, [])

View file

@ -96,6 +96,7 @@ if __name__ == "__main__":
# Only log skips for very small ranges to keep output clean
if (args.last - args.first) < 20:
logging.info(f"❌ SKIP: [{i}] Not found")
products[str(i)] = False
# Be gentle to the API
time.sleep(0.5)
@ -113,23 +114,41 @@ if __name__ == "__main__":
# Merge new products, preserving existing extra metadata if present
for pid, name in products.items():
if pid in catalog:
if name is False:
if isinstance(catalog[pid], dict):
catalog[pid]["active"] = False
else:
catalog[pid] = {
"name": catalog[pid],
"badge": "",
"archs": [],
"related": [],
"active": False
}
continue
# Update name but keep other fields like badge, archs, etc.
if isinstance(catalog[pid], dict):
catalog[pid]["name"] = name
catalog[pid]["active"] = True
else:
# Upgrade from old flat format
catalog[pid] = {
"name": name,
"badge": "",
"archs": [],
"related": []
"related": [],
"active": True
}
else:
if name is False:
continue # Do not add new dead entries to the catalog
catalog[pid] = {
"name": name,
"badge": "",
"archs": [],
"related": []
"related": [],
"active": True
}
with open(args.write, 'w', encoding='utf-8') as f: