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 @@