267 lines
13 KiB
PHP
267 lines
13 KiB
PHP
@php
|
|
$iconMappings = [
|
|
'ubuntu' => 'ubuntu',
|
|
'debian' => 'debian',
|
|
'centos' => 'centos',
|
|
'alpine' => 'alpine',
|
|
'freebsd' => 'freebsd',
|
|
'openbsd' => 'openbsd',
|
|
'fedora' => 'fedora',
|
|
'arch' => 'archlinux',
|
|
'opensuse' => 'opensuse',
|
|
'suse' => 'opensuse',
|
|
'gentoo' => 'gentoo',
|
|
'oracle' => null,
|
|
'alma' => 'almalinux',
|
|
'rocky' => 'rocky-linux',
|
|
'clear' => null,
|
|
'rhel' => 'redhat',
|
|
'red hat' => 'redhat',
|
|
'open cloud' => null,
|
|
'nethserver' => null,
|
|
'alt linux' => null,
|
|
'amazon' => null,
|
|
'windows' => null,
|
|
];
|
|
|
|
$iconOverrides = [
|
|
'proxmox' => asset('assets/os/proxmox.png'),
|
|
'amazon linux' => asset('assets/os/amazonlinux.png'),
|
|
'amazonlinux' => asset('assets/os/amazonlinux.png'),
|
|
'windows' => asset('assets/os/WIN.png'),
|
|
'oracle' => asset('assets/os/tux.svg'),
|
|
'clear' => asset('assets/os/tux.svg'),
|
|
'nethserver' => asset('assets/os/tux.svg'),
|
|
'open cloud' => asset('assets/os/tux.svg'),
|
|
'alt linux' => asset('assets/os/tux.svg'),
|
|
];
|
|
|
|
$badgeLabels = [
|
|
'ubuntu 24.04 (recommended)' => 'Recommended',
|
|
];
|
|
|
|
$defaultIcon = asset('assets/os/tux.svg');
|
|
|
|
$categoryDefinitions = [
|
|
'Ubuntu' => ['ubuntu'],
|
|
'Debian' => ['debian'],
|
|
'CentOS' => ['centos'],
|
|
'Enterprise / RHEL Family' => ['rhel', 'alma', 'rocky', 'oracle'],
|
|
'Community & Rolling' => ['fedora', 'arch', 'opensuse', 'gentoo', 'clear', 'alt linux'],
|
|
'Cloud & Appliances' => ['amazon', 'open cloud', 'nethserver'],
|
|
'Lightweight' => ['alpine'],
|
|
'BSD Family' => ['freebsd', 'netbsd', 'openbsd', 'dragonfly'],
|
|
'Proxmox Hypervisor' => ['proxmox'],
|
|
'Windows Server' => ['windows'],
|
|
'Other OS' => [],
|
|
];
|
|
|
|
$sortedOs = $osList->sortBy(fn ($os) => strtolower($os->name), SORT_NATURAL);
|
|
|
|
$groupedOs = [];
|
|
foreach ($categoryDefinitions as $label => $keywords) {
|
|
$groupedOs[$label] = [];
|
|
}
|
|
|
|
foreach ($sortedOs as $template) {
|
|
$normalized = \Illuminate\Support\Str::lower($template->name);
|
|
$matchedLabel = 'Other OS';
|
|
foreach ($categoryDefinitions as $label => $keywords) {
|
|
foreach ($keywords as $keyword) {
|
|
if ($keyword && \Illuminate\Support\Str::contains($normalized, $keyword)) {
|
|
$matchedLabel = $label;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
$groupedOs[$matchedLabel][] = $template;
|
|
}
|
|
|
|
$groupedOs = collect($groupedOs)->filter(fn ($items) => count($items) > 0);
|
|
@endphp
|
|
|
|
<div x-data="{
|
|
open: false,
|
|
activeSection: null,
|
|
pendingSection: null,
|
|
transitionDelay: 220,
|
|
toggleSection(key) {
|
|
if (this.activeSection === key) {
|
|
this.activeSection = null;
|
|
return;
|
|
}
|
|
|
|
if (this.activeSection) {
|
|
this.pendingSection = key;
|
|
this.activeSection = null;
|
|
this.$nextTick(() => {
|
|
setTimeout(() => {
|
|
this.activeSection = this.pendingSection;
|
|
this.pendingSection = null;
|
|
}, this.transitionDelay);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
this.activeSection = key;
|
|
}
|
|
}">
|
|
@if($isProtected)
|
|
<x-button.danger
|
|
@click="open = true"
|
|
disabled
|
|
title="Reinstall is disabled: This server is protected in Proxmox">
|
|
Reinstall
|
|
</x-button.danger>
|
|
@else
|
|
<x-button.danger @click="open = true">
|
|
Reinstall
|
|
</x-button.danger>
|
|
@endif
|
|
|
|
<template x-if="open">
|
|
<div class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 overflow-y-auto">
|
|
<div class="bg-white rounded-2xl p-6 w-full max-w-4xl shadow-2xl border border-neutral/30 my-8">
|
|
<div class="flex flex-col gap-2 mb-6">
|
|
<h2 class="text-2xl font-semibold text-base">Reinstall Server</h2>
|
|
@if($isProtected)
|
|
<div class="bg-warning/10 border border-warning/30 rounded-lg p-3 mb-2">
|
|
<p class="text-sm text-warning font-semibold flex items-center gap-2">
|
|
<x-ri-error-warning-line class="size-4" />
|
|
This server has the ability to reinstall disabled. Please contact support to enable reinstall.
|
|
</p>
|
|
</div>
|
|
@endif
|
|
<p class="text-sm text-base/70">Choose a new operating system. The selected template will be installed from scratch.</p>
|
|
</div>
|
|
|
|
<div class="space-y-3 max-h-[460px] overflow-y-auto pr-1" @if($isProtected) style="pointer-events: none; opacity: 0.6;" @endif>
|
|
@foreach($groupedOs as $categoryLabel => $templates)
|
|
@php
|
|
$categoryKey = \Illuminate\Support\Str::slug($categoryLabel);
|
|
@endphp
|
|
<div class="border border-neutral/40 rounded-xl overflow-hidden" wire:key="os-category-{{ $categoryKey }}">
|
|
<button type="button" class="w-full flex items-center justify-between px-4 py-3 bg-background-secondary/60 hover:bg-background-secondary transition"
|
|
@click="toggleSection('{{ $categoryKey }}')">
|
|
<div class="flex items-center gap-3">
|
|
<x-ri-stack-line class="size-4 text-primary" />
|
|
<span class="font-semibold text-base">{{ $categoryLabel }}</span>
|
|
<span class="text-xs text-base/60">({{ count($templates) }})</span>
|
|
</div>
|
|
<x-ri-arrow-down-s-line class="size-5 transition-transform duration-200"
|
|
x-bind:class="{ 'rotate-180': activeSection === '{{ $categoryKey }}' }"></x-ri-arrow-down-s-line>
|
|
</button>
|
|
|
|
<div class="px-4 pb-4 pt-2 bg-white"
|
|
x-show="activeSection === '{{ $categoryKey }}'"
|
|
x-collapse.duration.200ms>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
@foreach($templates as $template)
|
|
@php
|
|
$normalized = \Illuminate\Support\Str::lower($template->name);
|
|
$iconCode = null;
|
|
foreach ($iconMappings as $keyword => $code) {
|
|
if (\Illuminate\Support\Str::contains($normalized, $keyword)) {
|
|
$iconCode = $code;
|
|
break;
|
|
}
|
|
}
|
|
$iconPath = $defaultIcon;
|
|
$iconOverridePath = null;
|
|
foreach ($iconOverrides as $keyword => $path) {
|
|
if (\Illuminate\Support\Str::contains($normalized, $keyword)) {
|
|
$iconOverridePath = $path;
|
|
break;
|
|
}
|
|
}
|
|
if ($iconOverridePath) {
|
|
$iconPath = $iconOverridePath;
|
|
} elseif ($iconCode) {
|
|
$iconPath = asset('assets/os/' . $iconCode . '.svg');
|
|
}
|
|
$badge = $badgeLabels[$normalized] ?? null;
|
|
$isSelected = (string) $template->id === (string) ($os ?? '');
|
|
@endphp
|
|
<label wire:key="os-card-{{ $template->id }}"
|
|
class="relative flex items-center gap-4 rounded-xl border p-4 cursor-pointer transition-all duration-150
|
|
@if($isSelected) border-primary bg-primary/5 ring-2 ring-primary/40 shadow-lg @else border-neutral/60 hover:border-primary/60 @endif">
|
|
<input type="radio" class="sr-only" wire:model.live="os" value="{{ $template->id }}">
|
|
<div class="shrink-0">
|
|
<img src="{{ $iconPath ?? $defaultIcon }}" alt="{{ $template->name }} logo"
|
|
class="size-12 rounded-lg border border-neutral/30 bg-white object-contain p-1">
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold text-base">{{ $template->name }}</span>
|
|
<span class="text-xs text-base/60">Template ID: {{ $template->id }}</span>
|
|
@if($badge)
|
|
<span class="mt-1 inline-flex items-center gap-1 text-[11px] font-semibold uppercase tracking-wide text-secondary">
|
|
<x-ri-star-smile-fill class="size-3" /> {{ $badge }}
|
|
</span>
|
|
@endif
|
|
</div>
|
|
@if($isSelected)
|
|
<x-ri-check-line class="absolute top-3 right-3 size-5 text-primary" />
|
|
@endif
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<div class="mt-4 pt-4 border-t border-neutral/30">
|
|
@if($isProtected)
|
|
<x-form.checkbox
|
|
label="I confirm that I want to proceed with reinstalling this server, which will permanently erase all existing data, configurations, and applications. This action is irreversible and cannot be undone."
|
|
wire:model="confirmReinstall"
|
|
name="confirmReinstall"
|
|
required
|
|
disabled
|
|
/>
|
|
@else
|
|
<x-form.checkbox
|
|
label="I confirm that I want to proceed with reinstalling this server, which will permanently erase all existing data, configurations, and applications. This action is irreversible and cannot be undone."
|
|
wire:model="confirmReinstall"
|
|
name="confirmReinstall"
|
|
required
|
|
/>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="flex flex-row gap-4 mt-6">
|
|
<x-button.secondary @click="open = false">Cancel</x-button.secondary>
|
|
|
|
@if($isProtected)
|
|
<x-button.danger
|
|
wire:click="reinstall"
|
|
wire:loading.attr="disabled"
|
|
wire:loading.class="opacity-50"
|
|
wire:loading.attr="aria-busy"
|
|
wire:target="reinstall"
|
|
disabled
|
|
title="Reinstall is disabled: This server is protected in Proxmox"
|
|
>
|
|
<x-loading target="reinstall" />
|
|
<div wire:loading.remove wire:target="reinstall">Reinstall</div>
|
|
</x-button.danger>
|
|
@else
|
|
<x-button.danger
|
|
wire:click="reinstall"
|
|
wire:loading.attr="disabled"
|
|
wire:loading.class="opacity-50"
|
|
wire:loading.attr="aria-busy"
|
|
wire:target="reinstall"
|
|
:disabled="!$os || !$confirmReinstall"
|
|
:title="!$os ? 'Please select an operating system' : (!$confirmReinstall ? 'Please confirm the reinstallation' : 'Reinstall server')"
|
|
>
|
|
<x-loading target="reinstall" />
|
|
<div wire:loading.remove wire:target="reinstall">Reinstall</div>
|
|
</x-button.danger>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div> |