diff --git a/.env.example b/.env.example
index 71a87bb..79fafea 100644
--- a/.env.example
+++ b/.env.example
@@ -5,6 +5,8 @@ DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=9bplus
SCRAPE_DB_NAME=catalog_scrape
+DBGATE_URL=http://127.0.0.1:3000
+DBGATE_BROWSER_ENGINE=chromium
DB_USER=catalog_maker
DB_PASSWORD=
DB_CONNECTION_LIMIT=5
diff --git a/agent.md b/agent.md
index 8063b35..f7d74be 100644
--- a/agent.md
+++ b/agent.md
@@ -105,6 +105,7 @@ There are three selectable data sources:
- Uses the separate local MariaDB database named `catalog_scrape`.
- Stores scraper source configuration, manufacturer-to-source mapping, scrape runs, discovered products and assets.
- Supplier/manufacturer website settings such as source URL, search templates, fallback mode, browser engine, headless mode, wait time, notes and enabled/priority flags belong in Local scrape DB, not in the product catalog database.
+- The Settings modal should expose an `Open DBGate` action beside `Local scrape DB`; it opens the local DBGate URL from `DBGATE_URL` / `databaseGate.url` for inspecting `catalog_scrape`.
- It must never be used as a fallback for catalog reads. When selected in the UI, existing catalog reads continue using Local 9bplus DB until dedicated scrape-storage endpoints are added.
- The schema is versioned in `sql/local-scrape-db.sql` and can be created with `npm run db:create-scrape`.
- Existing legacy mapping rows from `Local 9bplus DB` can be copied into Local scrape DB with `npm run db:migrate-mapping-to-scrape`.
diff --git a/config/local.example.json b/config/local.example.json
index fc744a0..f315f78 100644
--- a/config/local.example.json
+++ b/config/local.example.json
@@ -24,5 +24,9 @@
"user": "catalog_maker",
"password": "",
"connectionLimit": 5
+ },
+ "databaseGate": {
+ "url": "http://127.0.0.1:3000",
+ "browserEngine": "chromium"
}
}
diff --git a/public/app.js b/public/app.js
index 765779a..b013cfc 100644
--- a/public/app.js
+++ b/public/app.js
@@ -50,6 +50,7 @@ const localDbFields = document.querySelector("#localDbFields");
const localDbInputs = document.querySelectorAll("#localDbFields input");
const testLocalDbButton = document.querySelector("#testLocalDbButton");
const localDbTestResult = document.querySelector("#localDbTestResult");
+const openDbGateButton = document.querySelector("#openDbGateButton");
const lightContrastSelect = document.querySelector("#lightContrastSelect");
const darkContrastSelect = document.querySelector("#darkContrastSelect");
const catalogScreen = document.querySelector(".catalog-screen");
@@ -239,6 +240,7 @@ for (const input of transactionModeInputs)
for (const input of localDbInputs)
input.addEventListener("change", saveLocalDbSettings);
testLocalDbButton.addEventListener("click", testLocalDbConnection);
+openDbGateButton.addEventListener("click", openDbGate);
lightContrastSelect.addEventListener("change", saveAppearance);
darkContrastSelect.addEventListener("change", saveAppearance);
loadDatabaseMode();
@@ -1057,6 +1059,32 @@ async function testLocalDbConnection() {
testLocalDbButton.disabled = false;
}
}
+async function openDbGate() {
+ openDbGateButton.disabled = true;
+ const previousNote = databaseModeNote.textContent;
+ databaseModeNote.textContent = "Opening DBGate...";
+ try {
+ const response = await fetch("/api/tools/open-dbgate");
+ const data = await response.json();
+ if (!response.ok || !data.opened)
+ throw new Error(data.message || "DBGate could not be opened.");
+ databaseModeNote.textContent = `DBGate opened: ${data.url}`;
+ }
+ catch (error) {
+ databaseModeNote.textContent = `DBGate error: ${error.message}`;
+ }
+ finally {
+ openDbGateButton.disabled = false;
+ window.setTimeout(() => {
+ if (databaseModeNote.textContent.startsWith("DBGate")) {
+ renderDatabaseModeNote(localStorage.getItem("catalog-maker:database-mode") || "local");
+ }
+ else if (databaseModeNote.textContent === "Opening DBGate...") {
+ databaseModeNote.textContent = previousNote;
+ }
+ }, 3500);
+ }
+}
function syncLocalDbFields(mode) {
localDbFields.hidden = mode !== "local";
}
diff --git a/public/app.ts b/public/app.ts
index 4638cbb..7fbaa8c 100644
--- a/public/app.ts
+++ b/public/app.ts
@@ -50,6 +50,7 @@ const localDbFields = document.querySelector("#localDbFields");
const localDbInputs = document.querySelectorAll("#localDbFields input");
const testLocalDbButton = document.querySelector("#testLocalDbButton");
const localDbTestResult = document.querySelector("#localDbTestResult");
+const openDbGateButton = document.querySelector("#openDbGateButton");
const lightContrastSelect = document.querySelector("#lightContrastSelect");
const darkContrastSelect = document.querySelector("#darkContrastSelect");
const catalogScreen = document.querySelector(".catalog-screen");
@@ -237,6 +238,7 @@ for (const input of permissionInputs) input.addEventListener("change", saveSetti
for (const input of transactionModeInputs) input.addEventListener("change", saveSettings);
for (const input of localDbInputs) input.addEventListener("change", saveLocalDbSettings);
testLocalDbButton.addEventListener("click", testLocalDbConnection);
+openDbGateButton.addEventListener("click", openDbGate);
lightContrastSelect.addEventListener("change", saveAppearance);
darkContrastSelect.addEventListener("change", saveAppearance);
@@ -1151,6 +1153,30 @@ async function testLocalDbConnection() {
}
}
+async function openDbGate() {
+ openDbGateButton.disabled = true;
+ const previousNote = databaseModeNote.textContent;
+ databaseModeNote.textContent = "Opening DBGate...";
+
+ try {
+ const response = await fetch("/api/tools/open-dbgate");
+ const data = await response.json();
+ if (!response.ok || !data.opened) throw new Error(data.message || "DBGate could not be opened.");
+ databaseModeNote.textContent = `DBGate opened: ${data.url}`;
+ } catch (error) {
+ databaseModeNote.textContent = `DBGate error: ${error.message}`;
+ } finally {
+ openDbGateButton.disabled = false;
+ window.setTimeout(() => {
+ if (databaseModeNote.textContent.startsWith("DBGate")) {
+ renderDatabaseModeNote(localStorage.getItem("catalog-maker:database-mode") || "local");
+ } else if (databaseModeNote.textContent === "Opening DBGate...") {
+ databaseModeNote.textContent = previousNote;
+ }
+ }, 3500);
+ }
+}
+
function syncLocalDbFields(mode) {
localDbFields.hidden = mode !== "local";
}
diff --git a/public/icons.svg b/public/icons.svg
index aae79fc..d4c2636 100644
--- a/public/icons.svg
+++ b/public/icons.svg
@@ -16,4 +16,5 @@
+
diff --git a/public/index.html b/public/index.html
index fd09933..e00ce11 100644
--- a/public/index.html
+++ b/public/index.html
@@ -175,6 +175,10 @@