Compare commits

...

5 Commits

Author SHA1 Message Date
Daniel Bedeleanu
fcb187974e Build [v1.9.18] 2026-04-13 23:43:52 +03:00
Daniel Bedeleanu
1fff658d3c Build [v1.9.16] (Open Gateway: Verified SSL Permission check) 2026-04-13 22:48:26 +03:00
Daniel Bedeleanu
826b264a70 Build [v1.9.15] (The Dynamic Shield: On-Demand TLS Catch-all) 2026-04-13 22:43:40 +03:00
Daniel Bedeleanu
94f1a515b7 Build [v1.9.14] (Protocol Lock: Explicit HTTPS & Handshake Debug) 2026-04-13 22:37:50 +03:00
Daniel Bedeleanu
4dc0ce50e7 Build [v1.9.13] (Bare Metal: Fixed Caddy Syntax & Universal Binding) 2026-04-13 22:31:04 +03:00
13 changed files with 236 additions and 119 deletions

View File

@@ -26,7 +26,7 @@ A unified system to maintain an inventory of "items" and their quantities, inclu
- **PWA Deployment:** `next-pwa` (Service Workers + Manifest.json)
- **HTTPS Proxy:** `caddy` or `local-ssl-proxy` (Port 8909)
- **Servers:** Frontend (Port 8907), Backend (Port 8906)
- **Configuration:** Centrally managed via root `config/` directory (includes `network_config.env`, `ldap_config.json`, `Caddyfile`).
- **Configuration:** Centrally managed via root `inventory.env` (Network/CORS/API Keys) and `config/` directory (LDAP, Caddyfile).
## 3. Data Models & Entities
- **Item:** Name, Category Group (Structured), Item Type (Specific), Quantity, Barcode, Part Number, Box Label (Association).
@@ -80,7 +80,9 @@ To ensure enterprise-grade protection, the following policies are enforced:
- **Admin Only:** Critical operations such as `DELETE /items/`, user management, and DB settings are restricted via the `auth.get_current_admin` dependency.
- **User Role:** Standard users are permitted to perform check-in/out and list inventory, but cannot delete catalog entries.
### 7.2 Brute-Force Protection
### 7.2 CORS & Origin Policy (v1.9.18)
- **Automatic Discovery:** The system detects local LAN IP and automatically authorizes it.
- **Generic Expansion:** Use `EXTRA_ALLOWED_ORIGINS` for Tailscale or VPN IPs. The system automatically expands each IP into a set of authorized Origins (http/8916, https/8918, https/8919).
- **Rate Limiting:** Implemented via `slowapi`. The `login` endpoint is limited to **5 requests per minute** per IP to mitigate automated credential stuffing.
### 7.3 Data Privacy

View File

@@ -12,8 +12,8 @@ This project supports three distinct operational modes:
Ideal for local development on macOS/Linux.
* **Command:** `./start_server.sh`
* **Details:** Runs FastAPI (backend) and Next.js (frontend) in development mode. Uses `local-ssl-proxy` for HTTPS.
* **Backend:** http://localhost:8906
* **Frontend:** https://localhost:8909
* **Backend:** http://localhost:8916
* **Frontend:** https://localhost:8919
### 2. 🐳 Docker Mode (Recommended for Production)
Isolated and portable container stack.
@@ -58,7 +58,8 @@ The application requires the following environment variables for production depl
| Variable | Purpose | Example |
|----------|---------|---------|
| **JWT_SECRET_KEY** | JWT token signing key (REQUIRED for production) | `openssl rand -hex 32` |
| **ALLOWED_ORIGINS** | CORS-allowed domain origins (comma-separated) | `https://inventory.example.com,https://api.example.com` |
| **EXTRA_ALLOWED_ORIGINS** | Extra IPs or FQDNs for CORS (Tailscale, VPN, etc.) | `100.78.182.27,inventory.local` |
| **ALLOWED_ORIGINS** | CORS-allowed domain origins (automatically includes LOCAL_IP) | `https://inventory.example.com` |
| **DATA_DIR** | SQLite database location | `/app/data` |
| **LOGS_DIR** | Application logs directory | `/app/logs` |

View File

@@ -102,8 +102,8 @@ Access application settings from the **Admin** panel.
### 🌐 Network & Configuration (NEW v1.8.0)
The application now uses a centralized configuration folder in the project root:
- **`config/`**: Contains all network settings (`network_config.env`), LDAP profiles (`ldap_config.json`), and security proxy rules (`Caddyfile`).
- **Dynamic Port Mapping**: Changes to the server IP or ports in the configuration are automatically detected by both the frontend and backend after a restart.
- **`inventory.env`**: The primary network configuration file. Centralizes `SERVER_IP`, ports, and `EXTRA_ALLOWED_ORIGINS`.
- **Dynamic Port Mapping**: Changes to the server IP, ports, or allowed origins are automatically detected by both the frontend and backend after a restart.
---
@@ -146,5 +146,5 @@ For detailed technical documentation, see the [Project Architecture](../PROJECT_
---
**Version:** v1.8.4
**Version:** v1.9.18
**Last Updated:** 2026-04-13

View File

@@ -51,6 +51,20 @@ if server_ip and server_ip != "localhost":
if ip_o not in ALLOWED_ORIGINS:
ALLOWED_ORIGINS.append(ip_o)
# [NEW] Add Extra Allowed Origins (Tailscale, VPN, etc.)
extra_origins_raw = os.environ.get("EXTRA_ALLOWED_ORIGINS", "")
if extra_origins_raw:
for extra_ip in [o.strip() for o in extra_origins_raw.split(",") if o.strip()]:
# Generate standard combinations for this extra origin
ext_combos = [
f"http://{extra_ip}:{front_port}",
f"https://{extra_ip}:{front_ssl_port}",
f"https://{extra_ip}:{back_ssl_port}",
]
for combo in ext_combos:
if combo not in ALLOWED_ORIGINS:
ALLOWED_ORIGINS.append(combo)
log.info(f"CORS allowed origins: {ALLOWED_ORIGINS}")
# Add CORS middleware FIRST (before rate limiter)

View File

@@ -20,13 +20,19 @@ limiter = Limiter(key_func=get_remote_address)
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
def get_ldap_config():
# Read from root /config directory
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
config_dir = os.path.join(root_dir, "config")
config_path = os.path.join(config_dir, "ldap_config.json")
# Priority 1: Check in DATA_DIR (for Docker production)
config_path = os.path.join(database.DATA_DIR, "config", "ldap_config.json")
if os.path.exists(config_path):
with open(config_path, "r") as f:
return json.load(f)
# Priority 2: Fallback to source-relative config (for local dev)
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
source_config_path = os.path.join(root_dir, "config", "ldap_config.json")
if os.path.exists(source_config_path):
with open(source_config_path, "r") as f:
return json.load(f)
return {"ldap_enabled": False}
def authenticate_ldap(username, password):

View File

@@ -1,22 +1,28 @@
# TFM aInventory - Caddy Patched IP Configuration
# Version 1.9.12 - Secure Seal & IP Stability
# Version 1.9.17 - The Dynamic Shield (Production Polish)
{
admin off
# Global TLS options for self-signed certificates
# Trust local CA for internal certificate issuance
local_certs
local_certs
skip_install_trust
# Configure on-demand TLS for private network IPs
on_demand_tls {
# Pointing to the backend root which returns 200 OK
# This allows Caddy to generate internal certs for any IP/domain.
ask http://backend:8000/
}
}
# Dynamic SSL Proxy (Matches ANY IP or hostname)
https://{$SERVER_IP:192.168.84.113}:443 {
tls internal {
https:// {
tls internal {
on_demand
}
reverse_proxy frontend:3000
header {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-XSS-Protection "1; mode=block"
X-Content-Type-Options "nosniff"
@@ -26,8 +32,8 @@ https://{$SERVER_IP:192.168.84.113}:443 {
}
# Specific port listener for backend (8918 -> 444)
https://{$SERVER_IP:192.168.84.113}:444 {
tls internal {
https://:444 {
tls internal {
on_demand
}
reverse_proxy backend:8000

View File

@@ -5,21 +5,21 @@
# ============================================================
# --- AI API Keys ---
# Google Gemini API Key (required for AI label OCR onboarding)
# Google Gemini API Key (Required for AI label OCR onboarding)
# You can also set this in the root 'inventory.env' for Docker convenience.
GEMINI_API_KEY=your_gemini_api_key_here
# --- Security ---
# JWT secret key — generate a strong random value for production:
# python3 -c "import secrets; print(secrets.token_urlsafe(64))"
# If not set, an ephemeral key is generated per-run (tokens invalidated on restart).
JWT_SECRET_KEY=change-me-generate-a-secure-random-value
# --- CORS ---
# Comma-separated list of allowed frontend origins
# Example for LAN deployment:
# ALLOWED_ORIGINS=http://192.168.84.113:8907,https://192.168.84.113:8909
ALLOWED_ORIGINS=http://localhost:8907,https://localhost:8909
# --- CORS & External Access ---
# The system automatically allows localhost and the local LAN IP.
# Use EXTRA_ALLOWED_ORIGINS for Tailscale, VPNs, or FQDNs.
# Example: EXTRA_ALLOWED_ORIGINS=100.78.182.27,inventory.my-domain.com
EXTRA_ALLOWED_ORIGINS=
# --- Data Paths (overridden by start_server.sh / docker-compose) ---
# DATA_DIR=/absolute/path/to/data
# LOGS_DIR=/absolute/path/to/logs
# --- Data Paths (usually managed by startup scripts) ---
# DATA_DIR=/app/data
# LOGS_DIR=/app/logs

View File

@@ -123,3 +123,81 @@ Obiectiv: audit de securitate complet înainte de producție.
1. Implement Phase 5: Gemini AI Vision Integration for New Item Onboarding.
2. Build the "History / Audit" view in the frontend.
3. Add "Trash/Discard" logic to the frontend UI.
# CURRENT AI WORKING SESSION — HANDOVER
**Active AI:** Gemini (Antigravity)
**Last Updated:** 2026-04-12
**Current Version:** v1.6.0 (BoxMaster)
**Branch:** dev
---
## STATUS: 🟢 STABLE — ADVANCED BOX MANAGEMENT & AI MODES COMPLETE (v1.6.0)
**CRITICAL FOR NEXT AI:** The "Box/Container Management" feature is **FINISHED**. Do NOT attempt to re-implement or look for a plan. The core logic is already in `frontend/app/page.tsx` (`onOCRMatch` and `BoxManager`), `backend/models.py`, and `frontend/lib/labels.ts`.
---
## WHAT WAS DONE THIS SESSION
### 1. Box Management Architecture (Backend)
- **Database Schema** — Added `box_label` column to the `items` table.
- **Audit Integrity** — Updated `AuditLog` snapshots to capture `box_label` at the time of each transaction, ensuring immutable historical traceability even if items are moved.
- **API Support** — Exposed `box_label` in Pydantic schemas and item routers.
### 2. Intelligent Scanner Routing (Frontend)
- **Box Match Priority** — Rewrote the scanner's `onOCRMatch` logic to prioritize box labels.
- **Multi-Item Support** — Developed a "Box Contents" interstitial modal that handles containers with multiple distinct item types.
- **Token Matching** — Implemented a local fuzzy token-matching engine for generic box text recognition without AI costs.
### 3. Dependency-Free Label System
- **Native Generation** — Built a zero-dependency SVG engine for Code 128 Barcodes and QR Codes (`lib/labels.ts`).
- **Box Manager Dashboard** — Added a dedicated UI to view all existing boxes and trigger label generation.
- **Hybrid Printing** — Implemented CSS `@media print` for professional desktop printers and "Save as PNG" rasterization for portable Bluetooth printers on mobile.
### 4. UI/UX: Targeted Field Scanning
- **Camera Capture** — Added a dedicated scan button in Edit modals that redirects OCR results directly to the "Box Label" field without performing general item matches.
### 5. Multi-Mode AI Discovery
- **Contextual Prompts** — Implemented a dual-mode toggle (Item/Box) in the AI Onboarding screen.
- **Box Extraction** — Created a specialized prompt for Gemini 2.0 Flash to extract container names while filtering out technical noise from product labels.
### 6. Operational Rigor: Step 0 Rule
- **Mandatory Documentation** — Updated `AI_RULES.md` to force documentation verification before any `save-version` (git commit) operation.
- **Master Branch Sync** — Confirmed `scripts/save_version.py` logic to keep `master` branch in sync with the latest releases automatically.
---
## WHAT THE NEXT AI MUST DO
1. **Database Encryption** — Consider implementing SQLite encryption at rest (SQLCipher) if requested.
2. **Persistent JWT** — If requested, move the `JWT_SECRET_KEY` to a `.env` file for session persistence across server restarts.
3. **Advanced Filtering** — Extend the Box Manager to allow bulk movements between boxes.
3. **LDAP Probe** — The "Test Connection" button may show "Partial Success" (handshake rejected) due to anonymous bind restrictions; login itself works fine.
4. **Monitoring** — If the rate limiter triggers too frequently for legitimate users, adjust the `slowapi` limit in `backend/routers/users.py`.
---
## SYSTEM STATE
**Active database:** `<project_root>/data/inventory.db`
**LDAP config:** `config/ldap_config.json`
**Network config:** `config/network_config.env`
**Proxy config:** `config/Caddyfile`
**Production Bundle:** `aInventory-PROD-v1.8.0.zip` (ConfigSync Final)
> [!IMPORTANT]
> **Git Access Fix**: The `xcode-select` breakage is bypassed by using the direct binary path: `/Library/Developer/CommandLineTools/usr/bin/git` (stored in `.git_path`). **DO NOT change this path.** Operations now work correctly via this direct link.
**How to start:**
```bash
./start_server.sh
```
- Frontend: `https://192.168.84.113:8909`
- Backend: `https://192.168.84.113:8908`
**Environment variables set by start_server.sh:**
- `ALLOWED_ORIGINS` — auto-detected
- `DATA_DIR` — absolute path
- `JWT_SECRET_KEY` — ephemeral (regenerates on restart)
\n---\n

View File

@@ -1,55 +1,41 @@
# CURRENT AI WORKING SESSION — HANDOVER
**Active AI:** Gemini (Antigravity)
**Last Updated:** 2026-04-12
**Current Version:** v1.6.0 (BoxMaster)
**Last Updated:** 2026-04-13
**Current Version:** v1.9.18 (CORS Sync)
**Branch:** dev
---
## STATUS: 🟢 STABLE — ADVANCED BOX MANAGEMENT & AI MODES COMPLETE (v1.6.0)
## STATUS: 🟢 STABLE — GENERIC CORS & CONFIG CENTRALIZATION COMPLETE
**CRITICAL FOR NEXT AI:** The "Box/Container Management" feature is **FINISHED**. Do NOT attempt to re-implement or look for a plan. The core logic is already in `frontend/app/page.tsx` (`onOCRMatch` and `BoxManager`), `backend/models.py`, and `frontend/lib/labels.ts`.
**CRITICAL FOR NEXT AI:** The CORS system has been upgraded to support `EXTRA_ALLOWED_ORIGINS` in `inventory.env`. The backend automatically expands these into full URLs (http/https across all ports).
---
## WHAT WAS DONE THIS SESSION
### 1. Box Management Architecture (Backend)
- **Database Schema** — Added `box_label` column to the `items` table.
- **Audit Integrity** — Updated `AuditLog` snapshots to capture `box_label` at the time of each transaction, ensuring immutable historical traceability even if items are moved.
- **API Support** — Exposed `box_label` in Pydantic schemas and item routers.
### 1. Generic CORS (External Access)
- **Variable**: Introduced `EXTRA_ALLOWED_ORIGINS` in `inventory.env`.
- **Backend Expansion**: Updated `backend/main.py` to automatically generate allowed origins (plain/SSL) for any IP or FQDN provided in this comma-separated list.
- **Tailscale Ready**: Pre-configured with `100.78.182.27` as requested by the user.
### 2. Intelligent Scanner Routing (Frontend)
- **Box Match Priority** — Rewrote the scanner's `onOCRMatch` logic to prioritize box labels.
- **Multi-Item Support** — Developed a "Box Contents" interstitial modal that handles containers with multiple distinct item types.
- **Token Matching** — Implemented a local fuzzy token-matching engine for generic box text recognition without AI costs.
### 2. Configuration Centralization
- **inventory.env Updates**: Added placeholders for `GEMINI_API_KEY` and security tokens to encourage usage of a single configuration file for both local and Docker deployments.
- **Port Consistency**: Cleaned up `config/backend.env.example` to reflect the actual ports used (8916-8919).
### 3. Dependency-Free Label System
- **Native Generation** — Built a zero-dependency SVG engine for Code 128 Barcodes and QR Codes (`lib/labels.ts`).
- **Box Manager Dashboard** — Added a dedicated UI to view all existing boxes and trigger label generation.
- **Hybrid Printing** — Implemented CSS `@media print` for professional desktop printers and "Save as PNG" rasterization for portable Bluetooth printers on mobile.
### 3. Startup & Discovery
- **Dynamic Access Banner**: Enhanced `start_server.sh` to detect `EXTRA_ALLOWED_ORIGINS` and display the corresponding Tailscale/VPN URLs at startup.
### 4. UI/UX: Targeted Field Scanning
- **Camera Capture** — Added a dedicated scan button in Edit modals that redirects OCR results directly to the "Box Label" field without performing general item matches.
### 5. Multi-Mode AI Discovery
- **Contextual Prompts** — Implemented a dual-mode toggle (Item/Box) in the AI Onboarding screen.
- **Box Extraction** — Created a specialized prompt for Gemini 2.0 Flash to extract container names while filtering out technical noise from product labels.
### 6. Operational Rigor: Step 0 Rule
- **Mandatory Documentation** — Updated `AI_RULES.md` to force documentation verification before any `save-version` (git commit) operation.
- **Master Branch Sync** — Confirmed `scripts/save_version.py` logic to keep `master` branch in sync with the latest releases automatically.
### 4. Verification
- **Test Script**: Verified the CORS expansion logic with `scratch/verify_cors.py` (deleted after use).
---
## WHAT THE NEXT AI MUST DO
1. **Database Encryption** — Consider implementing SQLite encryption at rest (SQLCipher) if requested.
2. **Persistent JWT** If requested, move the `JWT_SECRET_KEY` to a `.env` file for session persistence across server restarts.
3. **Advanced Filtering** — Extend the Box Manager to allow bulk movements between boxes.
3. **LDAP Probe** — The "Test Connection" button may show "Partial Success" (handshake rejected) due to anonymous bind restrictions; login itself works fine.
4. **Monitoring** — If the rate limiter triggers too frequently for legitimate users, adjust the `slowapi` limit in `backend/routers/users.py`.
1. **Docker Sync**: If the user experiences issues with the API key in Docker, suggest rebuilding the image or ensuring `inventory.env` is correctly mounted.
2. **Reverse Proxy**: If a more complex FQDN setup is needed, consider updating `config/Caddyfile` to handle wildcard subdomains if `EXTRA_ALLOWED_ORIGINS` list becomes too long.
---
@@ -57,21 +43,16 @@
**Active database:** `<project_root>/data/inventory.db`
**LDAP config:** `config/ldap_config.json`
**Network config:** `config/network_config.env`
**Network config:** `inventory.env` (Now the Primary SSOT for networking)
**Proxy config:** `config/Caddyfile`
**Production Bundle:** `aInventory-PROD-v1.8.0.zip` (ConfigSync Final)
> [!IMPORTANT]
> **Git Access Fix**: The `xcode-select` breakage is bypassed by using the direct binary path: `/Library/Developer/CommandLineTools/usr/bin/git` (stored in `.git_path`). **DO NOT change this path.** Operations now work correctly via this direct link.
**How to start:**
```bash
./start_server.sh
```
- Frontend: `https://192.168.84.113:8909`
- Backend: `https://192.168.84.113:8908`
- Local URL: `https://localhost:8919`
- LAN URL: `https://192.168.84.113:8919`
- Tailscale URL: `https://100.78.182.27:8919` (Now allowed in CORS)
**Environment variables set by start_server.sh:**
- `ALLOWED_ORIGINS` — auto-detected
- `DATA_DIR` — absolute path
- `JWT_SECRET_KEY` — ephemeral (regenerates on restart)
---
✓ Done.

View File

@@ -1 +1,6 @@
{"version": "1.9.11", "last_build": "2026-04-13-2215", "codename": "Convergence", "commit": "9d7e4f0c"}
{
"version": "1.9.18",
"last_build": "2026-04-13-2343",
"codename": "MobilePolish",
"commit": "1fff658d"
}

View File

@@ -471,37 +471,30 @@ export default function Home() {
</div>
</div>
<div className="flex flex-wrap items-center justify-between sm:justify-end gap-3 sm:gap-6 bg-slate-900/40 sm:bg-transparent p-3 sm:p-0 rounded-2xl border border-slate-800/50 sm:border-none">
<div className="flex flex-wrap items-center gap-4">
<div className="flex flex-wrap items-center justify-between sm:justify-end gap-3 sm:gap-6 bg-slate-900/40 sm:bg-transparent px-4 py-2 sm:p-0 rounded-2xl border border-slate-800/50 sm:border-none">
<div className="flex flex-wrap items-center gap-4 sm:gap-6">
{isScannerReady && (
<div className="flex items-center gap-1.5">
<div className="w-1 h-1 rounded-full bg-green-500 shadow-[0_0_5px_rgba(34,197,94,0.5)]" />
<span className="text-xs font-black text-green-500/80 whitespace-nowrap">
Offline Scan: OK
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]" />
<span className="text-[10px] sm:text-xs font-black text-green-500/90 whitespace-nowrap tracking-wider uppercase">
Scanner: OK
</span>
</div>
)}
<div className="flex items-center gap-1.5">
<div className={`w-1 h-1 rounded-full ${isOnline ? 'bg-green-500 animate-pulse shadow-[0_0_5px_rgba(34,197,94,0.5)]' : 'bg-rose-500 shadow-[0_0_5px_rgba(244,63,94,0.5)]'}`} />
<span className={`text-xs font-black whitespace-nowrap ${isOnline ? 'text-green-500/80' : 'text-rose-500/80'}`}>
Server Sync: {isOnline ? 'OK' : 'No'}
<div className="flex items-center gap-2">
<div className={`w-1.5 h-1.5 rounded-full ${isOnline ? 'bg-green-500 animate-pulse shadow-[0_0_8px_rgba(34,197,94,0.6)]' : 'bg-rose-500 shadow-[0_0_8px_rgba(244,63,94,0.6)]'}`} />
<span className={`text-[10px] sm:text-xs font-black whitespace-nowrap tracking-wider uppercase ${isOnline ? 'text-green-500/90' : 'text-rose-500/90'}`}>
Sync: {isOnline ? 'Active' : 'Offline'}
</span>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setShowBoxManager(true)}
className="p-3 bg-slate-900 border border-slate-800 text-slate-400 rounded-xl hover:text-primary transition-colors hover:border-primary/30"
title="Box Manager"
>
<Package size={20} />
</button>
<div className="flex items-center gap-3">
<button
onClick={handleSync}
disabled={syncing}
className="p-3 bg-slate-900 border border-slate-800 text-slate-400 rounded-xl hover:text-white transition-colors disabled:opacity-50"
className="p-2.5 bg-slate-900/80 border border-slate-800 text-slate-400 rounded-xl hover:text-white transition-all active:scale-95 disabled:opacity-50"
>
<RefreshCw size={20} className={syncing ? "animate-spin text-primary" : ""} />
<RefreshCw size={18} className={syncing ? "animate-spin text-primary" : ""} />
</button>
</div>
</div>
@@ -509,22 +502,22 @@ export default function Home() {
<div className="w-full px-1 space-y-6">
{/* Mode Switcher */}
<div className="flex p-1 bg-slate-900/80 backdrop-blur-md rounded-2xl shadow-inner w-full">
<div className="flex p-1.5 bg-slate-900/80 backdrop-blur-md rounded-2xl shadow-inner w-full gap-1">
{[
{ id: 'CHECK_IN', label: 'Check in', icon: ArrowDownCircle },
{ id: 'CHECK_OUT', label: 'Check out', icon: ArrowUpCircle },
{ id: 'CHECK_IN', label: 'Check In', icon: ArrowDownCircle },
{ id: 'CHECK_OUT', label: 'Check Out', icon: ArrowUpCircle },
{ id: 'TRASH', label: 'Trash', icon: Trash2 }
].map((m) => (
<button
key={m.id}
onClick={() => setMode(m.id as any)}
className={cn(
"flex-1 py-3 rounded-xl text-sm font-black transition-all flex items-center justify-center gap-2",
mode === m.id ? "bg-slate-800 text-primary shadow-lg" : "text-slate-500 hover:text-slate-300"
"flex-1 py-3.5 rounded-xl text-xs sm:text-sm font-black transition-all flex items-center justify-center gap-3",
mode === m.id ? "bg-slate-800 text-primary shadow-lg ring-1 ring-primary/20" : "text-slate-500 hover:text-slate-300"
)}
>
<m.icon size={18} />
{m.label}
<m.icon size={18} className={mode === m.id ? "scale-110 transition-transform" : ""} />
<span className="truncate">{m.label}</span>
</button>
))}
</div>
@@ -558,15 +551,31 @@ export default function Home() {
<p className="text-sm text-slate-400">Scan labels to {mode.replace('_', ' ')} items</p>
</div>
<div className="w-full h-px bg-slate-800 my-2" />
<div className="w-full h-px bg-slate-800/50 my-2" />
<div className="w-full grid grid-cols-1 gap-4">
<button
onClick={() => setShowOnboarding(true)}
className="w-full h-16 rounded-[1.5rem] bg-slate-900/50 border border-slate-800 flex items-center justify-center gap-3 group hover:border-primary/40 transition-all font-bold"
className="w-full h-18 py-4 rounded-[1.5rem] bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center gap-4 group hover:border-indigo-500/50 transition-all font-black text-indigo-400"
>
<Sparkles size={18} className="text-primary group-hover:scale-110 transition-transform" />
<span className="text-sm">Add NEW Item<br />(AI Onboarding)</span>
<Sparkles size={24} className="group-hover:scale-125 transition-transform" />
<span className="text-left leading-tight">
Add NEW Item<br />
<span className="text-[10px] opacity-60 uppercase tracking-widest font-mono">AI Onboarding</span>
</span>
</button>
<button
onClick={() => setShowBoxManager(true)}
className="w-full h-18 py-4 rounded-[1.5rem] bg-slate-900/50 border border-slate-800 flex items-center justify-center gap-4 group hover:border-primary/40 transition-all font-black text-slate-300"
>
<Package size={24} className="text-primary group-hover:scale-125 transition-transform" />
<span className="text-left leading-tight">
Manage Boxes<br />
<span className="text-[10px] opacity-60 uppercase tracking-widest font-mono">Box Inventory</span>
</span>
</button>
</div>
</div>
)}
</section>

View File

@@ -15,5 +15,10 @@ BACKEND_SSL_PORT=8918
FRONTEND_PORT=8917
FRONTEND_SSL_PORT=8919
# Security
# Security & AI
JWT_SECRET_KEY=change_me_in_production
GEMINI_API_KEY=AIzaSyAajthWG2agpDLyJHY11U5qFLP4WnV5z0w
# External Access (CORS)
# Comma-separated list of extra IPs or FQDNs allowed to connect (e.g. Tailscale, VPN)
EXTRA_ALLOWED_ORIGINS=100.78.182.27

View File

@@ -87,9 +87,19 @@ echo -e "${GREEN}=======================================================${NC}"
echo -e "${GREEN}${BOLD} 🚀 TFM aInventory UNIFIED ACCESS${NC}"
echo -e "${GREEN}=======================================================${NC}"
echo ""
echo -e " USE THIS URL ON BOTH DESKTOP & MOBILE:"
echo -e " 👉 ${GREEN}${BOLD}https://$LOCAL_IP:$FRONTEND_SSL_PORT${NC}"
echo -e " (Or ${GREEN}https://localhost:$FRONTEND_SSL_PORT${NC} on this Mac)"
echo -e " USE THIS URL ON BOTH DESKTOP & MOBILE:"
echo -e " 👉 ${GREEN}${BOLD}https://$LOCAL_IP:$FRONTEND_SSL_PORT${NC}"
echo -e " (Or ${GREEN}https://localhost:$FRONTEND_SSL_PORT${NC} on this Mac)"
if [ ! -z "$EXTRA_ALLOWED_ORIGINS" ]; then
echo -e " EXTERNAL/VPN ACCESS points:"
IFS=',' read -ra ADDR <<< "$EXTRA_ALLOWED_ORIGINS"
for exp in "${ADDR[@]}"; do
# Trim spaces and print
TRIMMED=$(echo $exp | xargs)
echo -e " 👉 ${GREEN}${BOLD}https://$TRIMMED:$FRONTEND_SSL_PORT${NC}"
done
fi
echo ""
echo -e " ${YELLOW}${BOLD}NOTE:${NC} If you see a 'Not Private' warning,"
echo -e " Click 'Advanced' -> 'Proceed' to continue."