Add web front-end and tailwindcss support
CI/CD Pipeline / test (push) Failing after 2m47s
CI/CD Pipeline / deploy-internal (push) Has been skipped

This commit is contained in:
chris committed 2026-08-29 13:16:54 -04:00
1 parent 3badea2832
commit 53482a24b7
7 files changed
+1395 -36

No files matched your search

+9 -2
View File
@@ -1,11 +1,18 @@
{
"name": "book-test web",
"name": "book-test-web",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "tsx src/index.ts"
"build:css": "tailwindcss -i ./src/input.css -o ./public/output.css --minify",
"watch:css": "tailwindcss -i ./src/input.css -o ./public/output.css --watch",
"start": "npm run build:css && tsx src/index.ts",
"dev:server": "tsx src/index.ts"
},
"dependencies": {
"express": "^5.2.1"
},
"devDependencies": {
"@tailwindcss/cli": "^4.3.3",
"tailwindcss": "^4.3.3"
}
}
+51 -29
View File
@@ -1,30 +1,32 @@
import express from 'express';
import { join } from 'node:path';
const app = express();
const PORT = process.env.WEB_PORT || 3001;
const API_URL = process.env.API_URL || 'http://localhost:8300';
// 1. Tell Express to serve static files (like your output.css) out of the public folder
app.use(express.static(join(import.meta.dirname, '../public')));
app.get('/', async (req, res) => {
try {
// 1. Fetch the hierarchy structure from your API workspace
const apiResponse = await fetch(`${API_URL}/api/categories/tree`);
if (!apiResponse.ok) {
throw new Error(`API responded with status: ${apiResponse.status}`);
}
if (!apiResponse.ok) throw new Error(`API responded with status: ${apiResponse.status}`);
const apiData = await apiResponse.json();
const categoriesTree = apiData.success ? apiData.data : [];
// 2. Recursive layout engine to convert nested JSON arrays to clean HTML lists
function renderTreeHTML(nodes: any[]): string {
if (!nodes || nodes.length === 0) return '';
let html = '<ul>';
let html = '<ul class="pl-6 border-l border-dashed border-slate-200 my-1 space-y-2">';
for (const node of nodes) {
html += `
<li>
<span class="folder-name">${node.name}</span>
<span class="path-tag">${node.path}</span>
<li class="relative transition-all duration-200">
<div class="folder-group inline-flex items-center gap-2 cursor-pointer select-none py-1 px-2 rounded-md hover:bg-slate-50">
<span class="folder-toggle-icon text-slate-400 font-mono text-xs inline-block transition-transform duration-200 w-4 text-center"></span>
<span class="folder-name font-semibold text-slate-700 hover:text-blue-600">${node.name}</span>
<span class="path-tag text-[10px] font-mono font-medium text-slate-400 bg-slate-100 border border-slate-200/60 px-1.5 py-0.5 rounded ml-2">${node.path}</span>
</div>
${renderTreeHTML(node.children)}
</li>
`;
@@ -35,38 +37,58 @@ app.get('/', async (req, res) => {
const treeHTML = renderTreeHTML(categoriesTree);
// 3. Serve the clean, dependency-free UI page
// 2. Link directly to the local static compiled stylesheet
res.send(`
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="h-full bg-slate-50">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hierarchical Ltree Viewer</title>
<!-- Linked to your local, compiled, minified CSS bundle -->
<link rel="stylesheet" href="/output.css">
<style>
body { font-family: -apple-system, sans-serif; padding: 3rem; background: #fafafa; color: #333; }
h1 { color: #111; margin-bottom: 0.5rem; }
.subtitle { color: #666; margin-bottom: 2rem; font-size: 0.9rem; }
ul { list-style-type: none; padding-left: 20px; border-left: 1px dashed #ccc; margin: 5px 0; }
li { margin: 8px 0; position: relative; }
.folder-name { font-weight: 600; color: #2c3e50; }
.path-tag { font-size: 0.75rem; color: #95a5a6; background: #eef2f3; padding: 2px 6px; border-radius: 4px; margin-left: 8px; font-family: monospace; }
.collapsed > ul { display: none; }
.collapsed > .folder-group .folder-toggle-icon { transform: rotate(-90deg); }
</style>
</head>
<body>
<h1>Hierarchy Tree Explorer</h1>
<div class="subtitle">Connected to API target: <code>${API_URL}</code></div>
<div id="tree-container">
${treeHTML || '<p>No root categories found. Add some paths to your ltree table!</p>'}
</div>
<body class="h-full antialiased text-slate-800 p-6 md:p-12 max-w-4xl mx-auto">
<header class="mb-8 border-b border-slate-200 pb-6">
<h1 class="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">Hierarchy Tree Explorer</h1>
<p class="mt-2 text-sm text-slate-500">Connected to API cluster target: <code class="bg-slate-100 text-slate-700 px-2 py-1 rounded font-mono text-xs">${API_URL}</code></p>
</header>
<main class="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8">
<div id="tree-container" class="flow-root">
${treeHTML || '<p class="text-slate-500 text-center py-4">No root categories found. Add some paths to your ltree table!</p>'}
</div>
</main>
<script>
document.querySelectorAll('.folder-group').forEach(group => {
const hasChildren = group.parentElement.querySelector('ul');
const iconSpan = group.querySelector('.folder-toggle-icon');
if (hasChildren) {
if (iconSpan) iconSpan.textContent = '▼';
group.addEventListener('click', () => {
group.parentElement.classList.toggle('collapsed');
});
} else {
if (iconSpan) {
iconSpan.textContent = '•';
iconSpan.classList.remove('text-slate-400');
iconSpan.classList.add('text-slate-300', 'translate-y-[-1px]');
}
}
});
</script>
</body>
</html>
`);
} catch (error) {
console.error('Frontend error fetching data:', error);
res.status(500).send(`
<h1>500 Frontend Render Failure</h1>
<p>Could not reach your API application container. Verify that the API server is active on port 3000.</p>
`);
res.status(500).send(`<h1>500 Frontend Render Failure</h1><p>Could not reach your API application container.</p>`);
}
});
+1
View File
@@ -0,0 +1 @@
@import "tailwindcss";