Skip to content

feat: add Laravel Kick integration#62

Merged
StuMason merged 8 commits intomainfrom
feature/introspection-api
Jan 27, 2026
Merged

feat: add Laravel Kick integration#62
StuMason merged 8 commits intomainfrom
feature/introspection-api

Conversation

@StuMason
Copy link
Owner

Summary

Adds optional integration with laravel-kick for enhanced application introspection.

When an app has KICK_TOKEN and KICK_ENABLED=true in its Coolify environment variables, a new Kick tab appears in the dashboard.

Features

Overview Tab

  • Health checks (database, cache, storage, redis) with latency
  • System stats (CPU, memory, disk, uptime)
  • Queue status summary

Logs Tab

  • File selector with size
  • Level filtering (DEBUG through EMERGENCY)
  • Search functionality
  • Configurable line count
  • Auto-refresh

Queue Tab

  • Queue sizes by name
  • Failed job count
  • Failed job details with exception messages

Artisan Tab

  • Whitelisted command list
  • Command execution
  • Output display with exit codes

Configuration

// config/coolify.php
'kick' => [
    'enabled' => env('COOLIFY_KICK_ENABLED', true),
    'cache_ttl' => env('COOLIFY_KICK_CACHE_TTL', 60),
    'timeout' => env('COOLIFY_KICK_TIMEOUT', 10),
],

Setup for Apps

Add to the app's environment in Coolify:

KICK_ENABLED=true
KICK_TOKEN=your-secure-token

Test Plan

  • PHPStan passes
  • All 230 tests pass
  • Frontend builds successfully
  • Manual test with devtrends (has kick v0.3.1)

Integrate laravel-kick for enhanced application introspection. When an app
has KICK_TOKEN and KICK_ENABLED=true in its environment variables, a new
"Kick" tab appears in the dashboard showing:

- Health checks (database, cache, storage, redis)
- System stats (CPU, memory, disk, uptime)
- Log viewer with level filtering and search
- Queue status and failed jobs list
- Artisan command execution

Backend:
- KickClient: HTTP client for kick endpoints
- KickRepository: Contract for kick integration
- CoolifyKickRepository: Implementation that fetches config from Coolify
- KickController: API endpoints proxying to remote kick
- DashboardStatsController: Includes kick availability in stats

Frontend:
- Kick.vue: Full-featured page with tabs for all kick features
- Layout.vue: Conditionally shows Kick nav item
- api.js: Kick API methods

Configuration:
- coolify.kick.enabled: Toggle integration globally
- coolify.kick.cache_ttl: Cache config lookups
- coolify.kick.timeout: Request timeout
Add tests for KickClient, KickRepository, and KickController covering:
- Health, stats, logs, queue, and artisan endpoints
- Authentication and error handling
- Config retrieval from Coolify env vars
- Caching behavior
@StuMason
Copy link
Owner Author

@claude review please

@claude
Copy link

claude bot commented Jan 27, 2026

Claude finished @StuMason's task in 1m 34s —— View job


Code Review Complete ✓

  • Analyze PR changes and understand the Laravel Kick integration
  • Review new contracts and repositories
  • Review KickClient service implementation
  • Review KickController and routes
  • Review frontend components
  • Review test coverage
  • Check configuration and documentation
  • Provide comprehensive review feedback

Summary

This is a well-structured PR that adds optional integration with laravel-kick for enhanced application introspection. The implementation follows the existing codebase patterns (Repository pattern, service bindings, etc.) and includes comprehensive test coverage.

✅ Strengths

Architecture & Design

  • Follows existing patterns: The KickRepository contract and CoolifyKickRepository implementation follow the established repository pattern used throughout the codebase
  • Clean separation of concerns: KickClient handles HTTP communication, repository handles business logic, controller handles HTTP layer
  • Proper service binding: Added to ServiceBindings trait for DI container registration
  • Exception hierarchy: Clean exception classes (KickApiException, KickAuthenticationException, KickUnavailableException)

Security

  • Token-based authentication: Uses bearer tokens for authentication with the Kick API
  • Command validation: Controller validates the command parameter is required before execution
  • Proper authentication middleware: Routes are protected by the existing Authenticate middleware
  • Whitelisted commands: The Artisan execution relies on laravel-kick's command whitelist

Testing

  • Comprehensive coverage: 663 lines of tests across 3 test files (KickControllerTest, KickClientTest, KickRepositoryTest)
  • Good test isolation: Uses Http::fake() and Cache::flush() to prevent stray requests and state leakage
  • Edge cases covered: Tests for unconfigured apps, disabled kick, authentication failures, 503 errors, etc.

Frontend

  • Well-organized Vue component: Kick.vue is comprehensive with good UX (loading states, error handling, auto-refresh)
  • Dynamic tab visibility: Kick tab only appears when the app has kick configured
  • Search debouncing: Log search is properly debounced (300ms)

Configuration

  • Sensible defaults: kick.enabled=true, cache_ttl=60, timeout=10
  • Environment variable support: All config values can be overridden via env vars
  • Good documentation: Clear comments explaining each config option

⚠️ Suggestions for Improvement

1. Input Validation on Log File Path

File: src/Http/Controllers/KickController.php:89-100

The $file parameter in logRead is passed directly to the Kick API without validation. While laravel-kick should handle this safely, consider adding basic validation:

public function logRead(Request $request, string $appUuid, string $file): JsonResponse
{
    // Consider validating $file doesn't contain path traversal characters
    if (preg_match('/\.\.\/|\.\.\\\\/', $file)) {
        return response()->json(['error' => 'Invalid file path'], 400);
    }
    // ...
}

Fix this →

2. Consider Rate Limiting for Artisan Execution

File: src/Http/Controllers/KickController.php:155-171

The artisanRun endpoint allows executing commands on remote servers. Consider adding rate limiting or throttling:

// In routes/web.php, add throttle middleware to the artisan.run route
Route::post('/artisan', 'KickController@artisanRun')
    ->middleware('throttle:10,1') // 10 requests per minute
    ->name('coolify.kick.artisan.run');

3. Missing declare(strict_types=1)

File: src/Contracts/KickRepository.php

The contract file is missing the declare(strict_types=1) declaration that other files in this PR have. For consistency:

<?php

declare(strict_types=1);

namespace Stumason\Coolify\Contracts;

Fix this →

4. Frontend Error Handling Enhancement

File: resources/js/pages/Kick.vue:52-63

The error detection logic could be more robust:

// Current: checks for '503' or '404' in error message string
if (e.message?.includes('503') || e.message?.includes('404')) {
    serviceUnavailable.value = true;
}

// Consider: also checking the actual HTTP status code if available
// Or use a more structured error response from the API

5. Consider Adding Cache Clear Method to Contract

File: src/Contracts/KickRepository.php

The CoolifyKickRepository has a clearCache() method, but it's not defined in the contract interface. If this is intended to be part of the public API:

/**
 * Clear cached kick configuration for an application.
 */
public function clearCache(string $appUuid): void;

6. Vue Component Memory Leak Prevention

File: resources/js/pages/Kick.vue:230-233

Good job cleaning up the interval on unmount. However, consider using onBeforeUnmount instead of onUnmounted for guaranteed cleanup before DOM removal:

import { onBeforeUnmount } from 'vue';

onBeforeUnmount(() => {
    if (pollInterval) clearInterval(pollInterval);
    if (searchTimeout) clearTimeout(searchTimeout);
});

📝 Minor Nits

  1. Consistent HTTP status codes: The controller returns 503 for "Kick not available", which is semantically correct (Service Unavailable). This is good.

  2. API response structure: All Kick endpoints follow a consistent pattern of returning the data directly or ['error' => 'Kick not available'] with 503. This is clean.

  3. Config documentation in README: The README update is clear and helpful with the setup instructions.

🧪 Test Coverage

The test coverage is excellent:

  • KickControllerTest: 263 lines covering all endpoints, auth, and error scenarios
  • KickClientTest: 216 lines testing HTTP communication, auth failures, retries
  • KickRepositoryTest: 184 lines testing config parsing, caching, multi-FQDN handling

Verdict

This PR is well-implemented and ready for merge with only minor suggestions above. The code follows established patterns, has good test coverage, and the feature is properly documented. The security model relies on both the Coolify dashboard authentication and the laravel-kick token, providing defense in depth.

Great work on this integration! 🎉


- Add logging to exception catch blocks instead of silent failures
- Catch specific exceptions (CoolifyApiException, KickApiException, etc.)
- Add path traversal validation in logRead endpoint
- Add clearCache() method to KickRepository interface
- Add declare(strict_types=1) to KickRepository interface
- Add rate limiting (10/min) to artisan execution endpoint
- Use onBeforeUnmount for guaranteed cleanup in Vue component
- Fix DashboardTest HTTP fake for kick config lookup
@StuMason StuMason merged commit 959dece into main Jan 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant