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:
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}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user