Implement environment variable setup for frontend; create .env.production and .env.development files if they don't exist, and update API URLs in frontend components to use dynamic API_BASE_URL for improved configuration management.
This commit is contained in:
210
DEPLOYMENT_CHECKLIST.md
Normal file
210
DEPLOYMENT_CHECKLIST.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# TimeClock v3 - Deployment Checkliste
|
||||
|
||||
Diese Checkliste stellt sicher, dass alle Schritte für ein erfolgreiches Deployment durchgeführt wurden.
|
||||
|
||||
## Pre-Deployment (Lokal)
|
||||
|
||||
- [ ] **Git Status prüfen**: `git status` - Alle Änderungen committed?
|
||||
- [ ] **Tests durchführen**: Lokal testen mit `npm run dev`
|
||||
- [ ] **Dependencies aktuell**: `npm install` in backend/ und frontend/
|
||||
- [ ] **Frontend-Build testen**: `cd frontend && npm run build`
|
||||
- [ ] **Keine hardcodierten URLs**: `grep -r "localhost:3010" frontend/src/`
|
||||
|
||||
## Git Push
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Deployment vX.X.X"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Server-Vorbereitung
|
||||
|
||||
- [ ] **SSH-Zugang**: `ssh tsschulz@tsschulz.de`
|
||||
- [ ] **Projekt-Verzeichnis**: `cd /var/www/timeclock`
|
||||
- [ ] **Git Pull**: `git pull origin main`
|
||||
|
||||
## Deployment Durchführen
|
||||
|
||||
### Option 1: Automatisches Deployment (Empfohlen)
|
||||
|
||||
```bash
|
||||
cd /var/www/timeclock
|
||||
./deploy.sh update
|
||||
```
|
||||
|
||||
Das Script führt automatisch aus:
|
||||
- ✅ Datenbank-Backup
|
||||
- ✅ Backend Dependencies installieren
|
||||
- ✅ Frontend .env.production erstellen
|
||||
- ✅ Frontend neu bauen (mit korrekter API-URL)
|
||||
- ✅ Backend neu starten
|
||||
- ✅ Apache neu laden
|
||||
|
||||
### Option 2: Manuelles Deployment
|
||||
|
||||
```bash
|
||||
# 1. Backup
|
||||
./deploy.sh backup
|
||||
|
||||
# 2. Backend
|
||||
cd /var/www/timeclock/backend
|
||||
npm install --production
|
||||
pm2 restart timeclock-backend
|
||||
|
||||
# 3. Frontend
|
||||
cd /var/www/timeclock/frontend
|
||||
|
||||
# .env.production prüfen/erstellen
|
||||
cat > .env.production << 'EOF'
|
||||
VITE_API_URL=/api
|
||||
EOF
|
||||
|
||||
npm install
|
||||
rm -rf dist/
|
||||
npm run build
|
||||
|
||||
# 4. Apache neu laden
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
## Post-Deployment Tests
|
||||
|
||||
- [ ] **Backend Health-Check**:
|
||||
```bash
|
||||
curl https://stechuhr3.tsschulz.de/api/health
|
||||
# Erwartung: {"status":"ok","message":"TimeClock API v3.0.0",...}
|
||||
```
|
||||
|
||||
- [ ] **Frontend lädt**:
|
||||
```bash
|
||||
curl -I https://stechuhr3.tsschulz.de
|
||||
# Erwartung: HTTP/2 200
|
||||
```
|
||||
|
||||
- [ ] **Keine localhost URLs**:
|
||||
```bash
|
||||
grep -r "localhost:3010" /var/www/timeclock/frontend/dist/
|
||||
# Erwartung: Keine Treffer (oder nur Kommentare)
|
||||
```
|
||||
|
||||
- [ ] **PM2 Status**:
|
||||
```bash
|
||||
pm2 status
|
||||
# Erwartung: timeclock-backend | online
|
||||
```
|
||||
|
||||
- [ ] **Browser-Tests**:
|
||||
- [ ] https://stechuhr3.tsschulz.de lädt
|
||||
- [ ] Login funktioniert
|
||||
- [ ] API-Calls gehen an `/api` (nicht `localhost:3010`)
|
||||
- [ ] Keine Console-Errors (F12)
|
||||
|
||||
## Rollback bei Problemen
|
||||
|
||||
```bash
|
||||
cd /var/www/timeclock
|
||||
./deploy.sh rollback
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
```bash
|
||||
# Backend-Logs live
|
||||
pm2 logs timeclock-backend
|
||||
|
||||
# Apache-Logs
|
||||
sudo tail -f /var/log/apache2/stechuhr3-error.log
|
||||
|
||||
# Backend-Status
|
||||
./deploy.sh status
|
||||
|
||||
# Alle Logs
|
||||
./deploy.sh logs
|
||||
```
|
||||
|
||||
## Häufige Probleme
|
||||
|
||||
### Problem: Frontend zeigt "localhost:3010" Fehler
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
cd /var/www/timeclock/frontend
|
||||
cat .env.production # Sollte VITE_API_URL=/api enthalten
|
||||
rm -rf dist/
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Problem: API 502 Bad Gateway
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
pm2 restart timeclock-backend
|
||||
pm2 logs timeclock-backend --lines 50
|
||||
```
|
||||
|
||||
### Problem: Apache zeigt Default-Page
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
sudo apache2ctl -S | grep stechuhr3 # Prüfe VirtualHost
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
### Problem: Browser-Cache
|
||||
|
||||
**Lösung:**
|
||||
- **Strg + Shift + R** (Hard Reload)
|
||||
- Oder Private/Incognito Window
|
||||
|
||||
## Deployment-Frequenz
|
||||
|
||||
- **Bugfixes**: Sofort
|
||||
- **Features**: Nach Testing auf Dev
|
||||
- **Breaking Changes**: Mit Ankündigung
|
||||
|
||||
## Backup-Strategie
|
||||
|
||||
- **Automatisch**: Täglich um 2 Uhr (via Cronjob)
|
||||
- **Manuell**: Vor jedem Deployment
|
||||
- **Retention**: 30 Tage
|
||||
- **Location**: `/var/backups/timeclock/`
|
||||
|
||||
## Secrets & Credentials
|
||||
|
||||
Niemals committen:
|
||||
- ❌ `.env` Dateien
|
||||
- ❌ SSL-Zertifikate
|
||||
- ❌ Datenbank-Passwörter
|
||||
- ❌ API-Keys
|
||||
|
||||
Verwende `.gitignore`:
|
||||
```
|
||||
.env
|
||||
.env.local
|
||||
.env.production.local
|
||||
*.key
|
||||
*.pem
|
||||
```
|
||||
|
||||
## Deployment-Log
|
||||
|
||||
Dokumentiere jedes Deployment:
|
||||
|
||||
```
|
||||
Datum: 2025-10-18
|
||||
Version: v3.0.1
|
||||
Änderungen:
|
||||
- API-URLs von hardcoded auf .env umgestellt
|
||||
- Apache2-Konfiguration korrigiert
|
||||
- SSL-Zertifikat eingerichtet
|
||||
Status: ✅ Erfolgreich
|
||||
Probleme: Keine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Viel Erfolg beim Deployment! 🚀**
|
||||
|
||||
Bei Fragen: Siehe `DEPLOYMENT.md`, `APACHE2_DEPLOYMENT.md` oder `CHECK_SERVICES.md`
|
||||
|
||||
68
deploy.sh
68
deploy.sh
@@ -214,6 +214,39 @@ setup_frontend() {
|
||||
print_info "Installiere Frontend-Dependencies..."
|
||||
npm install
|
||||
|
||||
# .env.production erstellen falls nicht vorhanden
|
||||
if [ ! -f ".env.production" ]; then
|
||||
print_info "Erstelle .env.production..."
|
||||
cat > .env.production << 'EOF'
|
||||
# TimeClock v3 - Frontend Production Environment
|
||||
# API Base URL (relativ, da Apache als Proxy dient)
|
||||
VITE_API_URL=/api
|
||||
EOF
|
||||
print_success ".env.production erstellt"
|
||||
else
|
||||
print_success ".env.production bereits vorhanden"
|
||||
fi
|
||||
|
||||
# .env.development erstellen falls nicht vorhanden (für lokale Entwicklung)
|
||||
if [ ! -f ".env.development" ]; then
|
||||
print_info "Erstelle .env.development..."
|
||||
cat > .env.development << 'EOF'
|
||||
# TimeClock v3 - Frontend Development Environment
|
||||
VITE_API_URL=http://localhost:3010/api
|
||||
EOF
|
||||
print_success ".env.development erstellt"
|
||||
fi
|
||||
|
||||
# Prüfe ob api.js existiert
|
||||
if [ ! -f "src/config/api.js" ]; then
|
||||
print_warning "src/config/api.js fehlt! Bitte git pull ausführen."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Alte dist/ löschen für sauberen Build
|
||||
print_info "Lösche alten Build..."
|
||||
rm -rf dist/
|
||||
|
||||
print_info "Erstelle Produktions-Build..."
|
||||
npm run build
|
||||
|
||||
@@ -222,6 +255,14 @@ setup_frontend() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Prüfe ob localhost:3010 noch im Build ist
|
||||
if grep -r "localhost:3010" dist/ > /dev/null 2>&1; then
|
||||
print_warning "⚠️ localhost:3010 noch im Build gefunden - eventuell falsches .env verwendet"
|
||||
print_info "Prüfe .env.production: $(cat .env.production 2>/dev/null || echo 'nicht gefunden')"
|
||||
else
|
||||
print_success "✅ Build verwendet korrekte API-URL"
|
||||
fi
|
||||
|
||||
print_success "Frontend Build erstellt!"
|
||||
}
|
||||
|
||||
@@ -390,15 +431,40 @@ do_update() {
|
||||
cd $BACKEND_DIR
|
||||
npm install --production
|
||||
|
||||
# Frontend neu bauen
|
||||
# Frontend aktualisieren
|
||||
print_info "Aktualisiere Frontend..."
|
||||
cd $FRONTEND_DIR
|
||||
|
||||
# .env.production prüfen/erstellen
|
||||
if [ ! -f ".env.production" ]; then
|
||||
print_info "Erstelle .env.production..."
|
||||
cat > .env.production << 'EOF'
|
||||
# TimeClock v3 - Frontend Production Environment
|
||||
VITE_API_URL=/api
|
||||
EOF
|
||||
fi
|
||||
|
||||
npm install
|
||||
|
||||
# Sauberer Build
|
||||
rm -rf dist/
|
||||
npm run build
|
||||
|
||||
# Prüfe Build
|
||||
if grep -r "localhost:3010" dist/ > /dev/null 2>&1; then
|
||||
print_warning "⚠️ localhost:3010 noch im Build - bitte prüfen"
|
||||
fi
|
||||
|
||||
# Service neu starten
|
||||
restart_backend
|
||||
|
||||
# Apache Cache leeren
|
||||
if [ "$WEBSERVER" = "apache2" ]; then
|
||||
sudo systemctl reload apache2
|
||||
else
|
||||
sudo systemctl reload nginx
|
||||
fi
|
||||
|
||||
print_success "Update abgeschlossen!"
|
||||
}
|
||||
|
||||
|
||||
2
frontend/.env.development
Normal file
2
frontend/.env.development
Normal file
@@ -0,0 +1,2 @@
|
||||
# TimeClock v3 - Frontend Development Environment
|
||||
VITE_API_URL=http://localhost:3010/api
|
||||
3
frontend/.env.production
Normal file
3
frontend/.env.production
Normal file
@@ -0,0 +1,3 @@
|
||||
# TimeClock v3 - Frontend Production Environment
|
||||
# API Base URL (relativ, da Apache als Proxy dient)
|
||||
VITE_API_URL=/api
|
||||
76
frontend/FIX_API_URLS.sh
Executable file
76
frontend/FIX_API_URLS.sh
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
|
||||
# =============================================================================
|
||||
# TimeClock v3 - Fix hardcodierte API URLs
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔧 Ersetze hardcodierte API-URLs durch zentrale Konfiguration..."
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Backup erstellen
|
||||
echo "📦 Erstelle Backup..."
|
||||
tar -czf ~/timeclock-frontend-backup-$(date +%Y%m%d_%H%M%S).tar.gz src/
|
||||
|
||||
# .env.production erstellen
|
||||
echo "📝 Erstelle .env.production..."
|
||||
cat > .env.production << 'EOF'
|
||||
# TimeClock v3 - Frontend Production Environment
|
||||
# API Base URL (relativ, da Apache als Proxy dient)
|
||||
VITE_API_URL=/api
|
||||
EOF
|
||||
|
||||
# .env.development erstellen
|
||||
echo "📝 Erstelle .env.development..."
|
||||
cat > .env.development << 'EOF'
|
||||
# TimeClock v3 - Frontend Development Environment
|
||||
VITE_API_URL=http://localhost:3010/api
|
||||
EOF
|
||||
|
||||
# Ersetze alle hardcodierten URLs in Views
|
||||
echo "🔄 Ersetze URLs in Views..."
|
||||
|
||||
# Pattern: http://localhost:3010/api → ${API_URL}
|
||||
# Muss in mehreren Schritten erfolgen, da die Syntax unterschiedlich ist
|
||||
|
||||
find src/views -name "*.vue" -type f -exec sed -i "s|'http://localhost:3010/api|'\${API_URL}|g" {} \;
|
||||
find src/views -name "*.vue" -type f -exec sed -i 's|"http://localhost:3010/api|"${API_URL}|g' {} \;
|
||||
find src/views -name "*.vue" -type f -exec sed -i 's|`http://localhost:3010/api|`${API_URL}|g' {} \;
|
||||
|
||||
# Füge API_URL Import hinzu wo benötigt
|
||||
echo "📦 Füge Imports hinzu..."
|
||||
|
||||
# Einfacher Ansatz: Erstelle eine Liste der Dateien mit API-Aufrufen
|
||||
grep -l "localhost:3010" src/views/*.vue 2>/dev/null | while read -r file; do
|
||||
# Prüfe ob Import bereits vorhanden
|
||||
if ! grep -q "import.*API_BASE_URL.*from.*@/config/api" "$file"; then
|
||||
# Füge Import nach dem ersten <script setup> hinzu
|
||||
sed -i '/<script setup>/a import { API_BASE_URL as API_URL } from '\''@/config/api'\''' "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
find src/components -name "*.vue" -type f -exec sed -i "s|'http://localhost:3010/api|'\${API_URL}|g" {} \;
|
||||
find src/components -name "*.vue" -type f -exec sed -i 's|"http://localhost:3010/api|"${API_URL}|g' {} \;
|
||||
find src/components -name "*.vue" -type f -exec sed -i 's|`http://localhost:3010/api|`${API_URL}|g' {} \;
|
||||
|
||||
grep -l "localhost:3010" src/components/*.vue 2>/dev/null | while read -r file; do
|
||||
if ! grep -q "import.*API_BASE_URL.*from.*@/config/api" "$file"; then
|
||||
sed -i '/<script setup>/a import { API_BASE_URL as API_URL } from '\''@/config/api'\''' "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✅ Fertig!"
|
||||
echo ""
|
||||
echo "📋 Nächste Schritte:"
|
||||
echo " 1. npm run build"
|
||||
echo " 2. Kopiere dist/ auf den Server"
|
||||
echo ""
|
||||
echo "🔍 Prüfe ob alle URLs ersetzt wurden:"
|
||||
echo " grep -r 'localhost:3010' src/"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -92,7 +92,7 @@ const fetchStats = async () => {
|
||||
|
||||
const fetchCurrentState = async () => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3010/api/time-entries/current-state', {
|
||||
const response = await fetch('${API_URL}/time-entries/current-state', {
|
||||
headers: authStore.getAuthHeaders()
|
||||
})
|
||||
|
||||
@@ -108,7 +108,7 @@ const fetchCurrentState = async () => {
|
||||
// Lade die aktuellen Worklog-Daten (nur einmal pro Minute)
|
||||
const fetchWorklogData = async () => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3010/api/time-entries/running', {
|
||||
const response = await fetch('${API_URL}/time-entries/running', {
|
||||
headers: authStore.getAuthHeaders()
|
||||
})
|
||||
|
||||
@@ -277,7 +277,7 @@ const handleAction = async (action) => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
const response = await fetch('http://localhost:3010/api/time-entries/clock', {
|
||||
const response = await fetch('${API_URL}/time-entries/clock', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...authStore.getAuthHeaders(),
|
||||
|
||||
66
frontend/src/config/api.js
Normal file
66
frontend/src/config/api.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// TimeClock v3 - API Configuration
|
||||
// Zentrale Konfiguration für alle API-Anfragen
|
||||
|
||||
// API Base URL aus Umgebungsvariablen
|
||||
// In Production: /api (relativ, durch Apache Proxy)
|
||||
// In Development: http://localhost:3010/api
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_URL || '/api'
|
||||
|
||||
// Vollständige API URL (für absolute URLs falls benötigt)
|
||||
export const getApiUrl = (endpoint) => {
|
||||
// Wenn endpoint mit / beginnt, direkt verwenden
|
||||
if (endpoint.startsWith('/')) {
|
||||
return `${API_BASE_URL}${endpoint}`
|
||||
}
|
||||
// Sonst / hinzufügen
|
||||
return `${API_BASE_URL}/${endpoint}`
|
||||
}
|
||||
|
||||
// Hilfsfunktion für Auth-Header
|
||||
export const getAuthHeaders = () => {
|
||||
const token = localStorage.getItem('timeclock_token')
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
// Hilfsfunktion für API-Requests mit Fehlerbehandlung
|
||||
export const apiRequest = async (endpoint, options = {}) => {
|
||||
const url = getApiUrl(endpoint)
|
||||
|
||||
const defaultOptions = {
|
||||
headers: getAuthHeaders()
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
headers: {
|
||||
...defaultOptions.headers,
|
||||
...options.headers
|
||||
}
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'API-Anfrage fehlgeschlagen')
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export default {
|
||||
API_BASE_URL,
|
||||
getApiUrl,
|
||||
getAuthHeaders,
|
||||
apiRequest
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { API_BASE_URL } from '@/config/api'
|
||||
|
||||
const API_URL = 'http://localhost:3010/api'
|
||||
const API_URL = API_BASE_URL
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref(null)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useAuthStore } from './authStore'
|
||||
import { API_BASE_URL } from '@/config/api'
|
||||
|
||||
const API_URL = 'http://localhost:3010/api'
|
||||
const API_URL = API_BASE_URL
|
||||
|
||||
export const useTimeStore = defineStore('time', () => {
|
||||
const entries = ref([])
|
||||
|
||||
@@ -101,7 +101,7 @@ async function loadCalendar() {
|
||||
error.value = null
|
||||
|
||||
const response = await fetch(
|
||||
`http://localhost:3010/api/calendar?year=${selectedYear.value}&month=${selectedMonth.value}`,
|
||||
`${API_URL}/calendar?year=${selectedYear.value}&month=${selectedMonth.value}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
|
||||
@@ -137,7 +137,7 @@ async function exportData() {
|
||||
|
||||
// Download CSV/Excel-Datei
|
||||
async function downloadFile(format) {
|
||||
const url = `http://localhost:3010/api/export/${format}?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
|
||||
const url = `${API_URL}/export/${format}?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
@@ -166,7 +166,7 @@ async function downloadFile(format) {
|
||||
|
||||
// PDF-Export (Browser-Print)
|
||||
async function exportPDF() {
|
||||
const url = `http://localhost:3010/api/export/pdf?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
|
||||
const url = `${API_URL}/export/pdf?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
|
||||
@@ -212,7 +212,7 @@ const form = ref({
|
||||
// Lade alle Bundesländer
|
||||
async function loadStates() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3010/api/holidays/states', {
|
||||
const response = await fetch('${API_URL}/holidays/states', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -233,7 +233,7 @@ async function loadStates() {
|
||||
async function loadHolidays() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/holidays', {
|
||||
const response = await fetch('${API_URL}/holidays', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -276,7 +276,7 @@ async function createHoliday() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/holidays', {
|
||||
const response = await fetch('${API_URL}/holidays', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -318,7 +318,7 @@ async function deleteHoliday(id) {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch(`http://localhost:3010/api/holidays/${id}`, {
|
||||
const response = await fetch(`${API_URL}/holidays/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
|
||||
@@ -91,7 +91,7 @@ const form = ref({
|
||||
// Lade alle Einladungen
|
||||
async function loadInvitations() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3010/api/invite', {
|
||||
const response = await fetch('${API_URL}/invite', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -116,7 +116,7 @@ async function sendInvite() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/invite', {
|
||||
const response = await fetch('${API_URL}/invite', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -145,7 +145,7 @@ const handleLogin = async () => {
|
||||
|
||||
const handleGoogleLogin = () => {
|
||||
// Redirect zu Google OAuth
|
||||
window.location.href = 'http://localhost:3010/api/auth/google'
|
||||
window.location.href = '${API_URL}/auth/google'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ async function changePassword() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/password', {
|
||||
const response = await fetch('${API_URL}/password', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -102,7 +102,7 @@ const form = ref({
|
||||
// Lade alle Watcher
|
||||
async function loadWatchers() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3010/api/watcher', {
|
||||
const response = await fetch('${API_URL}/watcher', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -128,7 +128,7 @@ async function addWatcher() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/watcher', {
|
||||
const response = await fetch('${API_URL}/watcher', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -172,7 +172,7 @@ async function removeWatcher(id, email) {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch(`http://localhost:3010/api/watcher/${id}`, {
|
||||
const response = await fetch(`${API_URL}/watcher/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
|
||||
@@ -135,7 +135,7 @@ const form = ref({
|
||||
async function loadProfile() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/profile', {
|
||||
const response = await fetch('${API_URL}/profile', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -165,7 +165,7 @@ async function loadProfile() {
|
||||
// Lade Bundesländer
|
||||
async function loadStates() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3010/api/profile/states', {
|
||||
const response = await fetch('${API_URL}/profile/states', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -185,7 +185,7 @@ async function loadStates() {
|
||||
async function saveProfile() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/profile', {
|
||||
const response = await fetch('${API_URL}/profile', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -82,7 +82,7 @@ const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal
|
||||
async function loadUsers() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/roles/users', {
|
||||
const response = await fetch('${API_URL}/roles/users', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -133,7 +133,7 @@ async function demoteToUser(userId, userName) {
|
||||
async function updateRole(userId, newRole) {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch(`http://localhost:3010/api/roles/users/${userId}`, {
|
||||
const response = await fetch(`${API_URL}/roles/users/${userId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -123,7 +123,7 @@ const form = ref({
|
||||
async function loadSickEntries() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/sick', {
|
||||
const response = await fetch('${API_URL}/sick', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -145,7 +145,7 @@ async function loadSickEntries() {
|
||||
// Lade alle Krankheitstypen
|
||||
async function loadSickTypes() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3010/api/sick/types', {
|
||||
const response = await fetch('${API_URL}/sick/types', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -171,7 +171,7 @@ async function createSick() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/sick', {
|
||||
const response = await fetch('${API_URL}/sick', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -212,7 +212,7 @@ async function deleteSick(id) {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch(`http://localhost:3010/api/sick/${id}`, {
|
||||
const response = await fetch(`${API_URL}/sick/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
|
||||
@@ -179,7 +179,7 @@ async function loadWorklogEntries() {
|
||||
if (!date) return
|
||||
|
||||
// Hole alle Worklog-Einträge für das Datum vom Backend
|
||||
const response = await fetch(`http://localhost:3010/api/timefix/worklog-entries?date=${date}`, {
|
||||
const response = await fetch(`${API_URL}/timefix/worklog-entries?date=${date}`, {
|
||||
headers: authStore.getAuthHeaders()
|
||||
})
|
||||
|
||||
@@ -224,7 +224,7 @@ function onEntrySelected() {
|
||||
async function loadTimefixes() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/timefix', {
|
||||
const response = await fetch('${API_URL}/timefix', {
|
||||
headers: authStore.getAuthHeaders()
|
||||
})
|
||||
|
||||
@@ -248,7 +248,7 @@ async function createTimefix() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/timefix', {
|
||||
const response = await fetch('${API_URL}/timefix', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...authStore.getAuthHeaders(),
|
||||
@@ -290,7 +290,7 @@ async function deleteTimefix(id) {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch(`http://localhost:3010/api/timefix/${id}`, {
|
||||
const response = await fetch(`${API_URL}/timefix/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: authStore.getAuthHeaders()
|
||||
})
|
||||
|
||||
@@ -177,7 +177,7 @@ function onWishtypeChange() {
|
||||
async function loadTimewishes() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/timewish', {
|
||||
const response = await fetch('${API_URL}/timewish', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
@@ -205,7 +205,7 @@ async function createTimewish() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/timewish', {
|
||||
const response = await fetch('${API_URL}/timewish', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -248,7 +248,7 @@ async function deleteTimewish(id) {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch(`http://localhost:3010/api/timewish/${id}`, {
|
||||
const response = await fetch(`${API_URL}/timewish/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
|
||||
@@ -135,7 +135,7 @@ function onStartDateChange() {
|
||||
async function loadVacations() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/vacation', {
|
||||
const response = await fetch('${API_URL}/vacation', {
|
||||
headers: authStore.getAuthHeaders()
|
||||
})
|
||||
|
||||
@@ -159,7 +159,7 @@ async function createVacation() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch('http://localhost:3010/api/vacation', {
|
||||
const response = await fetch('${API_URL}/vacation', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...authStore.getAuthHeaders(),
|
||||
@@ -197,7 +197,7 @@ async function deleteVacation(id) {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await fetch(`http://localhost:3010/api/vacation/${id}`, {
|
||||
const response = await fetch(`${API_URL}/vacation/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: authStore.getAuthHeaders()
|
||||
})
|
||||
|
||||
@@ -233,7 +233,7 @@ const loadWeekData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
const response = await fetch(`http://localhost:3010/api/week-overview?weekOffset=${weekOffset.value}`, {
|
||||
const response = await fetch(`${API_URL}/week-overview?weekOffset=${weekOffset.value}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -61,7 +61,7 @@ async function loadStatistics() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
const response = await fetch(`http://localhost:3010/api/workdays?year=${selectedYear.value}`, {
|
||||
const response = await fetch(`${API_URL}/workdays?year=${selectedYear.value}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authStore.token}`
|
||||
}
|
||||
|
||||
111
package-lock.json
generated
111
package-lock.json
generated
@@ -51,6 +51,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"terser": "^5.27.0",
|
||||
"vite": "^5.0.8"
|
||||
}
|
||||
},
|
||||
@@ -542,12 +543,55 @@
|
||||
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
@@ -1052,6 +1096,19 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
@@ -1441,6 +1498,13 @@
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buffer-indexof-polyfill": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
|
||||
@@ -1616,6 +1680,13 @@
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/compress-commons": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
|
||||
@@ -4207,6 +4278,16 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -4216,6 +4297,17 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spawn-command": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
|
||||
@@ -4324,6 +4416,25 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.44.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/timeclock-backend": {
|
||||
"resolved": "backend",
|
||||
"link": true
|
||||
|
||||
Reference in New Issue
Block a user