Build [v1.9.18]
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# TFM aInventory - Caddy Patched IP Configuration
|
||||
# Version 1.9.16 - The Open Gateway (Valid Permission check)
|
||||
# Version 1.9.17 - The Dynamic Shield (Production Polish)
|
||||
{
|
||||
admin off
|
||||
# Global TLS options for self-signed certificates
|
||||
|
||||
# Global TLS options for self-signed certificates
|
||||
local_certs
|
||||
skip_install_trust
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
{"version": "1.9.15", "last_build": "2026-04-13-2243", "codename": "DynamicShield", "commit": "826b264a"}
|
||||
{
|
||||
"version": "1.9.18",
|
||||
"last_build": "2026-04-13-2343",
|
||||
"codename": "MobilePolish",
|
||||
"commit": "1fff658d"
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,6 +90,16 @@ 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)"
|
||||
|
||||
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."
|
||||
|
||||
Reference in New Issue
Block a user