Hardware & network inventory Β· employee handovers with printable PDF receipts Β· software licenses Β· mobile lines Β· vendors & contracts Β· repairs Β· physical stock counts Β· a full audit trail β all behind a built-in, mobile-ready web UI running entirely on your own infrastructure.
π¬π§ English Β· πΉπ· TΓΌrkΓ§e β
- 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, audit history and uploaded documents all live in PostgreSQL, so a single backup captures everything.
- 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.
| Network & Server topology | Providers & Contracts |
|---|---|
![]() |
![]() |
| Mobile Lines | Physical Stock Count |
![]() |
![]() |
| Employee detail β assets, licenses, lines | System Audit Log |
![]() |
![]() |
| Printable handover receipt | Reports & builder |
![]() |
![]() |
Β Β
Responsive layout with bottom navigation and a center QR-scan button.
More screens (hardware inventory, product catalog, handover basket, login) live in
docs/screenshots/.
|
Served by the backend itself β no build step, strict same-origin CSP. 15 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. |
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. Lifecycle duration per category plus per-asset overrides (e.g. MacBooks at 5 years) β or untick EOL for a category (accessories) to exclude it entirely. Every asset shows its EOL date and "EOL soon" / overdue flags. 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. 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 β
npm run seed:demofills Postgres with a realistic company; scale it withSEED_EMPLOYEES=2000 npm run seed:demo -- --reset, and addnpm run seed:infra/npm run seed:providersfor network gear and vendors.
The sidebar maps 1:1 to the feature set:
| Module | What it does |
|---|---|
| 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, categories, locations, departments, lifecycle & 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 |
| 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 | 19 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/<you>/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 shows the onboarding wizard to set your company name/logo, pick a zimmet design and language, and create the Owner account.
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.
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_*). The schema and migrations are applied automatically on startup.
Your entire system β assets, employees, handover receipts, contracts, audit history and the document archive (scanned/generated PDFs) β lives in PostgreSQL. Back it up regularly.
npm run backup # β backups/itacm-YYYYMMDD-HHMMSS.sql.gz
npm run restore backups/itacm-20260707-120000.sql.gz # replaces current data (asks to confirm)A single dump captures everything (the document archive is stored inside the database). Copy the backups/ folder somewhere safe, or schedule the command 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.
| 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) |
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 |
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.
- 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-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
npm run seed:demo # ~500 employees
SEED_EMPLOYEES=2000 npm run seed:demo -- --reset # larger, historical dataset
npm run seed:infra # network/server gear + topology
npm run seed:providers # vendors + contractsReleased under the MIT license.
Built with β€οΈ by Enes YakΔ±ΕΔ±k Β· If ITACM helps you, consider giving it a β








