feat(agentRegistry): support agent download directory with multi-source
* ✨feat(docs): 更新数据库支持文档,增加新数据库和下载链接说明 - 在数据库支持列表中添加 ChromaDB、Cloudflare D1、Apache Spark、Dremio 和 OSCAR - 更新文档中关于下载来源的描述,增加对新数据库的支持说明 - 增加下载链接功能,支持 GitHub、CNB 和 AtomGit 下载 * ✨feat(agentRegistry): 增加代理下载目录功能,支持从多个源获取下载资产 - 添加新的接口和类型以支持代理下载 - 实现从 GitHub、CNB 和 AtomGit 获取下载资产的功能 - 更新下载链接格式化逻辑
This commit is contained in:
parent
b0a20e8364
commit
df1d31e74f
|
|
@ -20,6 +20,8 @@ const databaseSupport = [
|
|||
{ name: "Qdrant", icon: "/icons/database/qdrant.svg", tone: "#dc244c" },
|
||||
{ name: "Milvus", icon: "/icons/database/milvus.png", tone: "#00a1ea" },
|
||||
{ name: "Weaviate", icon: "/icons/database/weaviate.png", tone: "#00b894" },
|
||||
{ name: "ChromaDB", shortLabel: "CH", tone: "#ff7a59" },
|
||||
{ name: "Cloudflare D1", shortLabel: "D1", tone: "#f6821f" },
|
||||
{ name: "MariaDB", icon: "/icons/database/mariadb.svg", tone: "#003545" },
|
||||
{ name: "Doris", icon: "/icons/database/doris.svg", tone: "#5b7cfa" },
|
||||
{ name: "StarRocks", icon: "/icons/database/starrocks.svg", tone: "#6750ff" },
|
||||
|
|
@ -47,6 +49,7 @@ const databaseSupport = [
|
|||
{ name: "Trino", icon: "/icons/database/trino.svg", tone: "#dd00a1" },
|
||||
{ name: "PrestoSQL", icon: "/icons/database/presto.svg", tone: "#5890ff" },
|
||||
{ name: "Hive", icon: "/icons/database/hive.svg", tone: "#fdcb00" },
|
||||
{ name: "Apache Spark", shortLabel: "SP", tone: "#e25a1c" },
|
||||
{ name: "DB2", icon: "/icons/database/db2.svg", tone: "#054ada" },
|
||||
{ name: "SAP HANA", icon: "/icons/database/saphana.webp", tone: "#008fd3" },
|
||||
{ name: "Teradata", icon: "/icons/database/teradata.webp", tone: "#f37440" },
|
||||
|
|
@ -57,6 +60,8 @@ const databaseSupport = [
|
|||
{ name: "Neo4j", icon: "/icons/database/neo4j.svg", tone: "#018bff" },
|
||||
{ name: "Cassandra", icon: "/icons/database/cassandra.svg", tone: "#1287b1" },
|
||||
{ name: "Kylin", icon: "/icons/database/apache_kylin.svg", tone: "#fb8c00" },
|
||||
{ name: "Dremio", shortLabel: "DR", tone: "#30bdbe" },
|
||||
{ name: "OSCAR", shortLabel: "OS", tone: "#1b8dff" },
|
||||
{ name: "InfluxDB", icon: "/icons/database/influxdb.svg", tone: "#22adf6" },
|
||||
{ name: "QuestDB", icon: "/icons/database/questdb.svg", tone: "#dc2626" },
|
||||
{ name: "IoTDB", icon: "/icons/database/iotdb.svg", tone: "#3cb371" },
|
||||
|
|
@ -72,6 +77,7 @@ const databaseSupport = [
|
|||
{ name: "Etcd", icon: "/icons/database/etcd.svg", tone: "#419eda" },
|
||||
{ name: "ZooKeeper", icon: "/icons/database/zookeeper.svg", tone: "#3b82f6" },
|
||||
{ name: "Pulsar", icon: "/icons/database/pulsar.svg", tone: "#188fff" },
|
||||
{ name: "Apache Kafka", shortLabel: "KF", tone: "#231f20" },
|
||||
{ name: "Nacos", icon: "/icons/database/nacos.png", tone: "#2f80ed" },
|
||||
{ name: "IRIS", icon: "/icons/database/iris.png", tone: "#0085ca" },
|
||||
{ name: "JDBC", icon: "/icons/database/jdbc.svg", tone: "#6ea8ff" },
|
||||
|
|
@ -150,7 +156,7 @@ export default async function DatabasesPage({ params }: { params: Promise<{ lang
|
|||
) : db.icon ? (
|
||||
<img src={db.icon} alt="" width={38} height={38} className="block w-[38px] h-[38px] object-contain" />
|
||||
) : (
|
||||
<span className="grid place-items-center min-w-[46px] h-8 rounded-lg px-2 text-white text-xs font-[780]">{db.name.slice(0, 2).toUpperCase()}</span>
|
||||
<span className="grid place-items-center min-w-[46px] h-8 rounded-lg px-2 text-xs font-[780] text-white" style={{ backgroundColor: db.tone }}>{db.shortLabel ?? db.name.slice(0, 2).toUpperCase()}</span>
|
||||
)}
|
||||
</div>
|
||||
<strong className={`text-sm font-[650] leading-[1.2] text-center ${isCta ? "text-landing-blue" : "text-[color-mix(in_srgb,var(--color-landing-ink)_92%,var(--color-landing-muted))]"}`}>{db.name}</strong>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { LandingNav } from "@/components/landing/LandingNav";
|
||||
import { fetchAgentDownloadCatalog, formatSize, type AgentDownloadCatalog, type JreDisplayEntry, type NativeAgentDisplayEntry, type OfflineBundleEntry } from "@/lib/agentRegistry";
|
||||
import { downloadLinksFor, fetchAgentDownloadCatalog, formatSize, type AgentDownloadCatalog, type DownloadSource, type JreDisplayEntry, type NativeAgentDisplayEntry, type OfflineBundleEntry } from "@/lib/agentRegistry";
|
||||
import { AlertTriangle, Archive, Cpu, Database, Download, Loader2, Plug, Search, Terminal, X } from "lucide-react";
|
||||
|
||||
const i18n = {
|
||||
|
|
@ -37,6 +37,14 @@ const i18n = {
|
|||
showing: "Showing",
|
||||
of: "of",
|
||||
clearSearch: "Clear search",
|
||||
mirrorHint: "CNB and AtomGit mirrors are available for GitHub release assets on mainland China networks.",
|
||||
downloadSources: "Download source",
|
||||
sources: {
|
||||
github: "GitHub",
|
||||
cnb: "CNB",
|
||||
atomgit: "AtomGit",
|
||||
official: "Official",
|
||||
},
|
||||
downloadHint: "For air-gapped environments: download the bundle for your platform on an internet-connected machine, then transfer it to the offline machine and import it in DBX from Settings > Driver Manager. Use the driver and JRE tabs only when you need individual artifacts.",
|
||||
},
|
||||
cn: {
|
||||
|
|
@ -69,6 +77,14 @@ const i18n = {
|
|||
showing: "显示",
|
||||
of: "/",
|
||||
clearSearch: "清空搜索",
|
||||
mirrorHint: "中国大陆网络可选择 CNB 或 AtomGit 镜像下载,GitHub Release 资源保持同步。",
|
||||
downloadSources: "下载来源",
|
||||
sources: {
|
||||
github: "GitHub",
|
||||
cnb: "CNB",
|
||||
atomgit: "AtomGit",
|
||||
official: "官方下载",
|
||||
},
|
||||
downloadHint: "内网环境使用说明:在有网的电脑上下载对应平台的整包,然后传输到内网机器,在 DBX 的“设置 > 驱动管理”中导入。只有需要单个产物时再使用驱动和 JRE 标签页。",
|
||||
},
|
||||
};
|
||||
|
|
@ -94,6 +110,27 @@ type NativeAgentGroup = {
|
|||
options: NativeAgentDisplayEntry[];
|
||||
};
|
||||
|
||||
type DriverTranslations = (typeof i18n)["en"];
|
||||
|
||||
function DownloadLinks({ url, t }: { url: string; t: DriverTranslations }) {
|
||||
return (
|
||||
<div className="flex shrink-0 flex-nowrap justify-end gap-1.5 max-[760px]:flex-wrap max-[760px]:justify-start">
|
||||
{downloadLinksFor(url).map((link) => (
|
||||
<a
|
||||
key={link.source}
|
||||
href={link.url}
|
||||
download
|
||||
className={`landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border px-2 text-xs font-medium transition-colors hover:border-landing-blue ${link.source === "cnb" || link.source === "atomgit" ? "border-landing-blue/45 bg-landing-blue/10 text-landing-sky" : "border-landing-line"}`}
|
||||
aria-label={`${t.download}: ${t.sources[link.source as DownloadSource]}`}
|
||||
>
|
||||
<Download size={13} />
|
||||
{t.sources[link.source as DownloadSource]}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function matchesSearch(values: Array<string | number | undefined>, query: string): boolean {
|
||||
if (!query) return true;
|
||||
return values.filter(Boolean).join(" ").toLowerCase().includes(query);
|
||||
|
|
@ -227,6 +264,10 @@ export function DriversClient() {
|
|||
|
||||
{catalog && !loading && (
|
||||
<>
|
||||
<div className="mb-3 flex items-center gap-2 rounded-[7px] border border-landing-blue/30 bg-landing-blue/10 px-3 py-2 text-xs leading-[1.55] text-landing-sky">
|
||||
<Download size={14} className="shrink-0" />
|
||||
<span>{t.mirrorHint}</span>
|
||||
</div>
|
||||
<div className="landing-glass-card mb-12 overflow-hidden rounded-[10px]">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-landing-line bg-landing-panel/70 p-3">
|
||||
<div className="inline-flex shrink-0 rounded-[8px] border border-landing-line bg-black/10 p-1 max-[760px]:grid max-[760px]:w-full max-[760px]:grid-cols-2">
|
||||
|
|
@ -284,13 +325,13 @@ export function DriversClient() {
|
|||
{activeTab === "jdbcPlugin" && (
|
||||
<>
|
||||
<p className="border-b border-landing-line px-5 py-3 text-sm text-landing-muted whitespace-nowrap max-[760px]:whitespace-normal max-[760px]:px-4">{t.jdbcPluginDesc}</p>
|
||||
<table className="w-full table-auto border-collapse text-sm max-[760px]:block">
|
||||
<table className="w-full table-fixed border-collapse text-sm max-[760px]:block">
|
||||
<thead className="bg-landing-panel text-xs font-medium text-landing-muted max-[760px]:hidden">
|
||||
<tr className="border-b border-landing-line">
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.jdbcPluginFile}</th>
|
||||
<th className="w-[22%] px-5 py-2.5 text-left font-medium">{t.jdbcPluginFile}</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.filename}</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.installMethod}</th>
|
||||
<th className="w-24 px-5 py-2.5" />
|
||||
<th className="w-[340px] px-5 py-2.5 text-right font-medium">{t.downloadSources}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="max-[760px]:block">
|
||||
|
|
@ -303,13 +344,10 @@ export function DriversClient() {
|
|||
</div>
|
||||
<p className="mt-1 hidden text-xs leading-[1.55] text-landing-muted max-[760px]:block">{t.jdbcPluginInstallHint}</p>
|
||||
</td>
|
||||
<td className="px-5 py-3 font-mono text-[11px] text-landing-muted max-[760px]:hidden">{plugin.filename}</td>
|
||||
<td className="px-5 py-3 font-mono text-[11px] text-landing-muted max-[760px]:hidden"><span className="block truncate" title={plugin.filename}>{plugin.filename}</span></td>
|
||||
<td className="px-5 py-3 text-xs text-landing-muted max-[760px]:hidden">{t.jdbcPluginInstallHint}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:px-0">
|
||||
<a href={plugin.url} download className="landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border border-landing-line px-2.5 text-xs font-medium transition-colors hover:border-landing-blue">
|
||||
<Download size={13} />
|
||||
{t.download}
|
||||
</a>
|
||||
<td className="px-5 py-3 text-right max-[760px]:col-span-2 max-[760px]:px-0 max-[760px]:pt-0">
|
||||
<DownloadLinks url={plugin.url} t={t} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -322,13 +360,13 @@ export function DriversClient() {
|
|||
{activeTab === "bundles" && (
|
||||
<>
|
||||
<p className="border-b border-landing-line px-5 py-3 text-sm text-landing-muted whitespace-nowrap max-[760px]:whitespace-normal max-[760px]:px-4">{t.bundlesDesc}</p>
|
||||
<table className="w-full table-auto border-collapse text-sm max-[760px]:block">
|
||||
<table className="w-full table-fixed border-collapse text-sm max-[760px]:block">
|
||||
<thead className="bg-landing-panel text-xs font-medium text-landing-muted max-[760px]:hidden">
|
||||
<tr className="border-b border-landing-line">
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.platform}</th>
|
||||
<th className="w-[22%] px-5 py-2.5 text-left font-medium">{t.platform}</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.filename}</th>
|
||||
<th className="px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-24 px-5 py-2.5" />
|
||||
<th className="w-[116px] px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-[340px] px-5 py-2.5 text-right font-medium">{t.downloadSources}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="max-[760px]:block">
|
||||
|
|
@ -340,13 +378,10 @@ export function DriversClient() {
|
|||
<span className="hidden shrink-0 rounded-[5px] border border-landing-blue/35 bg-landing-blue/10 px-1.5 py-0.5 font-mono text-[11px] text-landing-sky max-[760px]:inline">ZIP</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-5 py-3 font-mono text-[11px] text-landing-muted max-[760px]:hidden">{bundle.filename}</td>
|
||||
<td className="px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(bundle.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:px-0">
|
||||
<a href={bundle.url} download className="landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border border-landing-line px-2.5 text-xs font-medium transition-colors hover:border-landing-blue">
|
||||
<Download size={13} />
|
||||
{t.download}
|
||||
</a>
|
||||
<td className="px-5 py-3 font-mono text-[11px] text-landing-muted max-[760px]:hidden"><span className="block truncate" title={bundle.filename}>{bundle.filename}</span></td>
|
||||
<td className="whitespace-nowrap px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(bundle.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:col-span-2 max-[760px]:px-0 max-[760px]:pt-0">
|
||||
<DownloadLinks url={bundle.url} t={t} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -359,15 +394,15 @@ export function DriversClient() {
|
|||
{activeTab === "drivers" && (
|
||||
<>
|
||||
<p className="border-b border-landing-line px-5 py-3 text-sm text-landing-muted whitespace-nowrap max-[760px]:whitespace-normal max-[760px]:px-4">{t.driversDesc}</p>
|
||||
<table className="w-full table-auto border-collapse text-sm max-[760px]:block">
|
||||
<table className="w-full table-fixed border-collapse text-sm max-[760px]:block">
|
||||
<thead className="bg-landing-panel text-xs font-medium text-landing-muted max-[760px]:hidden">
|
||||
<tr className="border-b border-landing-line">
|
||||
<th className="px-5 py-2.5 text-left font-medium">Driver</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">Key</th>
|
||||
<th className="w-[18%] px-5 py-2.5 text-left font-medium">Driver</th>
|
||||
<th className="w-[14%] px-5 py-2.5 text-left font-medium">Key</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.version}</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.requiresJre}</th>
|
||||
<th className="px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-24 px-5 py-2.5" />
|
||||
<th className="w-[116px] px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-[340px] px-5 py-2.5 text-right font-medium">{t.downloadSources}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="max-[760px]:block">
|
||||
|
|
@ -384,12 +419,9 @@ export function DriversClient() {
|
|||
</td>
|
||||
<td className="px-5 py-3 text-xs text-landing-muted max-[760px]:hidden">{d.version}</td>
|
||||
<td className="px-5 py-3 text-xs text-landing-muted max-[760px]:hidden">{d.jre}</td>
|
||||
<td className="px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(d.jar.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:px-0">
|
||||
<a href={d.jar.url} download className="landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border border-landing-line px-2.5 text-xs font-medium transition-colors hover:border-landing-blue">
|
||||
<Download size={13} />
|
||||
{t.download}
|
||||
</a>
|
||||
<td className="whitespace-nowrap px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(d.jar.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:col-span-2 max-[760px]:px-0 max-[760px]:pt-0">
|
||||
<DownloadLinks url={d.jar.url} t={t} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -402,14 +434,14 @@ export function DriversClient() {
|
|||
{activeTab === "native" && (
|
||||
<>
|
||||
<p className="border-b border-landing-line px-5 py-3 text-sm text-landing-muted whitespace-nowrap max-[760px]:whitespace-normal max-[760px]:px-4">{t.nativeAgentsDesc}</p>
|
||||
<table className="w-full table-auto border-collapse text-sm max-[760px]:block">
|
||||
<table className="w-full table-fixed border-collapse text-sm max-[760px]:block">
|
||||
<thead className="bg-landing-panel text-xs font-medium text-landing-muted max-[760px]:hidden">
|
||||
<tr className="border-b border-landing-line">
|
||||
<th className="px-5 py-2.5 text-left font-medium">Agent</th>
|
||||
<th className="w-[22%] px-5 py-2.5 text-left font-medium">Agent</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.platform}</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.version}</th>
|
||||
<th className="px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-24 px-5 py-2.5" />
|
||||
<th className="w-[116px] px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-[340px] px-5 py-2.5 text-right font-medium">{t.downloadSources}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="max-[760px]:block">
|
||||
|
|
@ -439,12 +471,9 @@ export function DriversClient() {
|
|||
</select>
|
||||
</td>
|
||||
<td className="px-5 py-3 text-xs text-landing-muted max-[760px]:hidden">{group.version}</td>
|
||||
<td className="px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(selectedAgent.info.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:px-0">
|
||||
<a href={selectedAgent.info.url} download className="landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border border-landing-line px-2.5 text-xs font-medium transition-colors hover:border-landing-blue">
|
||||
<Download size={13} />
|
||||
{t.download}
|
||||
</a>
|
||||
<td className="whitespace-nowrap px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(selectedAgent.info.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:col-span-2 max-[760px]:px-0 max-[760px]:pt-0">
|
||||
<DownloadLinks url={selectedAgent.info.url} t={t} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
@ -458,14 +487,14 @@ export function DriversClient() {
|
|||
{activeTab === "jre" && (
|
||||
<>
|
||||
<p className="border-b border-landing-line px-5 py-3 text-sm text-landing-muted whitespace-nowrap max-[760px]:whitespace-normal max-[760px]:px-4">{t.jreDesc}</p>
|
||||
<table className="w-full table-auto border-collapse text-sm max-[760px]:block">
|
||||
<table className="w-full table-fixed border-collapse text-sm max-[760px]:block">
|
||||
<thead className="bg-landing-panel text-xs font-medium text-landing-muted max-[760px]:hidden">
|
||||
<tr className="border-b border-landing-line">
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.platform}</th>
|
||||
<th className="w-[24%] px-5 py-2.5 text-left font-medium">{t.platform}</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">JRE</th>
|
||||
<th className="px-5 py-2.5 text-left font-medium">{t.version}</th>
|
||||
<th className="px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-24 px-5 py-2.5" />
|
||||
<th className="w-[116px] px-5 py-2.5 text-right font-medium">{t.size}</th>
|
||||
<th className="w-[340px] px-5 py-2.5 text-right font-medium">{t.downloadSources}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="max-[760px]:block">
|
||||
|
|
@ -483,12 +512,9 @@ export function DriversClient() {
|
|||
<span className="inline-flex rounded-[5px] border border-landing-green/35 bg-landing-green/10 px-1.5 py-0.5 font-mono text-[11px] text-landing-green">JRE {j.jreKey}</span>
|
||||
</td>
|
||||
<td className="px-5 py-3 text-xs text-landing-muted max-[760px]:hidden">{j.jreVersion}</td>
|
||||
<td className="px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(j.info.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:px-0">
|
||||
<a href={j.info.url} download className="landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border border-landing-line px-2.5 text-xs font-medium transition-colors hover:border-landing-blue">
|
||||
<Download size={13} />
|
||||
{t.download}
|
||||
</a>
|
||||
<td className="whitespace-nowrap px-5 py-3 text-right text-xs text-landing-muted max-[760px]:hidden">{formatSize(j.info.size)}</td>
|
||||
<td className="px-5 py-3 text-right max-[760px]:col-span-2 max-[760px]:px-0 max-[760px]:pt-0">
|
||||
<DownloadLinks url={j.info.url} t={t} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ const databaseSupport = [
|
|||
{ name: "Qdrant", icon: "/icons/database/qdrant.svg", tone: "#dc244c" },
|
||||
{ name: "Milvus", icon: "/icons/database/milvus.png", tone: "#00a1ea" },
|
||||
{ name: "Weaviate", icon: "/icons/database/weaviate.png", tone: "#00b894" },
|
||||
{ name: "ChromaDB", shortLabel: "CH", tone: "#ff7a59" },
|
||||
{ name: "Cloudflare D1", shortLabel: "D1", tone: "#f6821f" },
|
||||
{ name: "MariaDB", icon: "/icons/database/mariadb.svg", tone: "#003545" },
|
||||
{ name: "Doris", icon: "/icons/database/doris.svg", tone: "#5b7cfa" },
|
||||
{ name: "StarRocks", icon: "/icons/database/starrocks.svg", tone: "#6750ff" },
|
||||
|
|
@ -100,6 +102,7 @@ const databaseSupport = [
|
|||
{ name: "Trino", icon: "/icons/database/trino.svg", tone: "#dd00a1" },
|
||||
{ name: "PrestoSQL", icon: "/icons/database/presto.svg", tone: "#5890ff" },
|
||||
{ name: "Hive", icon: "/icons/database/hive.svg", tone: "#fdcb00" },
|
||||
{ name: "Apache Spark", shortLabel: "SP", tone: "#e25a1c" },
|
||||
{ name: "DB2", icon: "/icons/database/db2.svg", tone: "#054ada" },
|
||||
{ name: "SAP HANA", icon: "/icons/database/saphana.webp", tone: "#008fd3" },
|
||||
{ name: "Teradata", icon: "/icons/database/teradata.webp", tone: "#f37440" },
|
||||
|
|
@ -110,6 +113,8 @@ const databaseSupport = [
|
|||
{ name: "Neo4j", icon: "/icons/database/neo4j.svg", tone: "#018bff" },
|
||||
{ name: "Cassandra", icon: "/icons/database/cassandra.svg", tone: "#1287b1" },
|
||||
{ name: "Kylin", icon: "/icons/database/apache_kylin.svg", tone: "#fb8c00" },
|
||||
{ name: "Dremio", shortLabel: "DR", tone: "#30bdbe" },
|
||||
{ name: "OSCAR", shortLabel: "OS", tone: "#1b8dff" },
|
||||
{ name: "InfluxDB", icon: "/icons/database/influxdb.svg", tone: "#22adf6" },
|
||||
{ name: "QuestDB", icon: "/icons/database/questdb.svg", tone: "#dc2626" },
|
||||
{ name: "IoTDB", icon: "/icons/database/iotdb.svg", tone: "#3cb371" },
|
||||
|
|
@ -125,6 +130,7 @@ const databaseSupport = [
|
|||
{ name: "Etcd", icon: "/icons/database/etcd.svg", tone: "#419eda" },
|
||||
{ name: "ZooKeeper", icon: "/icons/database/zookeeper.svg", tone: "#3b82f6" },
|
||||
{ name: "Pulsar", icon: "/icons/database/pulsar.svg", tone: "#188fff" },
|
||||
{ name: "Apache Kafka", shortLabel: "KF", tone: "#231f20" },
|
||||
{ name: "Nacos", icon: "/icons/database/nacos.png", tone: "#2f80ed" },
|
||||
{ name: "IRIS", icon: "/icons/database/iris.png", tone: "#0085ca" },
|
||||
{ name: "JDBC", icon: "/icons/database/jdbc.svg", tone: "#6ea8ff" },
|
||||
|
|
@ -549,7 +555,7 @@ export default async function LandingPage({ params }: { params: Promise<{ lang:
|
|||
) : db.icon ? (
|
||||
<img src={db.icon} alt="" width={38} height={38} className="block w-[38px] h-[38px] object-contain" />
|
||||
) : (
|
||||
<span className="grid place-items-center min-w-[46px] h-8 rounded-lg px-2 text-white text-xs font-[780]">{db.name.slice(0, 2).toUpperCase()}</span>
|
||||
<span className="grid place-items-center min-w-[46px] h-8 rounded-lg px-2 text-xs font-[780] text-white" style={{ backgroundColor: db.tone }}>{db.shortLabel ?? db.name.slice(0, 2).toUpperCase()}</span>
|
||||
)}
|
||||
</div>
|
||||
<strong className={`text-sm font-[650] leading-[1.2] text-center ${isCta ? "text-landing-blue" : "text-[color-mix(in_srgb,var(--color-landing-ink)_92%,var(--color-landing-muted))]"}`}>{db.name}</strong>
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@ description: 了解 DBX 可以连接哪些数据库,以及每类高级功能
|
|||
| 分组 | 类型 | 说明 |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| 核心 SQL 数据库 | MySQL、PostgreSQL、SQLite、DuckDB、ClickHouse、SQL Server、Oracle、Redshift、DM、GaussDB、KWDB、Teradata、Vertica、Exasol、Firebird、SAP HANA、崖山 YashanDB、GBase | 直接出现在 DBX 连接选择器中 |
|
||||
| 文档、键值与配置服务 | Redis、MongoDB、Elasticsearch、etcd、Nacos | 根据数据模型使用专门浏览器、管理控制台或查询流程,不完全等同于关系型表格 |
|
||||
| 文档、键值、向量与配置服务 | Redis、MongoDB、Elasticsearch、Qdrant、Milvus、Weaviate、ChromaDB、etcd、Apache ZooKeeper、Nacos | 根据数据模型使用专门浏览器、管理控制台或查询流程,不完全等同于关系型表格 |
|
||||
| MySQL 兼容类型 | MariaDB、TiDB、OceanBase、Doris、SelectDB、StarRocks、Manticore Search、GoldenDB、TDSQL、PolarDB、GreatSQL、自定义 MySQL | 复用 MySQL 风格连接能力 |
|
||||
| PostgreSQL 兼容类型 | openGauss、KingBase、HighGo、Vastbase、CockroachDB、自定义 PostgreSQL | 复用 PostgreSQL 风格连接能力 |
|
||||
| 文件型数据库 | SQLite、DuckDB、Microsoft Access、RQLite、Turso | 选择本地数据库文件或 HTTP 端点,不填写主机和端口 |
|
||||
| 时序与边缘数据库 | Cloudflare D1、InfluxDB、IoTDB、TDengine、Databend、QuestDB | 托管 SQLite、时序、IoT 或边缘场景 |
|
||||
| Agent/JDBC 扩展类型 | H2、Snowflake、Trino、PrestoSQL、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、XuguDB、Databricks、IRIS、JDBC | 功能覆盖取决于对应驱动路径 |
|
||||
| 分析与消息服务 | Apache Spark、Dremio、Apache Pulsar、Apache Kafka | 用于分布式分析、消息和流管理场景 |
|
||||
| Agent/JDBC 扩展类型 | H2、Snowflake、Trino、PrestoSQL、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、OSCAR、XuguDB、Databricks、IRIS、JDBC | 功能覆盖取决于对应驱动路径 |
|
||||
|
||||
## 默认端口
|
||||
|
||||
|
|
@ -31,6 +32,10 @@ description: 了解 DBX 可以连接哪些数据库,以及每类高级功能
|
|||
| SQL Server | 1433 |
|
||||
| Oracle | 1521 |
|
||||
| Elasticsearch | 9200 |
|
||||
| Qdrant | 6333 |
|
||||
| Milvus | 19530 |
|
||||
| Weaviate | 8080 |
|
||||
| ChromaDB | 8000 |
|
||||
| TiDB | 4000 |
|
||||
| OceanBase | 2881 |
|
||||
| Doris / SelectDB / StarRocks / Databend | 9030 |
|
||||
|
|
@ -53,6 +58,7 @@ description: 了解 DBX 可以连接哪些数据库,以及每类高级功能
|
|||
| InfluxDB | 8086 |
|
||||
| IoTDB | 6667 |
|
||||
| etcd | 2379 |
|
||||
| Apache ZooKeeper | 2181 |
|
||||
| Nacos | 8848 |
|
||||
| Nacos 控制台/admin API | 8085 |
|
||||
| IRIS | 1972 |
|
||||
|
|
@ -62,16 +68,21 @@ description: 了解 DBX 可以连接哪些数据库,以及每类高级功能
|
|||
| Trino | 8080 |
|
||||
| PrestoSQL | 8080 |
|
||||
| Hive | 10000 |
|
||||
| Apache Spark | 10015 |
|
||||
| DB2 | 50000 |
|
||||
| Informix | 9088 |
|
||||
| Dremio | 31010 |
|
||||
| Neo4j | 7687 |
|
||||
| Cassandra | 9042 |
|
||||
| BigQuery | 443 |
|
||||
| Kylin | 7070 |
|
||||
| SunDB | 22000 |
|
||||
| OSCAR | 2003 |
|
||||
| TDengine | 6041 |
|
||||
| XuguDB | 5138 |
|
||||
| QuestDB | 8812 |
|
||||
| Apache Pulsar Admin API | 8080 |
|
||||
| Apache Kafka | 9092 |
|
||||
|
||||
<Callout type="info">SQLite、DuckDB、Access 和 JDBC 在不需要网络端口时,会在连接模型中保存为端口 `0`。</Callout>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@ The connection dialog exposes ready-to-use profiles with default ports and drive
|
|||
| Group | Profiles | Notes |
|
||||
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| Core SQL engines | MySQL, PostgreSQL, SQLite, DuckDB, ClickHouse, SQL Server, Oracle, Redshift, DM, GaussDB, KWDB, Teradata, Vertica, Exasol, Firebird, SAP HANA, YashanDB, GBase | Available directly in the DBX connection picker |
|
||||
| Document, key-value, and config services | Redis, MongoDB, Elasticsearch, etcd, Nacos | Use dedicated browsers, admin consoles, or query flows instead of the generic relational grid where appropriate |
|
||||
| Document, key-value, vector, and config services | Redis, MongoDB, Elasticsearch, Qdrant, Milvus, Weaviate, ChromaDB, etcd, Apache ZooKeeper, Nacos | Use dedicated browsers, admin consoles, or query flows instead of the generic relational grid where appropriate |
|
||||
| MySQL-compatible profiles | MariaDB, TiDB, OceanBase, Doris, SelectDB, StarRocks, Manticore Search, GoldenDB, TDSQL, PolarDB, GreatSQL, custom MySQL | Reuse MySQL-style connection handling where the engine speaks a compatible protocol |
|
||||
| PostgreSQL-compatible profiles | openGauss, KingBase, HighGo, Vastbase, CockroachDB, custom PostgreSQL | Reuse PostgreSQL-style connection handling where the engine speaks a compatible protocol |
|
||||
| File-based engines | SQLite, DuckDB, Microsoft Access, RQLite, Turso | Choose a local database file or HTTP endpoint instead of host and port |
|
||||
| Time-series and edge | Cloudflare D1, InfluxDB, IoTDB, TDengine, Databend, QuestDB | Managed SQLite, time-series, IoT, or edge workloads |
|
||||
| Agent/JDBC-oriented engines | H2, Snowflake, Trino, PrestoSQL, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, XuguDB, Databricks, IRIS, JDBC | Feature coverage depends on the driver path used by that engine |
|
||||
| Analytics and messaging services | Apache Spark, Dremio, Apache Pulsar, Apache Kafka | Distributed analytics, messaging, and stream-management workloads |
|
||||
| Agent/JDBC-oriented engines | H2, Snowflake, Trino, PrestoSQL, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, OSCAR, XuguDB, Databricks, IRIS, JDBC | Feature coverage depends on the driver path used by that engine |
|
||||
|
||||
## Default Ports
|
||||
|
||||
|
|
@ -31,6 +32,10 @@ The connection dialog exposes ready-to-use profiles with default ports and drive
|
|||
| SQL Server | 1433 |
|
||||
| Oracle | 1521 |
|
||||
| Elasticsearch | 9200 |
|
||||
| Qdrant | 6333 |
|
||||
| Milvus | 19530 |
|
||||
| Weaviate | 8080 |
|
||||
| ChromaDB | 8000 |
|
||||
| TiDB | 4000 |
|
||||
| OceanBase | 2881 |
|
||||
| Doris / SelectDB / StarRocks / Databend | 9030 |
|
||||
|
|
@ -53,6 +58,7 @@ The connection dialog exposes ready-to-use profiles with default ports and drive
|
|||
| InfluxDB | 8086 |
|
||||
| IoTDB | 6667 |
|
||||
| etcd | 2379 |
|
||||
| Apache ZooKeeper | 2181 |
|
||||
| Nacos | 8848 |
|
||||
| Nacos console/admin API | 8085 |
|
||||
| IRIS | 1972 |
|
||||
|
|
@ -62,16 +68,21 @@ The connection dialog exposes ready-to-use profiles with default ports and drive
|
|||
| Trino | 8080 |
|
||||
| PrestoSQL | 8080 |
|
||||
| Hive | 10000 |
|
||||
| Apache Spark | 10015 |
|
||||
| DB2 | 50000 |
|
||||
| Informix | 9088 |
|
||||
| Dremio | 31010 |
|
||||
| Neo4j | 7687 |
|
||||
| Cassandra | 9042 |
|
||||
| BigQuery | 443 |
|
||||
| Kylin | 7070 |
|
||||
| SunDB | 22000 |
|
||||
| OSCAR | 2003 |
|
||||
| TDengine | 6041 |
|
||||
| XuguDB | 5138 |
|
||||
| QuestDB | 8812 |
|
||||
| Apache Pulsar Admin API | 8080 |
|
||||
| Apache Kafka | 9092 |
|
||||
|
||||
<Callout type="info">SQLite, DuckDB, Access, and JDBC profiles use port `0` in the saved connection model when a network port is not part of the connection.</Callout>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
import { buildAgentDownloadCatalog } from "./agentRegistry";
|
||||
import { afterEach, test, vi } from "vitest";
|
||||
import { buildAgentDownloadCatalog, downloadLinksFor, fetchAgentDownloadCatalog, formatSize } from "./agentRegistry";
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test("offline download catalog includes the JDBC plugin ZIP", () => {
|
||||
const catalog = buildAgentDownloadCatalog([]);
|
||||
|
|
@ -11,3 +15,53 @@ test("offline download catalog includes the JDBC plugin ZIP", () => {
|
|||
url: "https://dl.dbxio.com/releases/latest/dbx-jdbc-plugin-latest.zip",
|
||||
});
|
||||
});
|
||||
|
||||
test("release assets expose GitHub, CNB, and AtomGit download links", () => {
|
||||
assert.deepEqual(downloadLinksFor("https://github.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip"), [
|
||||
{ source: "github", url: "https://github.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" },
|
||||
{ source: "cnb", url: "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" },
|
||||
{ source: "atomgit", url: "https://atomgit.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("non-release assets retain their official download link", () => {
|
||||
assert.deepEqual(downloadLinksFor("https://dl.dbxio.com/releases/latest/dbx-jdbc-plugin-latest.zip"), [
|
||||
{ source: "official", url: "https://dl.dbxio.com/releases/latest/dbx-jdbc-plugin-latest.zip" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("catalog falls back from GitHub to CNB and then AtomGit", async () => {
|
||||
const requestedUrls: string[] = [];
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async (input: string | URL | Request) => {
|
||||
const url = String(input);
|
||||
requestedUrls.push(url);
|
||||
if (url.includes("api.github.com")) return new Response("rate limited", { status: 403 });
|
||||
if (url.includes("cnb.cool")) throw new TypeError("CORS blocked");
|
||||
return Response.json({
|
||||
tag_name: "agents-latest",
|
||||
assets: [
|
||||
{ name: "dbx-agent-access.jar", type: "attach" },
|
||||
{ name: "dbx-jre-21-macos-aarch64.tar.gz", type: "attach" },
|
||||
{ name: "dbx-agents-offline-macos-aarch64.zip", type: "attach" },
|
||||
],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const catalog = await fetchAgentDownloadCatalog();
|
||||
|
||||
assert.deepEqual(requestedUrls, [
|
||||
"https://api.github.com/repos/t8y2/dbx/releases/tags/agents-latest",
|
||||
"https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/agent-registry.json",
|
||||
"https://api.atomgit.com/api/v5/repos/t8y2/dbx/releases/agents-latest",
|
||||
]);
|
||||
assert.equal(catalog?.drivers[0]?.key, "access");
|
||||
assert.equal(catalog?.jres[0]?.platformKey, "macos-aarch64");
|
||||
assert.equal(catalog?.bundles[0]?.platformKey, "macos-aarch64");
|
||||
});
|
||||
|
||||
test("unknown fallback asset sizes render as unavailable", () => {
|
||||
assert.equal(formatSize(0), "—");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ export interface ArtifactInfo {
|
|||
size: number;
|
||||
}
|
||||
|
||||
export type DownloadSource = "github" | "cnb" | "atomgit" | "official";
|
||||
|
||||
export interface DownloadLink {
|
||||
source: DownloadSource;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface GitHubReleaseAsset {
|
||||
name: string;
|
||||
size: number;
|
||||
|
|
@ -15,6 +22,26 @@ interface GitHubRelease {
|
|||
assets?: GitHubReleaseAsset[];
|
||||
}
|
||||
|
||||
interface AgentRegistryArtifact {
|
||||
url: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface AgentRegistryDriver {
|
||||
jar?: AgentRegistryArtifact;
|
||||
native?: Record<string, AgentRegistryArtifact>;
|
||||
}
|
||||
|
||||
interface AgentRegistry {
|
||||
jres?: Record<string, { platforms?: Record<string, AgentRegistryArtifact> }>;
|
||||
drivers?: Record<string, AgentRegistryDriver>;
|
||||
}
|
||||
|
||||
interface AtomGitRelease {
|
||||
tag_name?: string;
|
||||
assets?: Array<{ name?: string; type?: string }>;
|
||||
}
|
||||
|
||||
export interface OfflineBundleEntry {
|
||||
platformKey: string;
|
||||
platformLabel: string;
|
||||
|
|
@ -65,7 +92,12 @@ export interface AgentDownloadCatalog {
|
|||
}
|
||||
|
||||
const AGENTS_LATEST_RELEASE_API_URL = "https://api.github.com/repos/t8y2/dbx/releases/tags/agents-latest";
|
||||
const CNB_AGENT_REGISTRY_URL = "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/agent-registry.json";
|
||||
const ATOMGIT_LATEST_RELEASE_API_URL = "https://api.atomgit.com/api/v5/repos/t8y2/dbx/releases/agents-latest";
|
||||
const JDBC_PLUGIN_DOWNLOAD_URL = "https://dl.dbxio.com/releases/latest/dbx-jdbc-plugin-latest.zip";
|
||||
const GITHUB_RELEASE_DOWNLOAD_PREFIX = "https://github.com/t8y2/dbx/releases/download/";
|
||||
const CNB_RELEASE_DOWNLOAD_PREFIX = "https://cnb.cool/dbxio.com/dbx/-/releases/download/";
|
||||
const ATOMGIT_RELEASE_DOWNLOAD_PREFIX = "https://atomgit.com/t8y2/dbx/releases/download/";
|
||||
const MIN_APP_VERSION = "0.6.0";
|
||||
const driverVersionMap = driverVersions as Record<string, string>;
|
||||
const nativeDriverKeys = new Set(["oracle", "xugu"]);
|
||||
|
|
@ -136,23 +168,100 @@ function assetInfo(asset: GitHubReleaseAsset): ArtifactInfo {
|
|||
};
|
||||
}
|
||||
|
||||
function releaseAssetName(url: string, fallback: string): string {
|
||||
try {
|
||||
return decodeURIComponent(new URL(url).pathname.split("/").pop() || fallback);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function githubReleaseAsset(name: string, size: number, tag = "agents-latest"): GitHubReleaseAsset {
|
||||
return {
|
||||
name,
|
||||
size,
|
||||
browser_download_url: `${GITHUB_RELEASE_DOWNLOAD_PREFIX}${tag}/${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
function registryReleaseAssets(registry: AgentRegistry): GitHubReleaseAsset[] {
|
||||
const assets = new Map<string, GitHubReleaseAsset>();
|
||||
const addArtifact = (artifact: AgentRegistryArtifact | undefined, fallbackName: string) => {
|
||||
if (!artifact?.url) return;
|
||||
const name = releaseAssetName(artifact.url, fallbackName);
|
||||
assets.set(name, { name, size: artifact.size || 0, browser_download_url: artifact.url });
|
||||
};
|
||||
|
||||
for (const [jreKey, jre] of Object.entries(registry.jres ?? {})) {
|
||||
for (const [platformKey, artifact] of Object.entries(jre.platforms ?? {})) {
|
||||
addArtifact(artifact, `dbx-jre-${jreKey}-${platformKey}.tar.gz`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [driverKey, driver] of Object.entries(registry.drivers ?? {})) {
|
||||
addArtifact(driver.jar, `dbx-agent-${driverKey}.jar`);
|
||||
for (const [platformKey, artifact] of Object.entries(driver.native ?? {})) {
|
||||
addArtifact(artifact, `dbx-agent-${driverKey}-${platformKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const platformKey of Object.keys(registry.jres?.["21"]?.platforms ?? {})) {
|
||||
const name = `dbx-agents-offline-${platformKey}.zip`;
|
||||
assets.set(name, githubReleaseAsset(name, 0));
|
||||
}
|
||||
|
||||
return Array.from(assets.values());
|
||||
}
|
||||
|
||||
function atomGitReleaseAssets(release: AtomGitRelease): GitHubReleaseAsset[] {
|
||||
const tag = release.tag_name || "agents-latest";
|
||||
return (release.assets ?? [])
|
||||
.filter((asset): asset is { name: string; type?: string } => Boolean(asset.name) && asset.type !== "source")
|
||||
.map((asset) => githubReleaseAsset(asset.name, 0, tag));
|
||||
}
|
||||
|
||||
function hasDownloadAssets(catalog: AgentDownloadCatalog): boolean {
|
||||
return catalog.bundles.length > 0 || catalog.drivers.length > 0 || catalog.jres.length > 0 || catalog.nativeAgents.length > 0;
|
||||
}
|
||||
|
||||
async function fetchJson<T>(url: string, headers?: HeadersInit): Promise<T> {
|
||||
const response = await fetch(url, { cache: "no-store", headers });
|
||||
if (!response.ok) throw new Error(`Download catalog request failed with ${response.status}`);
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export function downloadLinksFor(url: string): DownloadLink[] {
|
||||
const releasePath = url.startsWith(GITHUB_RELEASE_DOWNLOAD_PREFIX) ? url.slice(GITHUB_RELEASE_DOWNLOAD_PREFIX.length) : null;
|
||||
if (!releasePath) return [{ source: "official", url }];
|
||||
|
||||
return [
|
||||
{ source: "github", url },
|
||||
{ source: "cnb", url: `${CNB_RELEASE_DOWNLOAD_PREFIX}${releasePath}` },
|
||||
{ source: "atomgit", url: `${ATOMGIT_RELEASE_DOWNLOAD_PREFIX}${releasePath}` },
|
||||
];
|
||||
}
|
||||
|
||||
function assetMap(assets: GitHubReleaseAsset[]): Map<string, GitHubReleaseAsset> {
|
||||
return new Map(assets.map((asset) => [asset.name, asset]));
|
||||
}
|
||||
|
||||
export async function fetchAgentDownloadCatalog(): Promise<AgentDownloadCatalog | null> {
|
||||
try {
|
||||
const res = await fetch(AGENTS_LATEST_RELEASE_API_URL, {
|
||||
cache: "no-store",
|
||||
headers: { Accept: "application/vnd.github+json" },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const loaders: Array<() => Promise<GitHubReleaseAsset[]>> = [
|
||||
async () => (await fetchJson<GitHubRelease>(AGENTS_LATEST_RELEASE_API_URL, { Accept: "application/vnd.github+json" })).assets ?? [],
|
||||
async () => registryReleaseAssets(await fetchJson<AgentRegistry>(CNB_AGENT_REGISTRY_URL)),
|
||||
async () => atomGitReleaseAssets(await fetchJson<AtomGitRelease>(ATOMGIT_LATEST_RELEASE_API_URL, { Accept: "application/json" })),
|
||||
];
|
||||
|
||||
const release = (await res.json()) as GitHubRelease;
|
||||
return buildAgentDownloadCatalog(release.assets ?? []);
|
||||
} catch {
|
||||
return null;
|
||||
for (const loadAssets of loaders) {
|
||||
try {
|
||||
const catalog = buildAgentDownloadCatalog(await loadAssets());
|
||||
if (hasDownloadAssets(catalog)) return catalog;
|
||||
} catch {
|
||||
// Try the next synchronized release source.
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function buildAgentDownloadCatalog(assets: GitHubReleaseAsset[]): AgentDownloadCatalog {
|
||||
|
|
@ -255,6 +364,7 @@ export function buildOfflineBundleEntries(assets: GitHubReleaseAsset[]): Offline
|
|||
}
|
||||
|
||||
export function formatSize(bytes: number): string {
|
||||
if (bytes <= 0) return "—";
|
||||
if (bytes >= 1024 * 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue