Private
Public Access
1
0

refactor: update components to shadcn-inspired styling and improve layout consistency

This commit is contained in:
2025-12-17 11:53:57 -05:00
parent eb015f3421
commit 71ea61a960
7 changed files with 165 additions and 169 deletions

View File

@@ -1,5 +1,5 @@
{{-- {{--
Action Toolbar Component - TailServer Theme Action Toolbar Component - shadcn-inspired
For VM control buttons (Start, Stop, Restart, etc.) For VM control buttons (Start, Stop, Restart, etc.)
Usage: Usage:
@@ -17,7 +17,7 @@
]) ])
<div {{ $attributes->class([ <div {{ $attributes->class([
'flex flex-wrap gap-2.5', 'flex flex-wrap gap-2',
'justify-center' => $centered, 'justify-center' => $centered,
'justify-start' => !$centered, 'justify-start' => !$centered,
]) }}> ]) }}>

View File

@@ -1,5 +1,5 @@
{{-- {{--
Button Component - TailServer Theme (shadcn-inspired) Button Component - shadcn-inspired
Usage: Usage:
<x-proxmox::ui.button>Click me</x-proxmox::ui.button> <x-proxmox::ui.button>Click me</x-proxmox::ui.button>

View File

@@ -1,5 +1,5 @@
{{-- {{--
Card Component - TailServer Theme (shadcn-inspired) Card Component - shadcn-inspired
Usage: Usage:
<x-proxmox::ui.card>Content</x-proxmox::ui.card> <x-proxmox::ui.card>Content</x-proxmox::ui.card>
@@ -24,8 +24,8 @@
<div <div
{{ $attributes->class([ {{ $attributes->class([
'flex flex-col gap-6 rounded-xl border border-neutral/15 bg-card text-card-foreground shadow-sm', 'rounded-xl border bg-card shadow-sm',
'hover:shadow-md hover:border-neutral/25 transition-shadow duration-150' => $hover || $clickable, 'hover:bg-accent/50 transition-colors duration-150' => $hover || $clickable,
'cursor-pointer' => $clickable, 'cursor-pointer' => $clickable,
]) }} ]) }}
@if($collapsible) @if($collapsible)
@@ -33,7 +33,7 @@
@endif @endif
> >
@if($title || $actions) @if($title || $actions)
<div class="@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6"> <div class="flex items-center justify-between gap-4 p-6 pb-0">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@if($icon) @if($icon)
<div class="flex items-center justify-center size-9 rounded-lg bg-primary/10"> <div class="flex items-center justify-center size-9 rounded-lg bg-primary/10">
@@ -42,7 +42,7 @@
@endif @endif
<div class="space-y-1"> <div class="space-y-1">
@if($title) @if($title)
<h3 class="leading-none font-semibold">{{ $title }}</h3> <h3 class="font-semibold leading-none tracking-tight">{{ $title }}</h3>
@endif @endif
@if($subtitle) @if($subtitle)
<p class="text-sm text-muted-foreground">{{ $subtitle }}</p> <p class="text-sm text-muted-foreground">{{ $subtitle }}</p>
@@ -50,7 +50,7 @@
</div> </div>
</div> </div>
@if($actions || $collapsible) @if($actions || $collapsible)
<div class="col-start-2 row-span-2 row-start-1 self-start justify-self-end flex items-center gap-2"> <div class="flex items-center gap-2">
@if($actions) @if($actions)
{{ $actions }} {{ $actions }}
@endif @endif
@@ -58,7 +58,7 @@
<button <button
type="button" type="button"
@click="isCollapsed = !isCollapsed" @click="isCollapsed = !isCollapsed"
class="p-1.5 rounded-md hover:bg-neutral/10 transition-colors" class="p-1.5 rounded-md hover:bg-muted transition-colors duration-150"
> >
<x-ri-arrow-down-s-line <x-ri-arrow-down-s-line
class="size-4 text-muted-foreground transition-transform duration-150" class="size-4 text-muted-foreground transition-transform duration-150"
@@ -73,8 +73,8 @@
<div <div
@class([ @class([
'px-6', 'p-6',
'py-6' => $padding && !$title, 'pt-4' => $title,
]) ])
@if($collapsible) @if($collapsible)
x-show="!isCollapsed" x-show="!isCollapsed"
@@ -90,7 +90,7 @@
</div> </div>
@if($footer) @if($footer)
<div class="flex items-center px-6 [.border-t]:pt-6 {{ $footerClass }}"> <div class="flex items-center p-6 pt-0 {{ $footerClass }}">
{{ $footer }} {{ $footer }}
</div> </div>
@endif @endif

View File

@@ -1,5 +1,5 @@
{{-- {{--
Editable Field Component - TailServer Theme Editable Field Component - shadcn-inspired
A field with display value and edit button that opens a modal A field with display value and edit button that opens a modal
Usage: Usage:
@@ -32,21 +32,19 @@
]) ])
<div class="space-y-2" x-data="{ showEditModal: @entangle($attributes->wire('model')->value() ? 'isEditing' . ucfirst($label) : 'editing') }"> <div class="space-y-2" x-data="{ showEditModal: @entangle($attributes->wire('model')->value() ? 'isEditing' . ucfirst($label) : 'editing') }">
<label class="text-sm font-semibold text-base/80">{{ $label }}</label> <label class="text-sm font-medium">{{ $label }}</label>
<div class="space-y-2"> <div class="flex items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2 text-sm">
<div class="flex items-center gap-2 border border-neutral/20 rounded-lg bg-neutral/5 px-3 py-2 text-sm text-base"> <span class="truncate flex-1">{{ $value ?? 'Not set' }}</span>
<span class="truncate flex-1">{{ $value ?? 'Not set' }}</span> @if($editClick)
@if($editClick) <button
<button wire:click="{{ $editClick }}"
wire:click="{{ $editClick }}" type="button"
type="button" class="text-muted-foreground hover:text-foreground transition-colors duration-150"
class="text-base/50 hover:text-base/70 transition-colors" aria-label="Edit {{ $label }}"
aria-label="Edit {{ $label }}" >
> <x-ri-edit-line class="size-4" />
<x-ri-edit-line class="size-4" /> </button>
</button> @endif
@endif
</div>
</div> </div>
@if($editing !== false) @if($editing !== false)
@@ -54,15 +52,15 @@
<div <div
x-cloak x-cloak
x-show="showEditModal" x-show="showEditModal"
x-transition:enter="transition ease-out duration-200" x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" x-transition:leave-end="opacity-0"
@click="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif" @click="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
@keydown.escape.window="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif" @keydown.escape.window="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm" class="fixed inset-0 z-50 bg-black/50"
style="display: none;" style="display: none;"
></div> ></div>
@@ -70,36 +68,36 @@
<div <div
x-cloak x-cloak
x-show="showEditModal" x-show="showEditModal"
x-transition:enter="transition ease-out duration-200" x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100" x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95" x-transition:leave-end="opacity-0 scale-95"
@click.away="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif" @click.away="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
class="fixed inset-0 z-50 flex items-center justify-center p-4" class="fixed inset-0 z-50 flex items-center justify-center p-4"
style="display: none;" style="display: none;"
> >
<div class="relative bg-background border border-neutral/20 rounded-xl shadow-2xl w-full max-w-xl p-5 space-y-4"> <div class="relative rounded-xl border bg-card shadow-lg w-full max-w-xl p-6 space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">Edit {{ $label }}</h4> <h4 class="text-base font-semibold">Edit {{ $label }}</h4>
<button <button
type="button" type="button"
class="p-1.5 rounded-lg hover:bg-neutral/10 transition-colors" class="p-1.5 rounded-lg hover:bg-muted transition-colors duration-150"
@click="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif" @click="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
aria-label="Close" aria-label="Close"
> >
<x-ri-close-line class="size-4 text-base/60" /> <x-ri-close-line class="size-4 text-muted-foreground" />
</button> </button>
</div> </div>
<div class="space-y-3"> <div class="space-y-3">
<div> <div>
<label class="text-xs font-semibold text-base/70">{{ $label }}</label> <label class="text-sm font-medium text-muted-foreground">{{ $label }}</label>
<input <input
type="text" type="text"
{{ $attributes->whereStartsWith('wire:model') }} {{ $attributes->whereStartsWith('wire:model') }}
class="w-full mt-1 px-3 py-2 bg-neutral/5 border border-neutral/20 rounded-lg text-sm text-base placeholder:text-base/40 focus:outline-none focus:ring-2 focus:ring-primary/50" class="w-full mt-1.5 px-3 py-2 rounded-lg border bg-background text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
placeholder="{{ $placeholder ?? 'Enter ' . strtolower($label) }}" placeholder="{{ $placeholder ?? 'Enter ' . strtolower($label) }}"
/> />
</div> </div>
@@ -109,7 +107,7 @@
@endif @endif
@if(isset($hint)) @if(isset($hint))
<div class="text-xs text-base/60">{{ $hint }}</div> <div class="text-xs text-muted-foreground">{{ $hint }}</div>
@endif @endif
</div> </div>
@@ -118,7 +116,7 @@
<button <button
type="button" type="button"
@click="showEditModal = false; $wire.{{ $cancelClick }}()" @click="showEditModal = false; $wire.{{ $cancelClick }}()"
class="px-4 py-2 text-sm font-medium text-base/70 hover:text-base bg-neutral/5 hover:bg-neutral/10 rounded-lg transition-colors" class="px-4 py-2 text-sm font-medium rounded-lg border bg-background hover:bg-muted transition-colors duration-150"
> >
Cancel Cancel
</button> </button>
@@ -127,7 +125,7 @@
<button <button
type="button" type="button"
wire:click="{{ $saveClick }}" wire:click="{{ $saveClick }}"
class="px-4 py-2 text-sm font-medium text-white bg-primary hover:bg-primary/90 rounded-lg transition-colors" class="px-4 py-2 text-sm font-medium text-primary-foreground bg-primary hover:bg-primary/90 rounded-lg transition-colors duration-150"
> >
Save Save
</button> </button>
@@ -136,7 +134,7 @@
<button <button
type="button" type="button"
wire:click="{{ $applyClick }}" wire:click="{{ $applyClick }}"
class="px-4 py-2 text-sm font-medium text-white bg-primary hover:bg-primary/90 rounded-lg transition-colors" class="px-4 py-2 text-sm font-medium text-primary-foreground bg-primary hover:bg-primary/90 rounded-lg transition-colors duration-150"
> >
{{ $applyLabel }} {{ $applyLabel }}
</button> </button>

View File

@@ -1,5 +1,5 @@
{{-- {{--
Icon Picker Component - TailServer Theme Icon Picker Component - shadcn-inspired
Modal-based icon picker with search and categories Modal-based icon picker with search and categories
Usage: Usage:
@@ -40,15 +40,15 @@
<button <button
type="button" type="button"
@click="open = true" @click="open = true"
class="flex items-center gap-2 px-3 py-2 bg-neutral/5 hover:bg-neutral/10 rounded-lg border border-neutral/20 transition-colors" class="flex items-center gap-2 px-3 py-2 rounded-lg border bg-background hover:bg-muted transition-colors duration-150"
> >
<template x-if="selected"> <template x-if="selected">
<x-dynamic-component x-bind:component="selected" class="size-5 text-primary" /> <x-dynamic-component x-bind:component="selected" class="size-5 text-primary" />
</template> </template>
<template x-if="!selected"> <template x-if="!selected">
<x-ri-image-add-line class="size-5 text-base/40" /> <x-ri-image-add-line class="size-5 text-muted-foreground" />
</template> </template>
<span class="text-sm text-base">Select Icon</span> <span class="text-sm">Select Icon</span>
</button> </button>
<!-- Modal --> <!-- Modal -->
@@ -62,10 +62,10 @@
<!-- Backdrop --> <!-- Backdrop -->
<div <div
x-show="open" x-show="open"
x-transition:enter="ease-out duration-200" x-transition:enter="ease-out duration-150"
x-transition:enter-start="opacity-0" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-150" x-transition:leave="ease-in duration-100"
x-transition:leave-start="opacity-100" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" x-transition:leave-end="opacity-0"
@click="open = false" @click="open = false"
@@ -75,50 +75,50 @@
<!-- Modal Content --> <!-- Modal Content -->
<div <div
x-show="open" x-show="open"
x-transition:enter="ease-out duration-200" x-transition:enter="ease-out duration-150"
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100" x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="ease-in duration-150" x-transition:leave="ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95" x-transition:leave-end="opacity-0 scale-95"
class="relative bg-background rounded-xl shadow-2xl w-full max-w-2xl max-h-[80vh] overflow-hidden" class="relative rounded-xl border bg-card shadow-lg w-full max-w-2xl max-h-[80vh] overflow-hidden"
> >
<!-- Header --> <!-- Header -->
<div class="p-4 border-b border-neutral/10"> <div class="p-4 border-b">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold text-base">Select Icon</h3> <h3 class="text-lg font-semibold">Select Icon</h3>
<button @click="open = false" class="p-1 rounded hover:bg-neutral/10 text-base/40 hover:text-base"> <button @click="open = false" class="p-1.5 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors duration-150">
<x-ri-close-line class="size-5" /> <x-ri-close-line class="size-5" />
</button> </button>
</div> </div>
<!-- Search --> <!-- Search -->
<div class="relative"> <div class="relative">
<x-ri-search-line class="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-base/40" /> <x-ri-search-line class="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<input <input
type="text" type="text"
x-model="search" x-model="search"
placeholder="Search icons..." placeholder="Search icons..."
class="w-full pl-10 pr-4 py-2 bg-neutral/5 border border-neutral/20 rounded-lg text-sm text-base placeholder:text-base/40 focus:outline-none focus:ring-2 focus:ring-primary/50" class="w-full pl-10 pr-4 py-2 rounded-lg border bg-background text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
/> />
</div> </div>
</div> </div>
<!-- Categories --> <!-- Categories -->
@if(count($categories) > 0) @if(count($categories) > 0)
<div class="flex gap-2 p-4 border-b border-neutral/10 overflow-x-auto"> <div class="flex gap-2 p-4 border-b overflow-x-auto">
<button <button
@click="activeCategory = 'all'" @click="activeCategory = 'all'"
:class="activeCategory === 'all' ? 'bg-primary text-white' : 'bg-neutral/5 text-base hover:bg-neutral/10'" :class="activeCategory === 'all' ? 'bg-primary text-primary-foreground' : 'bg-muted hover:bg-muted/80'"
class="px-3 py-1 rounded-full text-xs font-medium transition-colors whitespace-nowrap" class="px-3 py-1 rounded-full text-xs font-medium transition-colors duration-150 whitespace-nowrap"
> >
All All
</button> </button>
@foreach($categories as $category) @foreach($categories as $category)
<button <button
@click="activeCategory = '{{ $category['id'] }}'" @click="activeCategory = '{{ $category['id'] }}'"
:class="activeCategory === '{{ $category['id'] }}' ? 'bg-primary text-white' : 'bg-neutral/5 text-base hover:bg-neutral/10'" :class="activeCategory === '{{ $category['id'] }}' ? 'bg-primary text-primary-foreground' : 'bg-muted hover:bg-muted/80'"
class="px-3 py-1 rounded-full text-xs font-medium transition-colors whitespace-nowrap" class="px-3 py-1 rounded-full text-xs font-medium transition-colors duration-150 whitespace-nowrap"
> >
{{ $category['name'] }} {{ $category['name'] }}
</button> </button>
@@ -132,33 +132,33 @@
<template x-for="icon in filteredIcons" :key="icon.name"> <template x-for="icon in filteredIcons" :key="icon.name">
<button <button
@click="selected = icon.component; open = false;" @click="selected = icon.component; open = false;"
:class="selected === icon.component ? 'ring-2 ring-primary bg-primary/10' : 'hover:bg-neutral/10'" :class="selected === icon.component ? 'ring-2 ring-primary bg-primary/10' : 'hover:bg-muted'"
class="flex items-center justify-center p-3 rounded-lg transition-colors" class="flex items-center justify-center p-3 rounded-lg transition-colors duration-150"
:title="icon.name" :title="icon.name"
> >
<x-dynamic-component x-bind:component="icon.component" class="size-6 text-base" /> <x-dynamic-component x-bind:component="icon.component" class="size-6" />
</button> </button>
</template> </template>
</div> </div>
<!-- Empty State --> <!-- Empty State -->
<div x-show="filteredIcons.length === 0" class="py-8 text-center"> <div x-show="filteredIcons.length === 0" class="py-8 text-center">
<x-ri-search-line class="size-12 mx-auto text-base/20 mb-3" /> <x-ri-search-line class="size-12 mx-auto text-muted-foreground/30 mb-3" />
<p class="text-sm text-base/50">No icons found matching your search.</p> <p class="text-sm text-muted-foreground">No icons found matching your search.</p>
</div> </div>
</div> </div>
<!-- Footer --> <!-- Footer -->
<div class="p-4 border-t border-neutral/10 flex items-center justify-between"> <div class="p-4 border-t flex items-center justify-between">
<button <button
@click="selected = null" @click="selected = null"
class="px-3 py-1.5 text-sm text-base/60 hover:text-base" class="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors duration-150"
> >
Clear Selection Clear Selection
</button> </button>
<button <button
@click="open = false" @click="open = false"
class="px-4 py-2 bg-primary text-white rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors" class="px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors duration-150"
> >
Done Done
</button> </button>

View File

@@ -3,7 +3,15 @@
=================================== ===================================
This file documents all available UI components for the Proxmox server views. This file documents all available UI components for the Proxmox server views.
All components follow the TailServer theme using CSS variables for consistent styling. All components follow shadcn-inspired styling with TailServer theme CSS variables.
Design Patterns:
- Card: rounded-xl border bg-card shadow-sm
- Typography: text-2xl font-bold (values), text-sm font-medium (labels), text-xs text-muted-foreground (descriptions)
- Layout: flex/grid with gap-4/gap-6 spacing
- Transitions: duration-150 for all interactions (speed-optimized)
- Borders: border (uses CSS variable), rounded-lg/rounded-xl
- Backgrounds: bg-card, bg-muted, bg-muted/50 (no gradient overlays)
CSS Variables Used: CSS Variables Used:
- --color-background: Page background color - --color-background: Page background color

View File

@@ -1,5 +1,5 @@
{{-- {{--
Network Address Card Component - TailServer Theme Network Address Card Component - shadcn-inspired
For displaying IP address information with gateway, netmask, and tools For displaying IP address information with gateway, netmask, and tools
Usage: Usage:
@@ -24,116 +24,113 @@
'fromGuestAgent' => false, 'fromGuestAgent' => false,
]) ])
<x-proxmox::ui.card class="mb-4"> <div class="rounded-xl border bg-card p-6 shadow-sm mb-4">
{{-- Header --}} {{-- Header --}}
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-base">{{ strtoupper($type) }} Address</h3> <h3 class="text-base font-semibold">{{ strtoupper($type) }} Address</h3>
<x-proxmox::ui.badge variant="default"> <x-proxmox::ui.badge variant="default">
{{ $type === 'ipv6' ? 'IPv6' : 'IPv4' }} {{ $type === 'ipv6' ? 'IPv6' : 'IPv4' }}
</x-proxmox::ui.badge> </x-proxmox::ui.badge>
</div> </div>
{{-- Address Grid --}} {{-- Address Grid --}}
<div class="grid md:grid-cols-3 grid-cols-1 gap-4 network-tools-grid"> <div class="grid md:grid-cols-3 gap-4">
{{-- IP Address --}} {{-- IP Address --}}
<div class="flex flex-col gap-2 min-w-0"> <div class="space-y-1.5">
<h4 class="text-sm font-semibold text-base/70">IP Address</h4> <span class="text-sm font-medium text-muted-foreground">IP Address</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-base font-semibold text-base/80">{{ $ip }}</span> <span class="font-semibold font-mono">{{ $ip }}</span>
<x-proxmox::ui.copy-button :value="$ip" /> <x-proxmox::ui.copy-button :value="$ip" />
</div> </div>
@if($cidr) @if($cidr)
<p class="text-xs text-base/50 mt-1">CIDR: {{ $ip }}{{ $cidr }}</p> <p class="text-xs text-muted-foreground">CIDR: {{ $ip }}{{ $cidr }}</p>
@endif @endif
</div> </div>
{{-- Gateway --}} {{-- Gateway --}}
<div class="flex flex-col gap-2 min-w-0"> <div class="space-y-1.5">
<h4 class="text-sm font-semibold text-base/70">Gateway</h4> <span class="text-sm font-medium text-muted-foreground">Gateway</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-base font-semibold text-base/80">{{ $gateway ?? 'N/A' }}</span> <span class="font-semibold font-mono">{{ $gateway ?? 'N/A' }}</span>
@if($gateway) @if($gateway)
<x-proxmox::ui.copy-button :value="$gateway" /> <x-proxmox::ui.copy-button :value="$gateway" />
@endif @endif
</div> </div>
<p class="text-xs text-base/50 mt-1"> <p class="text-xs text-muted-foreground">
{{ $fromGuestAgent && !$gateway ? 'Not configured in database' : 'Default route for this network' }} {{ $fromGuestAgent && !$gateway ? 'Not configured in database' : 'Default route' }}
</p> </p>
</div> </div>
{{-- Netmask --}} {{-- Netmask --}}
<div class="flex flex-col gap-2 min-w-0"> <div class="space-y-1.5">
<h4 class="text-sm font-semibold text-base/70">Subnet Mask</h4> <span class="text-sm font-medium text-muted-foreground">Subnet Mask</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-base font-semibold text-base/80">{{ $netmask ?? 'N/A' }}</span> <span class="font-semibold font-mono">{{ $netmask ?? 'N/A' }}</span>
@if($netmask) @if($netmask)
<x-proxmox::ui.copy-button :value="$netmask" /> <x-proxmox::ui.copy-button :value="$netmask" />
@endif @endif
</div> </div>
<p class="text-xs text-base/50 mt-1 break-words"> <p class="text-xs text-muted-foreground">
{{ $fromGuestAgent && !$netmask ? 'Not configured in database' : 'Network address range mask' }} {{ $fromGuestAgent && !$netmask ? 'Not configured' : 'Network range' }}
</p> </p>
</div> </div>
{{-- Network Tools (for public IPs) --}}
@if($isPublic)
<div class="network-tools-column flex flex-col gap-2 relative min-w-0 @if($guestInterface) network-tools-with-details @endif">
<div class="flex-1"></div>
<div class="flex gap-2 justify-end mt-auto">
@if($isIpv6)
<x-proxmox::ui.button
variant="primary"
size="sm"
onclick="window.open('https://ping6.ping.pe/{{ $ip }}', '_blank')"
>
Ping
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="primary"
size="sm"
onclick="window.open('https://mtr6.ping.pe/{{ $ip }}', '_blank')"
>
MTR
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="primary"
size="sm"
onclick="window.open('https://bgp.ping.pe/{{ $ip }}', '_blank')"
>
BGP
</x-proxmox::ui.button>
@else
<x-proxmox::ui.button
variant="primary"
size="sm"
onclick="window.open('https://ping.pe/{{ $ip }}', '_blank')"
>
Ping
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="primary"
size="sm"
onclick="window.open('https://mtr.ping.pe/{{ $ip }}', '_blank')"
>
MTR
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="primary"
size="sm"
onclick="window.open('https://bgp.ping.pe/{{ $ip }}', '_blank')"
>
BGP
</x-proxmox::ui.button>
@endif
</div>
</div>
@endif
</div> </div>
{{-- Network Tools (for public IPs) --}}
@if($isPublic)
<div class="flex gap-2 justify-end mt-4 pt-4 border-t">
@if($isIpv6)
<x-proxmox::ui.button
variant="outline"
size="sm"
onclick="window.open('https://ping6.ping.pe/{{ $ip }}', '_blank')"
>
Ping
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="outline"
size="sm"
onclick="window.open('https://mtr6.ping.pe/{{ $ip }}', '_blank')"
>
MTR
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="outline"
size="sm"
onclick="window.open('https://bgp.ping.pe/{{ $ip }}', '_blank')"
>
BGP
</x-proxmox::ui.button>
@else
<x-proxmox::ui.button
variant="outline"
size="sm"
onclick="window.open('https://ping.pe/{{ $ip }}', '_blank')"
>
Ping
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="outline"
size="sm"
onclick="window.open('https://mtr.ping.pe/{{ $ip }}', '_blank')"
>
MTR
</x-proxmox::ui.button>
<x-proxmox::ui.button
variant="outline"
size="sm"
onclick="window.open('https://bgp.ping.pe/{{ $ip }}', '_blank')"
>
BGP
</x-proxmox::ui.button>
@endif
</div>
@endif
{{-- Guest Agent Technical Details --}} {{-- Guest Agent Technical Details --}}
@if($guestInterface) @if($guestInterface)
<div <div
class="border-t border-neutral/20 pt-4 mt-4" class="border-t pt-4 mt-4"
x-data="{ isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced' }" x-data="{ isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced' }"
x-init=" x-init="
isAdvancedView = localStorage.getItem('proxmox-view-mode') === 'advanced'; isAdvancedView = localStorage.getItem('proxmox-view-mode') === 'advanced';
@@ -142,50 +139,43 @@
}); });
" "
x-show="isAdvancedView" x-show="isAdvancedView"
x-transition x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
> >
<h4 class="text-sm font-semibold text-base/70 mb-3">Technical Details (Guest Agent)</h4> <h4 class="text-sm font-medium text-muted-foreground mb-3">Technical Details</h4>
<div class="grid md:grid-cols-2 grid-cols-1 gap-4"> <div class="grid md:grid-cols-2 gap-4">
@if(isset($guestInterface['hardware-address'])) @if(isset($guestInterface['hardware-address']))
<div class="flex flex-col gap-1"> <div class="space-y-1">
<span class="text-xs font-semibold text-base/60">MAC Address</span> <span class="text-xs font-medium text-muted-foreground">MAC Address</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-sm font-mono text-base/80">{{ $guestInterface['hardware-address'] }}</span> <span class="text-sm font-mono">{{ $guestInterface['hardware-address'] }}</span>
<x-proxmox::ui.copy-button :value="$guestInterface['hardware-address']" /> <x-proxmox::ui.copy-button :value="$guestInterface['hardware-address']" />
</div> </div>
<p class="text-xs text-base/50 mt-1">Hardware address for this interface</p>
</div> </div>
@endif @endif
@if(isset($guestInterface['name'])) @if(isset($guestInterface['name']))
<div class="flex flex-col gap-1"> <div class="space-y-1">
<span class="text-xs font-semibold text-base/60">Interface Name</span> <span class="text-xs font-medium text-muted-foreground">Interface</span>
<span class="text-sm font-mono text-base/80">{{ $guestInterface['name'] }}</span> <span class="text-sm font-mono block">{{ $guestInterface['name'] }}</span>
<p class="text-xs text-base/50 mt-1">Network interface identifier within the guest OS</p>
</div> </div>
@endif @endif
@if(isset($guestInterface['statistics'])) @if(isset($guestInterface['statistics']))
<div class="flex flex-col gap-1"> <div class="space-y-1 md:col-span-2">
<span class="text-xs font-semibold text-base/60">Statistics</span> <span class="text-xs font-medium text-muted-foreground">Statistics</span>
<div class="text-xs text-base/60 space-y-1"> <div class="flex flex-wrap gap-4 text-sm text-muted-foreground">
@if(isset($guestInterface['statistics']['rx-bytes'])) @if(isset($guestInterface['statistics']['rx-bytes']))
<div>RX: {{ number_format($guestInterface['statistics']['rx-bytes']) }} bytes</div> <span>RX: {{ number_format($guestInterface['statistics']['rx-bytes']) }} bytes</span>
@endif @endif
@if(isset($guestInterface['statistics']['tx-bytes'])) @if(isset($guestInterface['statistics']['tx-bytes']))
<div>TX: {{ number_format($guestInterface['statistics']['tx-bytes']) }} bytes</div> <span>TX: {{ number_format($guestInterface['statistics']['tx-bytes']) }} bytes</span>
@endif
@if(isset($guestInterface['statistics']['rx-packets']))
<div>RX Packets: {{ number_format($guestInterface['statistics']['rx-packets']) }}</div>
@endif
@if(isset($guestInterface['statistics']['tx-packets']))
<div>TX Packets: {{ number_format($guestInterface['statistics']['tx-packets']) }}</div>
@endif @endif
</div> </div>
<p class="text-xs text-base/50 mt-1">Network traffic statistics from guest OS</p>
</div> </div>
@endif @endif
</div> </div>
</div> </div>
@endif @endif
</x-proxmox::ui.card> </div>