mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
docs: improve legacy windows and linux install guidance
This commit is contained in:
@@ -200,7 +200,7 @@ jobs:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.windows_win7_bundle == 'true'
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
|
||||
|
||||
@@ -17,6 +17,25 @@ const ciWorkflow = readFileSync(resolve(process.cwd(), ".github/workflows/ci.yml
|
||||
const releaseWorkflow = readFileSync(resolve(process.cwd(), ".github/workflows/release.yml"), "utf8");
|
||||
|
||||
describe("Windows offline installer template", () => {
|
||||
it("routes supported legacy Windows users from generic installers to the fixed-runtime package", () => {
|
||||
expect(template).toContain("ManifestSupportedOS all");
|
||||
expect(template).toContain("!include WinVer.nsh");
|
||||
expect(template).toContain('!if "${INSTALLWEBVIEW2MODE}" != "fixedRuntime"');
|
||||
expect(template).toContain("${If} ${IsWin7}");
|
||||
expect(template).toContain("${OrIf} ${IsWin2012R2}");
|
||||
expect(template).toContain('MessageBox MB_ICONSTOP|MB_YESNO|MB_DEFBUTTON1 "$(dbxWin7InstallerRequired)" IDYES dbx_open_win7_installer');
|
||||
expect(template).toContain("https://dl.dbxio.com/releases/v${VERSION}/DBX_${VERSION}_x64-win7-server2012r2-webview2-109-offline-setup.exe?v=${VERSION}");
|
||||
expect(template).toContain("SetErrorLevel 1633");
|
||||
expect(template).toContain("${OrIf} $PassiveMode = 1");
|
||||
});
|
||||
|
||||
it("localizes the Windows 7 package guidance in every installer language", () => {
|
||||
expect(template.match(/LangString dbxWin7InstallerRequired/g)).toHaveLength(3);
|
||||
expect(template).toContain("This installer does not support Windows 7 or Windows Server 2012 R2.");
|
||||
expect(template).toContain("此安装包不支持 Windows 7 或 Windows Server 2012 R2。");
|
||||
expect(template).toContain("此安裝套件不支援 Windows 7 或 Windows Server 2012 R2。");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "continues after installer failure when a compatible Runtime remains",
|
||||
|
||||
@@ -417,7 +417,7 @@ fn export_agents_offline_unlocked(
|
||||
let plan = inspect_offline_package(package)?;
|
||||
let expected = drivers.iter().map(|driver| driver.db_type.clone()).collect::<BTreeSet<_>>();
|
||||
let actual = plan.driver_keys.into_iter().collect::<BTreeSet<_>>();
|
||||
if expected != actual || plan.includes_jre != !jres.is_empty() {
|
||||
if expected != actual || plan.includes_jre == jres.is_empty() {
|
||||
return Err("Generated offline Agent package failed compatibility validation".to_string());
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -5840,7 +5840,7 @@ async fn list_objects_once(
|
||||
let db_config = connection_config(state, connection_id).await;
|
||||
let force_local_table_name_filter = table_name_filter.is_some_and(|filter| !filter.is_empty());
|
||||
let (mysql_limit, mysql_offset) = if filter.is_none_or(|value| value.trim().is_empty())
|
||||
&& !table_name_filter.is_some_and(|filter| !filter.is_empty())
|
||||
&& table_name_filter.is_none_or(|filter| filter.is_empty())
|
||||
{
|
||||
(limit, offset)
|
||||
} else {
|
||||
@@ -9429,10 +9429,9 @@ fn postgres_missing_prokind_error(err: &str) -> bool {
|
||||
// PostgreSQL localizes the undefined-column message (for example, Chinese
|
||||
// servers report "字段 p.prokind 不存在"). Keep the column context so an
|
||||
// unrelated relation named `prokind` cannot trigger this compatibility path.
|
||||
let undefined_column = lower.contains("sqlstate 42703")
|
||||
lower.contains("sqlstate 42703")
|
||||
|| (lower.contains("does not exist") && lower.contains("column"))
|
||||
|| (err.contains("不存在") && (err.contains("字段") || err.contains("列 p.prokind")));
|
||||
undefined_column
|
||||
|| (err.contains("不存在") && (err.contains("字段") || err.contains("列 p.prokind")))
|
||||
}
|
||||
|
||||
fn opengauss_sequence_cache_metadata_error(err: &str) -> bool {
|
||||
|
||||
@@ -209,9 +209,9 @@ pub fn build_table_data_select_sql_with_database(
|
||||
let table = if let Some(database) = jdbc_tdengine_database {
|
||||
qualified_table_name(Some(DatabaseType::Tdengine), Some(database), &options.table_name)
|
||||
// Doris / StarRocks multi-catalog: prefix the catalog for external-catalog tables.
|
||||
} else if database_type == Some(DatabaseType::Iris) {
|
||||
table_data_qualified_table_name(database_type, schema, &options.table_name, options.identifier_quote.as_deref())
|
||||
} else if uses_connection_identifier_quote(database_type, options.identifier_quote.as_deref()) {
|
||||
} else if database_type == Some(DatabaseType::Iris)
|
||||
|| uses_connection_identifier_quote(database_type, options.identifier_quote.as_deref())
|
||||
{
|
||||
table_data_qualified_table_name(database_type, schema, &options.table_name, options.identifier_quote.as_deref())
|
||||
} else if include_database_name {
|
||||
database_qualified_table_name(
|
||||
|
||||
@@ -93,7 +93,7 @@ impl RuntimeConfig {
|
||||
validate_path(&path)?;
|
||||
|
||||
let is_loopback = ip.is_loopback();
|
||||
if !is_loopback && !(allow_remote && explicit_allow_remote) {
|
||||
if !(is_loopback || (allow_remote && explicit_allow_remote)) {
|
||||
return Err(
|
||||
"non-loopback HTTP binding requires both DBX_MCP_HTTP_ALLOW_REMOTE=1 and --http-allow-remote".into()
|
||||
);
|
||||
|
||||
@@ -224,7 +224,7 @@ async fn execute_query_injects_and_omits_timeout_secs_from_policy() {
|
||||
let captured = captured_query_arguments(backend, json!({ "connection_id": "scoped", "sql": "SELECT 1" })).await;
|
||||
assert_eq!(captured.len(), 1, "expected one captured execute_query call");
|
||||
assert!(
|
||||
!captured[0].get("timeout_secs").is_some(),
|
||||
captured[0].get("timeout_secs").is_none(),
|
||||
"no timeout_secs must be injected when the policy inherits the connection: {}",
|
||||
captured[0]
|
||||
);
|
||||
|
||||
@@ -755,6 +755,24 @@ a {
|
||||
color: var(--color-landing-ink);
|
||||
}
|
||||
|
||||
.landing-install-download-link:focus-visible {
|
||||
border-radius: 8px;
|
||||
outline: 2px solid rgba(255, 255, 255, 0.72);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
.landing-install-driver-link {
|
||||
color: #69c9ff;
|
||||
text-decoration-color: rgba(105, 201, 255, 0.78);
|
||||
transition: color 0.16s ease;
|
||||
}
|
||||
|
||||
.landing-install-driver-link:hover,
|
||||
.landing-install-driver-link:focus-visible {
|
||||
color: #a7e3ff;
|
||||
text-decoration-color: currentColor;
|
||||
}
|
||||
|
||||
.landing-install-option svg {
|
||||
fill: currentColor;
|
||||
stroke-width: 1.8;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronDown, Download, Server } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { createInstallOptions } from "@/lib/downloadLinks";
|
||||
import { Check, ChevronDown, ChevronRight, Copy, Download, Server, X } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { createInstallOptions, type InstallOption } from "@/lib/downloadLinks";
|
||||
import { detectPlatformId, type DownloadPlatformId } from "@/lib/platformDetection";
|
||||
|
||||
type InstallTabsProps = {
|
||||
@@ -14,8 +15,45 @@ const downloadLabel = { en: "Download DBX", cn: "下载 DBX" };
|
||||
const selectDownloadLabel = { en: "Choose a version", cn: "选择下载版本" };
|
||||
const selectMacLabel = { en: "Choose a macOS version", cn: "选择 macOS 版本" };
|
||||
const offlineHint = {
|
||||
en: "Offline network or Windows 7? Choose an offline installer",
|
||||
cn: "无法联网或使用 Windows 7?选择专用离线安装包",
|
||||
en: "Installing offline or using legacy Windows? View the matching installer",
|
||||
cn: "需要离线安装或使用旧版 Windows?查看对应安装包",
|
||||
};
|
||||
|
||||
const browserStaticText = {
|
||||
en: {
|
||||
eyebrow: "Linux browser package",
|
||||
intro: "For Kylin, UnionTech UOS, and other Linux distributions. DBX runs locally and opens in your browser.",
|
||||
extract: "Extract and enter the package directory",
|
||||
start: "Start DBX",
|
||||
open: "Open in your local browser",
|
||||
port: "Need a different port?",
|
||||
portHint: "Replace 8080 with any available local port.",
|
||||
browserStyle: "Page layout looks incorrect?",
|
||||
browserStyleHint: [
|
||||
"Some browsers in intranet or enterprise Linux environments use older browser engines that may not fully support the modern web standards used by DBX, resulting in missing or incorrect styles.",
|
||||
"For example, older QiAnXin browser versions based on Chromium 90 can show this behavior.",
|
||||
"Update the browser to its latest version, or use a current version of Chrome, Edge, or Firefox.",
|
||||
],
|
||||
download: "Download",
|
||||
close: "Close installation guide",
|
||||
copy: "Copy command",
|
||||
copied: "Copied",
|
||||
},
|
||||
cn: {
|
||||
eyebrow: "Linux 浏览器版",
|
||||
intro: "适用于麒麟、统信 UOS 等 Linux 发行版。DBX 在本机运行,通过浏览器访问。",
|
||||
extract: "解压并进入安装目录",
|
||||
start: "启动 DBX",
|
||||
open: "在本机浏览器打开",
|
||||
port: "需要修改默认端口?",
|
||||
portHint: "将 8080 替换为其他可用端口即可。",
|
||||
browserStyle: "网页样式显示异常?",
|
||||
browserStyleHint: ["部分信创或内网环境使用的浏览器内核版本较旧,可能无法完整支持 DBX 使用的现代 Web 标准,从而出现布局错位、样式缺失等问题。", "例如,基于 Chromium 90 内核的旧版奇安信浏览器可能出现此类情况。", "请优先升级当前浏览器;环境允许时,也可以改用新版 Chrome、Edge 或 Firefox。"],
|
||||
download: "下载",
|
||||
close: "关闭安装说明",
|
||||
copy: "复制命令",
|
||||
copied: "已复制",
|
||||
},
|
||||
};
|
||||
|
||||
const platformIconPaths = {
|
||||
@@ -26,6 +64,7 @@ const platformIconPaths = {
|
||||
"macos-intel": "/icons/platform/macos.png",
|
||||
"macos-unknown": "/icons/platform/macos.png",
|
||||
windows: "/icons/platform/windows.png",
|
||||
"windows-legacy": "/icons/platform/windows-legacy.png",
|
||||
},
|
||||
light: {
|
||||
"linux-arm": "/icons/platform/linux.svg",
|
||||
@@ -34,6 +73,7 @@ const platformIconPaths = {
|
||||
"macos-intel": "/icons/platform/macos-white.png",
|
||||
"macos-unknown": "/icons/platform/macos-white.png",
|
||||
windows: "/icons/platform/windows.png",
|
||||
"windows-legacy": "/icons/platform/windows-legacy.png",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,10 +83,141 @@ function PlatformIcon({ id, size, variant }: { id: string; size: number; variant
|
||||
return <img alt="" aria-hidden="true" height={size} src={src} width={size} />;
|
||||
}
|
||||
|
||||
function BrowserStaticDialog({ lang, option, version, onClose }: { lang: "en" | "cn"; option: InstallOption; version: string; onClose: () => void }) {
|
||||
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const [copiedCommand, setCopiedCommand] = useState<string | null>(null);
|
||||
const text = browserStaticText[lang];
|
||||
const archiveName = `DBX_${version}_*-browser-static.tar.gz`;
|
||||
const packageDirectory = "dbx-linux-*-browser-static";
|
||||
const extractCommand = `tar -xzf ${archiveName}\ncd ${packageDirectory}`;
|
||||
const downloads = option.browserStaticDownloads ?? [{ arch: "x64" as const, href: option.href }];
|
||||
|
||||
useEffect(() => {
|
||||
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
const previousOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = "hidden";
|
||||
closeButtonRef.current?.focus();
|
||||
|
||||
const closeOnEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") onClose();
|
||||
};
|
||||
window.addEventListener("keydown", closeOnEscape);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", closeOnEscape);
|
||||
document.body.style.overflow = previousOverflow;
|
||||
previousFocus?.focus();
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
const copyCommand = (command: string) => {
|
||||
void navigator.clipboard
|
||||
.writeText(command)
|
||||
.then(() => {
|
||||
setCopiedCommand(command);
|
||||
window.setTimeout(() => setCopiedCommand((current) => (current === command ? null : current)), 1600);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
};
|
||||
|
||||
const commandBlock = (command: string) => (
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_34px] items-start gap-2 rounded-md border border-white/10 bg-black/30 p-2 pl-3">
|
||||
<code className="min-w-0 overflow-x-auto whitespace-pre font-mono text-xs leading-relaxed text-[#dce5ee]">{command}</code>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={copiedCommand === command ? text.copied : text.copy}
|
||||
className="grid size-[34px] place-items-center rounded-md border-0 bg-white/[0.06] text-white/65 cursor-pointer hover:bg-white/10 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/70"
|
||||
title={copiedCommand === command ? text.copied : text.copy}
|
||||
onClick={() => copyCommand(command)}
|
||||
>
|
||||
{copiedCommand === command ? <Check size={15} /> : <Copy size={15} />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (typeof document === "undefined") return null;
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-[100] grid place-items-center overflow-y-auto bg-black/70 px-4 py-6 backdrop-blur-sm"
|
||||
role="presentation"
|
||||
onMouseDown={(event) => {
|
||||
if (event.target === event.currentTarget) onClose();
|
||||
}}
|
||||
>
|
||||
<section aria-labelledby="browser-static-dialog-title" aria-modal="true" className="w-[min(560px,calc(100vw-32px))] max-h-[calc(100vh-48px)] overflow-y-auto rounded-lg border border-white/15 bg-[#111419] text-white shadow-[0_28px_90px_rgba(0,0,0,0.58)]" role="dialog">
|
||||
<header className="grid grid-cols-[minmax(0,1fr)_36px] gap-4 border-b border-white/10 px-5 py-4">
|
||||
<div className="min-w-0">
|
||||
<small className="text-[11px] font-semibold text-[#69c9ff]">{text.eyebrow}</small>
|
||||
<h2 className="mt-1 text-lg font-semibold leading-tight" id="browser-static-dialog-title">
|
||||
{option.label}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm leading-relaxed text-white/55">{text.intro}</p>
|
||||
</div>
|
||||
<button
|
||||
ref={closeButtonRef}
|
||||
type="button"
|
||||
aria-label={text.close}
|
||||
className="grid size-9 place-items-center rounded-md border-0 bg-white/[0.06] text-white/65 cursor-pointer hover:bg-white/10 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/70"
|
||||
title={text.close}
|
||||
onClick={onClose}
|
||||
>
|
||||
<X size={17} />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div className="grid gap-4 px-5 py-4">
|
||||
<section>
|
||||
<h3 className="mb-2 text-sm font-semibold">1. {text.extract}</h3>
|
||||
{commandBlock(extractCommand)}
|
||||
</section>
|
||||
<section>
|
||||
<h3 className="mb-2 text-sm font-semibold">2. {text.start}</h3>
|
||||
{commandBlock("./dbx")}
|
||||
</section>
|
||||
<section>
|
||||
<h3 className="mb-2 text-sm font-semibold">3. {text.open}</h3>
|
||||
<a className="inline-flex text-sm font-semibold text-[#69c9ff] underline underline-offset-4 hover:text-[#9dddff]" href="http://127.0.0.1:4224" target="_blank" rel="noreferrer">
|
||||
http://127.0.0.1:4224
|
||||
</a>
|
||||
</section>
|
||||
<details className="group border-t border-white/10 pt-3">
|
||||
<summary className="w-fit cursor-pointer text-xs font-medium text-white/45 hover:text-white/70 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white/60">{text.port}</summary>
|
||||
<div className="mt-3">
|
||||
{commandBlock("DBX_PORT=8080 ./dbx")}
|
||||
<p className="mt-2 text-xs leading-relaxed text-white/45">{text.portHint}</p>
|
||||
</div>
|
||||
</details>
|
||||
<details className="group border-t border-white/10 pt-3">
|
||||
<summary className="w-fit cursor-pointer text-xs font-medium text-white/45 hover:text-white/70 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white/60">{text.browserStyle}</summary>
|
||||
<div className="mt-3 grid gap-2 text-xs leading-relaxed text-white/55">
|
||||
{text.browserStyleHint.map((paragraph) => (
|
||||
<p key={paragraph}>{paragraph}</p>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<footer className="flex flex-wrap justify-end gap-2 border-t border-white/10 px-5 py-4">
|
||||
{downloads.map((download) => (
|
||||
<a className="inline-flex min-h-10 items-center gap-2 rounded-md bg-white px-4 text-sm font-semibold text-[#111419] hover:bg-[#e9f5fb] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#69c9ff]" href={download.href} key={download.arch}>
|
||||
<Download size={16} />
|
||||
{text.download} {download.arch === "arm64" ? "ARM64" : "x64"}
|
||||
</a>
|
||||
))}
|
||||
</footer>
|
||||
</section>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
export function InstallTabs({ lang, version }: InstallTabsProps) {
|
||||
const options = useMemo(() => createInstallOptions(lang, version), [lang, version]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [platformId, setPlatformId] = useState<DownloadPlatformId>("unknown");
|
||||
const [browserStaticOption, setBrowserStaticOption] = useState<InstallOption | null>(null);
|
||||
const closeBrowserStaticDialog = useCallback(() => setBrowserStaticOption(null), []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -129,23 +300,50 @@ export function InstallTabs({ lang, version }: InstallTabsProps) {
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="landing-install-menu absolute z-30 top-[calc(100%+12px)] left-1/2 -translate-x-1/2 grid w-[min(380px,calc(100vw-32px))] border border-[rgba(173,176,182,0.17)] rounded-xl py-1.5 max-[760px]:left-auto max-[760px]:translate-x-0"
|
||||
className="landing-install-menu absolute z-30 top-[calc(100%+12px)] left-1/2 -translate-x-1/2 grid max-h-[min(680px,calc(100vh-140px))] w-[min(410px,calc(100vw-32px))] overflow-y-auto overscroll-contain border border-[rgba(173,176,182,0.17)] rounded-xl py-1.5 max-[760px]:left-auto max-[760px]:translate-x-0"
|
||||
id="landing-install-menu"
|
||||
role="menu"
|
||||
aria-label={lang === "cn" ? "下载选项" : "Download options"}
|
||||
>
|
||||
{menuOptions.map((item) => (
|
||||
<a className="landing-install-option grid grid-cols-[24px_minmax(0,1fr)_18px] gap-3 items-center min-h-11 min-w-0 border-0 px-[18px] py-3 bg-transparent text-left cursor-pointer" href={item.href} key={item.id} role="menuitem">
|
||||
<div className="landing-install-option relative grid grid-cols-[24px_minmax(0,1fr)_18px] gap-3 items-center min-h-11 min-w-0 border-0 px-[18px] py-3 bg-transparent text-left cursor-pointer" key={item.id} role="none">
|
||||
{item.action === "instructions" ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={lang === "cn" ? `查看 ${item.label} 安装说明` : `View installation guide for ${item.label}`}
|
||||
className="landing-install-download-link absolute inset-0 border-0 bg-transparent cursor-pointer"
|
||||
role="menuitem"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setBrowserStaticOption(item);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<a aria-label={lang === "cn" ? `下载 ${item.label}` : `Download ${item.label}`} className="landing-install-download-link absolute inset-0" href={item.href} role="menuitem" />
|
||||
)}
|
||||
<PlatformIcon id={item.iconId} size={20} variant="light" />
|
||||
<span className="grid min-w-0 gap-1">
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<strong className="overflow-hidden text-sm font-[640] leading-[1.2] truncate">{item.label}</strong>
|
||||
{item.badge ? <small className="shrink-0 rounded-full bg-white/10 px-2 py-0.5 text-[10px] font-[680] leading-none text-white/70">{item.badge}</small> : null}
|
||||
<span className="flex min-w-0 items-start gap-2">
|
||||
<strong className="min-w-0 text-sm font-[640] leading-[1.2]">{item.label}</strong>
|
||||
{item.badge ? <small className={`shrink-0 rounded-full bg-white/10 px-2 py-0.5 text-[10px] font-[680] leading-none text-white/70 ${item.id === "windows-7-offline" ? "max-[400px]:hidden" : ""}`}>{item.badge}</small> : null}
|
||||
</span>
|
||||
{item.description ? <small className="overflow-hidden text-xs leading-[1.3] text-white/45 truncate">{item.description}</small> : null}
|
||||
{item.description ? (
|
||||
<small className="pointer-events-none text-xs leading-[1.35] text-white/45">
|
||||
{item.description}
|
||||
{item.driverLinkLabel ? (
|
||||
<>
|
||||
{lang === "en" ? " " : null}
|
||||
<a className="landing-install-driver-link pointer-events-auto relative z-10 underline underline-offset-2" href={`/${lang}/drivers`}>
|
||||
{item.driverLinkLabel}
|
||||
</a>
|
||||
</>
|
||||
) : null}
|
||||
{item.descriptionSuffix}
|
||||
</small>
|
||||
) : null}
|
||||
</span>
|
||||
<Download size={15} aria-hidden="true" />
|
||||
</a>
|
||||
{item.action === "instructions" ? <ChevronRight size={15} aria-hidden="true" /> : <Download size={15} aria-hidden="true" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,6 +357,7 @@ export function InstallTabs({ lang, version }: InstallTabsProps) {
|
||||
{offlineHint[lang]}
|
||||
</button>
|
||||
) : null}
|
||||
{browserStaticOption ? <BrowserStaticDialog lang={lang} option={browserStaticOption} version={version} onClose={closeBrowserStaticDialog} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,29 +4,47 @@ import { test } from "vitest";
|
||||
import { createInstallOptions } from "./downloadLinks";
|
||||
|
||||
test("Windows downloads include standard, offline, and Windows 7 installers", () => {
|
||||
const windowsOptions = createInstallOptions("cn", "0.5.82").filter((option) => option.iconId === "windows");
|
||||
const windowsOptions = createInstallOptions("cn", "0.5.82").filter((option) => option.id.startsWith("windows"));
|
||||
|
||||
assert.deepEqual(
|
||||
windowsOptions.map(({ id, label, description, badge, href }) => ({ id, label, description, badge, href })),
|
||||
windowsOptions.map(({ id, iconId, label, description, driverLinkLabel, descriptionSuffix, badge, href }) => ({
|
||||
id,
|
||||
iconId,
|
||||
label,
|
||||
description,
|
||||
driverLinkLabel,
|
||||
descriptionSuffix,
|
||||
badge,
|
||||
href,
|
||||
})),
|
||||
[
|
||||
{
|
||||
id: "windows",
|
||||
iconId: "windows",
|
||||
label: "Windows 10/11 (x64)",
|
||||
description: "标准在线安装包",
|
||||
driverLinkLabel: undefined,
|
||||
descriptionSuffix: undefined,
|
||||
badge: "推荐",
|
||||
href: "https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_x64-setup.exe?v=0.5.82",
|
||||
},
|
||||
{
|
||||
id: "windows-offline",
|
||||
label: "Windows 完整离线安装包",
|
||||
description: "内置 WebView2 · 适用于内网部署或运行库缺失",
|
||||
badge: "离线",
|
||||
iconId: "windows",
|
||||
label: "Windows10内网环境安装包",
|
||||
description: "内置 WebView2 离线运行库 · 不含",
|
||||
driverLinkLabel: "数据库离线驱动",
|
||||
descriptionSuffix: undefined,
|
||||
badge: "内网",
|
||||
href: "https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_x64-webview2-offline-setup.exe?v=0.5.82",
|
||||
},
|
||||
{
|
||||
id: "windows-7-offline",
|
||||
label: "Windows 7 / Server 2012 R2 离线安装包",
|
||||
description: "内置 WebView2 109 · 仅支持 x64",
|
||||
iconId: "windows-legacy",
|
||||
label: "Windows 7 / Server\u00a02012\u00a0R2 专用包",
|
||||
description: "内置 WebView2 离线运行库 · 不含",
|
||||
driverLinkLabel: "数据库离线驱动",
|
||||
descriptionSuffix: undefined,
|
||||
badge: "旧系统",
|
||||
href: "https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_x64-win7-server2012r2-webview2-109-offline-setup.exe?v=0.5.82",
|
||||
},
|
||||
@@ -37,7 +55,33 @@ test("Windows downloads include standard, offline, and Windows 7 installers", ()
|
||||
test("all downloads use immutable versioned release paths", () => {
|
||||
const options = createInstallOptions("en", "0.5.82");
|
||||
|
||||
assert.equal(options.length, 7);
|
||||
assert.equal(options.length, 8);
|
||||
assert.ok(options.every((option) => option.href.startsWith("https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_")));
|
||||
assert.ok(options.every((option) => !option.href.includes("/releases/latest/")));
|
||||
});
|
||||
|
||||
test("browser static package opens one guide with both architecture downloads", () => {
|
||||
const options = createInstallOptions("cn", "0.6.0").filter((option) => option.action === "instructions");
|
||||
|
||||
assert.deepEqual(
|
||||
options.map(({ id, iconId, label, href, browserStaticDownloads }) => ({ id, iconId, label, href, browserStaticDownloads })),
|
||||
[
|
||||
{
|
||||
id: "linux-browser",
|
||||
iconId: "linux",
|
||||
label: "Linux 浏览器版",
|
||||
href: "https://dl.dbxio.com/releases/v0.6.0/DBX_0.6.0_x64-browser-static.tar.gz?v=0.6.0",
|
||||
browserStaticDownloads: [
|
||||
{
|
||||
arch: "x64",
|
||||
href: "https://dl.dbxio.com/releases/v0.6.0/DBX_0.6.0_x64-browser-static.tar.gz?v=0.6.0",
|
||||
},
|
||||
{
|
||||
arch: "arm64",
|
||||
href: "https://dl.dbxio.com/releases/v0.6.0/DBX_0.6.0_arm64-browser-static.tar.gz?v=0.6.0",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
export type InstallLang = "en" | "cn";
|
||||
export type BrowserStaticArch = "x64" | "arm64";
|
||||
|
||||
export type BrowserStaticDownload = {
|
||||
arch: BrowserStaticArch;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export type InstallOption = {
|
||||
id: string;
|
||||
iconId: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
driverLinkLabel?: string;
|
||||
descriptionSuffix?: string;
|
||||
badge?: string;
|
||||
href: string;
|
||||
action?: "download" | "instructions";
|
||||
browserStaticDownloads?: BrowserStaticDownload[];
|
||||
};
|
||||
|
||||
type DownloadArtifact = {
|
||||
@@ -14,8 +24,12 @@ type DownloadArtifact = {
|
||||
iconId: string;
|
||||
labels: Record<InstallLang, string>;
|
||||
descriptions?: Record<InstallLang, string>;
|
||||
driverLinkLabels?: Record<InstallLang, string>;
|
||||
descriptionSuffixes?: Record<InstallLang, string>;
|
||||
badges?: Record<InstallLang, string>;
|
||||
suffix: string;
|
||||
action?: "download" | "instructions";
|
||||
browserStaticSuffixes?: Record<BrowserStaticArch, string>;
|
||||
};
|
||||
|
||||
const DOWNLOAD_BASE_URL = "https://dl.dbxio.com/releases";
|
||||
@@ -44,16 +58,18 @@ const downloadArtifacts: DownloadArtifact[] = [
|
||||
{
|
||||
id: "windows-offline",
|
||||
iconId: "windows",
|
||||
labels: { en: "Windows complete offline installer", cn: "Windows 完整离线安装包" },
|
||||
descriptions: { en: "Includes WebView2 · For offline deployment or missing runtime", cn: "内置 WebView2 · 适用于内网部署或运行库缺失" },
|
||||
badges: { en: "Offline", cn: "离线" },
|
||||
labels: { en: "Windows 10 intranet environment installer", cn: "Windows10内网环境安装包" },
|
||||
descriptions: { en: "Includes WebView2 offline runtime · Does not include", cn: "内置 WebView2 离线运行库 · 不含" },
|
||||
driverLinkLabels: { en: "offline database drivers", cn: "数据库离线驱动" },
|
||||
badges: { en: "Intranet", cn: "内网" },
|
||||
suffix: "x64-webview2-offline-setup.exe",
|
||||
},
|
||||
{
|
||||
id: "windows-7-offline",
|
||||
iconId: "windows",
|
||||
labels: { en: "Windows 7 / Server 2012 R2 offline installer", cn: "Windows 7 / Server 2012 R2 离线安装包" },
|
||||
descriptions: { en: "Includes WebView2 109 · x64 only", cn: "内置 WebView2 109 · 仅支持 x64" },
|
||||
iconId: "windows-legacy",
|
||||
labels: { en: "Windows 7 / Server\u00a02012\u00a0R2 package", cn: "Windows 7 / Server\u00a02012\u00a0R2 专用包" },
|
||||
descriptions: { en: "Includes WebView2 offline runtime · Does not include", cn: "内置 WebView2 离线运行库 · 不含" },
|
||||
driverLinkLabels: { en: "offline database drivers", cn: "数据库离线驱动" },
|
||||
badges: { en: "Legacy", cn: "旧系统" },
|
||||
suffix: "x64-win7-server2012r2-webview2-109-offline-setup.exe",
|
||||
},
|
||||
@@ -69,6 +85,19 @@ const downloadArtifacts: DownloadArtifact[] = [
|
||||
labels: { en: "For Linux ARM64", cn: "适用于 Linux ARM64" },
|
||||
suffix: "aarch64.AppImage",
|
||||
},
|
||||
{
|
||||
id: "linux-browser",
|
||||
iconId: "linux",
|
||||
labels: { en: "Linux browser package", cn: "Linux 浏览器版" },
|
||||
descriptions: { en: "For Kylin, UnionTech UOS, and other Linux distributions", cn: "适用于麒麟、统信 UOS 等 Linux 发行版" },
|
||||
badges: { en: "Guide", cn: "安装说明" },
|
||||
suffix: "x64-browser-static.tar.gz",
|
||||
action: "instructions",
|
||||
browserStaticSuffixes: {
|
||||
x64: "x64-browser-static.tar.gz",
|
||||
arm64: "arm64-browser-static.tar.gz",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function createInstallOptions(lang: InstallLang, version: string): InstallOption[] {
|
||||
@@ -77,7 +106,16 @@ export function createInstallOptions(lang: InstallLang, version: string): Instal
|
||||
iconId: artifact.iconId,
|
||||
label: artifact.labels[lang],
|
||||
description: artifact.descriptions?.[lang],
|
||||
driverLinkLabel: artifact.driverLinkLabels?.[lang],
|
||||
descriptionSuffix: artifact.descriptionSuffixes?.[lang],
|
||||
badge: artifact.badges?.[lang],
|
||||
href: `${DOWNLOAD_BASE_URL}/v${version}/DBX_${version}_${artifact.suffix}?v=${version}`,
|
||||
action: artifact.action ?? "download",
|
||||
browserStaticDownloads: artifact.browserStaticSuffixes
|
||||
? Object.entries(artifact.browserStaticSuffixes).map(([arch, suffix]) => ({
|
||||
arch: arch as BrowserStaticArch,
|
||||
href: `${DOWNLOAD_BASE_URL}/v${version}/DBX_${version}_${suffix}?v=${version}`,
|
||||
}))
|
||||
: undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -1,5 +1,6 @@
|
||||
Unicode true
|
||||
ManifestDPIAware true
|
||||
ManifestSupportedOS all
|
||||
; Add in `dpiAwareness` `PerMonitorV2` to manifest for Windows 10 1607+ (note this should not affect lower versions since they should be able to ignore this and pick up `dpiAware` `true` set by `ManifestDPIAware true`)
|
||||
; Currently undocumented on NSIS's website but is in the Docs folder of source tree, see
|
||||
; https://github.com/kichik/nsis/blob/5fc0b87b819a9eec006df4967d08e522ddd651c9/Docs/src/attributes.but#L286-L300
|
||||
@@ -16,6 +17,7 @@ ManifestDPIAwareness PerMonitorV2
|
||||
!include MUI2.nsh
|
||||
!include FileFunc.nsh
|
||||
!include x64.nsh
|
||||
!include WinVer.nsh
|
||||
!include WordFunc.nsh
|
||||
!include "utils.nsh"
|
||||
!include "FileAssociation.nsh"
|
||||
@@ -503,6 +505,9 @@ LangString dbxFileWriteError ${LANG_SIMPCHINESE} "无法打开要写入的文件
|
||||
LangString dbxFileWriteErrorNoIgnore ${LANG_SIMPCHINESE} "无法打开要写入的文件:$\r$\n$\r$\n$0$\r$\n$\r$\n如果正在升级安装于 Program Files 的 DBX,请取消本次安装,然后右键单击安装程序并选择“以管理员身份运行”。$\r$\n$\r$\n单击“重试”再次尝试,或$\r$\n单击“取消”停止安装。"
|
||||
LangString dbxFileWriteError ${LANG_TRADCHINESE} "無法開啟要寫入的檔案:$\r$\n$\r$\n$0$\r$\n$\r$\n如果正在升級安裝於 Program Files 的 DBX,請中止本次安裝,然後以滑鼠右鍵按一下安裝程式並選擇「以系統管理員身分執行」。$\r$\n$\r$\n按一下「中止」以停止安裝,$\r$\n按一下「重試」以再次嘗試,或$\r$\n按一下「忽略」以略過此檔案。"
|
||||
LangString dbxFileWriteErrorNoIgnore ${LANG_TRADCHINESE} "無法開啟要寫入的檔案:$\r$\n$\r$\n$0$\r$\n$\r$\n如果正在升級安裝於 Program Files 的 DBX,請取消本次安裝,然後以滑鼠右鍵按一下安裝程式並選擇「以系統管理員身分執行」。$\r$\n$\r$\n按一下「重試」以再次嘗試,或$\r$\n按一下「取消」以停止安裝。"
|
||||
LangString dbxWin7InstallerRequired ${LANG_ENGLISH} "This installer does not support Windows 7 or Windows Server 2012 R2.$\r$\n$\r$\nPlease use the dedicated Windows 7 / Server 2012 R2 package instead.$\r$\n$\r$\nOpen the download now?"
|
||||
LangString dbxWin7InstallerRequired ${LANG_SIMPCHINESE} "此安装包不支持 Windows 7 或 Windows Server 2012 R2。$\r$\n$\r$\n请改用 Windows 7 / Server 2012 R2 专用包。$\r$\n$\r$\n是否立即打开下载地址?"
|
||||
LangString dbxWin7InstallerRequired ${LANG_TRADCHINESE} "此安裝套件不支援 Windows 7 或 Windows Server 2012 R2。$\r$\n$\r$\n請改用 Windows 7 / Server 2012 R2 專用套件。$\r$\n$\r$\n是否立即開啟下載網址?"
|
||||
FileErrorText "$(dbxFileWriteError)" "$(dbxFileWriteErrorNoIgnore)"
|
||||
|
||||
Function .onInit
|
||||
@@ -525,6 +530,28 @@ Function .onInit
|
||||
!insertmacro MUI_LANGDLL_DISPLAY
|
||||
!endif
|
||||
|
||||
; Evergreen WebView2 installers no longer run on Windows 7. Route interactive
|
||||
; users to the fixed WebView2 109 bundle before the generic installer fails.
|
||||
!if "${INSTALLWEBVIEW2MODE}" != "fixedRuntime"
|
||||
${If} ${IsWin7}
|
||||
${OrIf} ${IsWin2012R2}
|
||||
${If} ${Silent}
|
||||
${OrIf} $PassiveMode = 1
|
||||
SetErrorLevel 1633
|
||||
Quit
|
||||
${EndIf}
|
||||
|
||||
MessageBox MB_ICONSTOP|MB_YESNO|MB_DEFBUTTON1 "$(dbxWin7InstallerRequired)" IDYES dbx_open_win7_installer
|
||||
SetErrorLevel 1633
|
||||
Quit
|
||||
|
||||
dbx_open_win7_installer:
|
||||
ExecShell "open" "https://dl.dbxio.com/releases/v${VERSION}/DBX_${VERSION}_x64-win7-server2012r2-webview2-109-offline-setup.exe?v=${VERSION}"
|
||||
SetErrorLevel 1633
|
||||
Quit
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
!insertmacro SetContext
|
||||
|
||||
${If} $INSTDIR == "${PLACEHOLDER_INSTALL_DIR}"
|
||||
|
||||
Reference in New Issue
Block a user