mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
Compare commits
4 Commits
67dc5cc737
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dfdd500b4 | |||
| b539aa1b91 | |||
| 616568ca7b | |||
| 5da6360ed9 |
+10
-9
@@ -1,13 +1,14 @@
|
||||
# Trackeep Configuration for Casa OS
|
||||
# Only required variables - everything else is auto-configured
|
||||
# Trackeep All-in-One Configuration
|
||||
# PostgreSQL is bundled inside the container — no external database needed.
|
||||
# Everything below is optional; the container auto-generates sensible defaults.
|
||||
|
||||
# Host port for the application (default: 8080)
|
||||
# Host port mapping (default: 8080)
|
||||
HOST_PORT=8080
|
||||
|
||||
# Database Configuration
|
||||
DB_PASSWORD=your_secure_password_here
|
||||
DB_USER=trackeep
|
||||
DB_NAME=trackeep
|
||||
# Database credentials (auto-generated if left empty)
|
||||
# DB_PASSWORD=your_secure_password_here
|
||||
# DB_USER=trackeep
|
||||
# DB_NAME=trackeep
|
||||
|
||||
# JWT Secret (generate with: openssl rand -hex 32)
|
||||
JWT_SECRET=your_jwt_secret_here_64_hex_characters_long_exactly
|
||||
# JWT Secret (auto-generated and persisted in /data if left empty)
|
||||
# JWT_SECRET=your_jwt_secret_here_64_hex_characters_long_exactly
|
||||
|
||||
@@ -145,6 +145,11 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
# Optional repository variables (Settings > Secrets and variables > Actions > Variables).
|
||||
# VITE_API_URL defaults to empty for same-origin relative URLs in unified deployments.
|
||||
build-args: |
|
||||
VITE_API_URL=${{ vars.VITE_API_URL || '' }}
|
||||
VITE_DEMO_MODE=${{ vars.VITE_DEMO_MODE || 'false' }}
|
||||
|
||||
# deploy:
|
||||
# name: Deploy to Production
|
||||
|
||||
+16
-4
@@ -4,6 +4,14 @@
|
||||
# Stage 1: Build Frontend
|
||||
FROM node:22-alpine AS frontend-builder
|
||||
WORKDIR /app/frontend
|
||||
|
||||
# Accept build arguments for Vite environment variables.
|
||||
# If unset, the frontend falls back to same-origin relative URLs in production.
|
||||
ARG VITE_API_URL
|
||||
ARG VITE_DEMO_MODE=false
|
||||
ENV VITE_API_URL=${VITE_API_URL}
|
||||
ENV VITE_DEMO_MODE=${VITE_DEMO_MODE}
|
||||
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm install
|
||||
COPY frontend/ ./
|
||||
@@ -20,8 +28,12 @@ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
|
||||
# Stage 3: Final unified image
|
||||
FROM alpine:latest
|
||||
|
||||
# Install dependencies
|
||||
RUN apk --no-cache add ca-certificates tzdata nginx
|
||||
# Install dependencies including PostgreSQL
|
||||
RUN apk --no-cache add ca-certificates tzdata nginx postgresql postgresql-contrib
|
||||
|
||||
# Create postgres user directories and fix permissions
|
||||
RUN mkdir -p /var/lib/postgresql/data /run/postgresql /var/log/postgresql && \
|
||||
chown -R postgres:postgres /var/lib/postgresql /run/postgresql /var/log/postgresql
|
||||
|
||||
# Copy backend binary and migrations
|
||||
COPY --from=backend-builder /app/backend/main /app/main
|
||||
@@ -45,10 +57,10 @@ RUN mkdir -p /app/uploads /data /var/log/nginx
|
||||
EXPOSE 8080
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
||||
|
||||
# Start script to run both backend and nginx
|
||||
# Start script to run PostgreSQL, backend and nginx
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
|
||||
@@ -33,34 +33,23 @@
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### One-Command Deployment (GHCR Image - Recommended for Casa OS)
|
||||
### One-Command Deployment (Docker Run)
|
||||
|
||||
PostgreSQL is bundled inside the image. Zero external dependencies.
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name trackeep \
|
||||
-p 8080:8080 \
|
||||
-e DB_PASSWORD=your_password \
|
||||
-e DB_USER=trackeep \
|
||||
-e DB_NAME=trackeep \
|
||||
-e JWT_SECRET=your_jwt_secret \
|
||||
-e DB_PASSWORD=your_secure_password \
|
||||
-e JWT_SECRET=$(openssl rand -hex 32) \
|
||||
-v trackeep_postgres:/var/lib/postgresql/data \
|
||||
-v trackeep_uploads:/app/uploads \
|
||||
-v trackeep_data:/data \
|
||||
ghcr.io/dvorinka/trackeep:latest
|
||||
```
|
||||
|
||||
**Note**: This requires an external PostgreSQL database. For a complete deployment with the database included, use Docker Compose below.
|
||||
|
||||
### Production Deployment with Docker Compose
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dvorinka/trackeep.git
|
||||
cd trackeep
|
||||
cp .env.example .env
|
||||
# Edit .env file with your configuration
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The setup uses a unified Docker image with frontend and backend in a single container.
|
||||
|
||||
**Complete docker-compose.yml**:
|
||||
### CasaOS / Docker Compose (Copy-Paste Ready)
|
||||
|
||||
```yaml
|
||||
icon: https://github.com/Dvorinka/Trackeep/raw/main/trackeepfavi_bg.png
|
||||
@@ -68,78 +57,47 @@ icon: https://github.com/Dvorinka/Trackeep/raw/main/trackeepfavi_bg.png
|
||||
services:
|
||||
trackeep:
|
||||
image: ghcr.io/dvorinka/trackeep:latest
|
||||
container_name: trackeep
|
||||
ports:
|
||||
- "${HOST_PORT:-8080}:8080"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- BACKEND_PORT=8080
|
||||
- DB_HOST=postgres
|
||||
- DB_PORT=5432
|
||||
- GIN_MODE=release
|
||||
DB_PASSWORD: ${DB_PASSWORD:-}
|
||||
DB_USER: ${DB_USER:-trackeep}
|
||||
DB_NAME: ${DB_NAME:-trackeep}
|
||||
JWT_SECRET: ${JWT_SECRET:-}
|
||||
GIN_MODE: release
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
- ./data:/data
|
||||
- trackeep_postgres:/var/lib/postgresql/data
|
||||
- trackeep_uploads:/app/uploads
|
||||
- trackeep_data:/data
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME:-trackeep}
|
||||
POSTGRES_USER: ${DB_USER:-trackeep}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-trackeep} -d ${DB_NAME:-trackeep}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
trackeep_postgres:
|
||||
trackeep_uploads:
|
||||
trackeep_data:
|
||||
```
|
||||
|
||||
### Service Architecture
|
||||
**Why this is CasaOS-ready:**
|
||||
- **Single service** — PostgreSQL runs inside the same container
|
||||
- **No `BACKEND_PORT`** — internal backend runs on 8081, only port 8080 is exposed
|
||||
- **Named volumes** — CasaOS handles them automatically
|
||||
- **Optional env vars** — if `DB_PASSWORD` or `JWT_SECRET` are empty, the container auto-generates them
|
||||
- **Icon header** — CasaOS reads the `icon:` field for the app tile
|
||||
|
||||
Trackeep deployment consists of **2 services**:
|
||||
### Optional Environment Variables
|
||||
|
||||
#### **🎯 Trackeep Service (Unified)**
|
||||
- **Image**: Built from unified Dockerfile (frontend + backend in one)
|
||||
- **Ports**: `${HOST_PORT:-8080}:8080`
|
||||
- **Purpose**: Web interface, API server, and business logic combined
|
||||
- **Health**: HTTP health check endpoint
|
||||
- **Auto-configuration**: Frontend automatically connects to backend via nginx proxy
|
||||
|
||||
#### **🗄️ Database Service**
|
||||
- **Image**: `postgres:15-alpine`
|
||||
- **Purpose**: Data persistence and storage
|
||||
- **Health**: PostgreSQL readiness check
|
||||
- **Storage**: Persistent volume for data
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
Create a `.env` file from the provided `.env.example` and configure these required variables:
|
||||
All variables have sensible defaults. Only override what you need:
|
||||
|
||||
```env
|
||||
# Host port for the application (default: 8080)
|
||||
HOST_PORT=8080
|
||||
|
||||
# Database Configuration
|
||||
DB_PASSWORD=your_secure_password_here
|
||||
DB_PASSWORD=your_secure_password_here # auto-generated if empty
|
||||
DB_USER=trackeep
|
||||
DB_NAME=trackeep
|
||||
|
||||
# JWT Secret (generate with: openssl rand -hex 32)
|
||||
JWT_SECRET=your_jwt_secret_here_64_hex_characters_long_exactly
|
||||
JWT_SECRET=your_jwt_secret_here # auto-generated & persisted if empty
|
||||
```
|
||||
|
||||
**Note**: The frontend automatically connects to the backend via nginx proxy - no VITE_API_URL or additional configuration needed.
|
||||
**Note:** The frontend automatically connects to the backend via nginx proxy — no `VITE_API_URL` or additional configuration needed.
|
||||
|
||||
### AI Services Configuration
|
||||
|
||||
@@ -404,25 +362,15 @@ DISABLE_CHINESE_AI=true
|
||||
cd Trackeep
|
||||
```
|
||||
|
||||
2. **Configure environment**
|
||||
2. **Start the container**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your configuration
|
||||
```
|
||||
|
||||
3. **Start all services**
|
||||
```bash
|
||||
# Using the startup script
|
||||
./start.sh
|
||||
|
||||
# Or manually with Docker Compose
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
4. **Access the application**
|
||||
- Application: http://localhost:${HOST_PORT:-8080}
|
||||
- Health Check: http://localhost:${HOST_PORT:-8080}/health
|
||||
- API: http://localhost:${HOST_PORT:-8080}/api/
|
||||
3. **Access the application**
|
||||
- Application: http://localhost:8080
|
||||
- Health Check: http://localhost:8080/health
|
||||
- API: http://localhost:8080/api/
|
||||
|
||||
### Demo Login
|
||||
- Email: `demo@trackeep.com`
|
||||
@@ -489,22 +437,22 @@ Additional documentation files:
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Key environment variables to configure:
|
||||
Only override what you need — everything else auto-configures:
|
||||
|
||||
```bash
|
||||
# Host port for the application
|
||||
HOST_PORT=8080
|
||||
|
||||
# Database Configuration
|
||||
# Database credentials (auto-generated if omitted)
|
||||
DB_PASSWORD=your_secure_password_here
|
||||
DB_USER=trackeep
|
||||
DB_NAME=trackeep
|
||||
|
||||
# JWT Configuration (generate with: openssl rand -hex 32)
|
||||
# JWT Secret (auto-generated & persisted if omitted)
|
||||
JWT_SECRET=your_jwt_secret_here_64_hex_characters_long_exactly
|
||||
```
|
||||
|
||||
**Note**: All other configuration has sensible defaults. The frontend automatically connects to the backend via nginx proxy - no additional API URL configuration needed.
|
||||
**Note:** All other configuration has sensible defaults. The frontend automatically connects to the backend via nginx proxy — no additional API URL configuration needed.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
+25
-29
@@ -1,39 +1,35 @@
|
||||
icon: https://github.com/Dvorinka/Trackeep/raw/main/trackeepfavi_bg.png
|
||||
|
||||
services:
|
||||
trackeep:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: ghcr.io/dvorinka/trackeep:latest
|
||||
container_name: trackeep
|
||||
ports:
|
||||
- "${HOST_PORT:-8080}:8080"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- DB_HOST=postgres
|
||||
- DB_PORT=5432
|
||||
- GIN_MODE=release
|
||||
DB_PASSWORD: ${DB_PASSWORD:-}
|
||||
DB_USER: ${DB_USER:-trackeep}
|
||||
DB_NAME: ${DB_NAME:-trackeep}
|
||||
JWT_SECRET: ${JWT_SECRET:-}
|
||||
GIN_MODE: release
|
||||
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-*}
|
||||
# VITE_API_URL defaults to empty for same-origin relative URLs.
|
||||
# Set explicitly only when frontend and backend are on different origins.
|
||||
VITE_API_URL: ${VITE_API_URL:-}
|
||||
VITE_DEMO_MODE: ${VITE_DEMO_MODE:-false}
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
- ./data:/data
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME:-trackeep}
|
||||
POSTGRES_USER: ${DB_USER:-trackeep}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- trackeep_postgres:/var/lib/postgresql/data
|
||||
- trackeep_uploads:/app/uploads
|
||||
- trackeep_data:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-trackeep} -d ${DB_NAME:-trackeep}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
trackeep_postgres:
|
||||
trackeep_uploads:
|
||||
trackeep_data:
|
||||
|
||||
+89
-11
@@ -1,24 +1,90 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Unified entrypoint for Trackeep
|
||||
# Starts both backend and nginx in one container
|
||||
# All-in-one entrypoint for Trackeep
|
||||
# Initializes and starts PostgreSQL, then backend + nginx
|
||||
|
||||
set -e
|
||||
|
||||
# Backend configuration
|
||||
PGDATA=${PGDATA:-/var/lib/postgresql/data}
|
||||
|
||||
# Auto-generate DB_PASSWORD if not provided
|
||||
if [ -z "$DB_PASSWORD" ]; then
|
||||
DB_PASSWORD=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 32)
|
||||
echo "========================================"
|
||||
echo "WARNING: DB_PASSWORD was not set."
|
||||
echo "Auto-generated password: $DB_PASSWORD"
|
||||
echo "Set DB_PASSWORD explicitly to keep it stable across restarts."
|
||||
echo "========================================"
|
||||
fi
|
||||
|
||||
DB_USER=${DB_USER:-trackeep}
|
||||
DB_NAME=${DB_NAME:-trackeep}
|
||||
|
||||
# Ensure PostgreSQL directories are owned by postgres (fixes volume permission issues)
|
||||
mkdir -p "$PGDATA" /run/postgresql /var/log/postgresql
|
||||
chown -R postgres:postgres "$PGDATA" /run/postgresql /var/log/postgresql
|
||||
|
||||
# Initialize PostgreSQL if data directory is empty
|
||||
if [ ! -f "$PGDATA/PG_VERSION" ]; then
|
||||
echo "Initializing PostgreSQL database cluster..."
|
||||
su -s /bin/sh postgres -c "initdb -D $PGDATA --auth-local=trust --auth-host=md5"
|
||||
|
||||
# Allow local TCP connections
|
||||
echo "host all all 127.0.0.1/32 md5" >> "$PGDATA/pg_hba.conf"
|
||||
echo "host all all ::1/128 md5" >> "$PGDATA/pg_hba.conf"
|
||||
|
||||
# Start postgres temporarily to create user and database
|
||||
su -s /bin/sh postgres -c "pg_ctl -D $PGDATA -l /var/log/postgresql/server.log start"
|
||||
|
||||
# Wait until postgres accepts connections
|
||||
echo "Waiting for PostgreSQL to accept connections..."
|
||||
for i in $(seq 1 30); do
|
||||
if su -s /bin/sh postgres -c "pg_isready -q"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Create role and database
|
||||
su -s /bin/sh postgres -c "psql -c \"CREATE USER \\\"$DB_USER\\\" WITH PASSWORD '$DB_PASSWORD';\""
|
||||
su -s /bin/sh postgres -c "psql -c \"CREATE DATABASE \\\"$DB_NAME\\\" OWNER \\\"$DB_USER\\\";\""
|
||||
|
||||
su -s /bin/sh postgres -c "pg_ctl -D $PGDATA stop"
|
||||
echo "PostgreSQL initialized."
|
||||
fi
|
||||
|
||||
# Start PostgreSQL
|
||||
echo "Starting PostgreSQL..."
|
||||
su -s /bin/sh postgres -c "pg_ctl -D $PGDATA -l /var/log/postgresql/server.log start"
|
||||
|
||||
# Wait for PostgreSQL to be ready
|
||||
echo "Waiting for PostgreSQL to be ready..."
|
||||
for i in $(seq 1 30); do
|
||||
if su -s /bin/sh postgres -c "pg_isready -q"; then
|
||||
echo "PostgreSQL is ready."
|
||||
break
|
||||
fi
|
||||
echo "Waiting for PostgreSQL... ($i/30)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Backend connects to the bundled local PostgreSQL
|
||||
export BACKEND_PORT=8081
|
||||
export DB_HOST=${DB_HOST:-postgres}
|
||||
export DB_PORT=${DB_PORT:-5432}
|
||||
export DB_NAME=${DB_NAME:-trackeep}
|
||||
export DB_USER=${DB_USER:-trackeep}
|
||||
export DB_PASSWORD=${DB_PASSWORD}
|
||||
export JWT_SECRET=${JWT_SECRET}
|
||||
export DB_HOST=localhost
|
||||
export DB_PORT=5432
|
||||
export DB_NAME="$DB_NAME"
|
||||
export DB_USER="$DB_USER"
|
||||
export DB_PASSWORD="$DB_PASSWORD"
|
||||
export DB_SSL_MODE=disable
|
||||
export JWT_SECRET=${JWT_SECRET:-}
|
||||
export GIN_MODE=${GIN_MODE:-release}
|
||||
export CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-*}
|
||||
|
||||
# Start backend in background
|
||||
cd /app
|
||||
echo "Starting Trackeep backend on port ${BACKEND_PORT}..."
|
||||
./main &
|
||||
BACKEND_PID=$!
|
||||
|
||||
# Wait for backend to be ready
|
||||
echo "Waiting for backend to be ready..."
|
||||
@@ -31,6 +97,18 @@ for i in $(seq 1 30); do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Start nginx
|
||||
echo "Starting nginx..."
|
||||
# Runtime environment variable injection for frontend.
|
||||
# The frontend is built with placeholders; at container startup we replace
|
||||
# them so the same image works for any deployment target (Casa, local, etc.).
|
||||
HTML_FILE="/usr/share/nginx/html/index.html"
|
||||
if [ -f "$HTML_FILE" ]; then
|
||||
VITE_API_URL=${VITE_API_URL:-}
|
||||
VITE_DEMO_MODE=${VITE_DEMO_MODE:-false}
|
||||
sed -i "s|VITE_API_URL_PLACEHOLDER|$VITE_API_URL|g" "$HTML_FILE"
|
||||
sed -i "s|VITE_DEMO_MODE_PLACEHOLDER|$VITE_DEMO_MODE|g" "$HTML_FILE"
|
||||
echo "Frontend env injected: VITE_API_URL='$VITE_API_URL', VITE_DEMO_MODE='$VITE_DEMO_MODE'"
|
||||
fi
|
||||
|
||||
# Start nginx in foreground (keeps container alive)
|
||||
echo "Starting nginx on port 8080..."
|
||||
nginx -g "daemon off;"
|
||||
|
||||
@@ -40,6 +40,7 @@ http {
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
listen [::]:8080;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createSignal, onMount, Show, For } from 'solid-js';
|
||||
import { Button } from './ui/Button';
|
||||
import { getApiOrigin } from '@/lib/api-url';
|
||||
|
||||
interface TOTPSetupResponse {
|
||||
secret: string;
|
||||
@@ -42,7 +43,7 @@ export function TwoFactorAuth() {
|
||||
|
||||
const fetchTOTPStatus = async () => {
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/status`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/status`, {
|
||||
headers: getAuthHeaders(),
|
||||
});
|
||||
|
||||
@@ -66,7 +67,7 @@ export function TwoFactorAuth() {
|
||||
setSuccess(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/setup`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/setup`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -102,7 +103,7 @@ export function TwoFactorAuth() {
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/verify`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/verify`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -135,7 +136,7 @@ export function TwoFactorAuth() {
|
||||
setSuccess(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/enable`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/enable`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -171,7 +172,7 @@ export function TwoFactorAuth() {
|
||||
setSuccess(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/disable`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/disable`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -206,7 +207,7 @@ export function TwoFactorAuth() {
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/backup-codes/verify`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/verify`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -240,7 +241,7 @@ export function TwoFactorAuth() {
|
||||
setSuccess(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/backup-codes/regenerate`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/regenerate`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal, For, Show, onMount } from 'solid-js';
|
||||
import { getApiOrigin } from '@/lib/api-url';
|
||||
import { useSearchParams } from '@solidjs/router';
|
||||
import {
|
||||
IconSearch,
|
||||
@@ -118,7 +119,7 @@ export const EnhancedSearch = () => {
|
||||
|
||||
if (currentFilters.search_mode === 'semantic') {
|
||||
// Use semantic search API
|
||||
response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/semantic`, {
|
||||
response = await fetch(`${getApiOrigin()}/api/v1/search/semantic`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -145,7 +146,7 @@ export const EnhancedSearch = () => {
|
||||
}
|
||||
} else {
|
||||
// Use enhanced full-text search API
|
||||
response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/enhanced`, {
|
||||
response = await fetch(`${getApiOrigin()}/api/v1/search/enhanced`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal, For, Show, onMount } from 'solid-js';
|
||||
import { getApiOrigin } from '@/lib/api-url';
|
||||
import {
|
||||
IconBookmark,
|
||||
IconSearch,
|
||||
@@ -61,7 +62,7 @@ export const SavedSearches = () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
@@ -82,7 +83,7 @@ export const SavedSearches = () => {
|
||||
const loadTags = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved/tags`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved/tags`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
@@ -141,7 +142,7 @@ export const SavedSearches = () => {
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved/${id}`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
@@ -160,7 +161,7 @@ export const SavedSearches = () => {
|
||||
const runSavedSearch = async (id: number) => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved/${id}/run`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved/${id}/run`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
|
||||
Vendored
+4
@@ -49,6 +49,10 @@ interface ImportMeta {
|
||||
}
|
||||
|
||||
interface Window {
|
||||
ENV?: {
|
||||
VITE_API_URL?: string;
|
||||
VITE_DEMO_MODE?: string;
|
||||
};
|
||||
importMetaEnv?: {
|
||||
VITE_API_URL?: string;
|
||||
VITE_DEMO_MODE?: string;
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
/**
|
||||
* Centralized API URL resolver.
|
||||
*
|
||||
* Problem: Vite bakes import.meta.env values at build time. When the unified
|
||||
* Docker image is built without VITE_API_URL, every API call fell back to
|
||||
* 'http://localhost:8080', which broke production deployments (e.g. Casa).
|
||||
*
|
||||
* Solution: This helper checks the runtime-injected window.ENV first
|
||||
* (set by docker-entrypoint.sh via sed replacement in index.html), then
|
||||
* build-time import.meta.env, then dev fallback. In production unified
|
||||
* deployments (same origin) it returns '' so all API calls use relative
|
||||
* URLs like '/api/v1/...' that nginx proxies to the backend.
|
||||
*/
|
||||
|
||||
const DEFAULT_API_ORIGIN = 'http://localhost:8080';
|
||||
|
||||
const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, '');
|
||||
@@ -5,16 +19,30 @@ const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, '');
|
||||
const trimApiSuffix = (value: string): string => value.replace(/\/api\/v1$/, '');
|
||||
|
||||
export const getApiOrigin = (): string => {
|
||||
const raw = (import.meta.env.VITE_API_URL as string | undefined)?.trim();
|
||||
if (!raw) {
|
||||
// 1. Runtime injection from index.html (highest priority for Docker deployments)
|
||||
const runtimeUrl = ((window as any).ENV?.VITE_API_URL as string | undefined)?.trim();
|
||||
if (runtimeUrl && runtimeUrl !== 'VITE_API_URL_PLACEHOLDER') {
|
||||
const normalized = trimTrailingSlash(runtimeUrl);
|
||||
return trimApiSuffix(normalized);
|
||||
}
|
||||
|
||||
// 2. Build-time Vite env variable (for dev builds or pre-built images)
|
||||
const buildUrl = (import.meta.env.VITE_API_URL as string | undefined)?.trim();
|
||||
if (buildUrl) {
|
||||
const normalized = trimTrailingSlash(buildUrl);
|
||||
return trimApiSuffix(normalized);
|
||||
}
|
||||
|
||||
// 3. Development fallback
|
||||
if (import.meta.env.DEV) {
|
||||
return DEFAULT_API_ORIGIN;
|
||||
}
|
||||
|
||||
const normalized = trimTrailingSlash(raw);
|
||||
return trimApiSuffix(normalized);
|
||||
// 4. Production unified deployment: same-origin relative URLs
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getApiV1BaseUrl = (): string => {
|
||||
const origin = getApiOrigin();
|
||||
return `${origin}/api/v1`;
|
||||
return origin ? `${origin}/api/v1` : '/api/v1';
|
||||
};
|
||||
|
||||
@@ -67,7 +67,10 @@ export const getSearchProvider = (): string => {
|
||||
import.meta.env.VITE_SERPER_API_KEY ? 'serper' : 'demo');
|
||||
};
|
||||
|
||||
// Get API base URL
|
||||
// Delegates to getApiOrigin so all API URL resolution goes through the
|
||||
// centralized helper that supports runtime env injection.
|
||||
import { getApiOrigin } from './api-url';
|
||||
|
||||
export const getApiBaseUrl = (): string => {
|
||||
return import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
||||
return getApiOrigin();
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getMockStats
|
||||
} from './mockData';
|
||||
import { isDemoMode } from './demo-mode';
|
||||
import { getApiV1BaseUrl } from './api-url';
|
||||
|
||||
// Demo mode API client that falls back to mock data
|
||||
export class DemoModeApiClient {
|
||||
@@ -280,8 +281,8 @@ export class DemoModeApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Create demo mode API client
|
||||
const demoApi = new DemoModeApiClient(import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1');
|
||||
// Uses getApiV1BaseUrl so demo client respects runtime env injection.
|
||||
const demoApi = new DemoModeApiClient(getApiV1BaseUrl());
|
||||
|
||||
// Export demo mode API functions that match the regular API
|
||||
export const demoBookmarksApi = {
|
||||
|
||||
@@ -140,7 +140,10 @@ export interface WsEvent {
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
||||
// Switched from raw import.meta.env to getApiOrigin for runtime env support.
|
||||
import { getApiOrigin } from './api-url';
|
||||
|
||||
const API_BASE_URL = getApiOrigin();
|
||||
|
||||
function getToken() {
|
||||
return localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { useHaptics } from '@/lib/haptics';
|
||||
import { getApiOrigin } from '@/lib/api-url';
|
||||
|
||||
interface AnalyticsData {
|
||||
period: {
|
||||
@@ -183,7 +184,7 @@ export const Analytics = () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/analytics/dashboard?days=${selectedPeriod()}`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/analytics/dashboard?days=${selectedPeriod()}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from 'lucide-solid'
|
||||
import { AIProviderIcon } from '@/components/AIProviderIcon'
|
||||
import { useHaptics } from '@/lib/haptics'
|
||||
import { getApiOrigin } from '@/lib/api-url'
|
||||
|
||||
interface AIModel {
|
||||
id: string
|
||||
@@ -133,7 +134,7 @@ export const AIChat = () => {
|
||||
|
||||
const callAIAPI = async (message: string, modelId: string): Promise<string> => {
|
||||
const token = localStorage.getItem('token')
|
||||
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080'
|
||||
const apiUrl = getApiOrigin()
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/v1/ai/chat`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createEffect, createResource, createSignal, For, Show, onMount } from 'solid-js'
|
||||
import { getApiOrigin } from '@/lib/api-url'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
import { Card } from '@/components/ui/Card'
|
||||
@@ -64,7 +65,7 @@ const Chat = () => {
|
||||
const loadAIProviders = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/ai/providers`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
@@ -83,7 +84,7 @@ const Chat = () => {
|
||||
const loadAISettings = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
@@ -175,7 +176,7 @@ const Chat = () => {
|
||||
const fetchSessions = async () => {
|
||||
try {
|
||||
const token = getToken()
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions`, {
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
@@ -239,7 +240,7 @@ const Chat = () => {
|
||||
const loadSessionMessages = async (sessionId: string) => {
|
||||
try {
|
||||
const token = getToken()
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
@@ -340,7 +341,7 @@ const Chat = () => {
|
||||
payload.session_id = currentSessionId()
|
||||
}
|
||||
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/send`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/send`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -606,7 +607,7 @@ const Chat = () => {
|
||||
e.stopPropagation()
|
||||
try {
|
||||
const token = getToken()
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${session.id}`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${session.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal, For, Show, onCleanup, onMount } from 'solid-js';
|
||||
import { getApiOrigin } from '@/lib/api-url';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { toast } from '@/components/ui/Toast';
|
||||
@@ -973,7 +974,7 @@ export const Messages = () => {
|
||||
kind: 'voice_note',
|
||||
file_id: uploaded.id,
|
||||
title: uploaded.original_name || 'Voice note',
|
||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${uploaded.id}/download`,
|
||||
url: `${getApiOrigin()}/api/v1/files/${uploaded.id}/download`,
|
||||
}];
|
||||
|
||||
const transcript = `${voiceFinalTranscript} ${voiceInterimTranscript}`.trim();
|
||||
@@ -1366,7 +1367,7 @@ export const Messages = () => {
|
||||
const loadMembers = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/members?limit=200`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/members?limit=200`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1385,7 +1386,7 @@ export const Messages = () => {
|
||||
const loadTeams = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/teams?limit=200`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/teams?limit=200`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1403,7 +1404,7 @@ export const Messages = () => {
|
||||
const loadAIProviders = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/ai/providers`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1424,7 +1425,7 @@ export const Messages = () => {
|
||||
const loadAISettings = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1454,7 +1455,7 @@ export const Messages = () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
setAiShareLoadingSessions(true);
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/chat/sessions`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) {
|
||||
@@ -1486,7 +1487,7 @@ export const Messages = () => {
|
||||
if (aiShareMessagesBySession()[sessionId]) return;
|
||||
setAiShareLoadingMessages(true);
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) {
|
||||
@@ -1786,7 +1787,7 @@ export const Messages = () => {
|
||||
kind: uploaded.mime_type?.startsWith('image/') ? 'image' : 'file',
|
||||
file_id: uploaded.id,
|
||||
title: uploaded.original_name,
|
||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${uploaded.id}/download`,
|
||||
url: `${getApiOrigin()}/api/v1/files/${uploaded.id}/download`,
|
||||
});
|
||||
setUploadProgress({ done: i + 1, total: localFiles.length });
|
||||
}
|
||||
@@ -1796,7 +1797,7 @@ export const Messages = () => {
|
||||
kind: file.mime_type?.startsWith('image/') ? 'image' : 'file',
|
||||
file_id: file.id,
|
||||
title: file.original_name,
|
||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${file.id}/download`,
|
||||
url: `${getApiOrigin()}/api/v1/files/${file.id}/download`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,6 @@ export const Bookmarks = () => {
|
||||
|
||||
const handleAddBookmark = async (bookmarkData: any) => {
|
||||
try {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
||||
const response = await fetch(`${API_BASE_URL}/bookmarks`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -271,7 +270,6 @@ export const Bookmarks = () => {
|
||||
const deleteBookmark = async (bookmarkId: number) => {
|
||||
if (confirm('Are you sure you want to delete this bookmark?')) {
|
||||
try {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
||||
const response = await fetch(`${API_BASE_URL}/bookmarks/${bookmarkId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
@@ -322,7 +320,6 @@ export const Bookmarks = () => {
|
||||
if (!editingBookmark()) return;
|
||||
|
||||
try {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
||||
const response = await fetch(`${API_BASE_URL}/bookmarks/${editingBookmark()!.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal, createEffect, onMount, For, Show } from 'solid-js'
|
||||
import { getApiOrigin } from '@/lib/api-url'
|
||||
import { DateRangePicker } from '@/components/ui/DateRangePicker';
|
||||
import { ModalPortal } from '@/components/ui/ModalPortal';
|
||||
import {
|
||||
@@ -149,9 +150,9 @@ export function Calendar() {
|
||||
|
||||
// Fetch all calendar data in parallel
|
||||
const [upcomingRes, todayRes, deadlinesRes] = await Promise.all([
|
||||
fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/upcoming`, { headers }),
|
||||
fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/today`, { headers }),
|
||||
fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/deadlines`, { headers })
|
||||
fetch(`${getApiOrigin()}/api/v1/calendar/upcoming`, { headers }),
|
||||
fetch(`${getApiOrigin()}/api/v1/calendar/today`, { headers }),
|
||||
fetch(`${getApiOrigin()}/api/v1/calendar/deadlines`, { headers })
|
||||
])
|
||||
|
||||
if (upcomingRes.ok) {
|
||||
@@ -247,7 +248,7 @@ export function Calendar() {
|
||||
const token = localStorage.getItem('token')
|
||||
if (!token) return
|
||||
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/calendar`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
@@ -304,7 +305,7 @@ export function Calendar() {
|
||||
const token = localStorage.getItem('token')
|
||||
if (!token) return
|
||||
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/${eventId}/toggle-complete`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/calendar/${eventId}/toggle-complete`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { AIProviderIcon } from '@/components/AIProviderIcon';
|
||||
import { useHaptics } from '@/lib/haptics';
|
||||
import { getApiV1BaseUrl } from '@/lib/api-url';
|
||||
import { getApiV1BaseUrl, getApiOrigin } from '@/lib/api-url';
|
||||
|
||||
interface BrowserExtensionApiKey {
|
||||
id: number;
|
||||
@@ -199,7 +199,7 @@ export const Settings = () => {
|
||||
|
||||
const loadAISettings = async () => {
|
||||
try {
|
||||
const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`;
|
||||
const endpoint = `${getApiOrigin()}/api/v1/auth/ai/settings`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
@@ -219,7 +219,7 @@ export const Settings = () => {
|
||||
|
||||
const loadAvailableAIProviders = async () => {
|
||||
try {
|
||||
const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`;
|
||||
const endpoint = `${getApiOrigin()}/api/v1/ai/providers`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
@@ -241,7 +241,7 @@ export const Settings = () => {
|
||||
|
||||
const loadSearchSettings = async () => {
|
||||
try {
|
||||
const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/search/settings`;
|
||||
const endpoint = `${getApiOrigin()}/api/v1/auth/search/settings`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
@@ -293,7 +293,7 @@ export const Settings = () => {
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
@@ -371,7 +371,7 @@ export const Settings = () => {
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/search/settings`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/search/settings`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
@@ -1553,7 +1553,7 @@ export const Settings = () => {
|
||||
// Save email settings
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/email/settings`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/email/settings`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
@@ -1582,7 +1582,7 @@ export const Settings = () => {
|
||||
// Test email configuration
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/email/test`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/email/test`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
|
||||
Reference in New Issue
Block a user