Hardware & network inventory · employee handovers with printable PDF receipts · software licenses · mobile lines · vendors & contracts · repairs · physical stock counts · a natural-language AI assistant · a full audit trail — all behind a built-in, mobile-ready web UI running entirely on your own infrastructure.
🇬🇧 English · 🇹🇷 Türkçe →
📘 New here? Follow the simple, copy-paste visual install guide — available in 12 languages.
- Why ITACM?
- Screenshots
- Feature highlights
- Modules
- Mobile ready
- Tech stack
- Quick start — Docker Compose
- Deploying to a server
- Backup & recovery
- Configuration reference
- API reference
- Security notes
- Project structure
- Development
- License
Most asset trackers are either a spreadsheet that rots or a heavyweight SaaS you can't self-host. ITACM sits in the middle:
- One command to run.
docker compose up -dgives you the database, schema, first admin and a full web UI — no build step, no separate frontend to deploy. - Handovers that hold up. Every asset assignment is an atomic, row-locked transaction that produces a printable Zimmet Tutanağı (handover receipt) with your company branding.
- The whole company, one dump. Assets, employees, receipts, contracts and audit history live in PostgreSQL. Uploaded document files live on the filesystem (
DATA_DIR/documents); usenpm run migrate:exportfor a full move. - Works from the warehouse floor. The UI is fully responsive with a mobile bottom-nav and a camera QR/barcode scanner — count stock or hand over a laptop from your phone.
- Yours to keep. No telemetry, no vendor lock-in, MIT licensed.
Responsive layout with bottom navigation and a center QR-scan button.
More screens (handover basket, network map, login) live in
docs/screenshots/.
|
Served by the backend itself — no build step, strict same-origin CSP. 16 modules, global search (Cmd/Ctrl+K), QR codes, dark-mode aware, and a responsive shell with mobile bottom-nav + camera scanner. Just open Assign multiple assets to an employee in one all-or-nothing transaction, producing a printable handover receipt (Zimmet Tutanağı). Row locks make double-assignment impossible; reprints preserve the original issuer's name. Live-preview editor to pick which sections, columns, titles and labels appear on the printed/PDF form, plus multiple visual themes ( Infrastructure gear (switches, firewalls, routers, servers, storage) kept out of personal zimmet — assigned to a site + responsible person instead. Interactive dependency topology (per-site graphs, uplinks, cross-site parents) and rack-cabinet U-maps. Vendors / ISPs / MSPs as first-class records with contacts, account numbers and support lines. Attach contracts with renewal dates, cost, billing cycle and internal owner; 60-day renewal alerts and per-provider document storage. Company SIM cards & phone numbers as first-class inventory: operator, plan, ICCID, monthly cost. Assign / take back with full history — lines show up on the employee profile and on handover forms. Schedule a new hire's kit (reserve assets + lines), then complete it into a single handover. Offboarding is a transactional checklist that returns, reassigns, scraps or sells every asset, seat, line and infra responsibility before deactivating the employee. Departments (with a manager), teams (with a lead) and their members drawn as an interactive topology graph — the same node-and-edge style as the network view. Assign or change a manager/lead in one click, add teams, and move people between them. Departments are a single source of truth: add one in the Product Catalog and it shows up here instantly, ready for a manager. Great for helpdesk escalation — one glance shows who to contact. |
Ask about your inventory in plain language and get grounded answers — provider-agnostic: a local Ollama model or a cloud API (OpenAI, DeepSeek, Anthropic, Groq, Mistral, Together, OpenRouter, or a custom endpoint). Streaming replies, result tables, CSV export, auto charts and a collapsible "show SQL". Analytical questions run a guarded read-only query against a curated view schema — never the base tables — under a low-privilege role, honouring each user's RBAC (a user can't surface data the UI denies them), with SSRF-safe outbound and a per-user rate limit. Off by default — enable under Integrations → AI.
A unified, filterable timeline of all instance activity — assets, users, documents, handovers, logins, settings and more — merging the append-only audit table with legacy domain history. Search by source, actor and date; secrets are redacted before storage. EOL windows resolve in three tiers — per-asset override → per-catalog-model → per-category default. Set a category default in Settings, give a specific catalog model its own lifecycle (e.g. Apple MacBooks at 5 years while other laptops keep 4), or override a single device — or untick EOL for a category (accessories) to exclude it entirely. Every asset shows its EOL date and "EOL soon" / overdue flags. The same lifecycle window drives straight-line depreciation: enter a purchase cost (and optional salvage value) and each asset shows its current book value — exportable via the Asset Depreciation / Book Value report. Open a count session and scan from any signed-in device — start on the PC, keep scanning barcodes/QRs from your phone camera. Closing the session reconciles against live inventory: found / missing / unknown, with CSV export. Download the template, fill it with your existing zimmet spreadsheet, upload — a dry-run preview shows exactly what will be created, then one transaction auto-creates employees, catalog entries, assets (sequential tags) and one handover per employee with full history. Seat pools with atomic claim/release and 30-day expiry alerts. Print scannable Code 128 labels (size/fields/copies configurable). Pick your display currency for costs across the app. The alert digest (expiring licenses, low stock, EOL, onboarding due) can be auto-sent daily or weekly over SMTP — Integrations → SMTP & alert digest (Auto-send: Off / Daily / Weekly). 12 languages (EN, TR, DE, FR, ES, IT, PT, NL, PL, RU, AR, JA). Pick one on the onboarding screen, change it any time in Settings; untranslated strings fall back to English. |
🚀 First-run onboarding sets your company name, logo and Owner account; branding flows into the UI and every printed receipt. 🧪 Demo dataset (~100 employees by default) — seed inside the API container:
docker compose exec api npm run seed:all -- --reset(demo + infra + providers). Scale withSEED_EMPLOYEES=200. Password for demo IT/Portal users:Demo123!.
The sidebar maps 1:1 to the feature set, plus a floating AI assistant (⌘/Ctrl+J) on top:
| Module | What it does |
|---|---|
| AI Assistant | Natural-language queries over your inventory — floating launcher (⌘/Ctrl+J), provider-agnostic, guarded read-only. Off by default (Integrations → AI) |
| Dashboard | KPIs, attention-required alerts (licenses, low stock, EOL), asset distribution, recent activity |
| Hardware | Full device inventory — QR codes, bulk actions, cost/warranty, lifecycle, global search |
| Network & Server | Infra inventory + dependency topology + rack cabinets (site/owner, not personal zimmet) |
| Product Catalog | Approved brands/models (with per-model EOL lifecycle), categories, locations, departments & spec options |
| Software & Licenses | Seat pools, atomic claim/release, expiry alerts, per-license holder export |
| Mobile Lines | SIM/phone-number inventory with assignment history |
| Providers & Contracts | Vendor directory + commercial agreements with renewal tracking and documents |
| Consumables | Stock movements with low-stock alerts |
| Employees | Directory, per-person detail (assets/licenses/lines/infra), onboarding & offboarding |
| Organization | Department → team → member topology chart; assign managers/leads, move people, helpdesk escalation |
| Handover Ops | Atomic handover basket + printable/PDF receipts |
| Maintenance & Repair | Send to repair / return / scrap, with document attachments |
| Stock Count | Physical count sessions with camera scanning and reconciliation |
| Reports | 20 preset reports + a builder (data sources × columns × filters), CSV / letterhead print |
| Audit Log | Unified, filterable activity timeline (Owner/Admin) |
| IT Users | RBAC user management — create, role, disable/enable, delete (audited) |
The entire app is responsive — no separate mobile build:
- Collapsible sidebar with a bottom navigation bar and a center QR-scan button.
- Camera barcode/QR scanning (via a vendored ZXing build) for stock counts and quick asset lookup.
- Start on PC, continue on phone: open a stock-count session on your desktop and keep scanning from any signed-in device.
- Viewport-fit, theme-color and web-app meta so it behaves well when added to a home screen.
| Layer | Technology |
|---|---|
| Runtime | Node.js ≥ 20, Express 4 |
| Database | PostgreSQL 16 — idempotent schema.sql + tracked versioned migrations, applied on startup |
| Auth | JWT (HS256, pinned alg) + bcrypt (cost 12), role-based middleware re-checked per request |
| Frontend | Vanilla JS SPA served by the backend — no build step, split into per-view modules |
| PDF / labels | PDFKit + QR codes, custom handover templates, Code 128 barcodes |
| Scanning | Vendored ZXing browser build (camera QR/barcode) |
| Packaging | Docker + Docker Compose |
Everything is automatic: the database container is created, the schema + migrations are applied, and the first Admin (Owner) account is seeded.
git clone https://github.com/enesyaks/ITACM.git
cd itacm
npm install
npm run setup # generates .env with strong secrets (or copy .env.example)
docker compose up -d
docker compose logs api # first-run Owner credentials are printed hereThen open http://localhost:8000 — the first visit asks New workspace (product tour + company/Owner setup) or Migrate from another server.
Tip
If you leave ADMIN_PASSWORD empty, a strong random password is generated and printed once in the API logs. Change it after first login.
Prefer to configure by hand? Copy .env.example to .env, set at least JWT_SECRET (openssl rand -hex 32), then docker compose up -d.
The compose file works unchanged on any host with Docker. Put a reverse proxy (Caddy / Nginx / Traefik) with TLS in front of port 8000 and set CORS_ORIGINS to your frontend's origin if it differs.
The simplest path — no certbot, nginx, or manual certificates. The compose file ships an optional Caddy service (behind the tls profile) that fetches and renews a Let's Encrypt certificate automatically.
- Point a DNS A/AAAA record for your domain at the host.
- In
.envsetAPP_DOMAIN=itacm.company.com,TRUST_PROXY=1, andAPP_URL=https://itacm.company.com(or set the App URL in-app under Integrations → Notifications, so email links use the real domain). - Start with the profile:
docker compose --profile tls up -dCaddy listens on 80/443, redirects HTTP→HTTPS, proxies to the app, and renews the certificate on its own. With the proxy in front you don't need to expose the app port publicly — set API_PORT=127.0.0.1:8000 in .env to keep it host-local. Want certificate-expiry emails? Add { email you@example.com } to the top of the Caddyfile.
Behind Cloudflare? Don't use the
tlsprofile above — Caddy can't complete a Let's Encrypt challenge through Cloudflare's orange-cloud proxy. Use thecloudflareprofile instead (next section).
When your domain is proxied by Cloudflare (orange cloud), the turnkey path is a Cloudflare Origin Certificate — a cert Cloudflare pre-issues for your origin, so there's no Let's Encrypt challenge to break. You get full end-to-end HTTPS with SSL/TLS mode Full (strict).
- DNS — in Cloudflare, add an A record: your hostname → the server's public IP, Proxied (orange cloud). (once)
- Origin Certificate — Cloudflare → SSL/TLS → Origin Server → Create Certificate (keep defaults, list your hostname). Save the two blocks into
./certs: (once)- Origin Certificate →
certs/origin.pem - Private Key →
certs/origin.key(thenchmod 600 certs/origin.key)
- Origin Certificate →
- In
.env: setAPP_DOMAIN,TRUST_PROXY=1,APP_URL=https://your-domain, and keep the app host-local:API_PORT=127.0.0.1:8000. - Start with the profile:
docker compose --profile cloudflare up -d- Cloudflare → SSL/TLS → Overview → Full (strict), then open the origin firewall for Cloudflare:
sudo ufw allow 443/tcp(port 80 on the origin is no longer needed). Keep the record proxied. (once)
Caddy serves 443 with the Cloudflare-trusted certificate and proxies to the app; the key/cert are git-ignored (see certs/README.md). The tls and cloudflare profiles are mutually exclusive — pick the one that matches your setup.
Rate-limiting and brute-force protection key on the client IP, so when a proxy sits in front you must tell the app to trust it — otherwise every visitor is bucketed under the proxy's IP and legitimate users get throttled as one:
- Set
TRUST_PROXY=1in.env. The app then resolves the real client fromCF-Connecting-IP(behind Cloudflare) orX-Forwarded-For. With no trusted proxy declared it uses the raw TCP peer, so headers can't be forged to dodge limits. - Lock the origin to the proxy. With
TRUST_PROXY=1the app trusts those IP headers, so a client reaching the origin directly could spoof them. Use a Cloudflare Tunnel (origin has no public port), or firewall the origin to Cloudflare's IP ranges + enable Authenticated Origin Pulls. - In Cloudflare, turn OFF Rocket Loader, Auto Minify (JS/HTML) and Email Obfuscation — they rewrite/inject scripts and the strict
script-src 'self'CSP will block them. Use SSL mode Full (strict) and a cache rule that bypasses/api/*.
For managed platforms (Railway, Render, Fly.io, Cloud Run…), deploy the Dockerfile, attach a Postgres add-on, and set the same environment variables (DATABASE_URL, PGSSL=true, JWT_SECRET, ADMIN_*, and TRUST_PROXY=1 since these run behind a load balancer). The schema and migrations are applied automatically on startup.
Releases are tagged (v1.1.0, …) and listed under Releases; see CHANGELOG.md for what changed. Schema migrations run automatically on startup.
The easy way — one command:
npm run update # or: npm run update -- --dry-run (preview only)This backs up the database, pulls the latest code, and rebuilds with the compose profile your .env implies (plain / tls / cloudflare) — so you never have to remember which --profile or --build flag to pass — then prints the version now running. Your .env and certs/ are left untouched.
Or do it manually:
git pull # or: docker compose pull (if you use a published image)
docker compose up -d --buildUsing an HTTPS profile manually? If you started with
--profile tlsor--profile cloudflare, include the same flag when you update — otherwise the reverse-proxy (Caddy) container isn't recreated and HTTPS goes down. (npm run updatehandles this for you.)docker compose --profile cloudflare up -d --build
Take a backup first (npm run backup) — it's a one-liner and makes rollback trivial. After the new version boots, the running version is exposed at GET /api/health (version field) and shown in Help → About. The first time the Owner signs in on a newer version, a popup announces the update so at least the people running the instance know it changed.
Optional — get told when a new release is out. The Owner can turn this on under Integrations → Software updates (or set UPDATE_CHECK=1 as the default). The server then asks GitHub once a day whether a newer release exists; if so, the Owner sees an "update available" popup (with a link to the release). It's off by default so offline / air-gapped installs never reach out. Configure the repo with UPDATE_CHECK_REPO and, for a private repo or higher rate limits, UPDATE_CHECK_TOKEN (GITHUB_TOKEN also works).
PostgreSQL holds assets, employees, receipts, contracts, settings (SMTP, company, zimmet templates) and audit history. Uploaded document files live under the app-data volume (DATA_DIR/documents), not only in the database.
npm run backup # → backups/itacm-YYYYMMDD-HHMMSS.sql.gz (DB only)
npm run restore backups/itacm-20260707-120000.sql.gz # replaces current DB (asks to confirm)
# Full system move (DB + documents) — also available in the UI:
npm run migrate:export # → migrations/itacm-migrate-… (+ .zip if available)
npm run migrate:import path/to/itacm-migrate-… [--yes]First open of a fresh install offers New workspace or Migrate from another server. Copy JWT_SECRET from the source .env to the target (required for SMTP password decrypt). Owner can also export from Integrations → System migration.
Copy the backups/ / migrations/ folders somewhere safe, or schedule DB backups with cron, e.g. daily at 02:00:
0 2 * * * cd /path/to/ITACM && npm run backupPOSTGRES_PASSWORD is fixed when the database volume is first created. Editing it in .env and restarting will not work — the API will fail to authenticate. To rotate it safely, without losing any data:
npm run change-db-passwordWarning
Never run docker compose down -v. The -v flag deletes the database volume and permanently destroys all your data. If the API ever reports password authentication failed, run npm run change-db-password (or restore the previous password in .env) — do not wipe the volume.
There is no network "forgot password" endpoint — by design, so no one can reset an account remotely. Recovery runs on the server (shell access to the box = proof you are the legitimate operator):
# reset the password (forces a change on next login, revokes all sessions)
docker compose exec api npm run reset-password -- owner@example.com
# also clear MFA — for an Owner who lost their authenticator (they re-enrol on next login)
docker compose exec api npm run reset-password -- owner@example.com --clear-mfa
# set a specific password instead of a generated one
docker compose exec api npm run reset-password -- owner@example.com --password 'NewStrongPass123'A server-side attacker who already has shell/DB access can of course do this too — but they could also read
JWT_SECRETfrom.envand forge any session. Server compromise is total for any self-hosted app; protect the host (key-only SSH, firewall,chmod 600 .env, off-site encrypted backups, ideally a Cloudflare Tunnel so the origin has no public ports).
| Variable | Required | Description |
|---|---|---|
PORT / API_PORT |
– | HTTP port (default 8000) |
CORS_ORIGINS |
– | Comma-separated allowed origins (blank = same-origin) |
DATABASE_URL |
✅ | postgres://user:pass@host:5432/db (or POSTGRES_URL) |
PGSSL |
– | true for managed Postgres over TLS |
JWT_SECRET |
✅ | Min 32 chars — openssl rand -hex 32 |
JWT_EXPIRES_IN |
– | Token lifetime (default 12h) |
ADMIN_EMAIL / ADMIN_USERNAME / ADMIN_PASSWORD |
– | First-run Owner seed (password auto-generated if empty) |
TRUST_PROXY |
– | 1 (or a hop count) when behind a reverse proxy / Cloudflare, so rate limits key on the real client IP. Off by default. |
APP_URL |
– | Public URL used in outbound email links. Prefer setting it in-app (Integrations → Notifications → App URL); this env var is the fallback. Defaults to http://localhost:8000. |
APP_DOMAIN |
– | Domain for the HTTPS compose profiles (--profile tls / --profile cloudflare). |
AI_ENABLED / AI_PROVIDER / AI_MODEL / AI_BASE_URL / AI_API_KEY |
– | AI assistant defaults (optional). Normally configured in Integrations → AI, not via env. Assistant is off unless enabled. |
With docker compose, POSTGRES_DB / POSTGRES_USER / POSTGRES_PASSWORD feed both the database container and the API's DATABASE_URL.
All responses are { success, data } or { success: false, error, details? }. All endpoints (except login / health) require Authorization: Bearer <TOKEN>. Every router applies authenticate; writes/deletes additionally require a role.
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| POST | /api/auth/login |
public | Email/password → JWT |
| POST | /api/auth/verify-token |
any | Validate token, return profile + permissions |
| GET/POST | /api/auth/users |
Admin | List / create IT users |
| PATCH | /api/auth/users/:uid/role · /status |
Admin/Owner | Change role · disable/enable (audited) |
| DELETE | /api/auth/users/:uid |
Owner | Delete an IT user (audited) |
| GET | /api/dashboard/stats |
all | KPIs, alerts, recent activity |
| GET | /api/assets · /:id |
all | Inventory list (?status=&category=&search=) · detail + history |
| POST/PUT | /api/assets · /:id |
Admin, Helpdesk | Create / update hardware & infra |
| POST | /api/assets/:id/return |
Admin, Helpdesk | Return an assigned asset to stock |
| POST | /api/handovers |
Admin, Helpdesk | Atomic handover basket (below) |
| GET | /api/handovers · /:id |
all | Receipts (feed the printable form) |
| GET/POST | /api/onboardings … /:id/complete · /cancel |
Admin, Helpdesk | Schedule / complete / cancel onboarding |
| POST | /api/employees/:id/offboard |
Admin, Helpdesk | Transactional offboarding disposition |
| GET/POST | /api/maintenance · /:id/close |
Admin, Helpdesk | Repair logs / send / close ({scrap:true}) |
| GET | /api/employees |
all | Directory + handover selector |
| POST/PUT | /api/employees · /:id |
Admin, Helpdesk | Create / update |
| GET/POST | /api/licenses · /:id/assign · /revoke |
Admin, Helpdesk | Seat pools + atomic claim/release |
| GET/POST | /api/lines · /:id/assign · /unassign |
Admin, Helpdesk | Mobile lines + history |
| GET/POST | /api/providers · /contracts |
Admin, Helpdesk | Vendors & contracts (+ document upload/download) |
| GET/POST | /api/consumables · /:id/adjust |
Admin, Helpdesk | Stock + atomic movements |
| GET/POST | /api/counts · /:id/scan · /close |
Admin, Helpdesk | Physical stock-count sessions |
| GET/PUT | /api/catalog/* |
Admin, Helpdesk | Catalog, locations, departments, settings |
| POST | /api/import/inventory |
Owner, Admin | Excel/CSV migration (dry-run + commit) |
| GET | /api/documents/:id/download |
Owner, Admin, Helpdesk | Stream a stored handover document (auth required) |
| GET | /api/audit · /:bucket/:id |
Owner, Admin | Unified audit timeline + event detail |
| POST | /api/ai/query |
staff | AI assistant — SSE streaming, agentic tool loop, guarded read-only queries (per-user rate-limited) |
| GET | /api/ai/status |
staff | Assistant availability (provider/model) |
| GET/PUT/DELETE | /api/ai/config |
integration:read / manage | Read / save / clear AI settings (API key encrypted, masked) |
The atomic handover basket — how it works
POST /api/handovers
{
"employeeId": "…",
"documentType": "single",
"items": [
{ "assetId": "…", "conditionNote": "New, sealed box" },
{ "assetId": "…", "conditionNote": "Used, good condition" }
]
}In one transaction (Postgres BEGIN … FOR UPDATE): every asset is validated as In Stock → the receipt document is created → each asset flips to Assigned bound to the employee → the employee's activeAssetCount is incremented → one audit row is written per asset.
If any asset is locked, the API returns 409 with a per-asset conflict list and nothing is written. Row locks / transaction retries make it impossible for two operators to hand over the same laptop concurrently.
- Secrets never live in the repo.
.envis git-ignored; the setup wizard writes it with0600permissions and generates a strongJWT_SECRETand DB password for you. Database backups (backups/) are git-ignored too. - Auth: passwords are bcrypt-hashed (cost 12); JWTs are signed HS256 with the algorithm pinned on verify; login uses a single error message and a constant-time compare (dummy hash for unknown emails) so it can't be used to enumerate accounts; every request re-checks the user row so role changes / disables / deletes apply instantly;
Owneraccounts must have TOTP MFA enabled — until they do, the middleware blocks every route except MFA enrolment, token verification and logout. - Access control: every API router mounts
authenticate, and mutating routes addrequireRole(...). The audit log redacts sensitive keys (passwords, tokens, keys) before persisting. - Uploads: document routes validate the real file type by magic bytes (not the client's claim) and cap the body at 12 MB; downloads set a sanitized
Content-Disposition. All SQL is parameterized; all rendered values are HTML-escaped. - Hardening: strict Content-Security-Policy (no inline scripts, self-only), HSTS, nosniff / frame-deny / referrer / permissions-policy headers, login rate-limiting (20 / 15 min / IP), global API rate limit (1000 / 5 min / IP), same-origin-only CORS by default, 1 MB default body limit,
x-powered-bydisabled, a one-shot onboarding endpoint that locks itself after first use, and annpm audit-clean dependency tree. - Transport: front the API with HTTPS (Caddy / Nginx / Traefik). Set
CORS_ORIGINSto your exact frontend origin if it differs.
├── server.js Node/Docker entry (auto-migrates on startup)
├── public/ Built-in web UI (vanilla JS SPA, no build step)
│ ├── index.html App shell + onboarding/login
│ ├── css/app.css
│ └── js/
│ ├── api.js i18n.js ui.js money.js barcode.js mobile-shell.js
│ └── views/ One module per screen (dashboard, assets, network,
│ providers, audit, onboarding, stockcount, …)
├── src/
│ ├── app.js Express app, body limits, audit middleware, route mounting
│ ├── config/ Env parsing
│ ├── middleware/ Bearer auth + role gate, error handling
│ ├── routes/ Thin controllers (assets, providers, contracts, audit, …)
│ ├── utils/ PDF, uploadGuard, contentDisposition, permissions, defaults
│ ├── services/ Backend-agnostic service facade
│ └── providers/postgres/ JWT auth + PostgreSQL
│ ├── schema.sql Idempotent base schema
│ ├── migrations/ Tracked versioned migrations (schema_migrations)
│ ├── migrate.js Applies schema.sql + pending migrations
│ └── *Service.js assets, employees, providers, audit, offboard, onboarding, …
├── scripts/ setup · seed-all · seed-demo · seed-infra · seed-providers · backup · restore
├── docker-compose.yml Self-hosted stack (API + Postgres)
├── Dockerfile · docker-entrypoint.sh
└── .env.example Fully documented configuration template
npm install
npm run setup # or hand-write .env
npm run dev # auto-restarting local server
npm run lint # syntax check (server + all src/scripts)
npm run migrate # apply schema + pending migrations manually (optional)
# Demo data (run inside the API container — host `npm run seed:*` needs DB port published)
docker compose exec api npm run seed:all -- --reset # ~100 employees + infra + providers
docker compose exec -e SEED_EMPLOYEES=100 api npm run seed:demo -- --reset
docker compose exec api npm run seed:infra # network/server gear + topology
docker compose exec api npm run seed:providers # vendors + contracts
# Demo logins: demo.admin|helpdesk|viewer|user01@example.com / Demo123!Released under the MIT license.
Built with ❤️ by Enes Yakışık · If ITACM helps you, consider giving it a ⭐










