Restore DBGate beside Adminer
This commit is contained in:
@@ -7,6 +7,8 @@ DB_NAME=9bplus
|
|||||||
SCRAPE_DB_NAME=catalog_scrape
|
SCRAPE_DB_NAME=catalog_scrape
|
||||||
ADMINER_URL=http://127.0.0.1:8080
|
ADMINER_URL=http://127.0.0.1:8080
|
||||||
ADMINER_BROWSER_ENGINE=chromium
|
ADMINER_BROWSER_ENGINE=chromium
|
||||||
|
DBGATE_URL=http://127.0.0.1:3000
|
||||||
|
DBGATE_BROWSER_ENGINE=chromium
|
||||||
DB_USER=catalog_maker
|
DB_USER=catalog_maker
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
DB_CONNECTION_LIMIT=5
|
DB_CONNECTION_LIMIT=5
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ There are three selectable data sources:
|
|||||||
- Stores scraper source configuration, manufacturer-to-source mapping, scrape runs, discovered products and assets.
|
- 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.
|
- 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 Adminer` action beside `Local scrape DB`; it opens the local Adminer URL from `ADMINER_URL` / `databaseTool.url` for inspecting local MariaDB databases.
|
- The Settings modal should expose an `Open Adminer` action beside `Local scrape DB`; it opens the local Adminer URL from `ADMINER_URL` / `databaseTool.url` for inspecting local MariaDB databases.
|
||||||
|
- The Settings modal may also expose `Open DBGate` beside Adminer for a richer local database browser. DBGate should stay local, start with `npm run dbgate`, and use `DBGATE_URL` / `dbGate.url`.
|
||||||
- Start Adminer only through `npm run adminer`; the script downloads the ignored local `tools/adminer/adminer.php` file when missing and runs it on the configured local Adminer URL. Adminer requires local PHP CLI.
|
- Start Adminer only through `npm run adminer`; the script downloads the ignored local `tools/adminer/adminer.php` file when missing and runs it on the configured local Adminer URL. Adminer requires local PHP CLI.
|
||||||
- Adminer login help may show non-secret local connection fields such as system, host, port, database and user, but must never render or print the actual password. Show password source as `.env DB_PASSWORD` instead.
|
- Adminer login help may show non-secret local connection fields such as system, host, port, database and user, but must never render or print the actual password. Show password source as `.env DB_PASSWORD` instead.
|
||||||
- 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.
|
- 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.
|
||||||
|
|||||||
Generated
+11571
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@
|
|||||||
"db:create-scrape": "tsx scripts/create-local-scrape-db.ts",
|
"db:create-scrape": "tsx scripts/create-local-scrape-db.ts",
|
||||||
"db:migrate-mapping-to-scrape": "tsx scripts/migrate-mapping-to-scrape-db.ts",
|
"db:migrate-mapping-to-scrape": "tsx scripts/migrate-mapping-to-scrape-db.ts",
|
||||||
"adminer": "tsx scripts/start-adminer.ts",
|
"adminer": "tsx scripts/start-adminer.ts",
|
||||||
|
"dbgate": "dbgate-serve",
|
||||||
"test": "tsx --test test/*.test.ts",
|
"test": "tsx --test test/*.test.ts",
|
||||||
"test:ui": "playwright test",
|
"test:ui": "playwright test",
|
||||||
"test:e2e": "playwright test tests/ui/database.e2e.spec.ts",
|
"test:e2e": "playwright test tests/ui/database.e2e.spec.ts",
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/cli": "^4.3.3",
|
"@tailwindcss/cli": "^4.3.3",
|
||||||
"@types/node": "^26.1.2",
|
"@types/node": "^26.1.2",
|
||||||
|
"dbgate-serve": "^7.2.3",
|
||||||
"tailwindcss": "^4.3.3",
|
"tailwindcss": "^4.3.3",
|
||||||
"tsx": "^4.23.7",
|
"tsx": "^4.23.7",
|
||||||
"typescript": "^7.0.2"
|
"typescript": "^7.0.2"
|
||||||
|
|||||||
+31
-2
@@ -50,6 +50,7 @@ const localDbFields = document.querySelector("#localDbFields");
|
|||||||
const localDbInputs = document.querySelectorAll("#localDbFields input");
|
const localDbInputs = document.querySelectorAll("#localDbFields input");
|
||||||
const testLocalDbButton = document.querySelector("#testLocalDbButton");
|
const testLocalDbButton = document.querySelector("#testLocalDbButton");
|
||||||
const localDbTestResult = document.querySelector("#localDbTestResult");
|
const localDbTestResult = document.querySelector("#localDbTestResult");
|
||||||
|
const openAdminerButton = document.querySelector("#openAdminerButton");
|
||||||
const openDbGateButton = document.querySelector("#openDbGateButton");
|
const openDbGateButton = document.querySelector("#openDbGateButton");
|
||||||
const adminerServerValue = document.querySelector("#adminerServerValue");
|
const adminerServerValue = document.querySelector("#adminerServerValue");
|
||||||
const adminerUserValue = document.querySelector("#adminerUserValue");
|
const adminerUserValue = document.querySelector("#adminerUserValue");
|
||||||
@@ -243,6 +244,7 @@ for (const input of transactionModeInputs)
|
|||||||
for (const input of localDbInputs)
|
for (const input of localDbInputs)
|
||||||
input.addEventListener("change", saveLocalDbSettings);
|
input.addEventListener("change", saveLocalDbSettings);
|
||||||
testLocalDbButton.addEventListener("click", testLocalDbConnection);
|
testLocalDbButton.addEventListener("click", testLocalDbConnection);
|
||||||
|
openAdminerButton.addEventListener("click", openAdminer);
|
||||||
openDbGateButton.addEventListener("click", openDbGate);
|
openDbGateButton.addEventListener("click", openDbGate);
|
||||||
lightContrastSelect.addEventListener("change", saveAppearance);
|
lightContrastSelect.addEventListener("change", saveAppearance);
|
||||||
darkContrastSelect.addEventListener("change", saveAppearance);
|
darkContrastSelect.addEventListener("change", saveAppearance);
|
||||||
@@ -923,6 +925,7 @@ function closeSourceCandidates() {
|
|||||||
sourceList.replaceChildren();
|
sourceList.replaceChildren();
|
||||||
}
|
}
|
||||||
function openSettings() {
|
function openSettings() {
|
||||||
|
loadDatabaseMode();
|
||||||
settingsModal.hidden = false;
|
settingsModal.hidden = false;
|
||||||
}
|
}
|
||||||
function syncSidebarForViewport() {
|
function syncSidebarForViewport() {
|
||||||
@@ -1073,8 +1076,8 @@ async function testLocalDbConnection() {
|
|||||||
testLocalDbButton.disabled = false;
|
testLocalDbButton.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function openDbGate() {
|
async function openAdminer() {
|
||||||
openDbGateButton.disabled = true;
|
openAdminerButton.disabled = true;
|
||||||
const previousNote = databaseModeNote.textContent;
|
const previousNote = databaseModeNote.textContent;
|
||||||
databaseModeNote.textContent = "Opening Adminer...";
|
databaseModeNote.textContent = "Opening Adminer...";
|
||||||
try {
|
try {
|
||||||
@@ -1099,6 +1102,32 @@ async function openDbGate() {
|
|||||||
}, 3500);
|
}, 3500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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) {
|
function syncLocalDbFields(mode) {
|
||||||
localDbFields.hidden = mode !== "local";
|
localDbFields.hidden = mode !== "local";
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-2
@@ -50,6 +50,7 @@ const localDbFields = document.querySelector("#localDbFields");
|
|||||||
const localDbInputs = document.querySelectorAll("#localDbFields input");
|
const localDbInputs = document.querySelectorAll("#localDbFields input");
|
||||||
const testLocalDbButton = document.querySelector("#testLocalDbButton");
|
const testLocalDbButton = document.querySelector("#testLocalDbButton");
|
||||||
const localDbTestResult = document.querySelector("#localDbTestResult");
|
const localDbTestResult = document.querySelector("#localDbTestResult");
|
||||||
|
const openAdminerButton = document.querySelector("#openAdminerButton");
|
||||||
const openDbGateButton = document.querySelector("#openDbGateButton");
|
const openDbGateButton = document.querySelector("#openDbGateButton");
|
||||||
const adminerServerValue = document.querySelector("#adminerServerValue");
|
const adminerServerValue = document.querySelector("#adminerServerValue");
|
||||||
const adminerUserValue = document.querySelector("#adminerUserValue");
|
const adminerUserValue = document.querySelector("#adminerUserValue");
|
||||||
@@ -241,6 +242,7 @@ for (const input of permissionInputs) input.addEventListener("change", saveSetti
|
|||||||
for (const input of transactionModeInputs) input.addEventListener("change", saveSettings);
|
for (const input of transactionModeInputs) input.addEventListener("change", saveSettings);
|
||||||
for (const input of localDbInputs) input.addEventListener("change", saveLocalDbSettings);
|
for (const input of localDbInputs) input.addEventListener("change", saveLocalDbSettings);
|
||||||
testLocalDbButton.addEventListener("click", testLocalDbConnection);
|
testLocalDbButton.addEventListener("click", testLocalDbConnection);
|
||||||
|
openAdminerButton.addEventListener("click", openAdminer);
|
||||||
openDbGateButton.addEventListener("click", openDbGate);
|
openDbGateButton.addEventListener("click", openDbGate);
|
||||||
lightContrastSelect.addEventListener("change", saveAppearance);
|
lightContrastSelect.addEventListener("change", saveAppearance);
|
||||||
darkContrastSelect.addEventListener("change", saveAppearance);
|
darkContrastSelect.addEventListener("change", saveAppearance);
|
||||||
@@ -1010,6 +1012,7 @@ function closeSourceCandidates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openSettings() {
|
function openSettings() {
|
||||||
|
loadDatabaseMode();
|
||||||
settingsModal.hidden = false;
|
settingsModal.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,8 +1171,8 @@ async function testLocalDbConnection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDbGate() {
|
async function openAdminer() {
|
||||||
openDbGateButton.disabled = true;
|
openAdminerButton.disabled = true;
|
||||||
const previousNote = databaseModeNote.textContent;
|
const previousNote = databaseModeNote.textContent;
|
||||||
databaseModeNote.textContent = "Opening Adminer...";
|
databaseModeNote.textContent = "Opening Adminer...";
|
||||||
|
|
||||||
@@ -1192,6 +1195,30 @@ async function openDbGate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
function syncLocalDbFields(mode) {
|
||||||
localDbFields.hidden = mode !== "local";
|
localDbFields.hidden = mode !== "local";
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-4
@@ -175,10 +175,16 @@
|
|||||||
<label class="settings-option">
|
<label class="settings-option">
|
||||||
<input type="radio" name="databaseMode" value="scrape" />
|
<input type="radio" name="databaseMode" value="scrape" />
|
||||||
<span>Local scrape DB</span>
|
<span>Local scrape DB</span>
|
||||||
<button class="settings-inline-button" id="openDbGateButton" type="button">
|
<span class="settings-tool-actions">
|
||||||
<svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#external"></use></svg>
|
<button class="settings-inline-button" id="openAdminerButton" type="button">
|
||||||
<span>Open Adminer</span>
|
<svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#external"></use></svg>
|
||||||
</button>
|
<span>Adminer</span>
|
||||||
|
</button>
|
||||||
|
<button class="settings-inline-button" id="openDbGateButton" type="button">
|
||||||
|
<svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#external"></use></svg>
|
||||||
|
<span>DBGate</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="local-db-fields" id="localDbFields">
|
<div class="local-db-fields" id="localDbFields">
|
||||||
<label class="settings-field">
|
<label class="settings-field">
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -51,6 +51,14 @@ export function loadConfig({ configPath = "config/local.json", dryRun } = {}) {
|
|||||||
|| "chromium",
|
|| "chromium",
|
||||||
).toLowerCase(),
|
).toLowerCase(),
|
||||||
},
|
},
|
||||||
|
dbGate: {
|
||||||
|
url: env.DBGATE_URL || config.dbGate?.url || "http://127.0.0.1:3000",
|
||||||
|
browserEngine: String(
|
||||||
|
env.DBGATE_BROWSER_ENGINE
|
||||||
|
|| config.dbGate?.browserEngine
|
||||||
|
|| "chromium",
|
||||||
|
).toLowerCase(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,5 +112,9 @@ function mergeConfig(base, override) {
|
|||||||
...override.databaseTool,
|
...override.databaseTool,
|
||||||
...override.databaseGate,
|
...override.databaseGate,
|
||||||
},
|
},
|
||||||
|
dbGate: {
|
||||||
|
...base.dbGate,
|
||||||
|
...override.dbGate,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,16 @@ const server = http.createServer(async (request, response) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (url.pathname === "/api/tools/open-dbgate") {
|
||||||
|
await openInBrowser(config.dbGate.url, { engine: config.dbGate.browserEngine });
|
||||||
|
await sendJson(response, {
|
||||||
|
opened: true,
|
||||||
|
url: config.dbGate.url,
|
||||||
|
browserEngine: config.dbGate.browserEngine,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (url.pathname === "/api/catalog-maker/product-info") {
|
if (url.pathname === "/api/catalog-maker/product-info") {
|
||||||
await sendJson(
|
await sendJson(
|
||||||
response,
|
response,
|
||||||
|
|||||||
@@ -196,6 +196,8 @@
|
|||||||
.settings-group legend { @apply px-1 text-xs font-semibold text-slate-700; }
|
.settings-group legend { @apply px-1 text-xs font-semibold text-slate-700; }
|
||||||
.settings-option { @apply flex items-center gap-1 py-0.5 text-[11px] text-slate-700; }
|
.settings-option { @apply flex items-center gap-1 py-0.5 text-[11px] text-slate-700; }
|
||||||
.settings-option .settings-inline-button { @apply ml-auto py-0.5 text-[10px]; }
|
.settings-option .settings-inline-button { @apply ml-auto py-0.5 text-[10px]; }
|
||||||
|
.settings-tool-actions { @apply ml-auto flex items-center gap-1; }
|
||||||
|
.settings-tool-actions .settings-inline-button { @apply ml-0; }
|
||||||
.settings-option input { @apply accent-catalog-focus; }
|
.settings-option input { @apply accent-catalog-focus; }
|
||||||
.settings-option input:disabled + span { @apply text-slate-400; }
|
.settings-option input:disabled + span { @apply text-slate-400; }
|
||||||
.local-db-fields { @apply grid gap-1; }
|
.local-db-fields { @apply grid gap-1; }
|
||||||
|
|||||||
Reference in New Issue
Block a user