Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions app/Commands/Service/DownCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

declare(strict_types=1);

namespace App\Commands\Service;

use Illuminate\Support\Facades\Process;
use LaravelZero\Framework\Commands\Command;

use function Laravel\Prompts\confirm;
use function Termwind\render;

class DownCommand extends Command
{
protected $signature = 'service:down
{--volumes : Remove volumes}
{--odin : Use Odin (remote) configuration}
{--force : Skip confirmation prompts}';

protected $description = 'Stop knowledge services';

public function handle(): int
{
$composeFile = $this->option('odin') === true
? 'docker-compose.odin.yml'
: 'docker-compose.yml';

$environment = $this->option('odin') === true ? 'Odin (Remote)' : 'Local';

if (! file_exists(base_path($composeFile))) {
render(<<<HTML
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-red-900">
<div class="text-red-400 font-bold">✗ Configuration Error</div>
<div class="text-red-300 mt-1">Docker Compose file not found: {$composeFile}</div>
</div>
</div>
HTML);

return self::FAILURE;
}

// Show warning if removing volumes
if ($this->option('volumes') === true && $this->option('force') !== true) {
render(<<<'HTML'
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-yellow-900">
<div>
<span class="text-yellow-400 mr-3">⚠</span>
<span class="text-yellow-300 font-bold">Warning: Volume Removal</span>
</div>
<div class="text-gray-300 mt-1 ml-6">This will permanently delete all data stored in volumes</div>
</div>
</div>
HTML);

$confirmed = confirm(
label: 'Are you sure you want to remove volumes?',
default: false,
hint: 'This action cannot be undone'
);

if (! $confirmed) {
render(<<<'HTML'
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-gray-800">
<div class="text-gray-400">Operation cancelled</div>
</div>
</div>
HTML);

return self::SUCCESS;
}
}

// Display shutdown banner
render(<<<HTML
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-orange-900">
<div>
<span class="text-orange-400 mr-3">■</span>
<span class="text-orange-300 font-bold">Stopping Knowledge Services</span>
</div>
<div class="text-gray-400 ml-6">Environment: {$environment}</div>
</div>
</div>
HTML);

$args = ['docker', 'compose', '-f', $composeFile, 'down'];

if ($this->option('volumes') === true) {
$args[] = '-v';
}

$result = Process::forever()
->path(base_path())
->run($args);

$exitCode = $result->exitCode();

if ($exitCode === 0) {
$volumeText = $this->option('volumes') === true ? ' and volumes removed' : '';

render(<<<HTML
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-green-900">
<div>
<span class="text-green-400 mr-3">✓</span>
<span class="text-green-300 font-bold">Services Stopped Successfully</span>
</div>
<div class="text-gray-400 mt-1 ml-6">All containers have been stopped{$volumeText}</div>
</div>
</div>
HTML);

if ($this->option('volumes') !== true) {
render(<<<'HTML'
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-gray-800">
<div class="text-gray-400">
<span>Tip: Use </span>
<span class="text-cyan-400">know service:down --volumes</span>
<span> to remove data volumes</span>
</div>
</div>
</div>
HTML);
}

return self::SUCCESS;
}

render(<<<'HTML'
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-red-900">
<div>
<span class="text-red-400 mr-3">✗</span>
<span class="text-red-300 font-bold">Failed to Stop Services</span>
</div>
<div class="text-gray-400 mt-1 ml-6">Check the error output above for details</div>
</div>
</div>
HTML);

return self::FAILURE;
}
}
143 changes: 143 additions & 0 deletions app/Commands/Service/LogsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

declare(strict_types=1);

namespace App\Commands\Service;

use Illuminate\Support\Facades\Process;
use LaravelZero\Framework\Commands\Command;

use function Laravel\Prompts\select;
use function Termwind\render;

class LogsCommand extends Command
{
protected $signature = 'service:logs
{service? : Specific service (qdrant, redis, embeddings, ollama)}
{--f|follow : Follow log output}
{--tail=50 : Number of lines to show}
{--odin : Use Odin (remote) configuration}';

protected $description = 'View service logs';

public function handle(): int
{
$composeFile = $this->option('odin') === true
? 'docker-compose.odin.yml'
: 'docker-compose.yml';

$environment = $this->option('odin') === true ? 'Odin (Remote)' : 'Local';

if (! file_exists(base_path($composeFile))) {
render(<<<HTML
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-red-900">
<div class="text-red-400 font-bold">✗ Configuration Error</div>
<div class="text-red-300 mt-1">Docker Compose file not found: {$composeFile}</div>
</div>
</div>
HTML);

return self::FAILURE;
}

/** @var string|null $service */
$service = $this->argument('service');

// If no service specified and not following, offer selection
if ($service === null && $this->option('follow') !== true) {
$service = select(
label: 'Which service logs would you like to view?',
options: [
'all' => 'All Services',
'qdrant' => 'Qdrant (Vector Database)',
'redis' => 'Redis (Cache)',
'embeddings' => 'Embeddings (ML Service)',
'ollama' => 'Ollama (LLM Engine)',
],
default: 'all'
);

if ($service === 'all') {
$service = null;
}
}

$serviceDisplay = is_string($service) ? ucfirst($service) : 'All Services';
$followMode = $this->option('follow') === true ? 'Live' : 'Recent';
$tailOption = $this->option('tail');
$tailCount = is_string($tailOption) || is_int($tailOption) ? (string) $tailOption : '50';

// Display logs banner
render(<<<HTML
<div class="mx-2 my-1">
<div class="px-4 py-2 bg-purple-900">
<div>
<span class="text-purple-400 mr-3">📋</span>
<span class="text-purple-300 font-bold">Service Logs: {$serviceDisplay}</span>
</div>
<div class="ml-6">
<span class="text-gray-400">Environment: {$environment}</span>
<span class="text-gray-500 ml-2">·</span>
<span class="text-purple-300 font-bold ml-2">{$followMode}</span>
<span class="text-gray-400 ml-2">·</span>
<span class="text-gray-400 ml-2">Last {$tailCount} lines</span>
</div>
</div>
</div>
HTML);

if ($this->option('follow') === true) {
render(<<<'HTML'
<div class="mx-2 my-1">
<div class="px-4 py-1 bg-gray-800">
<div class="text-gray-400">
<span>Press </span><span class="text-cyan-400">Ctrl+C</span><span> to stop following logs</span>
</div>
</div>
</div>
HTML);
}

$this->newLine();

$args = ['docker', 'compose', '-f', $composeFile, 'logs'];

if ($this->option('follow') === true) {
$args[] = '-f';
}

$tailOption = $this->option('tail');
if (is_string($tailOption) || is_int($tailOption)) {
$args[] = '--tail='.(string) $tailOption;
}

if (is_string($service)) {
$args[] = $service;
}

$result = Process::forever()
->path(base_path())
->run($args);

$exitCode = $result->exitCode() ?? self::FAILURE;

// Only show footer if not following (Ctrl+C will interrupt)
if ($this->option('follow') !== true) {
$this->newLine();
render(<<<'HTML'
<div class="mx-2 my-1">
<div class="px-4 py-1 bg-gray-800">
<div class="text-gray-400">
<span>Tip: Use </span>
<span class="text-cyan-400">--follow</span>
<span> to stream logs in real-time</span>
</div>
</div>
</div>
HTML);
}

return $exitCode;
}
}
Loading
Loading