563 lines
35 KiB
PHP
563 lines
35 KiB
PHP
<div wire:poll.10000ms="checkStatus" class="pb-6">
|
|
@php
|
|
$servicePageItems = [
|
|
['label' => 'Configuration', 'route' => 'project.service.configuration', 'active' => request()->routeIs('project.service.configuration')],
|
|
['label' => 'Logs', 'route' => 'project.service.logs', 'active' => request()->routeIs('project.service.logs')],
|
|
['label' => 'Terminal', 'route' => 'project.service.command', 'active' => request()->routeIs('project.service.command'), 'navigate' => false, 'visible' => auth()->user()?->can('canAccessTerminal')],
|
|
];
|
|
|
|
$serviceConfigurationItems = [
|
|
['label' => 'General', 'route' => 'project.service.configuration', 'active' => request()->routeIs('project.service.configuration')],
|
|
['label' => 'Environment Variables', 'route' => 'project.service.environment-variables', 'active' => request()->routeIs('project.service.environment-variables')],
|
|
['label' => 'Persistent Storages', 'route' => 'project.service.storages', 'active' => request()->routeIs('project.service.storages')],
|
|
['label' => 'Scheduled Tasks', 'route' => 'project.service.scheduled-tasks.show', 'active' => request()->routeIs('project.service.scheduled-tasks.show', 'project.service.scheduled-tasks')],
|
|
['label' => 'Webhooks', 'route' => 'project.service.webhooks', 'active' => request()->routeIs('project.service.webhooks')],
|
|
['label' => 'Resource Operations', 'route' => 'project.service.resource-operations', 'active' => request()->routeIs('project.service.resource-operations')],
|
|
['label' => 'Tags', 'route' => 'project.service.tags', 'active' => request()->routeIs('project.service.tags')],
|
|
['label' => 'Danger Zone', 'route' => 'project.service.danger', 'active' => request()->routeIs('project.service.danger')],
|
|
];
|
|
$serviceResourceItems = [];
|
|
|
|
if (filled(data_get($parameters, 'stack_service_uuid'))) {
|
|
$serviceResourceItems = [
|
|
['label' => 'Back', 'route' => 'project.service.configuration', 'active' => false, 'parameters' => [...$parameters, 'stack_service_uuid' => null]],
|
|
['label' => 'General', 'route' => 'project.service.index', 'active' => request()->routeIs('project.service.index')],
|
|
['label' => 'Advanced', 'route' => 'project.service.index.advanced', 'active' => request()->routeIs('project.service.index.advanced')],
|
|
['label' => 'Backups', 'route' => 'project.service.database.backups', 'active' => request()->routeIs('project.service.database.backups')],
|
|
['label' => 'Import Backup', 'route' => 'project.service.database.import', 'active' => request()->routeIs('project.service.database.import')],
|
|
];
|
|
}
|
|
|
|
$servicePageItems = array_values(array_filter($servicePageItems, fn (array $item): bool => $item['visible'] ?? true));
|
|
$serviceResourceItems = array_values(array_filter($serviceResourceItems, fn (array $item): bool => $item['visible'] ?? true));
|
|
$activeServiceResourceItem = collect($serviceResourceItems)->firstWhere('active', true);
|
|
$activeServiceConfigurationItem = collect($serviceConfigurationItems)->firstWhere('active', true);
|
|
$activeServicePageItem = collect($servicePageItems)->firstWhere('active', true);
|
|
$activeServiceMobileItem = $activeServiceResourceItem ?? $activeServiceConfigurationItem ?? $activeServicePageItem ?? $servicePageItems[0];
|
|
$activeServiceMobileGroup = $activeServiceResourceItem ? 'resource' : ($activeServiceConfigurationItem ? 'configuration' : 'service');
|
|
$activeServiceMobileNavigation = ($activeServiceMobileItem['navigate'] ?? true) ? 'navigate' : 'location';
|
|
$activeServiceMobileValue = $activeServiceMobileNavigation.'|'.$activeServiceMobileGroup.'|'.route($activeServiceMobileItem['route'], $activeServiceMobileItem['parameters'] ?? $parameters);
|
|
$serviceLinks = collect([]);
|
|
$service->applications()->get()->each(function ($application) use ($serviceLinks) {
|
|
$type = $application->serviceType();
|
|
if ($type) {
|
|
generateServiceSpecificFqdns($application)
|
|
->map(fn ($link) => getFqdnWithoutPort($link))
|
|
->each(fn ($link) => $serviceLinks->push($link));
|
|
} else {
|
|
if ($application->fqdn) {
|
|
collect(str($application->fqdn)->explode(','))
|
|
->each(fn ($fqdn) => $serviceLinks->push(getFqdnWithoutPort($fqdn)));
|
|
}
|
|
if ($application->ports) {
|
|
collect(str($application->ports)->explode(','))
|
|
->each(function ($port) use ($serviceLinks) {
|
|
$hostPort = str($port)->contains(':') ? str($port)->before(':') : $port;
|
|
$serviceLinks->push(base_url(withPort: false).":{$hostPort}");
|
|
});
|
|
}
|
|
}
|
|
});
|
|
$serviceMobileMenuChangeHandler = <<<'JS'
|
|
const value = $event.target.value;
|
|
|
|
if (!value) {
|
|
return;
|
|
}
|
|
|
|
if (value.startsWith('navigate|')) {
|
|
const url = value.split('|').slice(2).join('|');
|
|
window.Livewire?.navigate ? window.Livewire.navigate(url) : window.location.href = url;
|
|
return;
|
|
}
|
|
|
|
if (value.startsWith('location|')) {
|
|
const url = value.split('|').slice(2).join('|');
|
|
window.location.href = url;
|
|
return;
|
|
}
|
|
|
|
resetToCurrent();
|
|
|
|
if (value.startsWith('external:')) {
|
|
window.open(value.slice(9), '_blank', 'noopener');
|
|
return;
|
|
}
|
|
|
|
const action = value.slice(7);
|
|
|
|
document.getElementById(`service-${action}-trigger`)?.click();
|
|
JS;
|
|
@endphp
|
|
<livewire:project.shared.configuration-checker :resource="$service" />
|
|
<x-slide-over @startservice.window="slideOverOpen = true" closeWithX fullScreen>
|
|
<x-slot:title>Service Startup</x-slot:title>
|
|
<x-slot:content>
|
|
<livewire:activity-monitor header="Logs" fullHeight />
|
|
</x-slot:content>
|
|
</x-slide-over>
|
|
<h1>{{ $title }}</h1>
|
|
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
|
<div class="navbar-main" x-data">
|
|
<div class="mb-4 w-full md:mb-0 md:hidden">
|
|
@if ($service->isDeployable)
|
|
<div id="service-mobile-actions" class="mt-2 mb-3 md:hidden">
|
|
<div class="mb-1 text-xs font-semibold uppercase tracking-wide text-neutral-500 dark:text-neutral-400">Actions</div>
|
|
<div class="flex flex-nowrap items-center gap-2 overflow-x-auto">
|
|
@if (str($service->status)->contains('running'))
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-restart-trigger')?.click()">
|
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
stroke-width="2">
|
|
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
<path d="M20 4v5h-5" />
|
|
</g>
|
|
</svg>
|
|
Restart
|
|
</button>
|
|
<x-forms.button isError class="shrink-0"
|
|
@click="document.getElementById('service-stop-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Stop
|
|
</x-forms.button>
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-pullAndRestart-trigger')?.click()">
|
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
stroke-width="2">
|
|
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
<path d="M20 4v5h-5" />
|
|
</g>
|
|
</svg>
|
|
Pull Latest Images & Restart
|
|
</button>
|
|
@elseif (str($service->status)->contains('degraded'))
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-restart-trigger')?.click()">
|
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
stroke-width="2">
|
|
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
<path d="M20 4v5h-5" />
|
|
</g>
|
|
</svg>
|
|
Restart
|
|
</button>
|
|
<x-forms.button isError class="shrink-0"
|
|
@click="document.getElementById('service-stop-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Stop
|
|
</x-forms.button>
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-forceDeploy-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M7 4v16l13 -8z" />
|
|
</svg>
|
|
Force Restart
|
|
</button>
|
|
@elseif (str($service->status)->contains('exited'))
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-start-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M7 4v16l13 -8z" />
|
|
</svg>
|
|
Deploy
|
|
</button>
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-forceDeploy-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M7 4v16l13 -8z" />
|
|
</svg>
|
|
Force Deploy
|
|
</button>
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-cleanup-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Force Cleanup Containers
|
|
</button>
|
|
@else
|
|
<x-forms.button isError class="shrink-0"
|
|
@click="document.getElementById('service-stop-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Stop
|
|
</x-forms.button>
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-start-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M7 4v16l13 -8z" />
|
|
</svg>
|
|
Deploy
|
|
</button>
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-forceDeploy-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M7 4v16l13 -8z" />
|
|
</svg>
|
|
Force Deploy
|
|
</button>
|
|
<button type="button" class="button shrink-0"
|
|
@click="document.getElementById('service-cleanup-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Force Cleanup Containers
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
<label id="service-mobile-section-label" for="service-mobile-section" class="mb-1 block text-xs font-semibold uppercase tracking-wide text-neutral-500 dark:text-neutral-400">Section</label>
|
|
<select id="service-mobile-section" class="select w-full" aria-label="Service menu"
|
|
data-current-value="{{ $activeServiceMobileValue }}"
|
|
x-data="{
|
|
init() {
|
|
this.syncFromLocation();
|
|
window.Livewire?.hook?.('morphed', ({ el }) => {
|
|
if (el.contains(this.$el)) {
|
|
queueMicrotask(() => this.syncFromLocation());
|
|
}
|
|
});
|
|
},
|
|
selected: $el.dataset.currentValue,
|
|
current: $el.dataset.currentValue,
|
|
syncFromLocation() {
|
|
const currentUrl = new URL(window.location.href);
|
|
const matchingOptions = Array.from(this.$el.options).filter((option) => {
|
|
if (!option.value.startsWith('navigate|') && !option.value.startsWith('location|')) {
|
|
return false;
|
|
}
|
|
|
|
const optionUrl = new URL(option.value.split('|').slice(2).join('|'), window.location.origin);
|
|
|
|
return optionUrl.pathname === currentUrl.pathname;
|
|
});
|
|
const selectedOption = matchingOptions.find((option) => {
|
|
return option.value.startsWith('navigate|configuration|') || option.value.startsWith('navigate|resource|');
|
|
}) || matchingOptions[0];
|
|
|
|
if (selectedOption) {
|
|
this.current = selectedOption.value;
|
|
this.selected = selectedOption.value;
|
|
}
|
|
},
|
|
resetToCurrent() {
|
|
this.selected = this.current;
|
|
},
|
|
}"
|
|
x-on:livewire:navigated.window="syncFromLocation()"
|
|
x-model="selected"
|
|
x-on:change="{{ $serviceMobileMenuChangeHandler }}">
|
|
<optgroup label="Service">
|
|
@foreach ($servicePageItems as $menuItem)
|
|
<option value="{{ ($menuItem['navigate'] ?? true) ? 'navigate' : 'location' }}|service|{{ route($menuItem['route'], $parameters) }}">
|
|
{{ $menuItem['label'] }}
|
|
</option>
|
|
@endforeach
|
|
</optgroup>
|
|
<optgroup label="Configuration">
|
|
@foreach ($serviceConfigurationItems as $menuItem)
|
|
<option value="navigate|configuration|{{ route($menuItem['route'], $parameters) }}">
|
|
{{ $menuItem['label'] }}
|
|
</option>
|
|
@endforeach
|
|
</optgroup>
|
|
@if (count($serviceResourceItems) > 0)
|
|
<optgroup label="Resource">
|
|
@foreach ($serviceResourceItems as $menuItem)
|
|
<option value="navigate|resource|{{ route($menuItem['route'], $menuItem['parameters'] ?? $parameters) }}">
|
|
{{ $menuItem['label'] }}
|
|
</option>
|
|
@endforeach
|
|
</optgroup>
|
|
@endif
|
|
<optgroup label="Links">
|
|
@if (filled($service->documentation()))
|
|
<option value="external:{{ $service->documentation() }}">Documentation</option>
|
|
@endif
|
|
@forelse ($serviceLinks as $link)
|
|
<option value="external:{{ $link }}">{{ $link }}</option>
|
|
@empty
|
|
@if (blank($service->documentation()))
|
|
<option disabled>No links available</option>
|
|
@endif
|
|
@endforelse
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
<nav
|
|
class="scrollbar hidden min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:flex md:w-auto md:overflow-visible">
|
|
<a class="shrink-0 {{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
|
href="{{ route('project.service.configuration', $parameters) }}">
|
|
<button>Configuration</button>
|
|
</a>
|
|
<a class="shrink-0 {{ request()->routeIs('project.service.logs') ? 'dark:text-white' : '' }}"
|
|
href="{{ route('project.service.logs', $parameters) }}">
|
|
<button>Logs</button>
|
|
</a>
|
|
@can('canAccessTerminal')
|
|
<a class="shrink-0 {{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
|
|
href="{{ route('project.service.command', $parameters) }}">
|
|
<button>Terminal</button>
|
|
</a>
|
|
@endcan
|
|
<div class="shrink-0">
|
|
<x-services.links :service="$service" />
|
|
</div>
|
|
</nav>
|
|
|
|
@if ($service->isDeployable)
|
|
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<x-services.advanced :service="$service" />
|
|
@if (str($service->status)->contains('running'))
|
|
|
|
<x-forms.button canGate="deploy" :canResource="$service" title="Restart" @click="document.getElementById('service-restart-trigger')?.click()">
|
|
|
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
stroke-width="2">
|
|
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
<path d="M20 4v5h-5" />
|
|
</g>
|
|
</svg>
|
|
Restart
|
|
</x-forms.button>
|
|
|
|
<x-forms.button canGate="stop" :canResource="$service" isError title="Stop" @click="document.getElementById('service-stop-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Stop
|
|
</x-forms.button>
|
|
@elseif (str($service->status)->contains('degraded'))
|
|
<x-forms.button canGate="deploy" :canResource="$service" title="Restart" @click="document.getElementById('service-restart-trigger')?.click()">
|
|
|
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
stroke-width="2">
|
|
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
<path d="M20 4v5h-5" />
|
|
</g>
|
|
</svg>
|
|
Restart
|
|
</x-forms.button>
|
|
|
|
<x-forms.button canGate="stop" :canResource="$service" isError title="Stop" @click="document.getElementById('service-stop-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Stop
|
|
</x-forms.button>
|
|
@elseif (str($service->status)->contains('exited'))
|
|
<x-forms.button canGate="deploy" :canResource="$service" @click="document.getElementById('service-start-trigger')?.click()" class="gap-2">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M7 4v16l13 -8z" />
|
|
</svg>
|
|
Deploy
|
|
</x-forms.button>
|
|
@else
|
|
|
|
<x-forms.button canGate="stop" :canResource="$service" isError title="Stop" @click="document.getElementById('service-stop-trigger')?.click()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
<path
|
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
</path>
|
|
</svg>
|
|
Stop
|
|
</x-forms.button>
|
|
<x-forms.button canGate="deploy" :canResource="$service" @click="document.getElementById('service-start-trigger')?.click()" class="gap-2">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M7 4v16l13 -8z" />
|
|
</svg>
|
|
Deploy
|
|
</x-forms.button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
|
<div class="text-error">
|
|
Unable to deploy. <a class="underline font-bold cursor-pointer" {{ wireNavigate() }}
|
|
href="{{ route('project.service.environment-variables', $parameters) }}">
|
|
Required environment variables missing.</a>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@if ($service->isDeployable)
|
|
<x-modal-confirmation title="Confirm Service Deployment?" buttonTitle="Deploy"
|
|
submitAction="startEvent" :dispatchAction="true" :actions="['This service will be deployed.']"
|
|
:confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Confirm">
|
|
<x-slot:trigger>
|
|
<button id="service-start-trigger" type="button" class="hidden">Deploy</button>
|
|
</x-slot:trigger>
|
|
</x-modal-confirmation>
|
|
<x-modal-confirmation title="Confirm Service Restart?" buttonTitle="Restart"
|
|
submitAction="restartEvent" :dispatchAction="true" :actions="['This service will be restarted.']"
|
|
:confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Confirm">
|
|
<x-slot:trigger>
|
|
<button id="service-restart-trigger" type="button" class="hidden">Restart</button>
|
|
</x-slot:trigger>
|
|
</x-modal-confirmation>
|
|
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop"
|
|
submitAction="stop" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
|
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
|
<x-slot:trigger>
|
|
<button id="service-stop-trigger" type="button" class="hidden">Stop</button>
|
|
</x-slot:trigger>
|
|
</x-modal-confirmation>
|
|
<x-modal-confirmation title="Confirm Service Force Deployment?" buttonTitle="Force Deploy"
|
|
submitAction="forceDeployEvent" :dispatchAction="true" :actions="['This service will be force deployed.']"
|
|
:confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Confirm">
|
|
<x-slot:trigger>
|
|
<button id="service-forceDeploy-trigger" type="button" class="hidden">Force Deploy</button>
|
|
</x-slot:trigger>
|
|
</x-modal-confirmation>
|
|
<x-modal-confirmation title="Confirm Pull Latest Images & Restart?" buttonTitle="Pull Latest Images & Restart"
|
|
submitAction="pullAndRestartEvent" :dispatchAction="true" :actions="['Latest images will be pulled and the service will be restarted.']"
|
|
:confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Confirm">
|
|
<x-slot:trigger>
|
|
<button id="service-pullAndRestart-trigger" type="button" class="hidden">Pull Latest Images & Restart</button>
|
|
</x-slot:trigger>
|
|
</x-modal-confirmation>
|
|
<x-modal-confirmation title="Confirm Force Cleanup Containers?" buttonTitle="Force Cleanup Containers"
|
|
submitAction="cleanupEvent" :dispatchAction="true" :actions="['Service containers will be force cleaned up.']"
|
|
:confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Confirm">
|
|
<x-slot:trigger>
|
|
<button id="service-cleanup-trigger" type="button" class="hidden">Force Cleanup Containers</button>
|
|
</x-slot:trigger>
|
|
</x-modal-confirmation>
|
|
@endif
|
|
|
|
@script
|
|
<script>
|
|
$wire.$on('stopEvent', () => {
|
|
$wire.$dispatch('info',
|
|
'Gracefully stopping service.<br/><br/>It could take a while depending on the service.');
|
|
$wire.$call('stop');
|
|
});
|
|
$wire.$on('startEvent', async () => {
|
|
const isDeploymentProgress = await $wire.$call('checkDeployments');
|
|
if (isDeploymentProgress) {
|
|
$wire.$dispatch('error',
|
|
'There is a deployment in progress.<br><br>You can force deploy in the "Advanced" section.'
|
|
);
|
|
return;
|
|
}
|
|
$wire.$call('start');
|
|
});
|
|
$wire.$on('forceDeployEvent', () => {
|
|
$wire.$call('forceDeploy');
|
|
});
|
|
$wire.$on('restartEvent', async () => {
|
|
const isDeploymentProgress = await $wire.$call('checkDeployments');
|
|
if (isDeploymentProgress) {
|
|
$wire.$dispatch('error',
|
|
'There is a deployment in progress.<br><br>You can force deploy in the "Advanced" section.'
|
|
);
|
|
return;
|
|
}
|
|
$wire.$dispatch('info',
|
|
'Gracefully stopping service.<br/><br/>It could take a while depending on the service.');
|
|
$wire.$call('restart');
|
|
});
|
|
$wire.$on('pullAndRestartEvent', () => {
|
|
$wire.$dispatch('info', 'Pulling new images and restarting service.');
|
|
$wire.$call('pullAndRestartEvent');
|
|
});
|
|
$wire.$on('cleanupEvent', () => {
|
|
$wire.$call('stop', true);
|
|
});
|
|
$wire.on('imagePulled', () => {
|
|
window.dispatchEvent(new CustomEvent('startservice'));
|
|
$wire.$dispatch('info', 'Restarting service.');
|
|
});
|
|
</script>
|
|
@endscript
|
|
</div>
|