haze-cms
v0.3.3
Published
Vue 3 UI for a generic data-table CMS shell (layout, tables, login, column config). This repo is both a **standalone app** (for development) and an **npm-style library** you can mount inside another Vue project for a unified CMS look.
Readme
haze-cms
Vue 3 UI for a generic data-table CMS shell (layout, tables, login, column config). This repo is both a standalone app (for development) and an npm-style library you can mount inside another Vue project for a unified CMS look.
Requirements
- Node.js and npm
- Vue 3, Vue Router 4, and Axios in the host project when you consume the library (they are listed as
peerDependencies)
Develop in this repo
npm install
npm run serveProduction bundle for deploying this UI as a full SPA (not the library artifact):
npm run buildLint:
npm run lintOptional: point the default REST adapter at your API (see Environment).
Build the library
npm run build:libOutput goes to dist-lib/:
| File | Purpose |
|------|---------|
| haze-cms.es.js | ESM entry (import / exports.import) |
| haze-cms.cjs.js | CommonJS entry (require / exports.require) |
| haze-cms.umd.js | UMD bundle for CDN (unpkg / jsdelivr) |
| haze-cms.css | Extracted styles — must be imported in the host (same build as the JS, so scoped data-v-* matches) |
All of the above are produced in one vite build (vite.lib.config.mjs). Do not combine this with vue-cli-service --target lib for the same package, or scoped SFC CSS will no longer match the compiled components.
package.json main, module, and exports point at haze-cms.cjs.js / haze-cms.es.js. The files field ships dist-lib.
Use in another Vue project
1. Add the package
Examples:
- Local path (monorepo):
"haze-cms": "file:../haze-cms" - Packed tarball: run
npm packin this repo, then depend on the generated.tgz - npm / private registry: publish this package, then
npm install haze-cms
Install peers in the host if they are not already present:
npm install vue@^3 vue-router@^4 axios@^12. Import styles
The CMS layout and components rely on bundled CSS:
import 'haze-cms/dist-lib/haze-cms.css';(If your resolver maps the package root differently, adjust the path so it resolves to haze-cms.css inside dist-lib.)
3. Bootstrap the app
The UI expects:
- A data adapter (
api) — object with async methods the screens call (see API adapter). - Auth helpers (
auth) —isLoggedIn,persistLogin,getUser, etc. (see Auth). createCms(recommended) — resolvestablesonce and returns bothrouterandpluginso routes, sidebar, home dashboard, and table-config labels never disagree on how many entities you have.
Alternatively, call createCmsRouter and createCmsPlugin separately only if you need full control; you must pass the same tables array to both.
Minimal host entry (same idea as src/main.js in this repo):
import { createApp } from 'vue';
import { createWebHistory } from 'vue-router';
import {
App,
createCms,
defaultCmsTables,
defaultAuthHelpers,
createRestApiAdapter
} from 'haze-cms';
import 'haze-cms/dist-lib/haze-cms.css';
const api = createRestApiAdapter({
baseURL: import.meta.env.VITE_API_URL ?? '/api' // or process.env.VUE_APP_API_URL in Vue CLI
});
const tables = defaultCmsTables();
// Or: const tables = getTables() from your host cms/schema.js
const { router, plugin } = createCms({
api,
auth: defaultAuthHelpers(),
tables,
history: createWebHistory(import.meta.env.BASE_URL)
});
createApp(App).use(plugin).use(router).mount('#app');If you split router and plugin manually, use the same auth and tables references for both so route names and sidebar stay in sync.
Public API (src/index.js)
| Export | Role |
|--------|------|
| App | Root component (<router-view /> only) |
| createCms({ api, auth?, tables?, history?, loginPath?, rootPath?, extraChildren? }) | Returns { router, plugin, tables, auth } with one shared tables list for routes + inject (recommended for hosts) |
| createCmsPlugin({ api, auth?, tables? }) | Registers provide for the adapter, auth, and sidebar tables (defaults to defaultCmsTables()) |
| createCmsRouter({ history?, tables?, loginPath?, rootPath?, auth, extraChildren? }) | Creates the CMS router and navigation guards |
| defaultCmsTables() | Demo default table definitions (path, name, db, title); hosts usually pass their own tables array |
| buildDefaultLayoutChildren(tables) | Builds child route records (advanced composition) |
| defaultAuthHelpers() | LocalStorage-based session helpers |
| createRestApiAdapter(options) | Reference REST client matching the original server routes |
| CMS_API_KEY, CMS_AUTH_KEY, CMS_TABLES_KEY | inject keys for adapter, auth, and table nav config |
API adapter
All table/login/config views use inject to receive the adapter. Your object should expose the same method names the UI calls.
If you use createRestApiAdapter, it expects a backend shaped like the original haze-cms server (REST paths under baseURL).
| Method | Typical use |
|--------|-------------|
| login(credentials) | Login form |
| register(user?) | Optional dev helper |
| getItems(db, params?) | Table rows |
| getItem(db, id) | Sidebar / detail |
| createItem(db, data) | Create row |
| updateItem(db, id, data) | Update row |
| getTitles(db) | Column metadata (see Column types) |
| getAvailableTables() | Table picker |
| saveTableConfig(entity, payload) | Column visibility, and optionally full column definitions (see below) |
| getDBOptions(db, db_col) | Sync options for type: 'db' fields (default implementation returns a static list; override via adapter options) |
saveTableConfig accepts either:
- Legacy:
visibleColumnsas astring[]— same as{ visibleColumns: [...] }. - Extended:
{ visibleColumns: string[], columns?: object[] }— whencolumnsis present, the host should persist those definitions (keys, labels,type,relation,db/db_col, etc.) and keepvisiblein sync withvisibleColumns. The REST adapter sends this object as the JSON body; your backend must accept optionalcolumnsalongsidevisibleColumns.
Replace createRestApiAdapter entirely with your own implementation when the host API differs; method names and semantics should stay compatible with the table and config screens.
Layout and home dashboard
- Mobile (viewport ≤ 720px): A top bar with a menu button opens the same sidebar as on desktop as an off-canvas drawer (backdrop tap closes it). Main content is full width with top padding for the bar.
- Wide tables: Entity table views wrap the grid in a horizontal scroll area; columns use a minimum width so many columns scroll sideways instead of shrinking.
- Home (
/): Users can checkbox which entities appear on the dashboard. The choice is stored inlocalStorageunderhaze-cms-dashboard-tables(JSON array of routenamestrings). First visit defaults to all configured tables. - “Recent” rows on home: For each selected table, the UI loads
getItems(db), sorts client-side byupdatedAt/updated_at, thencreatedAt/created_at(newest first), then_id, and shows the first five rows with up to four visible columns per preview. For large collections, implementgetItems(db, params)in your adapter (e.g.limit,sort) so the server returns a small sorted page; the UI still works if you only return unsorted rows (sorting happens in the browser).
Column types
Each object from getTitles can include:
| type | Meaning |
|--------|---------|
| (omit) or 'text' | Plain text; rendered as <input type="text"> unless you set another HTML input type via type: 'email', etc. |
| 'boolean' | Toggle switch; stored value should be boolean (the UI tolerates "true" / "false" strings when reading). |
| 'relation' | Foreign key: include relation: { targetDb, valueKey, labelKey }. targetDb is the related entity id passed to getItems. valueKey is the field stored on the current row (e.g. _id on the related row). labelKey is the field from the related row shown in the dropdown and in the table grid. Options are loaded with getItems(targetDb) and filtered client-side by search text (fine for small/medium catalogs; large catalogs may need a dedicated search API later). |
| 'db' | Existing static dropdown: db, db_col, and getDBOptions(db, db_col). |
Example relation column:
{
key: 'vendor_id',
label: 'Vendor',
visible: true,
type: 'relation',
relation: { targetDb: 'vendors', valueKey: '_id', labelKey: 'name' }
}Auth
defaultAuthHelpers() implements:
isLoggedIn()— used by the router guard (default: token inlocalStorage)persistLogin(response)— called after successful login (default: stores token and user JSON)clearSession()— optional logout cleanupgetUser()— used by the admin card in the sidebar
Pass auth into createCmsPlugin({ api, auth: { ...defaultAuthHelpers(), ...yourOverrides } }) and the same merged object into createCmsRouter({ auth, ... }) if you use cookies, memory tokens, or OIDC instead of localStorage.
Router customization
createCmsRouter options:
| Option | Default | Notes |
|--------|---------|--------|
| history | createWebHistory() | Pass createWebHistory(base) when the app lives under a subpath |
| tables | defaultCmsTables() | Each entry: { path, name, db, title } — drives routes and Table props. Optional navLabel or label for a shorter sidebar label (else title is shown). Pass the same array to createCmsPlugin({ tables }). |
| loginPath | '/login' | Login route path |
| rootPath | '/' | Where to send an already logged-in user who hits the login page |
| auth | required | Same helpers as the plugin |
| extraChildren | [] | Extra child routes under MainLayout |
Named routes used in the default sidebar include Home, Leads, Products, Vendors, Users, Tables, and Login. If you rename name fields in tables, update navigation in your fork or pass custom layout (advanced).
Environment
When running this repo in dev / SPA build, src/main.js uses:
VUE_APP_API_URL— base URL forcreateRestApiAdapter(falls back tohttp://localhost:3000)BASE_URL— passed tocreateWebHistoryfor a public path prefix
Host apps should set their own env convention (e.g. Vite VITE_*) and pass the value into createRestApiAdapter({ baseURL }).
Architecture notes
- No backend is bundled in the library build; the server in a full-stack repo is only needed if you point the REST adapter at it.
- Vue, Vue Router, and Axios are not bundled as application code for library consumers in the usual setup; keep them as peers in the host.
- After changing components or styles, run
npm run build:libagain before publishing or packing.
Vue CLI
This project uses Vue CLI. See the Configuration Reference for vue.config.js customization.
