From d8f17fc8be60cadc7f729e05c7d5b6323e4ac241 Mon Sep 17 00:00:00 2001 From: Zachariah Ngonyani Date: Fri, 7 Nov 2025 09:23:27 +0300 Subject: [PATCH 1/4] fix: resolve asdf and SDKMAN Java shims to actual installation paths - Detect when JAVA_HOME or java executable points to asdf shims directory - Use 'asdf which java' to resolve shims to actual Java installation - Add support for SDKMAN Java installations - Properly infer JAVA_HOME from resolved paths - Fixes services failing with "JAVA_HOME not defined correctly" when using asdf This resolves the issue where Vertex inherited JAVA_HOME pointing to /.asdf/shims instead of the actual Java installation path like /.asdf/installs/java/openjdk-21, causing Maven and Gradle wrappers to fail. --- internal/services/java_env.go | 47 ++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/internal/services/java_env.go b/internal/services/java_env.go index 46d512d..cbc9323 100644 --- a/internal/services/java_env.go +++ b/internal/services/java_env.go @@ -27,6 +27,18 @@ func DetectJavaEnvironment() *JavaEnvironment { // Method 1: Check JAVA_HOME environment variable if javaHome := os.Getenv("JAVA_HOME"); javaHome != "" { + // Check if JAVA_HOME points to asdf shims directory and resolve it + if strings.Contains(javaHome, "/.asdf/shims") { + log.Printf("[DEBUG] JAVA_HOME points to asdf shims, attempting to resolve: %s", javaHome) + // Try to resolve using asdf + cmd := exec.Command("asdf", "which", "java") + if output, err := cmd.Output(); err == nil && len(output) > 0 { + realJavaPath := strings.TrimSpace(string(output)) + javaHome = inferJavaHome(realJavaPath) + log.Printf("[DEBUG] Resolved JAVA_HOME from asdf to: %s", javaHome) + } + } + javaPath := filepath.Join(javaHome, "bin", getJavaExecutable()) if isExecutable(javaPath) && isWorkingJava(javaPath) { env.JavaHome = javaHome @@ -198,7 +210,40 @@ func isWorkingJava(javaPath string) bool { } func inferJavaHome(javaPath string) string { - // Remove /bin/java to get JAVA_HOME + // Check if this is an asdf shim and resolve it to the real Java path + if strings.Contains(javaPath, "/.asdf/shims/") { + log.Printf("[DEBUG] Detected asdf shim, attempting to resolve actual Java path: %s", javaPath) + + // Try to use 'asdf which java' to get the real path + cmd := exec.Command("asdf", "which", "java") + if output, err := cmd.Output(); err == nil && len(output) > 0 { + realJavaPath := strings.TrimSpace(string(output)) + log.Printf("[DEBUG] Resolved asdf shim to: %s", realJavaPath) + // Use the real Java path for inference + binDir := filepath.Dir(realJavaPath) + if filepath.Base(binDir) == "bin" { + resolvedHome := filepath.Dir(binDir) + log.Printf("[DEBUG] Inferred JAVA_HOME from resolved path: %s", resolvedHome) + return resolvedHome + } + } else { + log.Printf("[WARN] Failed to resolve asdf shim: %v", err) + } + } + + // Check if this is an SDKMAN installation and use the current symlink + if strings.Contains(javaPath, "/.sdkman/candidates/java/") && strings.Contains(javaPath, "/current/") { + log.Printf("[DEBUG] Detected SDKMAN Java installation: %s", javaPath) + // For SDKMAN, we can use the path as-is since 'current' is already resolved + binDir := filepath.Dir(javaPath) + if filepath.Base(binDir) == "bin" { + resolvedHome := filepath.Dir(binDir) + log.Printf("[DEBUG] Using SDKMAN Java home: %s", resolvedHome) + return resolvedHome + } + } + + // Standard inference: Remove /bin/java to get JAVA_HOME binDir := filepath.Dir(javaPath) if filepath.Base(binDir) == "bin" { return filepath.Dir(binDir) From 45869c311392ab7467cfb83a789660d1c77cb7bb Mon Sep 17 00:00:00 2001 From: Zachariah Ngonyani Date: Mon, 1 Dec 2025 11:23:30 +0300 Subject: [PATCH 2/4] feat: add per-service Java version override support - Allow services to override JAVA_HOME via service-specific environment variables - Service-level JAVA_HOME takes precedence over profile-level Java override - Automatically update PATH when service sets custom JAVA_HOME - Environment variable precedence: Service > Profile > Global This enables running services with different Java versions within the same profile by setting JAVA_HOME in each service's environment variables. Example usage: - Service A: JAVA_HOME=/Users/user/.asdf/installs/java/openjdk-21 - Service B: JAVA_HOME=/Users/user/.asdf/installs/java/openjdk-17 --- internal/services/operations.go | 36 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/internal/services/operations.go b/internal/services/operations.go index c26a7c1..dfb4b7e 100644 --- a/internal/services/operations.go +++ b/internal/services/operations.go @@ -379,14 +379,8 @@ func (sm *Manager) startServiceWithProjectsDir(service *models.Service, projects // Start with the current environment cmd.Env = os.Environ() - // Apply Java Home override - if sm.config.JavaHomeOverride != "" { - cmd.Env = append(cmd.Env, fmt.Sprintf("JAVA_HOME=%s", sm.config.JavaHomeOverride)) - cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s/bin:%s", sm.config.JavaHomeOverride, os.Getenv("PATH"))) - } - // Build environment variables with proper precedence - // Priority: Service-specific env vars > Global env vars + // Priority: Service-specific env vars > Profile Java Home override > Global env vars // Create a map to track which variables are set by service serviceEnvKeys := make(map[string]bool) @@ -394,6 +388,12 @@ func (sm *Manager) startServiceWithProjectsDir(service *models.Service, projects serviceEnvKeys[key] = true } + // Apply Java Home override if set (only if not overridden by service) + if sm.config.JavaHomeOverride != "" && !serviceEnvKeys["JAVA_HOME"] { + cmd.Env = append(cmd.Env, fmt.Sprintf("JAVA_HOME=%s", sm.config.JavaHomeOverride)) + cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s/bin:%s", sm.config.JavaHomeOverride, os.Getenv("PATH"))) + } + // Add global environment variables (only if not overridden by service) for key, value := range globalEnvVars { if !serviceEnvKeys[key] { @@ -404,6 +404,10 @@ func (sm *Manager) startServiceWithProjectsDir(service *models.Service, projects // Add service-specific environment variables (these take precedence) for key, envVar := range service.EnvVars { cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, envVar.Value)) + // If service sets JAVA_HOME, also update PATH to use that Java + if key == "JAVA_HOME" { + cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s/bin:%s", envVar.Value, os.Getenv("PATH"))) + } // Also set SPRING_PROFILES_ACTIVE for Spring Boot if ACTIVE_PROFILE is set if key == "ACTIVE_PROFILE" { cmd.Env = append(cmd.Env, fmt.Sprintf("SPRING_PROFILES_ACTIVE=%s", envVar.Value)) @@ -564,14 +568,8 @@ func (sm *Manager) startService(service *models.Service) error { // Set environment variables for the process cmd.Env = os.Environ() // Start with current environment - // Apply Java Home override if set - if sm.config.JavaHomeOverride != "" { - cmd.Env = append(cmd.Env, fmt.Sprintf("JAVA_HOME=%s", sm.config.JavaHomeOverride)) - cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s/bin:%s", sm.config.JavaHomeOverride, os.Getenv("PATH"))) - } - // Build environment variables with proper precedence - // Priority: Service-specific env vars > Global env vars + // Priority: Service-specific env vars > Profile Java Home override > Global env vars // Create a map to track which variables are set by service serviceEnvKeys := make(map[string]bool) @@ -579,6 +577,12 @@ func (sm *Manager) startService(service *models.Service) error { serviceEnvKeys[key] = true } + // Apply Java Home override if set (only if not overridden by service) + if sm.config.JavaHomeOverride != "" && !serviceEnvKeys["JAVA_HOME"] { + cmd.Env = append(cmd.Env, fmt.Sprintf("JAVA_HOME=%s", sm.config.JavaHomeOverride)) + cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s/bin:%s", sm.config.JavaHomeOverride, os.Getenv("PATH"))) + } + // Add global environment variables (only if not overridden by service) for key, value := range globalEnvVars { if !serviceEnvKeys[key] { @@ -589,6 +593,10 @@ func (sm *Manager) startService(service *models.Service) error { // Add service-specific environment variables (these take precedence) for key, envVar := range service.EnvVars { cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, envVar.Value)) + // If service sets JAVA_HOME, also update PATH to use that Java + if key == "JAVA_HOME" { + cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s/bin:%s", envVar.Value, os.Getenv("PATH"))) + } // Also set SPRING_PROFILES_ACTIVE for Spring Boot if ACTIVE_PROFILE is set if key == "ACTIVE_PROFILE" { cmd.Env = append(cmd.Env, fmt.Sprintf("SPRING_PROFILES_ACTIVE=%s", envVar.Value)) From 255695639f34ecc11bda1bfd43db28301b648cc1 Mon Sep 17 00:00:00 2001 From: Zachariah Ngonyani Date: Mon, 1 Dec 2025 11:26:03 +0300 Subject: [PATCH 3/4] feat: log Java version when starting services - Add logJavaVersion helper function to detect and display Java version - Show Java version, source (asdf/sdkman/system), and JAVA_HOME path - Log information at service startup for easy debugging - Helps verify per-service Java version override is working correctly Example log output: [INFO] Service EUREKA: Using Java 21.0.1 from asdf (/Users/user/.asdf/installs/java/openjdk-21) [INFO] Service LEGACY: Using Java 17.0.5 from asdf (/Users/user/.asdf/installs/java/openjdk-17) --- internal/services/operations.go | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/internal/services/operations.go b/internal/services/operations.go index dfb4b7e..75e1aa8 100644 --- a/internal/services/operations.go +++ b/internal/services/operations.go @@ -422,6 +422,9 @@ func (sm *Manager) startServiceWithProjectsDir(service *models.Service, projects } } + // Detect and log Java version being used + logJavaVersion(cmd.Env, service.Name) + // Log the final command and environment variables for profile services log.Printf("[DEBUG] Starting profile service %s with command: %s", service.Name, cmdString) log.Printf("[DEBUG] Working directory: %s", serviceDir) @@ -611,6 +614,9 @@ func (sm *Manager) startService(service *models.Service) error { } } + // Detect and log Java version being used + logJavaVersion(cmd.Env, service.Name) + // Log the final command and environment variables // log.Printf("[DEBUG] Starting service %s with command: %s", service.Name, cmdString) // log.Printf("[DEBUG] Working directory: %s", serviceDir) @@ -904,3 +910,57 @@ func isPortEnvironmentVariable(key string) bool { } return false } + +// logJavaVersion detects and logs the Java version being used for a service +func logJavaVersion(env []string, serviceName string) { + // Extract JAVA_HOME from environment + var javaHome string + for _, envVar := range env { + if strings.HasPrefix(envVar, "JAVA_HOME=") { + javaHome = strings.TrimPrefix(envVar, "JAVA_HOME=") + break + } + } + + if javaHome == "" { + log.Printf("[INFO] Service %s: No JAVA_HOME set, using system default Java", serviceName) + return + } + + // Try to get Java version + javaPath := filepath.Join(javaHome, "bin", "java") + cmd := exec.Command(javaPath, "-version") + output, err := cmd.CombinedOutput() + + if err != nil { + log.Printf("[WARN] Service %s: JAVA_HOME=%s but failed to detect version: %v", serviceName, javaHome, err) + return + } + + // Parse version from output + version := "unknown" + lines := strings.Split(string(output), "\n") + if len(lines) > 0 { + line := lines[0] + if strings.Contains(line, "version") { + // Extract version string between quotes + start := strings.Index(line, `"`) + if start != -1 { + end := strings.Index(line[start+1:], `"`) + if end != -1 { + version = line[start+1 : start+1+end] + } + } + } + } + + // Determine source of JAVA_HOME + source := "system" + if strings.Contains(javaHome, "/.asdf/installs/java/") { + source = "asdf" + } else if strings.Contains(javaHome, "/.sdkman/candidates/java/") { + source = "sdkman" + } + + log.Printf("[INFO] Service %s: Using Java %s from %s (%s)", serviceName, version, source, javaHome) +} From 73fac9d0779bf85feb17677c49b09d49b2cf7f23 Mon Sep 17 00:00:00 2001 From: Zachariah Ngonyani Date: Mon, 1 Dec 2025 11:35:33 +0300 Subject: [PATCH 4/4] feat: add configurable verbose logging for Maven and Gradle builds Backend changes: - Add VerboseLogging boolean field to Service model - Add verbose_logging column to services database table with migration - Update GetStartCommand to add verbose flags when enabled: * Maven: adds -X flag for debug output * Gradle: adds -i flag for info level logging - Update all database queries to include verbose_logging field Frontend changes: - Add verboseLogging field to Service TypeScript interface - Add checkbox in ServiceConfigModal for toggling verbose logging - Include helpful label: "Enable verbose logging (Maven: -X, Gradle: -i)" - Initialize verboseLogging to false for new services This feature helps debug build and startup issues by providing more detailed logging output from Maven and Gradle build tools when enabled on a per-service basis. --- internal/database/database.go | 34 +++++++++++ internal/models/service.go | 61 ++++++++++--------- internal/services/buildsystem.go | 13 +++- internal/services/database.go | 31 ++++++---- internal/services/operations.go | 4 +- .../ServiceConfigModal/ServiceConfigModal.tsx | 16 +++++ web/src/hooks/useServiceManagement.ts | 1 + web/src/types.ts | 1 + 8 files changed, 116 insertions(+), 45 deletions(-) diff --git a/internal/database/database.go b/internal/database/database.go index cb80838..fb5984d 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -349,6 +349,11 @@ func (db *Database) initTables() error { return fmt.Errorf("failed to migrate service name constraint: %w", err) } + // Add verbose_logging column for build tool verbose/debug output + if err := db.migrateAddVerboseLoggingColumn(); err != nil { + return fmt.Errorf("failed to add verbose_logging column: %w", err) + } + return nil } @@ -943,3 +948,32 @@ func (db *Database) DeleteDockerConfig(profileID string) error { } return nil } + +// migrateAddVerboseLoggingColumn adds the verbose_logging column to the services table +func (db *Database) migrateAddVerboseLoggingColumn() error { + // Check if column already exists + var columnExists bool + var sql string + err := db.QueryRow("SELECT sql FROM sqlite_master WHERE type='table' AND name='services'").Scan(&sql) + if err != nil { + return fmt.Errorf("failed to query services table schema: %w", err) + } + + columnExists = strings.Contains(sql, "verbose_logging") + + if columnExists { + log.Println("[INFO] Column 'verbose_logging' already exists in services table") + return nil + } + + log.Println("[INFO] Adding 'verbose_logging' column to services table") + + // Add the column with default value of FALSE + _, err = db.Exec(`ALTER TABLE services ADD COLUMN verbose_logging BOOLEAN DEFAULT FALSE`) + if err != nil { + return fmt.Errorf("failed to add verbose_logging column: %w", err) + } + + log.Println("[INFO] Successfully added 'verbose_logging' column to services table") + return nil +} diff --git a/internal/models/service.go b/internal/models/service.go index d99a6fa..d47bbc8 100644 --- a/internal/models/service.go +++ b/internal/models/service.go @@ -8,34 +8,35 @@ import ( ) type Service struct { - ID string `json:"id"` // UUID - unique identifier for the service - Name string `json:"name"` - Dir string `json:"dir"` - ExtraEnv string `json:"extraEnv"` - JavaOpts string `json:"javaOpts"` - Status string `json:"status"` - HealthStatus string `json:"healthStatus"` - HealthURL string `json:"healthUrl"` - Port int `json:"port"` - PID int `json:"pid"` - Order int `json:"order"` - LastStarted time.Time `json:"lastStarted"` - Uptime string `json:"uptime"` - Description string `json:"description"` - IsEnabled bool `json:"isEnabled"` - BuildSystem string `json:"buildSystem"` // "maven", "gradle", or "auto" - EnvVars map[string]EnvVar `json:"envVars"` - Cmd *exec.Cmd `json:"-"` - Logs []LogEntry `json:"logs"` - Mutex sync.RWMutex `json:"-"` - CPUPercent float64 `json:"cpuPercent"` - MemoryUsage uint64 `json:"memoryUsage"` // in bytes - MemoryPercent float32 `json:"memoryPercent"` - DiskUsage uint64 `json:"diskUsage"` // in bytes - NetworkRx uint64 `json:"networkRx"` // bytes received - NetworkTx uint64 `json:"networkTx"` // bytes transmitted - Metrics ServiceMetrics `json:"metrics"` - Dependencies []ServiceDependency `json:"dependencies"` - DependentOn []string `json:"dependentOn"` // Services that depend on this one - StartupDelay time.Duration `json:"startupDelay"` // Delay before starting after dependencies + ID string `json:"id"` // UUID - unique identifier for the service + Name string `json:"name"` + Dir string `json:"dir"` + ExtraEnv string `json:"extraEnv"` + JavaOpts string `json:"javaOpts"` + Status string `json:"status"` + HealthStatus string `json:"healthStatus"` + HealthURL string `json:"healthUrl"` + Port int `json:"port"` + PID int `json:"pid"` + Order int `json:"order"` + LastStarted time.Time `json:"lastStarted"` + Uptime string `json:"uptime"` + Description string `json:"description"` + IsEnabled bool `json:"isEnabled"` + BuildSystem string `json:"buildSystem"` // "maven", "gradle", or "auto" + VerboseLogging bool `json:"verboseLogging"` // Enable verbose/debug logging for build tools + EnvVars map[string]EnvVar `json:"envVars"` + Cmd *exec.Cmd `json:"-"` + Logs []LogEntry `json:"logs"` + Mutex sync.RWMutex `json:"-"` + CPUPercent float64 `json:"cpuPercent"` + MemoryUsage uint64 `json:"memoryUsage"` // in bytes + MemoryPercent float32 `json:"memoryPercent"` + DiskUsage uint64 `json:"diskUsage"` // in bytes + NetworkRx uint64 `json:"networkRx"` // bytes received + NetworkTx uint64 `json:"networkTx"` // bytes transmitted + Metrics ServiceMetrics `json:"metrics"` + Dependencies []ServiceDependency `json:"dependencies"` + DependentOn []string `json:"dependentOn"` // Services that depend on this one + StartupDelay time.Duration `json:"startupDelay"` // Delay before starting after dependencies } diff --git a/internal/services/buildsystem.go b/internal/services/buildsystem.go index 731534e..aee54f2 100644 --- a/internal/services/buildsystem.go +++ b/internal/services/buildsystem.go @@ -107,7 +107,7 @@ func HasGradleWrapper(serviceDir string) bool { } // GetStartCommand returns the appropriate start command for the service -func GetStartCommand(serviceDir, buildSystem string, javaOpts string, extraEnv string) (string, error) { +func GetStartCommand(serviceDir, buildSystem string, javaOpts string, extraEnv string, verboseLogging bool) (string, error) { effectiveBuildSystem := GetEffectiveBuildSystem(serviceDir, buildSystem) commands := GetBuildSystemCommands(effectiveBuildSystem) @@ -124,6 +124,17 @@ func GetStartCommand(serviceDir, buildSystem string, javaOpts string, extraEnv s baseCommand = commands.Start } + // Add verbose/debug logging flags if enabled + if verboseLogging { + if effectiveBuildSystem == BuildSystemMaven { + // Maven: use -X for debug output + baseCommand = strings.Replace(baseCommand, "spring-boot:run", "spring-boot:run -X", 1) + } else if effectiveBuildSystem == BuildSystemGradle { + // Gradle: use -i for info level logging + baseCommand = strings.Replace(baseCommand, "bootRun", "bootRun -i", 1) + } + } + // Construct the full command with directory change and environment var fullCommand string if extraEnv != "" { diff --git a/internal/services/database.go b/internal/services/database.go index 4145aa2..66085fd 100644 --- a/internal/services/database.go +++ b/internal/services/database.go @@ -46,29 +46,31 @@ func (sm *Manager) loadServices(config models.Config) error { // Try to load existing service from database var dbService models.Service row := sm.db.QueryRow(` - SELECT id, name, dir, extra_env, java_opts, status, health_status, health_url, port, pid, service_order, last_started, description, is_enabled, build_system + SELECT id, name, dir, extra_env, java_opts, status, health_status, health_url, port, pid, service_order, last_started, description, is_enabled, build_system, verbose_logging FROM services WHERE id = ?`, service.ID) var description sql.NullString var isEnabled sql.NullBool var buildSystem sql.NullString + var verboseLogging sql.NullBool err := row.Scan(&dbService.ID, &dbService.Name, &dbService.Dir, &dbService.ExtraEnv, &dbService.JavaOpts, &dbService.Status, &dbService.HealthStatus, &dbService.HealthURL, &dbService.Port, - &dbService.PID, &dbService.Order, &dbService.LastStarted, &description, &isEnabled, &buildSystem) + &dbService.PID, &dbService.Order, &dbService.LastStarted, &description, &isEnabled, &buildSystem, &verboseLogging) if err == sql.ErrNoRows { // Service doesn't exist in DB, insert it _, err = sm.db.Exec(` - INSERT INTO services (id, name, dir, extra_env, java_opts, status, health_status, health_url, port, service_order, description, is_enabled, build_system, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + INSERT INTO services (id, name, dir, extra_env, java_opts, status, health_status, health_url, port, service_order, description, is_enabled, build_system, verbose_logging, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, service.ID, service.Name, service.Dir, service.ExtraEnv, service.JavaOpts, service.Status, - service.HealthStatus, service.HealthURL, service.Port, service.Order, "", true, "auto") + service.HealthStatus, service.HealthURL, service.Port, service.Order, "", true, "auto", false) if err != nil { return fmt.Errorf("failed to insert service UUID %s: %w", service.ID, err) } service.EnvVars = make(map[string]models.EnvVar) service.Logs = []models.LogEntry{} service.BuildSystem = "auto" + service.VerboseLogging = false sm.services[service.ID] = service } else if err != nil { return fmt.Errorf("failed to query service UUID %s: %w", service.ID, err) @@ -106,6 +108,11 @@ func (sm *Manager) loadServices(config models.Config) error { } else { dbService.BuildSystem = "auto" } + if verboseLogging.Valid { + dbService.VerboseLogging = verboseLogging.Bool + } else { + dbService.VerboseLogging = false + } // Load environment variables for this service dbService.EnvVars = make(map[string]models.EnvVar) @@ -368,11 +375,11 @@ func (sm *Manager) loadDynamicServices() error { func (sm *Manager) insertServiceInDB(service *models.Service) error { _, err := sm.db.Exec(` - INSERT INTO services (id, name, dir, extra_env, java_opts, status, health_status, health_url, port, service_order, description, is_enabled, build_system, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + INSERT INTO services (id, name, dir, extra_env, java_opts, status, health_status, health_url, port, service_order, description, is_enabled, build_system, verbose_logging, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, service.ID, service.Name, service.Dir, service.ExtraEnv, service.JavaOpts, service.Status, service.HealthStatus, service.HealthURL, service.Port, service.Order, - service.Description, service.IsEnabled, service.BuildSystem) + service.Description, service.IsEnabled, service.BuildSystem, service.VerboseLogging) return err } @@ -418,12 +425,12 @@ func (sm *Manager) UpdateServiceInDB(service *models.Service) error { func (sm *Manager) UpdateServiceConfigInDB(service *models.Service) error { _, err := sm.db.Exec(` - UPDATE services - SET name = ?, java_opts = ?, health_url = ?, port = ?, service_order = ?, description = ?, - is_enabled = ?, build_system = ?, updated_at = CURRENT_TIMESTAMP + UPDATE services + SET name = ?, java_opts = ?, health_url = ?, port = ?, service_order = ?, description = ?, + is_enabled = ?, build_system = ?, verbose_logging = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, service.Name, service.JavaOpts, service.HealthURL, service.Port, service.Order, - service.Description, service.IsEnabled, service.BuildSystem, service.ID) + service.Description, service.IsEnabled, service.BuildSystem, service.VerboseLogging, service.ID) return err } diff --git a/internal/services/operations.go b/internal/services/operations.go index 75e1aa8..e2a2760 100644 --- a/internal/services/operations.go +++ b/internal/services/operations.go @@ -359,7 +359,7 @@ func (sm *Manager) startServiceWithProjectsDir(service *models.Service, projects } // Get start command - cmdString, err := GetStartCommand(serviceDir, string(effectiveBuildSystem), service.JavaOpts, service.ExtraEnv) + cmdString, err := GetStartCommand(serviceDir, string(effectiveBuildSystem), service.JavaOpts, service.ExtraEnv, service.VerboseLogging) if err != nil { return fmt.Errorf("failed to construct start command: %w", err) } @@ -545,7 +545,7 @@ func (sm *Manager) startService(service *models.Service) error { } // Get the start command for the detected build system - cmdString, err := GetStartCommand(serviceDir, string(effectiveBuildSystem), service.JavaOpts, service.ExtraEnv) + cmdString, err := GetStartCommand(serviceDir, string(effectiveBuildSystem), service.JavaOpts, service.ExtraEnv, service.VerboseLogging) if err != nil { return fmt.Errorf("failed to construct start command: %w", err) } diff --git a/web/src/components/ServiceConfigModal/ServiceConfigModal.tsx b/web/src/components/ServiceConfigModal/ServiceConfigModal.tsx index 64e05aa..1d64d9c 100644 --- a/web/src/components/ServiceConfigModal/ServiceConfigModal.tsx +++ b/web/src/components/ServiceConfigModal/ServiceConfigModal.tsx @@ -315,6 +315,22 @@ export function ServiceConfigModal({ +
+ + setEditingService({ + ...editingService, + verboseLogging: checked === true, + }) + } + /> + +
+ {/* Environment Variables */}
diff --git a/web/src/hooks/useServiceManagement.ts b/web/src/hooks/useServiceManagement.ts index 538d8f5..a83804f 100644 --- a/web/src/hooks/useServiceManagement.ts +++ b/web/src/hooks/useServiceManagement.ts @@ -41,6 +41,7 @@ export function useServiceManagement(onServiceUpdated: () => void) { description: "", isEnabled: true, buildSystem: "auto", + verboseLogging: false, envVars: {}, logs: [], uptime: "", diff --git a/web/src/types.ts b/web/src/types.ts index c71d88b..99b637a 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -56,6 +56,7 @@ export interface Service { description: string; isEnabled: boolean; buildSystem: string; // "maven", "gradle", or "auto" + verboseLogging: boolean; // Enable verbose/debug logging for build tools envVars: { [key: string]: EnvVar }; logs: LogEntry[]; // Resource monitoring fields