This commit is contained in:
Tomas Dvorak
2025-05-30 09:21:52 +02:00
parent c24e88e4f5
commit 844c85b498
2 changed files with 160 additions and 80 deletions
+45 -30
View File
@@ -1459,15 +1459,36 @@ function loadHardcodedApps() {
// Load dynamic apps // Load dynamic apps
async function loadDynamicApps() { async function loadDynamicApps() {
const dynamicAppsList = document.getElementById('dynamicAppsList'); console.log("Loading dynamic apps...");
const dynamicAppsContainer = document.getElementById('dynamicApps');
try { try {
const response = await fetch('/api/apps'); const token = localStorage.getItem('token');
if (!response.ok) throw new Error('Nepodařilo se načíst seznam aplikací'); if (!token) {
window.location.href = '/login.html';
return;
}
const response = await fetch('/api/apps', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
if (response.status === 401) {
// Token expired or invalid, redirect to login
window.location.href = '/login.html';
return;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const apps = await response.json(); const apps = await response.json();
console.log("Loaded dynamic apps:", apps);
if (apps.length === 0) { if (!Array.isArray(apps) || apps.length === 0) {
dynamicAppsList.innerHTML = ` dynamicAppsList.innerHTML = `
<div class="text-center py-8"> <div class="text-center py-8">
<i class="fas fa-inbox text-4xl text-gray-300 mb-2"></i> <i class="fas fa-inbox text-4xl text-gray-300 mb-2"></i>
@@ -1477,7 +1498,8 @@ async function loadDynamicApps() {
return; return;
} }
dynamicAppsList.innerHTML = apps // Filter out hardcoded apps and map to HTML
const dynamicApps = apps
.filter(app => !app.id || !app.id.startsWith('hardcoded-')) .filter(app => !app.id || !app.id.startsWith('hardcoded-'))
.map(app => ` .map(app => `
<div class="bg-white rounded-lg shadow p-4 flex items-center justify-between" data-app-id="${app.id}"> <div class="bg-white rounded-lg shadow p-4 flex items-center justify-between" data-app-id="${app.id}">
@@ -1489,43 +1511,34 @@ async function loadDynamicApps() {
</div>` </div>`
} }
<div> <div>
<h4 class="font-medium">${app.name}</h4> <h4 class="font-medium">${app.name || 'Neznámá aplikace'}</h4>
<p class="text-sm text-gray-500">${app.url}</p> <p class="text-sm text-gray-500">${app.url || ''}</p>
${app.description ? `<p class="text-sm text-gray-400">${app.description}</p>` : ''}
</div> </div>
</div> </div>
<div class="flex space-x-2"> <div class="flex space-x-2">
<button class="edit-app-btn p-2 text-blue-500 hover:text-blue-700" data-app-id="${app.id}"> <button onclick="editApp('${app.id}')" class="text-blue-500 hover:text-blue-700">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
<button class="delete-app-btn p-2 text-red-500 hover:text-red-700" data-app-id="${app.id}"> <button onclick="deleteApp('${app.id}')" class="text-red-500 hover:text-red-700">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button> </button>
</div> </div>
</div> </div>
`).join(''); `).join('');
// Add event listeners to buttons if (dynamicApps.length > 0) {
document.querySelectorAll('.edit-app-btn').forEach(btn => { dynamicAppsList.innerHTML = dynamicApps;
btn.addEventListener('click', (e) => { } else {
e.stopPropagation(); dynamicAppsList.innerHTML = `
const appId = btn.dataset.appId; <div class="text-center py-8">
editApp(appId); <i class="fas fa-inbox text-4xl text-gray-300 mb-2"></i>
}); <p class="text-gray-500">Žádné vlastní aplikace nebyly nalezeny</p>
}); </div>
`;
document.querySelectorAll('.delete-app-btn').forEach(btn => { }
btn.addEventListener('click', (e) => {
e.stopPropagation();
const appId = btn.dataset.appId;
if (confirm('Opravdu chcete tuto aplikaci smazat?')) {
deleteApp(appId);
}
});
});
} catch (error) { } catch (error) {
console.error('Chyba při načítání vlastních aplikací:', error); console.error('Error loading dynamic apps:', error);
dynamicAppsList.innerHTML = ` dynamicAppsList.innerHTML = `
<div class="bg-red-50 border-l-4 border-red-400 p-4"> <div class="bg-red-50 border-l-4 border-red-400 p-4">
<div class="flex"> <div class="flex">
@@ -1533,7 +1546,9 @@ async function loadDynamicApps() {
<i class="fas fa-exclamation-circle text-red-400"></i> <i class="fas fa-exclamation-circle text-red-400"></i>
</div> </div>
<div class="ml-3"> <div class="ml-3">
<p class="text-sm text-red-700">Chyba při načítání vlastních aplikací: ${error.message}</p> <p class="text-sm text-red-700">
Chyba při načítání aplikací: ${error.message}
</p>
</div> </div>
</div> </div>
</div> </div>
+113 -48
View File
@@ -65,6 +65,60 @@ func main() {
kontaktURL, _ := url.Parse("http://webportal:8080") kontaktURL, _ := url.Parse("http://webportal:8080")
kontaktProxy := httputil.NewSingleHostReverseProxy(kontaktURL) kontaktProxy := httputil.NewSingleHostReverseProxy(kontaktURL)
// CORS middleware
corsMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Allow all origins for development
origin := r.Header.Get("Origin")
if origin == "" {
origin = "*"
}
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
// Handle preflight requests
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// Auth middleware
authMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Skip auth for GET requests and OPTIONS
if r.Method == "GET" || r.Method == "OPTIONS" {
next.ServeHTTP(w, r)
return
}
// Check for Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing authorization token", http.StatusUnauthorized)
return
}
// Verify token (in a real app, you would validate this against your auth system)
tokenParts := strings.Split(authHeader, " ")
if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
return
}
// In a real app, you would validate the token here
next.ServeHTTP(w, r)
})
}
// Apply CORS middleware to all routes
r.Use(corsMiddleware)
// Public routes // Public routes
r.PathPrefix("/kontakt/").Handler(http.StripPrefix("/kontakt", kontaktProxy)) r.PathPrefix("/kontakt/").Handler(http.StripPrefix("/kontakt", kontaktProxy))
r.PathPrefix("/uploads/").Handler(http.StripPrefix("/uploads/", http.FileServer(http.Dir("./uploads")))) r.PathPrefix("/uploads/").Handler(http.StripPrefix("/uploads/", http.FileServer(http.Dir("./uploads"))))
@@ -76,9 +130,15 @@ func main() {
// Authentication routes // Authentication routes
r.HandleFunc("/api/login", LoginHandler).Methods("POST", "OPTIONS") r.HandleFunc("/api/login", LoginHandler).Methods("POST", "OPTIONS")
// Protected API routes // Public endpoints (must be defined before protected ones)
r.HandleFunc("/api/banner", GetBannerHandler).Methods("GET", "OPTIONS")
r.HandleFunc("/submit", handleSubmit).Methods("POST", "OPTIONS") // Public submit endpoint for evidence-aut.html
// Protected API routes with auth middleware
api := r.PathPrefix("/api").Subrouter() api := r.PathPrefix("/api").Subrouter()
api.Use(AuthMiddleware) api.Use(authMiddleware)
// Protected API endpoints
api.HandleFunc("/submit", handleSubmit).Methods("POST") api.HandleFunc("/submit", handleSubmit).Methods("POST")
api.HandleFunc("/banner/update", UpdateBannerHandler).Methods("POST", "OPTIONS") api.HandleFunc("/banner/update", UpdateBannerHandler).Methods("POST", "OPTIONS")
@@ -89,27 +149,9 @@ func main() {
api.HandleFunc("/apps/{id}", UpdateAppHandler).Methods("PUT") api.HandleFunc("/apps/{id}", UpdateAppHandler).Methods("PUT")
api.HandleFunc("/apps/{id}", DeleteAppHandler).Methods("DELETE") api.HandleFunc("/apps/{id}", DeleteAppHandler).Methods("DELETE")
// Public endpoints // Serve static files (must be the last route)
r.HandleFunc("/api/banner", GetBannerHandler).Methods("GET", "OPTIONS") fs := http.FileServer(http.Dir("."))
r.PathPrefix("/").Handler(fs)
// Important: This public submit endpoint must be defined BEFORE the static file server
r.HandleFunc("/submit", handleSubmit).Methods("POST", "OPTIONS") // Public submit endpoint for evidence-aut.html
// Add CORS middleware for API
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
})
// Admin routes // Admin routes
r.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) { r.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
@@ -132,31 +174,12 @@ func main() {
http.ServeFile(w, r, "evidence-aut.html") http.ServeFile(w, r, "evidence-aut.html")
}).Methods("GET") }).Methods("GET")
// Contact page route
r.HandleFunc("/kontakt", contactHandler).Methods("GET")
// Static file server for public files - must be the last route defined // Static file server for public files - must be the last route defined
fs := http.FileServer(http.Dir(".")) fileServer := http.FileServer(http.Dir("."))
r.PathPrefix("/").Handler(fs) r.PathPrefix("/").Handler(fileServer)
r.HandleFunc("/kontakt", func(w http.ResponseWriter, r *http.Request) {
// Check if kontakt service is already running
resp, err := http.Get("http://webportal:8080/health")
if err == nil && resp.StatusCode == 200 {
http.Redirect(w, r, "http://webportal:8080/", http.StatusFound)
return
}
// Start the service if not running
cmd := exec.Command("make", "dev")
cmd.Dir = "kontakt"
err = cmd.Start()
if err != nil {
http.Error(w, "Failed to start kontakt service", http.StatusInternalServerError)
return
}
// Wait briefly for service to start
time.Sleep(2 * time.Second)
http.Redirect(w, r, "http://webportal:8080/", http.StatusFound)
}).Methods("GET")
// Apply CORS middleware to all routes // Apply CORS middleware to all routes
handler := enableCORS(r) handler := enableCORS(r)
@@ -173,6 +196,29 @@ func main() {
} }
} }
// contactHandler handles the contact page request
func contactHandler(w http.ResponseWriter, r *http.Request) {
// Check if kontakt service is already running
resp, err := http.Get("http://webportal:8080/health")
if err == nil && resp.StatusCode == 200 {
http.Redirect(w, r, "http://webportal:8080/", http.StatusFound)
return
}
// Start the service if not running
cmd := exec.Command("make", "dev")
cmd.Dir = "kontakt"
err = cmd.Start()
if err != nil {
http.Error(w, "Failed to start kontakt service", http.StatusInternalServerError)
return
}
// Wait briefly for service to start
time.Sleep(2 * time.Second)
http.Redirect(w, r, "http://webportal:8080/", http.StatusFound)
}
func enableCORS(next http.Handler) http.Handler { func enableCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
@@ -238,6 +284,24 @@ func saveApps(apps []App) error {
// App Handlers // App Handlers
func GetAppsHandler(w http.ResponseWriter, r *http.Request) { func GetAppsHandler(w http.ResponseWriter, r *http.Request) {
// Set CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// Handle preflight requests
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
// Only allow GET requests
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Load apps from JSON file
apps, err := loadApps() apps, err := loadApps()
if err != nil { if err != nil {
log.Printf("Error loading apps: %v", err) log.Printf("Error loading apps: %v", err)
@@ -483,8 +547,9 @@ func UpdateAppHandler(w http.ResponseWriter, r *http.Request) {
url := r.FormValue("url") url := r.FormValue("url")
description := r.FormValue("description") description := r.FormValue("description")
// Handle file upload if a new file is provided
var iconPath string var iconPath string
// Handle file upload
file, handler, err := r.FormFile("icon") file, handler, err := r.FormFile("icon")
if err == nil { if err == nil {
defer file.Close() defer file.Close()