{/* Title Row */}
@@ -141,6 +144,10 @@ const DatasetsPage = () => {
{/* {deleteMode ? "Cancel Delete" : "- Delete Dataset"}*/}
{/**/}
+
+
@@ -148,6 +155,8 @@ const DatasetsPage = () => {
+
setLoadUrlOpen(false)}/>
+
{/* Main Content */}
{/* Left Filter Panel */}
diff --git a/frontend/src/store/DatatableStore.js b/frontend/src/store/DatatableStore.js
index 22b567a..5acc61f 100644
--- a/frontend/src/store/DatatableStore.js
+++ b/frontend/src/store/DatatableStore.js
@@ -2,6 +2,7 @@ import {create} from "zustand";
import {toast} from "react-toastify";
import {getDatatable_get, getDatasetList, getSampletable_get} from "../api/api.js";
import {checkBWDataExists} from "./GenomicRegionStore.js";
+import useRemoteDatasetStore from "./RemoteDatasetStore.js";
const useDatatableStore = create((set) => ({
dataRecords: [],
@@ -61,6 +62,7 @@ const useDatatableStore = create((set) => ({
},
fetchDatasetList: async () => {
+ const remoteDatasets = useRemoteDatasetStore.getState().remoteDatasets || [];
try {
const response = await getDatasetList();
// console.log(response);
@@ -75,18 +77,21 @@ const useDatatableStore = create((set) => ({
})
);
- await set({datasetRecords: recordsWithBW, datasetFilters: data[1], datasetfetchStatus: "success"});
+ await set({datasetRecords: [...recordsWithBW, ...remoteDatasets], datasetFilters: data[1], datasetfetchStatus: "success"});
// toast.success("Sample loaded successfully!");
} else {
console.error("Error fetching data:", response.data);
- await set({datasetRecords: [], datasetfetchStatus: "failed"});
+ await set({datasetRecords: [...remoteDatasets], datasetfetchStatus: "failed"});
toast.error("Failed to fetch datasets.");
}
} catch (error) {
console.error("Error fetching data:", error);
- await set({datasetRecords: [], datasetfetchStatus: "error"});
- toast.info(error.response.data.detail);
+ // Use browser-local remote datasets if the server dataset table is unavailable.
+ await set({datasetRecords: [...remoteDatasets], datasetfetchStatus: "error"});
+ if (error?.response?.data?.detail) {
+ toast.info(error.response.data.detail);
+ }
}
},
diff --git a/frontend/src/store/RemoteDatasetStore.js b/frontend/src/store/RemoteDatasetStore.js
new file mode 100644
index 0000000..462c4d4
--- /dev/null
+++ b/frontend/src/store/RemoteDatasetStore.js
@@ -0,0 +1,64 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+export const buildRemoteRecord = (url, name, info = {}) => {
+ let assay = info.assay || "";
+ const hasBw = !!info.has_bw;
+ const hasQtl = !!info.has_qtl;
+
+ // Normalize the assay string so the view filters recognize it:
+ // - xQTL view lists datasets whose assay ends with "qtl"
+ // - Genomic-Region view lists datasets whose assay ends with RNAseq/ATACseq
+ if (hasQtl && !assay.toLowerCase().endsWith("qtl")) {
+ assay = info.is_caqtl ? "caQTL" : "eQTL";
+ }
+ if (hasBw && !/(RNAseq|ATACseq)$/i.test(assay)) {
+ assay =
+ assay && assay.toLowerCase().endsWith("qtl") ? assay : "scATACseq";
+ }
+
+ return {
+ dataset_id: url,
+ dataset_name: name || info.dataset_name || url,
+ PI_full_name: (info.info && info.info.PI_full_name) || "—",
+ first_contributor: (info.info && info.info.first_contributor) || "—",
+ n_samples: (info.info && info.info.n_samples) || 0,
+ brain_region: (info.info && info.info.brain_region) || "",
+ brain_super_region: (info.info && info.info.brain_super_region) || "",
+ disease: (info.info && info.info.disease) || "",
+ organism: (info.info && info.info.organism) || "",
+ tissue: (info.info && info.info.tissue) || "",
+ assay: assay,
+ sample_sheet: "None",
+ has_bw: hasBw,
+ is_remote: true,
+ };
+};
+
+const useRemoteDatasetStore = create(
+ persist(
+ (set) => ({
+ remoteDatasets: [],
+
+ addRemoteDataset: (record) =>
+ set((state) => ({
+ remoteDatasets: [
+ ...state.remoteDatasets.filter(
+ (r) => r.dataset_id !== record.dataset_id,
+ ),
+ record,
+ ],
+ })),
+
+ removeRemoteDataset: (datasetId) =>
+ set((state) => ({
+ remoteDatasets: state.remoteDatasets.filter(
+ (r) => r.dataset_id !== datasetId,
+ ),
+ })),
+ }),
+ { name: "vizit-remote-datasets" },
+ ),
+);
+
+export default useRemoteDatasetStore;
From 11860003d92e551af3b8e002616271af5207de7d Mon Sep 17 00:00:00 2001
From: Rehpotsirhc <86068565+Rehpotsirhc-z@users.noreply.github.com>
Date: Mon, 8 Jun 2026 15:47:16 -0400
Subject: [PATCH 2/6] Use display name for remote backend
---
frontend/src/pages/Datasets/DatasetDisplay.jsx | 2 +-
frontend/src/pages/GenomicRegionView/index.jsx | 9 ++++++++-
frontend/src/pages/XQTLView/index.jsx | 9 ++++++++-
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/frontend/src/pages/Datasets/DatasetDisplay.jsx b/frontend/src/pages/Datasets/DatasetDisplay.jsx
index 8165199..f75a7fc 100644
--- a/frontend/src/pages/Datasets/DatasetDisplay.jsx
+++ b/frontend/src/pages/Datasets/DatasetDisplay.jsx
@@ -208,7 +208,7 @@ const DatasetDisplay = ({dataRecords, deleteMode}) => {
{displayedData.map((record) => (
- {record.sample_sheet === "None" || record.sample_sheet === null || record.sample_sheet.trim() === "" ? record.dataset_id :
+ {record.is_remote || record.sample_sheet === "None" || record.sample_sheet === null || record.sample_sheet.trim() === "" ? (record.dataset_name || record.dataset_id) :
{record.dataset_id}}
{record.PI_full_name}
diff --git a/frontend/src/pages/GenomicRegionView/index.jsx b/frontend/src/pages/GenomicRegionView/index.jsx
index b93ea18..3e02cc2 100644
--- a/frontend/src/pages/GenomicRegionView/index.jsx
+++ b/frontend/src/pages/GenomicRegionView/index.jsx
@@ -86,6 +86,12 @@ function GenomicRegionView() {
.filter((d) => /[A-Za-z]*?(RNAseq|ATACseq)$/i.test(d.assay) && d.has_bw)
.map((d) => d.dataset_id);
+ // Map dataset_id to display name
+ const datasetLabel = (id) => {
+ const ds = datasetRecords.find((d) => d.dataset_id === id);
+ return (ds && ds.dataset_name) || id || "";
+ };
+
const [datasetId, setDatasetId] = useState(urlDataset);
const [datasetSearchText, setDatasetSearchText] = useState("");
@@ -786,6 +792,7 @@ function GenomicRegionView() {
options={datasetOptions}
value={datasetId ?? null}
onChange={handleDatasetChange}
+ getOptionLabel={(option) => datasetLabel(option)}
inputValue={datasetSearchText}
onInputChange={(event, newInputValue) =>
setDatasetSearchText(newInputValue)
@@ -802,7 +809,7 @@ function GenomicRegionView() {
const { key, ...rest } = props;
return (
- {option}
+ {datasetLabel(option)}
//
// {option}
diff --git a/frontend/src/pages/XQTLView/index.jsx b/frontend/src/pages/XQTLView/index.jsx
index 6198e44..5fb92b6 100644
--- a/frontend/src/pages/XQTLView/index.jsx
+++ b/frontend/src/pages/XQTLView/index.jsx
@@ -87,6 +87,12 @@ function XQTLView() {
.filter((d) => d.assay.toLowerCase().endsWith("qtl"))
.map((d) => d.dataset_id);
+ // Map dataset_id to display name
+ const datasetLabel = (id) => {
+ const ds = datasetRecords.find((d) => d.dataset_id === id);
+ return (ds && ds.dataset_name) || id || "";
+ };
+
const [datasetId, setDatasetId] = useState(urlDataset);
const [datasetSearchText, setDatasetSearchText] = useState("");
@@ -551,6 +557,7 @@ function XQTLView() {
options={datasetOptions}
value={datasetId ?? null}
onChange={handleDatasetChange}
+ getOptionLabel={(option) => datasetLabel(option)}
inputValue={datasetSearchText}
onInputChange={(event, newInputValue) =>
setDatasetSearchText(newInputValue)
@@ -567,7 +574,7 @@ function XQTLView() {
const { key, ...rest } = props;
return (
- {option}
+ {datasetLabel(option)}
//
// {option}
From 9505cd5d77c6aef94ed518e0d8241d9abe3c5f4f Mon Sep 17 00:00:00 2001
From: Rehpotsirhc <86068565+Rehpotsirhc-z@users.noreply.github.com>
Date: Tue, 9 Jun 2026 12:08:55 -0400
Subject: [PATCH 3/6] Clear cache on remote dataset inspect
---
backend/funcs/remote_io.py | 6 +++++-
backend/routes/qtl_routes.py | 3 +++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/backend/funcs/remote_io.py b/backend/funcs/remote_io.py
index 0b32c6b..67c4dca 100644
--- a/backend/funcs/remote_io.py
+++ b/backend/funcs/remote_io.py
@@ -135,7 +135,11 @@ def _remote_exists(url: str) -> bool:
resp.close()
-@lru_cache(maxsize=64)
+def clear_remote_cache() -> None:
+ _fetch_bytes_cached.cache_clear()
+
+
+@lru_cache(maxsize=512)
def _fetch_bytes_cached(url: str) -> bytes:
try:
resp = _request("GET", url, stream=True)
diff --git a/backend/routes/qtl_routes.py b/backend/routes/qtl_routes.py
index c377ffd..36a5e27 100644
--- a/backend/routes/qtl_routes.py
+++ b/backend/routes/qtl_routes.py
@@ -3,6 +3,7 @@
# from backend.funcs.get_data import *
+from backend.funcs.remote_io import is_remote, clear_remote_cache
from backend.funcs.get_data import (
get_qtl_gene_list,
get_qtl_snp_list,
@@ -33,6 +34,8 @@ async def read_root():
@router.get("/inspectdataset")
async def inspectdataset(request: Request):
dataset_id = request.query_params.get("dataset")
+ if is_remote(dataset_id):
+ clear_remote_cache()
return inspect_dataset(dataset_id)
From dc90b1051313f30180c2a87e63ff3837ae5a0717 Mon Sep 17 00:00:00 2001
From: Rehpotsirhc <86068565+Rehpotsirhc-z@users.noreply.github.com>
Date: Tue, 9 Jun 2026 12:45:02 -0400
Subject: [PATCH 4/6] Fetch dataset_name on shared link
---
.../src/pages/GenomicRegionView/index.jsx | 17 ++++++++++++
frontend/src/pages/XQTLView/index.jsx | 17 ++++++++++++
frontend/src/store/RemoteDatasetStore.js | 27 ++++++++++++++++++-
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/frontend/src/pages/GenomicRegionView/index.jsx b/frontend/src/pages/GenomicRegionView/index.jsx
index 3e02cc2..516ffce 100644
--- a/frontend/src/pages/GenomicRegionView/index.jsx
+++ b/frontend/src/pages/GenomicRegionView/index.jsx
@@ -33,6 +33,7 @@ import "./GenomicRegionView.css";
import useDataStore from "../../store/DatatableStore.js";
import useSignalStore from "../../store/GenomicRegionStore.js";
+import useRemoteDatasetStore from "../../store/RemoteDatasetStore.js";
import RegionViewPlotlyPlot from "./RegionViewPlotlyPlot.jsx";
@@ -77,11 +78,27 @@ function GenomicRegionView() {
const urlRegion = queryParams.get("region") ?? "";
const { datasetRecords, fetchDatasetList } = useDataStore();
+ const { ensureRemoteDataset } = useRemoteDatasetStore();
useEffect(() => {
fetchDatasetList();
}, []);
+ // When a remote dataset is referenced by URL but not registered locally
+ // (e.g. when pasting in a shared link), register it so its display name is
+ // fetched from the dataset_info.toml
+ useEffect(() => {
+ if (
+ urlDataset &&
+ /^https?:\/\//i.test(urlDataset) &&
+ !datasetRecords.some((d) => d.dataset_id === urlDataset)
+ ) {
+ ensureRemoteDataset(urlDataset).then((added) => {
+ if (added) fetchDatasetList();
+ });
+ }
+ }, [urlDataset, datasetRecords]);
+
const datasetOptions = datasetRecords
.filter((d) => /[A-Za-z]*?(RNAseq|ATACseq)$/i.test(d.assay) && d.has_bw)
.map((d) => d.dataset_id);
diff --git a/frontend/src/pages/XQTLView/index.jsx b/frontend/src/pages/XQTLView/index.jsx
index 5fb92b6..fa9a146 100644
--- a/frontend/src/pages/XQTLView/index.jsx
+++ b/frontend/src/pages/XQTLView/index.jsx
@@ -33,6 +33,7 @@ import "./XQTLView.css";
import useDataStore from "../../store/DatatableStore.js";
import useQtlStore from "../../store/QtlStore.js";
+import useRemoteDatasetStore from "../../store/RemoteDatasetStore.js";
import GeneViewPlotlyPlot from "./GeneViewPlotlyPlot.jsx";
import SNPViewPlotlyPlot from "./SNPViewPlotlyPlot.jsx";
@@ -79,10 +80,26 @@ function XQTLView() {
const urlDataset = queryParams.get("dataset") ?? "";
const { datasetRecords, fetchDatasetList } = useDataStore();
+ const { ensureRemoteDataset } = useRemoteDatasetStore();
useEffect(() => {
fetchDatasetList();
}, []);
+ // When a remote dataset is referenced by URL but not registered locally
+ // (e.g. when pasting in a shared link), register it so its display name is
+ // fetched from the dataset_info.toml
+ useEffect(() => {
+ if (
+ urlDataset &&
+ /^https?:\/\//i.test(urlDataset) &&
+ !datasetRecords.some((d) => d.dataset_id === urlDataset)
+ ) {
+ ensureRemoteDataset(urlDataset).then((added) => {
+ if (added) fetchDatasetList();
+ });
+ }
+ }, [urlDataset, datasetRecords]);
+
const datasetOptions = datasetRecords
.filter((d) => d.assay.toLowerCase().endsWith("qtl"))
.map((d) => d.dataset_id);
diff --git a/frontend/src/store/RemoteDatasetStore.js b/frontend/src/store/RemoteDatasetStore.js
index 462c4d4..0f3f76b 100644
--- a/frontend/src/store/RemoteDatasetStore.js
+++ b/frontend/src/store/RemoteDatasetStore.js
@@ -1,6 +1,8 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
+import { inspectDataset } from "../api/qtl.js";
+
export const buildRemoteRecord = (url, name, info = {}) => {
let assay = info.assay || "";
const hasBw = !!info.has_bw;
@@ -37,7 +39,7 @@ export const buildRemoteRecord = (url, name, info = {}) => {
const useRemoteDatasetStore = create(
persist(
- (set) => ({
+ (set, get) => ({
remoteDatasets: [],
addRemoteDataset: (record) =>
@@ -50,6 +52,29 @@ const useRemoteDatasetStore = create(
],
})),
+ ensureRemoteDataset: async (url) => {
+ if (!url || !/^https?:\/\//i.test(url)) return false;
+ if (get().remoteDatasets.some((r) => r.dataset_id === url)) {
+ return false;
+ }
+ try {
+ const info = await inspectDataset(url);
+ if (!info || !info.reachable) return false;
+ const record = buildRemoteRecord(url, "", info);
+ set((state) => ({
+ remoteDatasets: [
+ ...state.remoteDatasets.filter(
+ (r) => r.dataset_id !== url,
+ ),
+ record,
+ ],
+ }));
+ return true;
+ } catch {
+ return false;
+ }
+ },
+
removeRemoteDataset: (datasetId) =>
set((state) => ({
remoteDatasets: state.remoteDatasets.filter(
From 9e7324e501fb4b6e372a6fb58bcf766d3c26a57e Mon Sep 17 00:00:00 2001
From: Rehpotsirhc <86068565+Rehpotsirhc-z@users.noreply.github.com>
Date: Tue, 9 Jun 2026 13:53:34 -0400
Subject: [PATCH 5/6] Fix dataset list for remote entries
---
frontend/src/store/DatatableStore.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/frontend/src/store/DatatableStore.js b/frontend/src/store/DatatableStore.js
index 5acc61f..c3f2523 100644
--- a/frontend/src/store/DatatableStore.js
+++ b/frontend/src/store/DatatableStore.js
@@ -97,4 +97,11 @@ const useDatatableStore = create((set) => ({
}));
+// When remote datasets change, refresh the merged list so all views pick up the
+// updated remote datasets.
+useRemoteDatasetStore.subscribe(
+ (state) => state.remoteDatasets,
+ () => useDatatableStore.getState().fetchDatasetList(),
+);
+
export default useDatatableStore;
From cd319575351ac2774025f8fdb2b3ea0208ceaf95 Mon Sep 17 00:00:00 2001
From: Rehpotsirhc <86068565+Rehpotsirhc-z@users.noreply.github.com>
Date: Tue, 9 Jun 2026 14:16:30 -0400
Subject: [PATCH 6/6] Add remote capabilities endpoint
Don't allow loading from URL if server does not support it
---
backend/funcs/remote_capabilities.py | 151 ++++++++++++++++++++++++
backend/main.py | 45 ++++++-
frontend/src/api/api.js | 10 ++
frontend/src/pages/Datasets/index.jsx | 17 ++-
frontend/src/store/ServerConfigStore.js | 29 +++++
5 files changed, 247 insertions(+), 5 deletions(-)
create mode 100644 backend/funcs/remote_capabilities.py
create mode 100644 frontend/src/store/ServerConfigStore.js
diff --git a/backend/funcs/remote_capabilities.py b/backend/funcs/remote_capabilities.py
new file mode 100644
index 0000000..ca46465
--- /dev/null
+++ b/backend/funcs/remote_capabilities.py
@@ -0,0 +1,151 @@
+from __future__ import annotations
+
+import importlib
+import logging
+from dataclasses import dataclass, field
+from typing import Callable, List, Optional
+
+logger = logging.getLogger("vizit.remote_capabilities")
+
+
+@dataclass
+class CapabilityResult:
+ name: str # short id
+ required_for: str # description of the feature
+ ok: Optional[bool] # True = available, False = missing, None = could not determine
+ detail: str # description of the check and its result
+ fix_hint: str = "" # how to install
+
+
+# --------------------------------------------------------------------------- #
+# Registry
+# --------------------------------------------------------------------------- #
+CapabilityCheck = Callable[[], CapabilityResult]
+_CHECKS: List[CapabilityCheck] = []
+
+
+def register_capability_check(fn: CapabilityCheck) -> CapabilityCheck:
+ _CHECKS.append(fn)
+ return fn
+
+
+def run_capability_checks() -> List[CapabilityResult]:
+ results: List[CapabilityResult] = []
+ for fn in _CHECKS:
+ try:
+ results.append(fn())
+ except Exception as exc:
+ results.append(
+ CapabilityResult(
+ name=getattr(fn, "__name__", "unknown"),
+ required_for="unknown",
+ ok=None,
+ detail=f"capability check raised {type(exc).__name__}: {exc}",
+ )
+ )
+ return results
+
+
+# --------------------------------------------------------------------------- #
+# Probes
+# --------------------------------------------------------------------------- #
+def _module_available(modname: str) -> bool:
+ try:
+ importlib.import_module(modname)
+ return True
+ except Exception:
+ return False
+
+
+@register_capability_check
+def check_polars_http() -> CapabilityResult:
+ have_fsspec = _module_available("fsspec")
+ have_aiohttp = _module_available("aiohttp")
+ ok = have_fsspec and have_aiohttp
+ if ok:
+ detail = "fsspec + aiohttp present; polars can read parquet/CSV over http(s)."
+ fix = ""
+ else:
+ missing = [
+ m
+ for m, present in (("fsspec", have_fsspec), ("aiohttp", have_aiohttp))
+ if not present
+ ]
+ detail = (
+ f"missing {', '.join(missing)}; polars cannot read parquet/CSV from "
+ "remote URLs (gene/SNP locations, QTL parquet, GWAS tables)."
+ )
+ fix = "pip install fsspec aiohttp"
+ return CapabilityResult(
+ name="polars-http",
+ required_for="remote QTL parquet / gene & SNP locations / GWAS tables",
+ ok=ok,
+ detail=detail,
+ fix_hint=fix,
+ )
+
+
+@register_capability_check
+def check_pybigwig_remote() -> CapabilityResult:
+ try:
+ import pyBigWig
+ except Exception as exc:
+ return CapabilityResult(
+ name="pybigwig-remote",
+ required_for="remote BigWig signal tracks",
+ ok=None,
+ detail=f"pyBigWig import failed: {type(exc).__name__}: {exc}",
+ fix_hint="pip install pyBigWig",
+ )
+
+ ok = bool(getattr(pyBigWig, "remote", 0))
+ if ok:
+ detail = (
+ "pyBigWig was built with libcurl; remote .bw/.bigWig URLs are supported."
+ )
+ fix = ""
+ else:
+ detail = (
+ "pyBigWig was built WITHOUT libcurl; "
+ "remote BigWig signal tracks will fail to open."
+ )
+ fix = (
+ "Install a curl-enabled build, e.g. install libcurl "
+ "and then run `pip install --no-binary pyBigWig pyBigWig`"
+ )
+ return CapabilityResult(
+ name="pybigwig-remote",
+ required_for="remote BigWig signal tracks",
+ ok=ok,
+ detail=detail,
+ fix_hint=fix,
+ )
+
+
+# --------------------------------------------------------------------------- #
+# Startup
+# --------------------------------------------------------------------------- #
+def warn_on_missing_remote_capabilities() -> List[CapabilityResult]:
+ results = run_capability_checks()
+ missing = [r for r in results if r.ok is False]
+ unknown = [r for r in results if r.ok is None]
+
+ if not missing and not unknown:
+ logger.info(
+ "Remote datasets enabled; all %d optional capabilities available.",
+ len(results),
+ )
+ return results
+
+ logger.warning(
+ "Remote datasets are ENABLED but %d capability(ies) are unavailable. "
+ "Affected features will error for remote datasets:",
+ len(missing) + len(unknown),
+ )
+ for r in missing + unknown:
+ state = "MISSING" if r.ok is False else "UNKNOWN"
+ logger.warning(" [%s] %s — required for: %s", state, r.name, r.required_for)
+ logger.warning(" %s", r.detail)
+ if r.fix_hint:
+ logger.warning(" fix: %s", r.fix_hint)
+ return results
diff --git a/backend/main.py b/backend/main.py
index e413502..97a367b 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -4,18 +4,38 @@
from starlette.requests import Request
from backend.db import create_db_and_tables
-from backend.routes import db_routes, api_routes, visium_routes, qtl_routes, dm_routes,signal_routes
+from backend.routes import (
+ db_routes,
+ api_routes,
+ visium_routes,
+ qtl_routes,
+ dm_routes,
+ signal_routes,
+)
from backend.funcs.remote_io import RemoteDatasetError
+from backend.funcs.remote_capabilities import (
+ warn_on_missing_remote_capabilities,
+ run_capability_checks,
+)
from backend.settings import settings
app = FastAPI(debug=settings.debug)
+def check_remote_capabilities_on_startup():
+ # Only relevant remote datasets is enabled
+ if settings.allow_remote_datasets:
+ warn_on_missing_remote_capabilities()
+
+
@app.exception_handler(RemoteDatasetError)
async def remote_dataset_error_handler(request: Request, exc: RemoteDatasetError):
# A remote (client-supplied) dataset URL was disallowed or unreachable.
- return JSONResponse(status_code=400, content={"detail": f"Remote dataset error: {exc}"})
+ return JSONResponse(
+ status_code=400, content={"detail": f"Remote dataset error: {exc}"}
+ )
+
app.add_middleware(
CORSMiddleware,
@@ -28,11 +48,31 @@ async def remote_dataset_error_handler(request: Request, exc: RemoteDatasetError
)
app.add_event_handler("startup", create_db_and_tables)
+app.add_event_handler("startup", check_remote_capabilities_on_startup)
+
@app.get("/")
async def root():
return "Hello, Welcome to VizIt!"
+
+@app.get("/serverconfig")
+async def server_config():
+ caps = run_capability_checks() if settings.allow_remote_datasets else []
+ return {
+ "allow_remote_datasets": settings.allow_remote_datasets,
+ "remote_capabilities": [
+ {
+ "name": c.name,
+ "required_for": c.required_for,
+ "ok": c.ok,
+ "detail": c.detail,
+ }
+ for c in caps
+ ],
+ }
+
+
app.include_router(db_routes.router, prefix="/db")
app.include_router(api_routes.router, prefix="/api")
app.include_router(visium_routes.router, prefix="/visium")
@@ -42,6 +82,7 @@ async def root():
if __name__ == "__main__":
import uvicorn
+
print("Starting FastAPI server on port", settings.uvicorn_port)
uvicorn.run(app, host=settings.uvicorn_host, port=settings.uvicorn_port)
diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js
index 9d274c6..f1066ad 100644
--- a/frontend/src/api/api.js
+++ b/frontend/src/api/api.js
@@ -3,6 +3,16 @@ import axios from "axios";
const BASE_URL = import.meta.env.VITE_BACKEND_URL;
const API_URL = `${BASE_URL}/api`;
+export const getServerConfig = async () => {
+ try {
+ const response = await axios.get(`${BASE_URL}/serverconfig`);
+ return response.data;
+ } catch (error) {
+ console.error("Error getServerConfig:", error);
+ throw error;
+ }
+}
+
export const getHomeData = async () => {
try {
const response = await axios.get(`${API_URL}/gethomedata`);
diff --git a/frontend/src/pages/Datasets/index.jsx b/frontend/src/pages/Datasets/index.jsx
index 252be5a..587a015 100644
--- a/frontend/src/pages/Datasets/index.jsx
+++ b/frontend/src/pages/Datasets/index.jsx
@@ -6,11 +6,13 @@ import DatasetDisplay from "./DatasetDisplay.jsx";
import LoadRemoteDatasetDialog from "./LoadRemoteDatasetDialog.jsx";
import "./DatasetPage.css";
import useDatatableStore from "../../store/DatatableStore.js";
+import useServerConfigStore from "../../store/ServerConfigStore.js";
import {useSearchParams} from "react-router-dom";
const DatasetsPage = () => {
const {datasetRecords, fetchDatasetList} = useDatatableStore();
+ const {allowRemoteDatasets, fetchServerConfig} = useServerConfigStore();
const [searchParams, setSearchParams] = useSearchParams();
// Initialize filters from URL params
@@ -27,6 +29,13 @@ const DatasetsPage = () => {
fetchDatasetList()
}, [fetchDatasetList]);
+ useEffect(() => {
+ fetchServerConfig()
+ }, [fetchServerConfig]);
+
+ // null keeps button visible for backward compatibility
+ const remoteEnabled = allowRemoteDatasets !== false;
+
// Update URL when filters change
useEffect(() => {
const newSearchParams = new URLSearchParams()
@@ -144,9 +153,11 @@ const DatasetsPage = () => {
{/* {deleteMode ? "Cancel Delete" : "- Delete Dataset"}*/}
{/**/}
-
+ {remoteEnabled && (
+
+ )}