spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { Metadata } from "next";2import Link from "next/link";3import { Breadcrumbs } from "@/components/entity/breadcrumbs";4import { SectionNav } from "@/components/entity/section-nav";5import { SourcesFooter } from "@/components/entity/sources-footer";6import { DatasetForm } from "@/components/intelligence/download/dataset-form";7import { ApiNote, HonestEmpty } from "@/components/intelligence/shared";8import { Badge } from "@/components/ui/badge";9import { Page, PageHeader, Section } from "@/components/ui/card";10import { EmptyState, OfflineNotice } from "@/components/ui/states";11import { api } from "@/lib/api";12import { fmtInt } from "@/lib/format";13import { routes } from "@/lib/routes";14import { pageMetadata } from "@/lib/seo";1516export const revalidate = 120;1718export const metadata: Metadata = pageMetadata({19 title: "Downloads",20 description: "Download the index as CSV, JSON or GeoJSON: facilities, projects, operators, events, cloud regions, internet exchanges, countries and markets — licence-gated, with attribution and excluded sources listed per dataset.",21 path: routes.download(),22});2324export default async function DownloadPage() {25 const r = await api.downloadDatasets();26 const datasets = r.ok ? r.data : [];27 return (28 <Page>29 <Breadcrumbs items={[{ name: "Downloads", href: routes.download() }]} className="pt-3" />30 <PageHeader eyebrow="Downloads" title="Datasets" description="Every dataset streams straight from the API with the filters below. Rows whose only sources forbid redistribution are excluded and listed; each row keeps the names of the sources it was built from." className="pt-3" />31 {!r.ok && <OfflineNotice detail={r.error} className="mb-3" />}32 <ApiNote label="Limits" note="Large datasets are capped per request (50 000 rows) — narrow with filters to fetch a complete slice. JSON responses carry meta.excludedSources; CSV/GeoJSON carry the same list in the x-dci-excluded-sources header. Figures are published values only, with the coverage caveats of the API." className="mb-4">33 {" "}34 <Link href={routes.apiDocsPage()} className="underline decoration-hair-2 hover:text-ink">35 API documentation36 </Link>37 </ApiNote>3839 {datasets.length === 0 ? (40 <EmptyState title="No dataset available" description="The API did not return the dataset registry." />41 ) : (42 <>43 <SectionNav items={datasets.map((d) => ({ id: d.key, label: d.label, count: d.rows }))} />44 <div className="mt-6 flex flex-col gap-10">45 {datasets.map((d) => (46 <Section47 key={d.key}48 id={d.key}49 title={d.label}50 description={d.description}51 className="scroll-mt-28"52 aside={53 <div className="flex flex-col items-end gap-1">54 <span className="figure text-[13px] text-ink">55 {fmtInt(d.rows)} <span className="text-ink-3">rows</span>56 </span>57 <span className="inline-flex gap-1">58 {d.formats.map((f) => (59 <Badge key={f} tone="outline" size="xs" mono>60 {f}61 </Badge>62 ))}63 </span>64 </div>65 }66 >67 <div className="grid gap-5 lg:grid-cols-[minmax(0,3fr)_minmax(0,2fr)]">68 <div className="min-w-0">69 <DatasetForm dataset={d} />70 </div>71 <div className="min-w-0 flex flex-col gap-3 text-[12.5px]">72 <div>73 <div className="label-caps mb-1">Licence</div>74 <p className="leading-relaxed text-ink-2">{d.license}</p>75 </div>76 <div>77 <div className="label-caps mb-1">Attribution ({fmtInt(d.attribution.length)})</div>78 {d.attribution.length === 0 ? (79 <p className="text-ink-3">No attribution line recorded for this dataset’s sources — the source registry applies.</p>80 ) : d.attribution.length <= 6 ? (81 <ul className="flex flex-wrap gap-1">82 {d.attribution.map((a) => (83 <li key={a}>84 <Badge tone="outline" size="xs" className="max-w-[260px]">85 <span className="truncate">{a}</span>86 </Badge>87 </li>88 ))}89 </ul>90 ) : (91 <details>92 <summary className="inline-flex min-h-10 cursor-pointer items-center text-ink-2 hover:text-ink sm:min-h-0">Show all {fmtInt(d.attribution.length)} attribution lines</summary>93 <ul className="mt-1.5 flex flex-wrap gap-1">94 {d.attribution.map((a) => (95 <li key={a}>96 <Badge tone="outline" size="xs" className="max-w-[260px]">97 <span className="truncate">{a}</span>98 </Badge>99 </li>100 ))}101 </ul>102 </details>103 )}104 </div>105 <div>106 <div className="label-caps mb-1">Excluded sources</div>107 {d.excludedSources.length === 0 ? (108 <p className="text-ink-3">None — every source behind this dataset allows redistribution with attribution.</p>109 ) : (110 <ul className="divide-y divide-hair">111 {d.excludedSources.map((s) => (112 <li key={s.id} className="py-1.5">113 <Link href={routes.source(s.id)} className="font-medium text-ink hover:text-accent hover:underline">114 {s.name}115 </Link>116 <span className="text-ink-3"> — {s.reason}</span>117 </li>118 ))}119 </ul>120 )}121 </div>122 {d.filters.length === 0 && <HonestEmpty title="Whole-table dataset" detail="no filter is defined for this key" />}123 </div>124 </div>125 </Section>126 ))}127 </div>128 </>129 )}130131 <SourcesFooter sources={r.ok ? r.sources : undefined} note="Re-users must keep the per-row source attribution and respect each source's licence (see /sources). Nothing is imputed; rows without a published figure carry an empty cell, not zero." />132 </Page>133 );134}135