Implement inactive product state, restore backend tracker limits, and add dedicated catalog error UI panel
This commit is contained in:
parent
1910ce621a
commit
2d688ae7e8
7 changed files with 278 additions and 125 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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(() => {})
|
||||
}, [])
|
||||
|
||||
|
|
|
|||
|
|
@ -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(() => {})
|
||||
}, [])
|
||||
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}, [])
|
||||
|
|
|
|||
23
msdls_v3.py
23
msdls_v3.py
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue