UsersLinker is a Flask-based web application designed to manage hardware assets assigned to remote workers in a company. It tracks the link between staff users and their equipment (computers, monitors, peripherals, phones) and provides a full audit trail of movements.
- Overview
- Requirements
- Project Structure
- Installation
- Configuration
- Roles and Permissions
- Features
- Tech Stack
- Known Limitations
- License
UsersLinker was built to address the needs of IT departments managing equipment issued to remote workers. It keeps track of which employee holds which device at any point in time, logs every assignment and return, and provides reporting tools to audit the fleet.
Key capabilities:
- Role-based access control (Administrator, Manager, Reader)
- Step-by-step event workflow for linking and unlinking hardware to users
- Full movement history with timestamps and responsible manager
- Optional import your datas (csv format) from external databases
- PDF export of filtered reports
- Pie charts showing distribution by model, type, and department
- Trash/soft-delete mechanism with permanent deletion on demand
Warning
Python 3.12 and above remove pkg_resources from the standard library, which breaks several dependencies used by this application. Use exactly Python 3.11.
- PostgreSQL 14 or later
userslinker/
├── app.py # Main Flask application — routes and business logic
├── install.py # Web-based installation wizard (blueprint)
├── db_setup.py # SQLAlchemy model definitions
├── config.py # Centralized Flask configuration
├── forms.py # WTForms form definitions
├── users_import_export.py # CSV import/export helpers for users
├── check_env.py # Utility to verify the .env file
├── requirements.txt # Python dependencies
├── setup.py # Package metadata
├── babel.cfg # Babel configuration for i18n
├── messages.po # Translation strings
│
├── postgresql_step1.sql # SQL — create role
├── postgresql_step2.sql # SQL — create database
├── postgresql_step3.sql # SQL — create schema + grant privileges
│
├── .env # Generated by the installer (not committed to git)
├── installed.lock # Sentinel file written after successful install
│
├── static/
│ └── style.css # Custom CSS (Bootstrap 5 overrides)
│
└── templates/
├── base.html # Master layout
├── base_install.html # Layout for the installer
├── dashboard.html # Home page (post-login)
├── login_user.html # Login page
├── install/
│ ├── step1.html
│ ├── step2.html
│ ├── step3.html
│ └── finish.html
├── users/
│ ├── users.html
│ ├── create_user.html
│ ├── edit_user.html
│ └── import_users.html
├── materials/
│ ├── list.html
│ ├── edit.html
│ ├── create.html
│ └── trash.html
├── events/
│ ├── select_user.html
│ ├── select_material_to_link.html
│ └── select_material_to_unlink.html
├── reports/
│ ├── reports.html
│ ├── by_service.html
│ ├── by_user.html
│ └── by_material_type.html
└── ...
Three SQL scripts are provided to prepare the database server. Run them in order as the postgres superuser, either via pgAdmin (Query Tool) or the psql command line.
# From the Windows command prompt:
psql -U postgres -f postgresql_step1.sql
psql -U postgres -f postgresql_step2.sql
psql -U postgres -f postgresql_step3.sql
| Script | What it does |
|---|---|
postgresql_step1.sql |
Creates the userslinker_app PostgreSQL role |
postgresql_step2.sql |
Creates the bd_userslinker database owned by that role |
postgresql_step3.sql |
Creates the app schema and grants the necessary privileges |
Important: Before running
postgresql_step1.sql, open the file and replace the placeholder password'userslinker'with a strong password of your choice. You will need this password during the web installer (Step 3 below).
-
Make sure Python 3.11 is active in your environment.
-
Create and activate a virtual environment:
python -m venv venv
venv\Scripts\activate- Install dependencies:
pip install -r requirements.txtTo go to production, proceed to step 3 or :
Start the application for the first time:
python app.py- Windows
Install Waitress
Waitress is the recommended production server for Flask on Windows. It replaces the mini-server integrated into Flask.
pip install waitressIn app.py add comment for :
if __name__ == '__main__':
app.run(debug=False, host='127.0.0.1', port=5000)and remove comment for :
if __name__ == '__main__':
from waitress import serve
serve(app, host='0.0.0.0', port=8000, threads=4)Create a Windows Service with NSSM
NSSM (Non-Sucking Service Manager) allows you to transform any application into a Windows service. The service will start automatically with Windows and restart in case of a crash.
-
Download and Install NSSM
- Go to: https://nssm.cc/download
- Download the 64-bit version (nssm-2.24.zip).
- Unzip the ZIP file. Copy the nssm.exe file (from the win64 folder) to C:\Windows\System32\ to use it from any terminal.
-
Create the Service
Open a terminal as an administrator (right-click on the Start menu > Administrator Terminal) and type:
nssm install UsersLinkerA graphical window will open. Fill in the fields as follows:
| Onglet Application | |
|---|---|
| Path | C:\userslinker\venv\Scripts\python.exe |
| Startup directory | C:\userslinker| |
| Arguments | app.py |
| Onglet Details | |
| Display name | UsersLinker Flask App |
| Description | IT asset management application |
| Startup type | Automatic (auto startup) |
| Onglet Environment | |
| Environment | FLASK_ENV=production |
-
Click on "Install service".
-
Start the service:
nssm start UsersLinker- Verify that the service is running :
nssm status UsersLinkerYou should see : SERVICE_RUNNING
- Useful commands NSSM
| command | description |
|---|---|
nssm start UsersLinker |
Start the service |
nssm stop UsersLinker |
Stop the service |
nssm restart UsersLinker |
Restart the service |
nssm edit UsersLinker |
Edit configuration |
nssm remove UsersLinker |
Remove service |
- Open the port in the Windows Firewall
By default, Windows blocks incoming connections on port 8000. You need to create a firewall rule.
In an administrator terminal:
netsh advfirewall firewall add rule name="UsersLinker" protocol=TCP dir=in localport=8000 action=allow- Linux
Install Gunicorn
Gunicorn is the most widely used production Python server on Linux. It handles multiple simultaneous requests much better than the built-in Flask server.
pip install gunicornUnder Linux with Gunicorn, app.run() is not called in production. Gunicorn directly imports the app object from app.py. Therefore, the if name == 'main' statement is only used in development.
Activate the virtual environment and test:
source venv/bin/activate
gunicorn --workers 3 --bind 0.0.0.0:8000 app:appOpen a browser from another computer http://192.168.X.X:8000
If you see the login page, great. Exit with Ctrl+C.
ℹ️ Note: The syntax app:app means: app.py file, Flask object named app.
Create a systemd service (automatic startup)
Systemd is the Linux service manager. We will create a service file for UsersLinker.
Create the service file (replace /home/your_user/userslinker with the actual path):
sudo nano /etc/systemd/system/userslinker.servicePaste the following content (adjust the paths):
[Unit]
Description=UsersLinker Flask Application
After=network.target postgresql.service
[Service]
User=www-data
Group=www-data
WorkingDirectory=/home/votre_user/userslinker
Environment="FLASK_ENV=production"
EnvironmentFile=/home/votre_user/userslinker/.env
ExecStart=/home/votre_user/userslinker/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/userslinker.sock \
app:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetℹ️ Note: Save with Ctrl+O then Enter, exit with Ctrl+X.
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable userslinker
sudo systemctl start userslinker
sudo systemctl status userslinkerYou should see "active" (running) in green.
Install and configure Nginx
Nginx is a web server that acts as a front end. It receives HTTP requests from browsers and forwards them to Nginx. It also manages static files (CSS, images) directly, which is faster.
sudo apt install nginx -yCreate the site configuration sudo nano /etc/nginx/sites-available/userslinker
Paste the following content:
server {
listen 80;
server_name 192.168.X.X; # Your IP address or domain name
# Fichiers statiques servis directement par Nginx
location /static {
alias /home/votre_user/userslinker/static;
}
# Tout le reste va vers Gunicorn
location / {
include proxy_params;
proxy_pass http://unix:/run/userslinker.sock;
}
}Enable the configuration and restart Nginx:
sudo ln -s /etc/nginx/sites-available/userslinker /etc/nginx/sites-enabled/
sudo nginx -tsudo nginx -t checks that your configuration does not contain any syntax errors. Only proceed to the next step if you see "syntax is ok".
sudo systemctl restart nginxOpen the port in the Linux firewall (UFW)
🔴 Important: Don't forget to enable ssh before activating UFW, otherwise you could cut yourself off from SSH access to your server!
sudo ufw allow 'Nginx Full'
sudo ufw allow ssh
sudo ufw enable
sudo ufw statusAccessing from other computers on the network
With Nginx on port 80, users simply type the IP address without specifying a port:
To find the IP address of your Linux server:
ip a | grep inetUseful commands for Linux
| command | description |
|---|---|
sudo systemctl start userslinker |
Start the application |
sudo systemctl stop userslinker |
Stop the application |
sudo systemctl restart userslinker |
Restart after a change |
sudo systemctl status userslinker |
View the service status |
sudo journalctl -u userslinker -f |
View real-time logs |
sudo systemctl restart nginx |
Restart Nginx |
- Open your browser and navigate to:
http://127.0.0.1:5000/install
The installer guides you through three steps:
| Step | Description |
|---|---|
| Step 1 | Enter PostgreSQL connection settings. The installer writes the .env file and generates secure secret keys. |
| Step 2 | Tests the database connection and creates all application tables in the app schema. |
| Step 3 | Creates the first administrator account (email + password). |
Once the installer completes successfully, it writes an installed.lock file. On every subsequent startup, the /install route is disabled and returns 404.
If you need to reinstall: Delete
installed.lockand.env, then restart the application and visit/installagain.
- About security (Production)
These steps are important before making the application accessible on the network.
| steps |
|---|
In the .env file, verify that FLASK_DEBUG=0. Debug mode should never be enabled in production (it exposes the source code in case of an error). |
| Change the password for the administrator account created during installation if you used a weak password. |
The .env file contains the database secret keys and password. Verify that this file is not publicly accessible (it is in the application folder, not in the public web folder). |
| Only make the application accessible from your company's internal network. Avoid exposing it directly to the internet without HTTPS. |
| If you need to access it from outside the network, use a corporate VPN rather than opening ports on your router/firewall. |
- Troubleshooting Common Problems (Production)
| Problem | Solution |
|---|---|
| Unable to access from another PC (Windows) | Check the firewall rule. Verify that Waitress is running correctly (nssm status UsersLinker). Test first from the server itself on localhost:8000 . |
| NSSM service not starting (Windows) | Check the path to python.exe in the NSSM configuration. Open the "I/O" tab of NSSM to configure a log file and view the error. |
| Error 502 Bad Gateway (Linux) | Gunicorn is not running. Check sudo systemctl status userslinker and sudo journalctl -u userslinker -n 50 |
| Error 403 Forbidden (Linux) | Permissions problem. Verify that www-data can read the directory sudo chown -R www-data:www-data /home/your_user/userslinker |
| The application runs, but the CSS/image files do not. | Check the /static path in the Nginx configuration. Restart Nginx after any changes. |
| After modifying the code, the changes are not displayed. | Restart the service: nssm restart UsersLinker (Windows) or sudo systemctl restart userslinker (Linux). |
| The application has difficulty connecting to PostgreSQL from production. | Verify that the .env file is present and contains the correct parameters. The WorkingDirectory path in systemd must point to the directory containing .env. |
In config.py, a single line is sufficient:
BABEL_DEFAULT_LOCALE = 'en' # English
# BABEL_DEFAULT_LOCALE = 'fr' # FrenchRestart Flask — the entire interface switches.
Currently French and English languages are supported.
The .env file is generated automatically by the installer. It contains:
# PostgreSQL connection
PG_HOST=host
PG_PORT=5432
PG_USER=userslinker_app
PG_PASSWORD=your_strong_password
PG_DB=bd_userslinker
# Flask security keys — stable, never regenerated after first install
SECRET_KEY=<auto-generated>
SECURITY_PASSWORD_SALT=<auto-generated>Critical:
SECRET_KEYandSECURITY_PASSWORD_SALTmust remain stable between restarts. Changing them will invalidate all active sessions and make stored passwords unverifiable.
| Permission | Administrator | Manager | Reader |
|---|---|---|---|
| Create / edit / delete app users | ✅ | ❌ | ❌ |
| Assign roles to app users | ✅ | ❌ | ❌ |
| Create events (link / unlink hardware) | ❌ | ✅ | ❌ |
| Edit users and hardware entries | ❌ | ✅ | ❌ |
| Add / delete users and hardware | ❌ | ✅ | ❌ |
| Access reports | ❌ | ✅ | ✅ |
| View user and hardware lists | ❌ | ✅ | ✅ |
| Export PDF reports | ❌ | ✅ | ✅ |
The Administrator role is dedicated to managing access to the application itself. They do not interact with the operational data (users, hardware, events).
Accessible to Managers. The event wizard mirrors the experience of a guided installer:
- Select or create a user — search by username with auto-completion (if
bdd.jsonis configured). If the user does not exist, create them on the spot. - Choose an action —
Link(assign hardware) orUnlink(recover hardware). - Link flow — Select equipment to assign. Items already linked to another user appear in red with the current owner's name. No alert = confirm and close the event. A timestamped record is written to the
historytable with the manager's name and the user's identifier. - Unlink flow — The user's currently linked equipment is displayed. Select the item(s) to recover. Items not in the list can be added manually, with a duplicate-ownership check.
Accessible to Managers (read + edit) and Readers (read only).
- Sub-menus for each category: Users, Computers, Monitors, Peripherals, Phones.
- Search, add, edit, soft-delete (move to trash), and permanently delete from trash.
- Items in the trash display a red Deleted badge in lists.
- Permanent deletion of a hardware or user record cascades to linked
historyentries.
Accessible to Managers and Readers.
- Filter movement history by department, user name, and date range.
- Full chronological audit trail with all link / unlink events.
- PDF export — data is reformatted for readability using ReportLab.
- Pie charts — distribution by model, type, and department, generated with Matplotlib.
| Component | Library / Version |
|---|---|
| Web framework | Flask 2.3.3 |
| ORM | Flask-SQLAlchemy 3.0.5 |
| Authentication | Flask-Security-Too 5.3.0 |
| Database | PostgreSQL 14+ via psycopg2-binary |
| Forms | Flask-WTF 1.2.1 |
| Password hashing | Flask-Bcrypt 1.0.1 + passlib 1.7.4 |
| Frontend | Bootstrap 5 + Font Awesome (free) |
| PDF export | ReportLab 4.0.7 |
| Charts | Matplotlib 3.8.2 |
| Translations | Flask-Babel |
| Environment | python-dotenv 1.0.0 |
- Python 3.11 only —
pkg_resourcesis removed in Python 3.12+. Do not upgrade the Python interpreter until all dependencies have been updated. - PostgreSQL only — The application was migrated from MySQL to PostgreSQL. MySQL is no longer supported as the internal database.
mysql.connectoris only used for optional external GLPI connections. - Windows + psycopg2 — The
search_pathURI parameter is unreliable with psycopg2 on Windows. All models explicitly declareschema = 'app'to work around this. Do not remove the__table_args__declarations. - No Docker support — This application is designed to run natively on Windows with a local PostgreSQL instance.
- Single administrator account at first install — Additional administrator accounts can be created after the first login.
See LICENSE for details.


