Files
Virtnet.bond/extensions/Servers/Proxmox/views/components/layout.blade.php
Joseph.Rawlings b7b81b0d3a Refactor components to align with AdminLTE styles
- Updated alert component to use AdminLTE variant classes and added callout support.
- Refined badge component with AdminLTE base and variant classes, including size adjustments.
- Modified button component to implement AdminLTE button styles and improved loading indicators.
- Enhanced card component with AdminLTE variant classes and improved header/footer structure.
- Adjusted input component to follow AdminLTE design, including size classes and icon handling.
- Updated layout component to streamline button styles and loading indicators.
- Refined progress component to utilize AdminLTE styles for size and variant classes.
- Enhanced table component with AdminLTE table classes and improved loading state representation.
2025-12-17 17:55:03 -05:00

3002 lines
178 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@props([
'server',
'view',
'status' => null,
'statusPolling' => false,
'statusPollingInterval' => '30s',
])
@php
$vmStatus = optional($status)->status;
$pollSuffix = $statusPollingInterval ? '.' . $statusPollingInterval : '';
$pollAttribute = $statusPolling ? 'wire:poll' . $pollSuffix . '="loadVmStatus"' : '';
@endphp
<div {!! $pollAttribute !!} class="flex flex-col" style="min-height: calc(100vh - 30rem);">
@if($server->status === 'active')
<div class="flex flex-col flex-1" x-data="{
isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced',
isAdvancedOnlyView: {{ in_array($view, ['boot-order', 'cloud-init', 'display', 'system', 'storage', 'subdomain', 'statistics', 'help', 'firewall']) ? 'true' : 'false' }},
toggleView() {
if (!this.isAdvancedOnlyView) {
this.isAdvancedView = !this.isAdvancedView;
localStorage.setItem('proxmox-view-mode', this.isAdvancedView ? 'advanced' : 'basic');
window.dispatchEvent(new CustomEvent('proxmox-view-mode-changed', {
detail: { isAdvancedView: this.isAdvancedView }
}));
}
}
}" x-init="
if (!localStorage.getItem('proxmox-view-mode')) {
localStorage.setItem('proxmox-view-mode', 'basic');
isAdvancedView = false;
}">
@php
$advancedOnlyViews = ['boot-order', 'cloud-init', 'display', 'system', 'subdomain', 'statistics', 'help', 'firewall'];
$isAdvancedOnlyView = in_array($view, $advancedOnlyViews);
@endphp
<div class="flex flex-col gap-4 pb-6 pt-6 lg:flex-row lg:items-center lg:justify-between">
<div class="flex items-center gap-5" x-data="serverIconModal({
serverId: '{{ $server->id }}',
initialIcon: {{ $server->user_set_icon ? "'" . $server->user_set_icon . "'" : 'null' }},
initialColor: {{ $server->user_set_icon_color ? "'" . $server->user_set_icon_color . "'" : 'null' }},
initialPrefix: {{ $server->user_set_icon_prefix ? "'" . $server->user_set_icon_prefix . "'" : "'ri'" }}
})">
<button @click="openModal()" :title="icon ? 'Customize server icon' : 'Set server icon'" class="group flex items-center gap-5 hover:opacity-80 transition-opacity">
<div class="relative flex items-center justify-center">
<div x-show="icon" :style="color ? 'color: ' + color : ''" x-html="getIconSvg(icon, iconPrefix)" class="size-10 flex items-center justify-center"></div>
<x-ri-server-line x-show="!icon" class="size-10 text-secondary" />
<!-- Edit indicator badge -->
<div class="absolute -top-1 -right-1 size-4 bg-primary/90 rounded-full flex items-center justify-center shadow-sm opacity-60 group-hover:opacity-100 transition-opacity">
<x-ri-pencil-fill class="size-2.5 text-white" />
</div>
</div>
<span class="text-2xl font-bold">
{{ $server->hostname }}
</span>
</button>
<!-- Modal -->
<div x-show="show" x-cloak class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div class="bg-white dark:bg-background-secondary rounded-xl shadow-2xl p-6 w-full max-w-md relative">
<button @click="closeModal()" class="absolute top-3 right-3 text-base/50 hover:text-base transition-colors">
<x-ri-close-line class="size-5" />
</button>
<h3 class="text-lg font-semibold mb-4">Customize Server Icon</h3>
<div class="flex items-center gap-4 mb-4">
<div class="size-12 rounded-xl flex items-center justify-center" :style="color ? 'border-color: ' + color : ''">
<div x-show="icon" :style="color ? 'color: ' + color : ''" x-html="getIconSvg(icon, iconPrefix)"></div>
<x-ri-server-line x-show="!icon" class="size-8 text-base/40" />
</div>
<input x-model="search" type="text" placeholder="Search icons..." class="border rounded px-2 py-1 text-sm w-40 dark:bg-background dark:border-base/20" />
</div>
<div class="grid grid-cols-6 gap-2 mb-4 max-h-32 overflow-y-auto p-1">
@php
$iconList = [
// Remix Icons
['icon' => 'server-line', 'prefix' => 'ri'],
['icon' => 'database-2-line', 'prefix' => 'ri'],
['icon' => 'computer-line', 'prefix' => 'ri'],
['icon' => 'cpu-line', 'prefix' => 'ri'],
['icon' => 'hard-drive-2-line', 'prefix' => 'ri'],
['icon' => 'cloud-line', 'prefix' => 'ri'],
['icon' => 'terminal-box-line', 'prefix' => 'ri'],
['icon' => 'code-s-slash-line', 'prefix' => 'ri'],
['icon' => 'braces-line', 'prefix' => 'ri'],
['icon' => 'global-line', 'prefix' => 'ri'],
['icon' => 'earth-line', 'prefix' => 'ri'],
['icon' => 'wifi-line', 'prefix' => 'ri'],
['icon' => 'signal-tower-line', 'prefix' => 'ri'],
['icon' => 'shield-check-line', 'prefix' => 'ri'],
['icon' => 'shield-keyhole-line', 'prefix' => 'ri'],
['icon' => 'lock-line', 'prefix' => 'ri'],
['icon' => 'lock-unlock-line', 'prefix' => 'ri'],
['icon' => 'key-2-line', 'prefix' => 'ri'],
['icon' => 'rocket-line', 'prefix' => 'ri'],
['icon' => 'rocket-2-line', 'prefix' => 'ri'],
['icon' => 'send-plane-line', 'prefix' => 'ri'],
['icon' => 'flashlight-line', 'prefix' => 'ri'],
['icon' => 'fire-line', 'prefix' => 'ri'],
['icon' => 'sparkling-line', 'prefix' => 'ri'],
['icon' => 'star-line', 'prefix' => 'ri'],
['icon' => 'heart-line', 'prefix' => 'ri'],
['icon' => 'home-line', 'prefix' => 'ri'],
['icon' => 'home-2-line', 'prefix' => 'ri'],
['icon' => 'building-line', 'prefix' => 'ri'],
['icon' => 'building-2-line', 'prefix' => 'ri'],
['icon' => 'store-line', 'prefix' => 'ri'],
['icon' => 'box-3-line', 'prefix' => 'ri'],
['icon' => 'archive-line', 'prefix' => 'ri'],
['icon' => 'inbox-line', 'prefix' => 'ri'],
['icon' => 'inbox-2-line', 'prefix' => 'ri'],
['icon' => 'folder-line', 'prefix' => 'ri'],
['icon' => 'folder-open-line', 'prefix' => 'ri'],
['icon' => 'settings-3-line', 'prefix' => 'ri'],
['icon' => 'settings-4-line', 'prefix' => 'ri'],
['icon' => 'tools-line', 'prefix' => 'ri'],
['icon' => 'hammer-line', 'prefix' => 'ri'],
['icon' => 'flask-line', 'prefix' => 'ri'],
['icon' => 'bug-line', 'prefix' => 'ri'],
['icon' => 'puzzle-line', 'prefix' => 'ri'],
['icon' => 'bar-chart-box-line', 'prefix' => 'ri'],
['icon' => 'pie-chart-line', 'prefix' => 'ri'],
['icon' => 'line-chart-line', 'prefix' => 'ri'],
['icon' => 'money-dollar-circle-line', 'prefix' => 'ri'],
['icon' => 'money-euro-circle-line', 'prefix' => 'ri'],
['icon' => 'bank-card-line', 'prefix' => 'ri'],
['icon' => 'wallet-line', 'prefix' => 'ri'],
['icon' => 'user-line', 'prefix' => 'ri'],
['icon' => 'user-3-line', 'prefix' => 'ri'],
['icon' => 'group-line', 'prefix' => 'ri'],
['icon' => 'team-line', 'prefix' => 'ri'],
['icon' => 'mail-line', 'prefix' => 'ri'],
['icon' => 'mail-open-line', 'prefix' => 'ri'],
['icon' => 'chat-3-line', 'prefix' => 'ri'],
['icon' => 'discuss-line', 'prefix' => 'ri'],
['icon' => 'camera-line', 'prefix' => 'ri'],
['icon' => 'vidicon-line', 'prefix' => 'ri'],
['icon' => 'film-line', 'prefix' => 'ri'],
['icon' => 'music-line', 'prefix' => 'ri'],
['icon' => 'book-open-line', 'prefix' => 'ri'],
['icon' => 'file-text-line', 'prefix' => 'ri'],
['icon' => 'sun-line', 'prefix' => 'ri'],
['icon' => 'moon-line', 'prefix' => 'ri'],
['icon' => 'palette-line', 'prefix' => 'ri'],
['icon' => 'brush-line', 'prefix' => 'ri'],
['icon' => 'trophy-line', 'prefix' => 'ri'],
['icon' => 'graduation-cap-line', 'prefix' => 'ri'],
['icon' => 'lightbulb-line', 'prefix' => 'ri'],
['icon' => 'fingerprint-line', 'prefix' => 'ri'],
['icon' => 'eye-line', 'prefix' => 'ri'],
['icon' => 'map-2-line', 'prefix' => 'ri'],
['icon' => 'map-pin-line', 'prefix' => 'ri'],
['icon' => 'time-line', 'prefix' => 'ri'],
['icon' => 'calendar-line', 'prefix' => 'ri'],
['icon' => 'gift-line', 'prefix' => 'ri'],
['icon' => 'cake-2-line', 'prefix' => 'ri'],
['icon' => 'gamepad-line', 'prefix' => 'ri'],
['icon' => 'ghost-line', 'prefix' => 'ri'],
['icon' => 'aliens-line', 'prefix' => 'ri'],
['icon' => 'space-ship-line', 'prefix' => 'ri'],
['icon' => 'android-line', 'prefix' => 'ri'],
['icon' => 'apple-line', 'prefix' => 'ri'],
['icon' => 'windows-line', 'prefix' => 'ri'],
['icon' => 'ubuntu-line', 'prefix' => 'ri'],
['icon' => 'github-line', 'prefix' => 'ri'],
['icon' => 'gitlab-line', 'prefix' => 'ri'],
['icon' => 'discord-line', 'prefix' => 'ri'],
['icon' => 'slack-line', 'prefix' => 'ri'],
['icon' => 'desktop', 'prefix' => 'vaadin'],
['icon' => 'laptop', 'prefix' => 'vaadin'],
['icon' => 'cluster', 'prefix' => 'vaadin'],
['icon' => 'cloud', 'prefix' => 'vaadin'],
['icon' => 'cloud-o', 'prefix' => 'vaadin'],
['icon' => 'cloud-upload', 'prefix' => 'vaadin'],
['icon' => 'cloud-download', 'prefix' => 'vaadin'],
['icon' => 'storage', 'prefix' => 'vaadin'],
['icon' => 'harddrive', 'prefix' => 'vaadin'],
['icon' => 'compile', 'prefix' => 'vaadin'],
['icon' => 'code', 'prefix' => 'vaadin'],
['icon' => 'terminal', 'prefix' => 'vaadin'],
['icon' => 'globe', 'prefix' => 'vaadin'],
['icon' => 'globe-wire', 'prefix' => 'vaadin'],
['icon' => 'connect', 'prefix' => 'vaadin'],
['icon' => 'sitemap', 'prefix' => 'vaadin'],
['icon' => 'automation', 'prefix' => 'vaadin'],
['icon' => 'cogs', 'prefix' => 'vaadin'],
['icon' => 'cog', 'prefix' => 'vaadin'],
['icon' => 'wrench', 'prefix' => 'vaadin'],
['icon' => 'tools', 'prefix' => 'vaadin'],
['icon' => 'hammer', 'prefix' => 'vaadin'],
['icon' => 'shield', 'prefix' => 'vaadin'],
['icon' => 'lock', 'prefix' => 'vaadin'],
['icon' => 'unlock', 'prefix' => 'vaadin'],
['icon' => 'key', 'prefix' => 'vaadin'],
['icon' => 'key-o', 'prefix' => 'vaadin'],
['icon' => 'rocket', 'prefix' => 'vaadin'],
['icon' => 'flash', 'prefix' => 'vaadin'],
['icon' => 'bolt', 'prefix' => 'vaadin'],
['icon' => 'fire', 'prefix' => 'vaadin'],
['icon' => 'star', 'prefix' => 'vaadin'],
['icon' => 'star-o', 'prefix' => 'vaadin'],
['icon' => 'heart', 'prefix' => 'vaadin'],
['icon' => 'heart-o', 'prefix' => 'vaadin'],
['icon' => 'home', 'prefix' => 'vaadin'],
['icon' => 'home-o', 'prefix' => 'vaadin'],
['icon' => 'building', 'prefix' => 'vaadin'],
['icon' => 'building-o', 'prefix' => 'vaadin'],
['icon' => 'office', 'prefix' => 'vaadin'],
['icon' => 'server', 'prefix' => 'vaadin'],
['icon' => 'database', 'prefix' => 'vaadin'],
['icon' => 'cloud', 'prefix' => 'vaadin'],
['icon' => 'cloud-o', 'prefix' => 'vaadin'],
['icon' => 'storage', 'prefix' => 'vaadin'],
['icon' => 'harddrive', 'prefix' => 'vaadin'],
['icon' => 'package', 'prefix' => 'vaadin'],
['icon' => 'archive', 'prefix' => 'vaadin'],
['icon' => 'inbox', 'prefix' => 'vaadin'],
['icon' => 'folder', 'prefix' => 'vaadin'],
['icon' => 'folder-o', 'prefix' => 'vaadin'],
['icon' => 'folder-open', 'prefix' => 'vaadin'],
['icon' => 'folder-open-o', 'prefix' => 'vaadin'],
['icon' => 'bug', 'prefix' => 'vaadin'],
['icon' => 'chart', 'prefix' => 'vaadin'],
['icon' => 'bar-chart', 'prefix' => 'vaadin'],
['icon' => 'line-chart', 'prefix' => 'vaadin'],
['icon' => 'pie-chart', 'prefix' => 'vaadin'],
['icon' => 'dashboard', 'prefix' => 'vaadin'],
['icon' => 'progressbar', 'prefix' => 'vaadin'],
['icon' => 'money', 'prefix' => 'vaadin'],
['icon' => 'dollar', 'prefix' => 'vaadin'],
['icon' => 'euro', 'prefix' => 'vaadin'],
['icon' => 'credit-card', 'prefix' => 'vaadin'],
['icon' => 'wallet', 'prefix' => 'vaadin'],
['icon' => 'user', 'prefix' => 'vaadin'],
['icon' => 'users', 'prefix' => 'vaadin'],
['icon' => 'group', 'prefix' => 'vaadin'],
['icon' => 'user-card', 'prefix' => 'vaadin'],
['icon' => 'male', 'prefix' => 'vaadin'],
['icon' => 'female', 'prefix' => 'vaadin'],
['icon' => 'envelope', 'prefix' => 'vaadin'],
['icon' => 'envelope-o', 'prefix' => 'vaadin'],
['icon' => 'envelope-open', 'prefix' => 'vaadin'],
['icon' => 'mailbox', 'prefix' => 'vaadin'],
['icon' => 'comment', 'prefix' => 'vaadin'],
['icon' => 'comments', 'prefix' => 'vaadin'],
['icon' => 'comments-o', 'prefix' => 'vaadin'],
['icon' => 'camera', 'prefix' => 'vaadin'],
['icon' => 'film', 'prefix' => 'vaadin'],
['icon' => 'music', 'prefix' => 'vaadin'],
['icon' => 'book', 'prefix' => 'vaadin'],
['icon' => 'file-text', 'prefix' => 'vaadin'],
['icon' => 'file-text-o', 'prefix' => 'vaadin'],
['icon' => 'sun-o', 'prefix' => 'vaadin'],
['icon' => 'moon-o', 'prefix' => 'vaadin'],
['icon' => 'palete', 'prefix' => 'vaadin'],
['icon' => 'paintbrush', 'prefix' => 'vaadin'],
['icon' => 'trophy', 'prefix' => 'vaadin'],
['icon' => 'academy-cap', 'prefix' => 'vaadin'],
['icon' => 'lightbulb', 'prefix' => 'vaadin'],
['icon' => 'eye', 'prefix' => 'vaadin'],
['icon' => 'eye-slash', 'prefix' => 'vaadin'],
['icon' => 'map-marker', 'prefix' => 'vaadin'],
['icon' => 'location-arrow', 'prefix' => 'vaadin'],
['icon' => 'clock', 'prefix' => 'vaadin'],
['icon' => 'calendar', 'prefix' => 'vaadin'],
['icon' => 'calendar-o', 'prefix' => 'vaadin'],
['icon' => 'gift', 'prefix' => 'vaadin'],
['icon' => 'gamepad', 'prefix' => 'vaadin'],
['icon' => 'controller', 'prefix' => 'vaadin'],
// File Icons (codeat3/blade-file-icons)
['icon' => 'docker', 'prefix' => 'fileicon'],
['icon' => 'kubernetes', 'prefix' => 'fileicon'],
['icon' => 'nginx', 'prefix' => 'fileicon'],
['icon' => 'alpine-linux', 'prefix' => 'fileicon'],
['icon' => 'arch-linux', 'prefix' => 'fileicon'],
['icon' => 'xubuntu', 'prefix' => 'fileicon'],
['icon' => 'virtualbox', 'prefix' => 'fileicon'],
['icon' => 'virtualbox-alt', 'prefix' => 'fileicon'],
['icon' => 'vmware', 'prefix' => 'fileicon'],
['icon' => 'terminal', 'prefix' => 'fileicon'],
['icon' => 'powershell', 'prefix' => 'fileicon'],
['icon' => 'serverless', 'prefix' => 'fileicon'],
['icon' => 'devcontainer', 'prefix' => 'fileicon'],
['icon' => 'config', 'prefix' => 'fileicon'],
['icon' => 'editorconfig', 'prefix' => 'fileicon'],
['icon' => 'php', 'prefix' => 'fileicon'],
['icon' => 'go', 'prefix' => 'fileicon'],
['icon' => 'kotlin', 'prefix' => 'fileicon'],
['icon' => 'c', 'prefix' => 'fileicon'],
['icon' => 'npm', 'prefix' => 'fileicon'],
['icon' => 'yarn', 'prefix' => 'fileicon'],
['icon' => 'vscode', 'prefix' => 'fileicon'],
['icon' => 'nano', 'prefix' => 'fileicon'],
['icon' => 'gitlab', 'prefix' => 'fileicon'],
['icon' => 'sqlite', 'prefix' => 'fileicon'],
['icon' => 'json-1', 'prefix' => 'fileicon'],
['icon' => 'json-2', 'prefix' => 'fileicon'],
['icon' => 'yaml', 'prefix' => 'fileicon'],
['icon' => 'typescript', 'prefix' => 'fileicon'],
['icon' => 'vue', 'prefix' => 'fileicon'],
['icon' => 'svelte', 'prefix' => 'fileicon'],
['icon' => 'laravel', 'prefix' => 'fileicon'],
['icon' => 'flask', 'prefix' => 'fileicon'],
['icon' => 'terraform', 'prefix' => 'fileicon'],
['icon' => 'ansible', 'prefix' => 'fileicon'],
['icon' => 'ansible-alt', 'prefix' => 'fileicon'],
['icon' => 'puppet', 'prefix' => 'fileicon'],
['icon' => 'jenkins', 'prefix' => 'fileicon'],
['icon' => 'jsx', 'prefix' => 'fileicon'],
['icon' => 'tsx', 'prefix' => 'fileicon'],
['icon' => 'perl6', 'prefix' => 'fileicon'],
['icon' => 'rubygems', 'prefix' => 'fileicon'],
['icon' => 'toml', 'prefix' => 'fileicon'],
['icon' => 'tailwind', 'prefix' => 'fileicon'],
['icon' => 'webpack', 'prefix' => 'fileicon'],
['icon' => 'vite', 'prefix' => 'fileicon'],
['icon' => 'nextjs', 'prefix' => 'fileicon'],
['icon' => 'nestjs', 'prefix' => 'fileicon'],
['icon' => 'nodemon', 'prefix' => 'fileicon'],
['icon' => 'vagrant', 'prefix' => 'fileicon'],
['icon' => 'saltstack', 'prefix' => 'fileicon'],
['icon' => 'chef', 'prefix' => 'fileicon'],
['icon' => 'circleci', 'prefix' => 'fileicon'],
['icon' => 'r', 'prefix' => 'fileicon'],
['icon' => 'apple', 'prefix' => 'fileicon'],
['icon' => 'arduino', 'prefix' => 'fileicon'],
['icon' => 'babel', 'prefix' => 'fileicon'],
['icon' => 'composer', 'prefix' => 'fileicon'],
['icon' => 'snyk', 'prefix' => 'fileicon'],
['icon' => 'storybook', 'prefix' => 'fileicon'],
// Dev Icons (codeat3/blade-devicons) - Technology logos
['icon' => 'docker', 'prefix' => 'devicon'],
['icon' => 'kubernetes', 'prefix' => 'devicon'],
['icon' => 'linux', 'prefix' => 'devicon'],
['icon' => 'ubuntu', 'prefix' => 'devicon'],
['icon' => 'debian', 'prefix' => 'devicon'],
['icon' => 'centos', 'prefix' => 'devicon'],
['icon' => 'redhat', 'prefix' => 'devicon'],
['icon' => 'fedora', 'prefix' => 'devicon'],
['icon' => 'opensuse', 'prefix' => 'devicon'],
['icon' => 'nixos', 'prefix' => 'devicon'],
['icon' => 'gentoo', 'prefix' => 'devicon'],
['icon' => 'android', 'prefix' => 'devicon'],
['icon' => 'apache', 'prefix' => 'devicon'],
['icon' => 'mysql', 'prefix' => 'devicon'],
['icon' => 'postgresql', 'prefix' => 'devicon'],
['icon' => 'mongodb', 'prefix' => 'devicon'],
['icon' => 'redis', 'prefix' => 'devicon'],
['icon' => 'sqlite', 'prefix' => 'devicon'],
['icon' => 'neo4j', 'prefix' => 'devicon'],
['icon' => 'couchdb', 'prefix' => 'devicon'],
['icon' => 'microsoftsqlserver', 'prefix' => 'devicon'],
['icon' => 'python', 'prefix' => 'devicon'],
['icon' => 'php', 'prefix' => 'devicon'],
['icon' => 'java', 'prefix' => 'devicon'],
['icon' => 'javascript', 'prefix' => 'devicon'],
['icon' => 'typescript', 'prefix' => 'devicon'],
['icon' => 'nodejs', 'prefix' => 'devicon'],
['icon' => 'go', 'prefix' => 'devicon'],
['icon' => 'rust', 'prefix' => 'devicon'],
['icon' => 'c', 'prefix' => 'devicon'],
['icon' => 'cplusplus', 'prefix' => 'devicon'],
['icon' => 'csharp', 'prefix' => 'devicon'],
['icon' => 'ruby', 'prefix' => 'devicon'],
['icon' => 'perl', 'prefix' => 'devicon'],
['icon' => 'swift', 'prefix' => 'devicon'],
['icon' => 'kotlin', 'prefix' => 'devicon'],
['icon' => 'scala', 'prefix' => 'devicon'],
['icon' => 'r', 'prefix' => 'devicon'],
['icon' => 'lua', 'prefix' => 'devicon'],
['icon' => 'dart', 'prefix' => 'devicon'],
['icon' => 'elixir', 'prefix' => 'devicon'],
['icon' => 'erlang', 'prefix' => 'devicon'],
['icon' => 'haskell', 'prefix' => 'devicon'],
['icon' => 'julia', 'prefix' => 'devicon'],
['icon' => 'bash', 'prefix' => 'devicon'],
['icon' => 'html5', 'prefix' => 'devicon'],
['icon' => 'css3', 'prefix' => 'devicon'],
['icon' => 'laravel', 'prefix' => 'devicon'],
['icon' => 'django', 'prefix' => 'devicon'],
['icon' => 'rails', 'prefix' => 'devicon'],
['icon' => 'spring', 'prefix' => 'devicon'],
['icon' => 'fastapi', 'prefix' => 'devicon'],
['icon' => 'nestjs', 'prefix' => 'devicon'],
['icon' => 'vuejs', 'prefix' => 'devicon'],
['icon' => 'angularjs', 'prefix' => 'devicon'],
['icon' => 'svelte', 'prefix' => 'devicon'],
['icon' => 'nuxtjs', 'prefix' => 'devicon'],
['icon' => 'gatsby', 'prefix' => 'devicon'],
['icon' => 'bootstrap', 'prefix' => 'devicon'],
['icon' => 'tailwindcss', 'prefix' => 'devicon'],
['icon' => 'materialui', 'prefix' => 'devicon'],
['icon' => 'git', 'prefix' => 'devicon'],
['icon' => 'gitlab', 'prefix' => 'devicon'],
['icon' => 'azure', 'prefix' => 'devicon'],
['icon' => 'googlecloud', 'prefix' => 'devicon'],
['icon' => 'digitalocean', 'prefix' => 'devicon'],
['icon' => 'heroku', 'prefix' => 'devicon'],
['icon' => 'firebase', 'prefix' => 'devicon'],
['icon' => 'terraform', 'prefix' => 'devicon'],
['icon' => 'ansible', 'prefix' => 'devicon'],
['icon' => 'jenkins', 'prefix' => 'devicon'],
['icon' => 'circleci', 'prefix' => 'devicon'],
['icon' => 'travis', 'prefix' => 'devicon'],
['icon' => 'argocd', 'prefix' => 'devicon'],
['icon' => 'vagrant', 'prefix' => 'devicon'],
['icon' => 'podman', 'prefix' => 'devicon'],
['icon' => 'webpack', 'prefix' => 'devicon'],
['icon' => 'babel', 'prefix' => 'devicon'],
['icon' => 'gradle', 'prefix' => 'devicon'],
['icon' => 'vscode', 'prefix' => 'devicon'],
['icon' => 'visualstudio', 'prefix' => 'devicon'],
['icon' => 'intellij', 'prefix' => 'devicon'],
['icon' => 'pycharm', 'prefix' => 'devicon'],
['icon' => 'phpstorm', 'prefix' => 'devicon'],
['icon' => 'webstorm', 'prefix' => 'devicon'],
['icon' => 'vim', 'prefix' => 'devicon'],
['icon' => 'xcode', 'prefix' => 'devicon'],
['icon' => 'androidstudio', 'prefix' => 'devicon'],
['icon' => 'jupyter', 'prefix' => 'devicon'],
['icon' => 'chrome', 'prefix' => 'devicon'],
['icon' => 'firefox', 'prefix' => 'devicon'],
['icon' => 'safari', 'prefix' => 'devicon'],
['icon' => 'opera', 'prefix' => 'devicon'],
['icon' => 'wordpress', 'prefix' => 'devicon'],
['icon' => 'drupal', 'prefix' => 'devicon'],
['icon' => 'woocommerce', 'prefix' => 'devicon'],
['icon' => 'graphql', 'prefix' => 'devicon'],
['icon' => 'arduino', 'prefix' => 'devicon'],
['icon' => 'flutter', 'prefix' => 'devicon'],
['icon' => 'figma', 'prefix' => 'devicon'],
['icon' => 'photoshop', 'prefix' => 'devicon'],
['icon' => 'illustrator', 'prefix' => 'devicon'],
['icon' => 'premierepro', 'prefix' => 'devicon'],
['icon' => 'aftereffects', 'prefix' => 'devicon'],
['icon' => 'slack', 'prefix' => 'devicon'],
['icon' => 'trello', 'prefix' => 'devicon'],
['icon' => 'jira', 'prefix' => 'devicon'],
['icon' => 'putty', 'prefix' => 'devicon'],
['icon' => 'solidity', 'prefix' => 'devicon'],
// Coolicons (codeat3/blade-coolicons)
['icon' => 'globe', 'prefix' => 'coolicon'],
['icon' => 'planet', 'prefix' => 'coolicon'],
['icon' => 'desktop', 'prefix' => 'coolicon'],
['icon' => 'desktop-tower', 'prefix' => 'coolicon'],
['icon' => 'laptop', 'prefix' => 'coolicon'],
['icon' => 'monitor', 'prefix' => 'coolicon'],
['icon' => 'monitor-play', 'prefix' => 'coolicon'],
['icon' => 'tablet', 'prefix' => 'coolicon'],
['icon' => 'tablet-button', 'prefix' => 'coolicon'],
['icon' => 'mobile', 'prefix' => 'coolicon'],
['icon' => 'mobile-button', 'prefix' => 'coolicon'],
['icon' => 'devices', 'prefix' => 'coolicon'],
['icon' => 'cloud', 'prefix' => 'coolicon'],
['icon' => 'cloud-add', 'prefix' => 'coolicon'],
['icon' => 'cloud-check', 'prefix' => 'coolicon'],
['icon' => 'cloud-close', 'prefix' => 'coolicon'],
['icon' => 'cloud-download', 'prefix' => 'coolicon'],
['icon' => 'cloud-upload', 'prefix' => 'coolicon'],
['icon' => 'cloud-off', 'prefix' => 'coolicon'],
['icon' => 'cloud-remove', 'prefix' => 'coolicon'],
['icon' => 'data', 'prefix' => 'coolicon'],
['icon' => 'cylinder', 'prefix' => 'coolicon'],
['icon' => 'instance', 'prefix' => 'coolicon'],
['icon' => 'layers', 'prefix' => 'coolicon'],
['icon' => 'layer', 'prefix' => 'coolicon'],
['icon' => 'terminal', 'prefix' => 'coolicon'],
['icon' => 'code', 'prefix' => 'coolicon'],
['icon' => 'window', 'prefix' => 'coolicon'],
['icon' => 'window-check', 'prefix' => 'coolicon'],
['icon' => 'window-close', 'prefix' => 'coolicon'],
['icon' => 'window-code-block', 'prefix' => 'coolicon'],
['icon' => 'window-sidebar', 'prefix' => 'coolicon'],
['icon' => 'window-terminal', 'prefix' => 'coolicon'],
['icon' => 'keyboard', 'prefix' => 'coolicon'],
['icon' => 'mouse', 'prefix' => 'coolicon'],
['icon' => 'wifi-high', 'prefix' => 'coolicon'],
['icon' => 'wifi-medium', 'prefix' => 'coolicon'],
['icon' => 'wifi-low', 'prefix' => 'coolicon'],
['icon' => 'wifi-none', 'prefix' => 'coolicon'],
['icon' => 'wifi-off', 'prefix' => 'coolicon'],
['icon' => 'wifi-problem', 'prefix' => 'coolicon'],
['icon' => 'shield', 'prefix' => 'coolicon'],
['icon' => 'shield-check', 'prefix' => 'coolicon'],
['icon' => 'shield-warning', 'prefix' => 'coolicon'],
['icon' => 'lock', 'prefix' => 'coolicon'],
['icon' => 'lock-open', 'prefix' => 'coolicon'],
['icon' => 'settings', 'prefix' => 'coolicon'],
['icon' => 'settings-future', 'prefix' => 'coolicon'],
['icon' => 'slider-01', 'prefix' => 'coolicon'],
['icon' => 'slider-02', 'prefix' => 'coolicon'],
['icon' => 'slider-03', 'prefix' => 'coolicon'],
['icon' => 'filter', 'prefix' => 'coolicon'],
['icon' => 'filter-off', 'prefix' => 'coolicon'],
['icon' => 'chart-bar-horizontal-01', 'prefix' => 'coolicon'],
['icon' => 'chart-bar-vertical-01', 'prefix' => 'coolicon'],
['icon' => 'chart-line', 'prefix' => 'coolicon'],
['icon' => 'chart-pie', 'prefix' => 'coolicon'],
['icon' => 'trending-up', 'prefix' => 'coolicon'],
['icon' => 'trending-down', 'prefix' => 'coolicon'],
['icon' => 'bell', 'prefix' => 'coolicon'],
['icon' => 'bell-add', 'prefix' => 'coolicon'],
['icon' => 'bell-notification', 'prefix' => 'coolicon'],
['icon' => 'bell-off', 'prefix' => 'coolicon'],
['icon' => 'bell-ring', 'prefix' => 'coolicon'],
['icon' => 'mail', 'prefix' => 'coolicon'],
['icon' => 'mail-open', 'prefix' => 'coolicon'],
['icon' => 'chat', 'prefix' => 'coolicon'],
['icon' => 'chat-add', 'prefix' => 'coolicon'],
['icon' => 'chat-check', 'prefix' => 'coolicon'],
['icon' => 'chat-close', 'prefix' => 'coolicon'],
['icon' => 'chat-dots', 'prefix' => 'coolicon'],
['icon' => 'chat-conversation', 'prefix' => 'coolicon'],
['icon' => 'folder', 'prefix' => 'coolicon'],
['icon' => 'folder-add', 'prefix' => 'coolicon'],
['icon' => 'folder-check', 'prefix' => 'coolicon'],
['icon' => 'folder-close', 'prefix' => 'coolicon'],
['icon' => 'folder-code', 'prefix' => 'coolicon'],
['icon' => 'folder-document', 'prefix' => 'coolicon'],
['icon' => 'folder-download', 'prefix' => 'coolicon'],
['icon' => 'folder-open', 'prefix' => 'coolicon'],
['icon' => 'folder-upload', 'prefix' => 'coolicon'],
['icon' => 'folders', 'prefix' => 'coolicon'],
['icon' => 'file-blank', 'prefix' => 'coolicon'],
['icon' => 'file-add', 'prefix' => 'coolicon'],
['icon' => 'file-check', 'prefix' => 'coolicon'],
['icon' => 'file-close', 'prefix' => 'coolicon'],
['icon' => 'file-code', 'prefix' => 'coolicon'],
['icon' => 'file-document', 'prefix' => 'coolicon'],
['icon' => 'file-download', 'prefix' => 'coolicon'],
['icon' => 'file-upload', 'prefix' => 'coolicon'],
['icon' => 'files', 'prefix' => 'coolicon'],
['icon' => 'user-01', 'prefix' => 'coolicon'],
['icon' => 'user-02', 'prefix' => 'coolicon'],
['icon' => 'user-03', 'prefix' => 'coolicon'],
['icon' => 'user-add', 'prefix' => 'coolicon'],
['icon' => 'user-check', 'prefix' => 'coolicon'],
['icon' => 'user-circle', 'prefix' => 'coolicon'],
['icon' => 'user-close', 'prefix' => 'coolicon'],
['icon' => 'user-remove', 'prefix' => 'coolicon'],
['icon' => 'user-square', 'prefix' => 'coolicon'],
['icon' => 'users', 'prefix' => 'coolicon'],
['icon' => 'users-group', 'prefix' => 'coolicon'],
['icon' => 'star', 'prefix' => 'coolicon'],
['icon' => 'heart-01', 'prefix' => 'coolicon'],
['icon' => 'heart-02', 'prefix' => 'coolicon'],
['icon' => 'bookmark', 'prefix' => 'coolicon'],
['icon' => 'flag', 'prefix' => 'coolicon'],
['icon' => 'tag', 'prefix' => 'coolicon'],
['icon' => 'label', 'prefix' => 'coolicon'],
['icon' => 'house-01', 'prefix' => 'coolicon'],
['icon' => 'house-02', 'prefix' => 'coolicon'],
['icon' => 'house-03', 'prefix' => 'coolicon'],
['icon' => 'house-add', 'prefix' => 'coolicon'],
['icon' => 'house-check', 'prefix' => 'coolicon'],
['icon' => 'building-01', 'prefix' => 'coolicon'],
['icon' => 'building-02', 'prefix' => 'coolicon'],
['icon' => 'building-03', 'prefix' => 'coolicon'],
['icon' => 'building-04', 'prefix' => 'coolicon'],
['icon' => 'sun', 'prefix' => 'coolicon'],
['icon' => 'moon', 'prefix' => 'coolicon'],
['icon' => 'bulb', 'prefix' => 'coolicon'],
['icon' => 'compass', 'prefix' => 'coolicon'],
['icon' => 'map', 'prefix' => 'coolicon'],
['icon' => 'map-pin', 'prefix' => 'coolicon'],
['icon' => 'navigation', 'prefix' => 'coolicon'],
['icon' => 'clock', 'prefix' => 'coolicon'],
['icon' => 'timer', 'prefix' => 'coolicon'],
['icon' => 'timer-add', 'prefix' => 'coolicon'],
['icon' => 'calendar', 'prefix' => 'coolicon'],
['icon' => 'calendar-add', 'prefix' => 'coolicon'],
['icon' => 'calendar-check', 'prefix' => 'coolicon'],
['icon' => 'calendar-event', 'prefix' => 'coolicon'],
['icon' => 'gift', 'prefix' => 'coolicon'],
['icon' => 'credit-card-01', 'prefix' => 'coolicon'],
['icon' => 'credit-card-02', 'prefix' => 'coolicon'],
['icon' => 'shopping-bag-01', 'prefix' => 'coolicon'],
['icon' => 'shopping-bag-02', 'prefix' => 'coolicon'],
['icon' => 'shopping-cart-01', 'prefix' => 'coolicon'],
['icon' => 'shopping-cart-02', 'prefix' => 'coolicon'],
['icon' => 'suitcase', 'prefix' => 'coolicon'],
['icon' => 'archive', 'prefix' => 'coolicon'],
['icon' => 'book', 'prefix' => 'coolicon'],
['icon' => 'book-open', 'prefix' => 'coolicon'],
['icon' => 'notebook', 'prefix' => 'coolicon'],
['icon' => 'note', 'prefix' => 'coolicon'],
['icon' => 'note-edit', 'prefix' => 'coolicon'],
['icon' => 'note-search', 'prefix' => 'coolicon'],
['icon' => 'camera', 'prefix' => 'coolicon'],
['icon' => 'image-01', 'prefix' => 'coolicon'],
['icon' => 'image-02', 'prefix' => 'coolicon'],
['icon' => 'headphones', 'prefix' => 'coolicon'],
['icon' => 'printer', 'prefix' => 'coolicon'],
['icon' => 'airplay', 'prefix' => 'coolicon'],
['icon' => 'chromecast', 'prefix' => 'coolicon'],
['icon' => 'download', 'prefix' => 'coolicon'],
['icon' => 'download-package', 'prefix' => 'coolicon'],
['icon' => 'cloud-upload', 'prefix' => 'coolicon'],
['icon' => 'link', 'prefix' => 'coolicon'],
['icon' => 'link-horizontal', 'prefix' => 'coolicon'],
['icon' => 'link-vertical', 'prefix' => 'coolicon'],
['icon' => 'link-break', 'prefix' => 'coolicon'],
['icon' => 'external-link', 'prefix' => 'coolicon'],
['icon' => 'share-android', 'prefix' => 'coolicon'],
['icon' => 'share-ios-export', 'prefix' => 'coolicon'],
['icon' => 'qr-code', 'prefix' => 'coolicon'],
['icon' => 'copy', 'prefix' => 'coolicon'],
['icon' => 'save', 'prefix' => 'coolicon'],
['icon' => 'trash-empty', 'prefix' => 'coolicon'],
['icon' => 'trash-full', 'prefix' => 'coolicon'],
['icon' => 'puzzle', 'prefix' => 'coolicon'],
['icon' => 'command', 'prefix' => 'coolicon'],
['icon' => 'option', 'prefix' => 'coolicon'],
['icon' => 'rainbow', 'prefix' => 'coolicon'],
['icon' => 'leaf', 'prefix' => 'coolicon'],
['icon' => 'water-drop', 'prefix' => 'coolicon'],
['icon' => 'cookie', 'prefix' => 'coolicon'],
['icon' => 'coffee', 'prefix' => 'coolicon'],
['icon' => 'coffe-to-go', 'prefix' => 'coolicon'],
['icon' => 'cupcake', 'prefix' => 'coolicon'],
['icon' => 'first-aid', 'prefix' => 'coolicon'],
['icon' => 'car-auto', 'prefix' => 'coolicon'],
['icon' => 'phone', 'prefix' => 'coolicon'],
['icon' => 'alarm', 'prefix' => 'coolicon'],
['icon' => 'help', 'prefix' => 'coolicon'],
['icon' => 'info', 'prefix' => 'coolicon'],
['icon' => 'warning', 'prefix' => 'coolicon'],
['icon' => 'circle-check', 'prefix' => 'coolicon'],
['icon' => 'circle-warning', 'prefix' => 'coolicon'],
['icon' => 'circle-help', 'prefix' => 'coolicon'],
['icon' => 'square-check', 'prefix' => 'coolicon'],
['icon' => 'square-warning', 'prefix' => 'coolicon'],
['icon' => 'square-help', 'prefix' => 'coolicon'],
['icon' => 'triangle-check', 'prefix' => 'coolicon'],
['icon' => 'triangle-warning', 'prefix' => 'coolicon'],
['icon' => 'octagon-check', 'prefix' => 'coolicon'],
['icon' => 'octagon-warning', 'prefix' => 'coolicon'],
['icon' => 'wavy-check', 'prefix' => 'coolicon'],
['icon' => 'wavy-warning', 'prefix' => 'coolicon'],
['icon' => 'wavy-help', 'prefix' => 'coolicon'],
['icon' => 'play', 'prefix' => 'coolicon'],
['icon' => 'play-circle', 'prefix' => 'coolicon'],
['icon' => 'pause', 'prefix' => 'coolicon'],
['icon' => 'pause-circle', 'prefix' => 'coolicon'],
['icon' => 'stop', 'prefix' => 'coolicon'],
['icon' => 'stop-circle', 'prefix' => 'coolicon'],
['icon' => 'stop-sign', 'prefix' => 'coolicon'],
['icon' => 'forward', 'prefix' => 'coolicon'],
['icon' => 'rewind', 'prefix' => 'coolicon'],
['icon' => 'skip-forward', 'prefix' => 'coolicon'],
['icon' => 'skip-back', 'prefix' => 'coolicon'],
['icon' => 'shuffle', 'prefix' => 'coolicon'],
['icon' => 'volume-max', 'prefix' => 'coolicon'],
['icon' => 'volume-min', 'prefix' => 'coolicon'],
['icon' => 'volume-off', 'prefix' => 'coolicon'],
['icon' => 'expand', 'prefix' => 'coolicon'],
['icon' => 'shrink', 'prefix' => 'coolicon'],
['icon' => 'crop', 'prefix' => 'coolicon'],
['icon' => 'move', 'prefix' => 'coolicon'],
['icon' => 'show', 'prefix' => 'coolicon'],
['icon' => 'hide', 'prefix' => 'coolicon'],
['icon' => 'search-magnifying-glass', 'prefix' => 'coolicon'],
['icon' => 'magnifying-glass-plus', 'prefix' => 'coolicon'],
['icon' => 'magnifying-glass-minus', 'prefix' => 'coolicon'],
['icon' => 'edit-pencil-01', 'prefix' => 'coolicon'],
['icon' => 'edit-pencil-02', 'prefix' => 'coolicon'],
['icon' => 'edit-pencil-line-01', 'prefix' => 'coolicon'],
['icon' => 'edit-pencil-line-02', 'prefix' => 'coolicon'],
['icon' => 'check', 'prefix' => 'coolicon'],
['icon' => 'check-big', 'prefix' => 'coolicon'],
['icon' => 'check-all', 'prefix' => 'coolicon'],
['icon' => 'check-all-big', 'prefix' => 'coolicon'],
['icon' => 'close-lg', 'prefix' => 'coolicon'],
['icon' => 'close-md', 'prefix' => 'coolicon'],
['icon' => 'close-sm', 'prefix' => 'coolicon'],
['icon' => 'add-plus', 'prefix' => 'coolicon'],
['icon' => 'add-plus-circle', 'prefix' => 'coolicon'],
['icon' => 'add-plus-square', 'prefix' => 'coolicon'],
['icon' => 'remove-minus', 'prefix' => 'coolicon'],
['icon' => 'remove-minus-circle', 'prefix' => 'coolicon'],
['icon' => 'more-horizontal', 'prefix' => 'coolicon'],
['icon' => 'more-vertical', 'prefix' => 'coolicon'],
['icon' => 'more-grid-big', 'prefix' => 'coolicon'],
['icon' => 'more-grid-small', 'prefix' => 'coolicon'],
['icon' => 'hamburger-lg', 'prefix' => 'coolicon'],
['icon' => 'hamburger-md', 'prefix' => 'coolicon'],
['icon' => 'menu-alt-01', 'prefix' => 'coolicon'],
['icon' => 'menu-alt-02', 'prefix' => 'coolicon'],
['icon' => 'menu-alt-03', 'prefix' => 'coolicon'],
['icon' => 'menu-alt-04', 'prefix' => 'coolicon'],
['icon' => 'menu-alt-05', 'prefix' => 'coolicon'],
['icon' => 'list-checklist', 'prefix' => 'coolicon'],
['icon' => 'list-ordered', 'prefix' => 'coolicon'],
['icon' => 'list-unordered', 'prefix' => 'coolicon'],
['icon' => 'columns', 'prefix' => 'coolicon'],
['icon' => 'rows', 'prefix' => 'coolicon'],
['icon' => 'table', 'prefix' => 'coolicon'],
['icon' => 'table-add', 'prefix' => 'coolicon'],
['icon' => 'table-remove', 'prefix' => 'coolicon'],
['icon' => 'figma', 'prefix' => 'coolicon'],
['icon' => 'swatches-palette', 'prefix' => 'coolicon'],
['icon' => 'ruler', 'prefix' => 'coolicon'],
['icon' => 'path', 'prefix' => 'coolicon'],
['icon' => 'text', 'prefix' => 'coolicon'],
['icon' => 'font', 'prefix' => 'coolicon'],
['icon' => 'bold', 'prefix' => 'coolicon'],
['icon' => 'italic', 'prefix' => 'coolicon'],
['icon' => 'underline', 'prefix' => 'coolicon'],
['icon' => 'strikethrough', 'prefix' => 'coolicon'],
['icon' => 'paragraph', 'prefix' => 'coolicon'],
['icon' => 'heading', 'prefix' => 'coolicon'],
['icon' => 'text-align-left', 'prefix' => 'coolicon'],
['icon' => 'text-align-center', 'prefix' => 'coolicon'],
['icon' => 'text-align-right', 'prefix' => 'coolicon'],
['icon' => 'text-align-justify', 'prefix' => 'coolicon'],
['icon' => 'undo', 'prefix' => 'coolicon'],
['icon' => 'redo', 'prefix' => 'coolicon'],
['icon' => 'log-out', 'prefix' => 'coolicon'],
['icon' => 'exit', 'prefix' => 'coolicon'],
['icon' => 'paper-plane', 'prefix' => 'coolicon'],
['icon' => 'loading', 'prefix' => 'coolicon'],
['icon' => 'arrows-reload-01', 'prefix' => 'coolicon'],
['icon' => 'arrow-reload-02', 'prefix' => 'coolicon'],
// Material Design Icons (postare/blade-mdi)
// Server & Infrastructure
['icon' => 'server', 'prefix' => 'mdi'],
['icon' => 'server-network', 'prefix' => 'mdi'],
['icon' => 'server-network-off', 'prefix' => 'mdi'],
['icon' => 'server-off', 'prefix' => 'mdi'],
['icon' => 'server-security', 'prefix' => 'mdi'],
['icon' => 'database', 'prefix' => 'mdi'],
['icon' => 'database-outline', 'prefix' => 'mdi'],
['icon' => 'database-cog', 'prefix' => 'mdi'],
['icon' => 'database-sync', 'prefix' => 'mdi'],
['icon' => 'database-lock', 'prefix' => 'mdi'],
['icon' => 'database-check', 'prefix' => 'mdi'],
['icon' => 'database-search', 'prefix' => 'mdi'],
['icon' => 'database-export', 'prefix' => 'mdi'],
['icon' => 'database-import', 'prefix' => 'mdi'],
['icon' => 'harddisk', 'prefix' => 'mdi'],
['icon' => 'harddisk-plus', 'prefix' => 'mdi'],
['icon' => 'harddisk-remove', 'prefix' => 'mdi'],
['icon' => 'memory', 'prefix' => 'mdi'],
['icon' => 'chip', 'prefix' => 'mdi'],
['icon' => 'cpu-32-bit', 'prefix' => 'mdi'],
['icon' => 'cpu-64-bit', 'prefix' => 'mdi'],
// Cloud
['icon' => 'cloud', 'prefix' => 'mdi'],
['icon' => 'cloud-outline', 'prefix' => 'mdi'],
['icon' => 'cloud-check', 'prefix' => 'mdi'],
['icon' => 'cloud-sync', 'prefix' => 'mdi'],
['icon' => 'cloud-download', 'prefix' => 'mdi'],
['icon' => 'cloud-upload', 'prefix' => 'mdi'],
['icon' => 'cloud-lock', 'prefix' => 'mdi'],
['icon' => 'cloud-cog', 'prefix' => 'mdi'],
['icon' => 'cloud-refresh', 'prefix' => 'mdi'],
['icon' => 'cloud-off', 'prefix' => 'mdi'],
['icon' => 'cloud-braces', 'prefix' => 'mdi'],
['icon' => 'clouds', 'prefix' => 'mdi'],
// Network
['icon' => 'lan', 'prefix' => 'mdi'],
['icon' => 'lan-connect', 'prefix' => 'mdi'],
['icon' => 'lan-disconnect', 'prefix' => 'mdi'],
['icon' => 'lan-check', 'prefix' => 'mdi'],
['icon' => 'lan-pending', 'prefix' => 'mdi'],
['icon' => 'ethernet', 'prefix' => 'mdi'],
['icon' => 'ethernet-cable', 'prefix' => 'mdi'],
['icon' => 'ethernet-off', 'prefix' => 'mdi'],
['icon' => 'wifi', 'prefix' => 'mdi'],
['icon' => 'wifi-off', 'prefix' => 'mdi'],
['icon' => 'wifi-strength-4', 'prefix' => 'mdi'],
['icon' => 'wifi-strength-3', 'prefix' => 'mdi'],
['icon' => 'wifi-strength-2', 'prefix' => 'mdi'],
['icon' => 'wifi-strength-1', 'prefix' => 'mdi'],
['icon' => 'router', 'prefix' => 'mdi'],
['icon' => 'router-network', 'prefix' => 'mdi'],
['icon' => 'router-wireless', 'prefix' => 'mdi'],
['icon' => 'router-wireless-settings', 'prefix' => 'mdi'],
['icon' => 'ip', 'prefix' => 'mdi'],
['icon' => 'ip-network', 'prefix' => 'mdi'],
['icon' => 'ip-outline', 'prefix' => 'mdi'],
['icon' => 'dns', 'prefix' => 'mdi'],
['icon' => 'dns-outline', 'prefix' => 'mdi'],
['icon' => 'antenna', 'prefix' => 'mdi'],
['icon' => 'access-point', 'prefix' => 'mdi'],
['icon' => 'access-point-network', 'prefix' => 'mdi'],
['icon' => 'broadcast', 'prefix' => 'mdi'],
['icon' => 'broadcast-off', 'prefix' => 'mdi'],
['icon' => 'network', 'prefix' => 'mdi'],
['icon' => 'network-outline', 'prefix' => 'mdi'],
['icon' => 'network-off', 'prefix' => 'mdi'],
['icon' => 'network-off-outline', 'prefix' => 'mdi'],
// Security
['icon' => 'shield', 'prefix' => 'mdi'],
['icon' => 'shield-outline', 'prefix' => 'mdi'],
['icon' => 'shield-check', 'prefix' => 'mdi'],
['icon' => 'shield-lock', 'prefix' => 'mdi'],
['icon' => 'shield-key', 'prefix' => 'mdi'],
['icon' => 'shield-account', 'prefix' => 'mdi'],
['icon' => 'lock', 'prefix' => 'mdi'],
['icon' => 'lock-outline', 'prefix' => 'mdi'],
['icon' => 'lock-open', 'prefix' => 'mdi'],
['icon' => 'lock-open-outline', 'prefix' => 'mdi'],
['icon' => 'key', 'prefix' => 'mdi'],
['icon' => 'key-outline', 'prefix' => 'mdi'],
['icon' => 'key-variant', 'prefix' => 'mdi'],
['icon' => 'key-chain', 'prefix' => 'mdi'],
['icon' => 'certificate', 'prefix' => 'mdi'],
['icon' => 'certificate-outline', 'prefix' => 'mdi'],
['icon' => 'security', 'prefix' => 'mdi'],
['icon' => 'security-network', 'prefix' => 'mdi'],
['icon' => 'fingerprint', 'prefix' => 'mdi'],
['icon' => 'two-factor-authentication', 'prefix' => 'mdi'],
// Development & Code
['icon' => 'code-braces', 'prefix' => 'mdi'],
['icon' => 'code-brackets', 'prefix' => 'mdi'],
['icon' => 'code-tags', 'prefix' => 'mdi'],
['icon' => 'code-json', 'prefix' => 'mdi'],
['icon' => 'code-string', 'prefix' => 'mdi'],
['icon' => 'code-array', 'prefix' => 'mdi'],
['icon' => 'console', 'prefix' => 'mdi'],
['icon' => 'console-line', 'prefix' => 'mdi'],
['icon' => 'console-network', 'prefix' => 'mdi'],
['icon' => 'powershell', 'prefix' => 'mdi'],
['icon' => 'bash', 'prefix' => 'mdi'],
['icon' => 'api', 'prefix' => 'mdi'],
['icon' => 'api-off', 'prefix' => 'mdi'],
['icon' => 'webhook', 'prefix' => 'mdi'],
['icon' => 'function', 'prefix' => 'mdi'],
['icon' => 'function-variant', 'prefix' => 'mdi'],
['icon' => 'variable', 'prefix' => 'mdi'],
['icon' => 'variable-box', 'prefix' => 'mdi'],
['icon' => 'xml', 'prefix' => 'mdi'],
['icon' => 'graphql', 'prefix' => 'mdi'],
// Containers & DevOps
['icon' => 'docker', 'prefix' => 'mdi'],
['icon' => 'kubernetes', 'prefix' => 'mdi'],
['icon' => 'package', 'prefix' => 'mdi'],
['icon' => 'package-variant', 'prefix' => 'mdi'],
['icon' => 'package-variant-closed', 'prefix' => 'mdi'],
['icon' => 'cube', 'prefix' => 'mdi'],
['icon' => 'cube-outline', 'prefix' => 'mdi'],
['icon' => 'cube-scan', 'prefix' => 'mdi'],
['icon' => 'cube-send', 'prefix' => 'mdi'],
['icon' => 'layers', 'prefix' => 'mdi'],
['icon' => 'layers-outline', 'prefix' => 'mdi'],
['icon' => 'layers-triple', 'prefix' => 'mdi'],
['icon' => 'layers-triple-outline', 'prefix' => 'mdi'],
// Operating Systems & Platforms
['icon' => 'linux', 'prefix' => 'mdi'],
['icon' => 'ubuntu', 'prefix' => 'mdi'],
['icon' => 'debian', 'prefix' => 'mdi'],
['icon' => 'fedora', 'prefix' => 'mdi'],
['icon' => 'redhat', 'prefix' => 'mdi'],
['icon' => 'centos', 'prefix' => 'mdi'],
['icon' => 'microsoft-windows', 'prefix' => 'mdi'],
['icon' => 'apple', 'prefix' => 'mdi'],
['icon' => 'android', 'prefix' => 'mdi'],
['icon' => 'freebsd', 'prefix' => 'mdi'],
// Version Control
['icon' => 'git', 'prefix' => 'mdi'],
['icon' => 'github', 'prefix' => 'mdi'],
['icon' => 'gitlab', 'prefix' => 'mdi'],
['icon' => 'source-branch', 'prefix' => 'mdi'],
['icon' => 'source-commit', 'prefix' => 'mdi'],
['icon' => 'source-merge', 'prefix' => 'mdi'],
['icon' => 'source-pull', 'prefix' => 'mdi'],
['icon' => 'source-fork', 'prefix' => 'mdi'],
// Devices
['icon' => 'monitor', 'prefix' => 'mdi'],
['icon' => 'monitor-multiple', 'prefix' => 'mdi'],
['icon' => 'monitor-dashboard', 'prefix' => 'mdi'],
['icon' => 'desktop-tower', 'prefix' => 'mdi'],
['icon' => 'desktop-tower-monitor', 'prefix' => 'mdi'],
['icon' => 'desktop-classic', 'prefix' => 'mdi'],
['icon' => 'laptop', 'prefix' => 'mdi'],
['icon' => 'cellphone', 'prefix' => 'mdi'],
['icon' => 'tablet', 'prefix' => 'mdi'],
['icon' => 'television', 'prefix' => 'mdi'],
// Power & Energy
['icon' => 'power', 'prefix' => 'mdi'],
['icon' => 'power-plug', 'prefix' => 'mdi'],
['icon' => 'power-plug-off', 'prefix' => 'mdi'],
['icon' => 'power-cycle', 'prefix' => 'mdi'],
['icon' => 'power-on', 'prefix' => 'mdi'],
['icon' => 'power-off', 'prefix' => 'mdi'],
['icon' => 'power-standby', 'prefix' => 'mdi'],
['icon' => 'bolt', 'prefix' => 'mdi'],
['icon' => 'lightning-bolt', 'prefix' => 'mdi'],
['icon' => 'lightning-bolt-circle', 'prefix' => 'mdi'],
['icon' => 'battery', 'prefix' => 'mdi'],
['icon' => 'battery-charging', 'prefix' => 'mdi'],
['icon' => 'battery-outline', 'prefix' => 'mdi'],
// Actions & Operations
['icon' => 'sync', 'prefix' => 'mdi'],
['icon' => 'sync-circle', 'prefix' => 'mdi'],
['icon' => 'sync-off', 'prefix' => 'mdi'],
['icon' => 'refresh', 'prefix' => 'mdi'],
['icon' => 'refresh-circle', 'prefix' => 'mdi'],
['icon' => 'reload', 'prefix' => 'mdi'],
['icon' => 'restart', 'prefix' => 'mdi'],
['icon' => 'backup-restore', 'prefix' => 'mdi'],
['icon' => 'download', 'prefix' => 'mdi'],
['icon' => 'download-outline', 'prefix' => 'mdi'],
['icon' => 'download-circle', 'prefix' => 'mdi'],
['icon' => 'upload', 'prefix' => 'mdi'],
['icon' => 'upload-outline', 'prefix' => 'mdi'],
['icon' => 'upload-circle', 'prefix' => 'mdi'],
['icon' => 'content-copy', 'prefix' => 'mdi'],
['icon' => 'content-paste', 'prefix' => 'mdi'],
['icon' => 'content-cut', 'prefix' => 'mdi'],
// Settings & Configuration
['icon' => 'cog', 'prefix' => 'mdi'],
['icon' => 'cog-outline', 'prefix' => 'mdi'],
['icon' => 'cogs', 'prefix' => 'mdi'],
['icon' => 'cog-transfer', 'prefix' => 'mdi'],
['icon' => 'cog-sync', 'prefix' => 'mdi'],
['icon' => 'wrench', 'prefix' => 'mdi'],
['icon' => 'wrench-outline', 'prefix' => 'mdi'],
['icon' => 'hammer-wrench', 'prefix' => 'mdi'],
['icon' => 'toolbox', 'prefix' => 'mdi'],
['icon' => 'toolbox-outline', 'prefix' => 'mdi'],
['icon' => 'tune', 'prefix' => 'mdi'],
['icon' => 'tune-vertical', 'prefix' => 'mdi'],
// Files & Folders
['icon' => 'folder', 'prefix' => 'mdi'],
['icon' => 'folder-outline', 'prefix' => 'mdi'],
['icon' => 'folder-open', 'prefix' => 'mdi'],
['icon' => 'folder-open-outline', 'prefix' => 'mdi'],
['icon' => 'folder-network', 'prefix' => 'mdi'],
['icon' => 'folder-network-outline', 'prefix' => 'mdi'],
['icon' => 'folder-cog', 'prefix' => 'mdi'],
['icon' => 'folder-lock', 'prefix' => 'mdi'],
['icon' => 'folder-zip', 'prefix' => 'mdi'],
['icon' => 'file', 'prefix' => 'mdi'],
['icon' => 'file-outline', 'prefix' => 'mdi'],
['icon' => 'file-document', 'prefix' => 'mdi'],
['icon' => 'file-document-outline', 'prefix' => 'mdi'],
['icon' => 'file-code', 'prefix' => 'mdi'],
['icon' => 'file-code-outline', 'prefix' => 'mdi'],
['icon' => 'file-cog', 'prefix' => 'mdi'],
['icon' => 'file-lock', 'prefix' => 'mdi'],
// Charts & Analytics
['icon' => 'chart-line', 'prefix' => 'mdi'],
['icon' => 'chart-bar', 'prefix' => 'mdi'],
['icon' => 'chart-pie', 'prefix' => 'mdi'],
['icon' => 'chart-donut', 'prefix' => 'mdi'],
['icon' => 'chart-areaspline', 'prefix' => 'mdi'],
['icon' => 'chart-box', 'prefix' => 'mdi'],
['icon' => 'chart-timeline', 'prefix' => 'mdi'],
['icon' => 'chart-gantt', 'prefix' => 'mdi'],
['icon' => 'poll', 'prefix' => 'mdi'],
['icon' => 'finance', 'prefix' => 'mdi'],
['icon' => 'trending-up', 'prefix' => 'mdi'],
['icon' => 'trending-down', 'prefix' => 'mdi'],
['icon' => 'trending-neutral', 'prefix' => 'mdi'],
// Users & Accounts
['icon' => 'account', 'prefix' => 'mdi'],
['icon' => 'account-outline', 'prefix' => 'mdi'],
['icon' => 'account-circle', 'prefix' => 'mdi'],
['icon' => 'account-circle-outline', 'prefix' => 'mdi'],
['icon' => 'account-group', 'prefix' => 'mdi'],
['icon' => 'account-group-outline', 'prefix' => 'mdi'],
['icon' => 'account-multiple', 'prefix' => 'mdi'],
['icon' => 'account-key', 'prefix' => 'mdi'],
['icon' => 'account-lock', 'prefix' => 'mdi'],
['icon' => 'account-cog', 'prefix' => 'mdi'],
// Communication
['icon' => 'email', 'prefix' => 'mdi'],
['icon' => 'email-outline', 'prefix' => 'mdi'],
['icon' => 'email-open', 'prefix' => 'mdi'],
['icon' => 'message', 'prefix' => 'mdi'],
['icon' => 'message-outline', 'prefix' => 'mdi'],
['icon' => 'chat', 'prefix' => 'mdi'],
['icon' => 'chat-outline', 'prefix' => 'mdi'],
['icon' => 'forum', 'prefix' => 'mdi'],
['icon' => 'forum-outline', 'prefix' => 'mdi'],
// Notifications
['icon' => 'bell', 'prefix' => 'mdi'],
['icon' => 'bell-outline', 'prefix' => 'mdi'],
['icon' => 'bell-ring', 'prefix' => 'mdi'],
['icon' => 'bell-ring-outline', 'prefix' => 'mdi'],
['icon' => 'bell-badge', 'prefix' => 'mdi'],
['icon' => 'bell-off', 'prefix' => 'mdi'],
['icon' => 'bell-alert', 'prefix' => 'mdi'],
// Status & Alerts
['icon' => 'check', 'prefix' => 'mdi'],
['icon' => 'check-bold', 'prefix' => 'mdi'],
['icon' => 'check-circle', 'prefix' => 'mdi'],
['icon' => 'check-circle-outline', 'prefix' => 'mdi'],
['icon' => 'check-all', 'prefix' => 'mdi'],
['icon' => 'check-decagram', 'prefix' => 'mdi'],
['icon' => 'close', 'prefix' => 'mdi'],
['icon' => 'close-circle', 'prefix' => 'mdi'],
['icon' => 'close-circle-outline', 'prefix' => 'mdi'],
['icon' => 'alert', 'prefix' => 'mdi'],
['icon' => 'alert-circle', 'prefix' => 'mdi'],
['icon' => 'alert-circle-outline', 'prefix' => 'mdi'],
['icon' => 'alert-octagon', 'prefix' => 'mdi'],
['icon' => 'alert-decagram', 'prefix' => 'mdi'],
['icon' => 'information', 'prefix' => 'mdi'],
['icon' => 'information-outline', 'prefix' => 'mdi'],
['icon' => 'help-circle', 'prefix' => 'mdi'],
['icon' => 'help-circle-outline', 'prefix' => 'mdi'],
// Time & Calendar
['icon' => 'clock', 'prefix' => 'mdi'],
['icon' => 'clock-outline', 'prefix' => 'mdi'],
['icon' => 'clock-fast', 'prefix' => 'mdi'],
['icon' => 'clock-alert', 'prefix' => 'mdi'],
['icon' => 'timer', 'prefix' => 'mdi'],
['icon' => 'timer-outline', 'prefix' => 'mdi'],
['icon' => 'calendar', 'prefix' => 'mdi'],
['icon' => 'calendar-outline', 'prefix' => 'mdi'],
['icon' => 'calendar-clock', 'prefix' => 'mdi'],
['icon' => 'calendar-check', 'prefix' => 'mdi'],
['icon' => 'history', 'prefix' => 'mdi'],
// Navigation & Search
['icon' => 'magnify', 'prefix' => 'mdi'],
['icon' => 'magnify-plus', 'prefix' => 'mdi'],
['icon' => 'magnify-minus', 'prefix' => 'mdi'],
['icon' => 'magnify-scan', 'prefix' => 'mdi'],
['icon' => 'eye', 'prefix' => 'mdi'],
['icon' => 'eye-outline', 'prefix' => 'mdi'],
['icon' => 'eye-off', 'prefix' => 'mdi'],
['icon' => 'eye-off-outline', 'prefix' => 'mdi'],
['icon' => 'filter', 'prefix' => 'mdi'],
['icon' => 'filter-outline', 'prefix' => 'mdi'],
['icon' => 'sort', 'prefix' => 'mdi'],
['icon' => 'sort-ascending', 'prefix' => 'mdi'],
['icon' => 'sort-descending', 'prefix' => 'mdi'],
// World & Globe
['icon' => 'earth', 'prefix' => 'mdi'],
['icon' => 'earth-arrow-right', 'prefix' => 'mdi'],
['icon' => 'earth-box', 'prefix' => 'mdi'],
['icon' => 'earth-off', 'prefix' => 'mdi'],
['icon' => 'web', 'prefix' => 'mdi'],
['icon' => 'web-box', 'prefix' => 'mdi'],
['icon' => 'web-clock', 'prefix' => 'mdi'],
['icon' => 'domain', 'prefix' => 'mdi'],
// Home & Building
['icon' => 'home', 'prefix' => 'mdi'],
['icon' => 'home-outline', 'prefix' => 'mdi'],
['icon' => 'home-city', 'prefix' => 'mdi'],
['icon' => 'home-city-outline', 'prefix' => 'mdi'],
['icon' => 'office-building', 'prefix' => 'mdi'],
['icon' => 'office-building-outline', 'prefix' => 'mdi'],
['icon' => 'office-building-cog', 'prefix' => 'mdi'],
['icon' => 'domain', 'prefix' => 'mdi'],
// Favorites & Ratings
['icon' => 'star', 'prefix' => 'mdi'],
['icon' => 'star-outline', 'prefix' => 'mdi'],
['icon' => 'star-circle', 'prefix' => 'mdi'],
['icon' => 'star-circle-outline', 'prefix' => 'mdi'],
['icon' => 'heart', 'prefix' => 'mdi'],
['icon' => 'heart-outline', 'prefix' => 'mdi'],
['icon' => 'thumb-up', 'prefix' => 'mdi'],
['icon' => 'thumb-up-outline', 'prefix' => 'mdi'],
['icon' => 'thumb-down', 'prefix' => 'mdi'],
['icon' => 'thumb-down-outline', 'prefix' => 'mdi'],
// Misc
['icon' => 'rocket', 'prefix' => 'mdi'],
['icon' => 'rocket-launch', 'prefix' => 'mdi'],
['icon' => 'rocket-launch-outline', 'prefix' => 'mdi'],
['icon' => 'fire', 'prefix' => 'mdi'],
['icon' => 'fire-circle', 'prefix' => 'mdi'],
['icon' => 'bug', 'prefix' => 'mdi'],
['icon' => 'bug-outline', 'prefix' => 'mdi'],
['icon' => 'bug-check', 'prefix' => 'mdi'],
['icon' => 'puzzle', 'prefix' => 'mdi'],
['icon' => 'puzzle-outline', 'prefix' => 'mdi'],
['icon' => 'tag', 'prefix' => 'mdi'],
['icon' => 'tag-outline', 'prefix' => 'mdi'],
['icon' => 'tag-multiple', 'prefix' => 'mdi'],
['icon' => 'bookmark', 'prefix' => 'mdi'],
['icon' => 'bookmark-outline', 'prefix' => 'mdi'],
['icon' => 'link', 'prefix' => 'mdi'],
['icon' => 'link-variant', 'prefix' => 'mdi'],
['icon' => 'link-off', 'prefix' => 'mdi'],
['icon' => 'view-dashboard', 'prefix' => 'mdi'],
['icon' => 'view-dashboard-outline', 'prefix' => 'mdi'],
['icon' => 'view-grid', 'prefix' => 'mdi'],
['icon' => 'view-grid-outline', 'prefix' => 'mdi'],
['icon' => 'view-list', 'prefix' => 'mdi'],
['icon' => 'view-list-outline', 'prefix' => 'mdi'],
['icon' => 'menu', 'prefix' => 'mdi'],
['icon' => 'dots-horizontal', 'prefix' => 'mdi'],
['icon' => 'dots-vertical', 'prefix' => 'mdi'],
['icon' => 'apps', 'prefix' => 'mdi'],
['icon' => 'apps-box', 'prefix' => 'mdi'],
['icon' => 'application', 'prefix' => 'mdi'],
['icon' => 'application-outline', 'prefix' => 'mdi'],
['icon' => 'window-maximize', 'prefix' => 'mdi'],
['icon' => 'window-minimize', 'prefix' => 'mdi'],
['icon' => 'window-restore', 'prefix' => 'mdi'],
['icon' => 'monitor-cellphone', 'prefix' => 'mdi'],
['icon' => 'cellphone-link', 'prefix' => 'mdi'],
['icon' => 'responsive', 'prefix' => 'mdi'],
['icon' => 'table', 'prefix' => 'mdi'],
['icon' => 'table-large', 'prefix' => 'mdi'],
['icon' => 'table-cog', 'prefix' => 'mdi'],
['icon' => 'archive', 'prefix' => 'mdi'],
['icon' => 'archive-outline', 'prefix' => 'mdi'],
['icon' => 'archive-cog', 'prefix' => 'mdi'],
['icon' => 'sun-wireless', 'prefix' => 'mdi'],
['icon' => 'white-balance-sunny', 'prefix' => 'mdi'],
['icon' => 'weather-night', 'prefix' => 'mdi'],
['icon' => 'moon-waning-crescent', 'prefix' => 'mdi'],
['icon' => 'camera', 'prefix' => 'mdi'],
['icon' => 'camera-outline', 'prefix' => 'mdi'],
['icon' => 'image', 'prefix' => 'mdi'],
['icon' => 'image-outline', 'prefix' => 'mdi'],
['icon' => 'image-multiple', 'prefix' => 'mdi'],
['icon' => 'video', 'prefix' => 'mdi'],
['icon' => 'video-outline', 'prefix' => 'mdi'],
['icon' => 'music', 'prefix' => 'mdi'],
['icon' => 'music-note', 'prefix' => 'mdi'],
['icon' => 'microphone', 'prefix' => 'mdi'],
['icon' => 'microphone-outline', 'prefix' => 'mdi'],
['icon' => 'headphones', 'prefix' => 'mdi'],
['icon' => 'speaker', 'prefix' => 'mdi'],
['icon' => 'play', 'prefix' => 'mdi'],
['icon' => 'pause', 'prefix' => 'mdi'],
['icon' => 'stop', 'prefix' => 'mdi'],
['icon' => 'skip-next', 'prefix' => 'mdi'],
['icon' => 'skip-previous', 'prefix' => 'mdi'],
['icon' => 'fast-forward', 'prefix' => 'mdi'],
['icon' => 'rewind', 'prefix' => 'mdi'],
];
@endphp
@foreach($iconList as $iconData)
<button x-show="isIconVisible('{{ $iconData['prefix'] }}', '{{ $iconData['icon'] }}')" @click="icon = '{{ $iconData['icon'] }}'; iconPrefix = '{{ $iconData['prefix'] }}'" :class="icon === '{{ $iconData['icon'] }}' && iconPrefix === '{{ $iconData['prefix'] }}' ? 'ring-2 ring-primary' : ''" class="icon-btn rounded p-2 border flex items-center justify-center hover:bg-secondary/10 dark:border-base/20" data-icon="{{ $iconData['icon'] }}" data-prefix="{{ $iconData['prefix'] }}">
@php
if ($iconData['prefix'] === 'vaadin') {
$componentName = 'vaadin-' . str_replace('_', '-', $iconData['icon']);
} else {
$componentName = $iconData['prefix'] . '-' . $iconData['icon'];
}
@endphp
<x-dynamic-component :component="$componentName" class="size-5" />
</button>
@endforeach
</div>
<div class="flex flex-wrap gap-2 mb-4">
<template x-for="c in colors" :key="c">
<button @click="color = c" :style="'background:' + (c || '#e5e7eb')" :class="color === c ? 'ring-2 ring-primary' : ''" class="size-6 rounded-full border"></button>
</template>
</div>
<div class="flex gap-2 justify-end">
<button @click="resetIcon()" class="text-xs text-base/60 hover:text-danger">Reset</button>
<button @click="saveIcon()" class="px-3 py-1 rounded bg-primary text-white text-xs hover:bg-primary/80">Save</button>
</div>
<template x-if="message">
<div class="mt-2 text-xs text-success" x-text="message"></div>
</template>
</div>
</div>
<script>
function serverIconModal({serverId, initialIcon, initialColor, initialPrefix}) {
return {
show: false,
icon: initialIcon || '',
iconPrefix: initialPrefix || 'ri',
color: initialColor || '',
search: '',
message: '',
colors: [
'', '#ef4444', '#f97316', '#f59e0b', '#eab308', '#84cc16', '#22c55e', '#10b981',
'#14b8a6', '#06b6d4', '#0ea5e9', '#3b82f6', '#6366f1', '#8b5cf6', '#a855f7',
'#d946ef', '#ec4899', '#f43f5e', '#78716c', '#64748b'
],
openModal() {
this.show = true;
this.message = '';
this.search = '';
},
closeModal() { this.show = false; },
resetIcon() {
this.icon = '';
this.iconPrefix = 'ri';
this.color = '';
},
isIconVisible(prefix, iconName) {
if (!this.search) return true;
const query = this.search.toLowerCase();
const searchText = (prefix + '-' + iconName).toLowerCase();
return searchText.includes(query) || iconName.toLowerCase().includes(query);
},
getIconSvg(iconName, prefix) {
const btn = document.querySelector(`.icon-btn[data-icon="${iconName}"][data-prefix="${prefix}"]`);
if (btn) {
const svg = btn.querySelector('svg');
if (svg) {
const cloned = svg.cloneNode(true);
cloned.classList.remove('size-5');
cloned.classList.add('size-8');
return cloned.outerHTML;
}
}
return '';
},
saveIcon() {
fetch(`/proxmox/server/${serverId}/icon`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content
},
body: JSON.stringify({ icon: this.icon, color: this.color, prefix: this.iconPrefix })
})
.then(r => r.json())
.then(data => {
if (data.success) {
this.message = 'Icon saved!';
setTimeout(() => { this.closeModal(); location.reload(); }, 800);
}
});
}
}
}
</script>
</div>
<div class="hidden lg:flex flex-row gap-2 ml-auto justify-end items-center">
<button wire:click="do('start')"
wire:loading.attr="disabled"
@if($vmStatus !== 'stopped') disabled @endif
class="lte-btn lte-btn-success"
style="border-radius: 0.375rem;">
<span wire:loading wire:target="do('start')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-play-fill wire:loading.remove wire:target="do('start')" class="size-4" />
<span wire:loading.remove wire:target="do('start')">Start</span>
<span wire:loading wire:target="do('start')">Starting...</span>
</button>
<div x-show="isAdvancedView" x-transition>
<button wire:click="do('shutdown')"
wire:loading.attr="disabled"
@if($vmStatus !== 'running') disabled @endif
class="lte-btn lte-btn-warning"
style="border-radius: 0.375rem;">
<span wire:loading wire:target="do('shutdown')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-stop-fill wire:loading.remove wire:target="do('shutdown')" class="size-4" />
<span wire:loading.remove wire:target="do('shutdown')">Stop (ACPI)</span>
<span wire:loading wire:target="do('shutdown')">Stopping...</span>
</button>
</div>
<div x-show="isAdvancedView" x-transition>
<button wire:click="do('reboot')"
wire:loading.attr="disabled"
@if($vmStatus !== 'running') disabled @endif
class="lte-btn lte-btn-info"
style="border-radius: 0.375rem;">
<span wire:loading wire:target="do('reboot')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-restart-line wire:loading.remove wire:target="do('reboot')" class="size-4" />
<span wire:loading.remove wire:target="do('reboot')">Reboot (ACPI)</span>
<span wire:loading wire:target="do('reboot')">Rebooting...</span>
</button>
</div>
<button wire:click="do('stop')"
wire:loading.attr="disabled"
@if($vmStatus !== 'running') disabled @endif
class="lte-btn lte-btn-danger"
style="border-radius: 0.375rem;">
<span wire:loading wire:target="do('stop')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-shut-down-line wire:loading.remove wire:target="do('stop')" class="size-4" />
<span wire:loading.remove wire:target="do('stop')">Power Off</span>
<span wire:loading wire:target="do('stop')">Powering Off...</span>
</button>
</div>
<div class="flex lg:hidden w-full flex-col gap-2">
<div class="flex flex-col gap-2 w-full">
<button wire:click="do('start')"
wire:loading.attr="disabled"
@if($vmStatus !== 'stopped') disabled @endif
class="lte-btn lte-btn-success"
style="width: 100%; justify-content: center; border-radius: 0.375rem; padding: 0.625rem 1rem;">
<span wire:loading wire:target="do('start')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-play-fill wire:loading.remove wire:target="do('start')" class="size-4" />
<span wire:loading.remove wire:target="do('start')">Start</span>
<span wire:loading wire:target="do('start')">Starting...</span>
</button>
<div x-show="isAdvancedView" x-transition>
<button wire:click="do('shutdown')"
wire:loading.attr="disabled"
@if($vmStatus !== 'running') disabled @endif
class="lte-btn lte-btn-warning"
style="width: 100%; justify-content: center; border-radius: 0.375rem; padding: 0.625rem 1rem;">
<span wire:loading wire:target="do('shutdown')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-stop-fill wire:loading.remove wire:target="do('shutdown')" class="size-4" />
<span wire:loading.remove wire:target="do('shutdown')">Stop (ACPI)</span>
<span wire:loading wire:target="do('shutdown')">Stopping...</span>
</button>
</div>
<div x-show="isAdvancedView" x-transition>
<button wire:click="do('reboot')"
wire:loading.attr="disabled"
@if($vmStatus !== 'running') disabled @endif
class="lte-btn lte-btn-info"
style="width: 100%; justify-content: center; border-radius: 0.375rem; padding: 0.625rem 1rem;">
<span wire:loading wire:target="do('reboot')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-restart-line wire:loading.remove wire:target="do('reboot')" class="size-4" />
<span wire:loading.remove wire:target="do('reboot')">Reboot (ACPI)</span>
<span wire:loading wire:target="do('reboot')">Rebooting...</span>
</button>
</div>
<button wire:click="do('stop')"
wire:loading.attr="disabled"
@if($vmStatus !== 'running') disabled @endif
class="lte-btn lte-btn-danger"
style="width: 100%; justify-content: center; border-radius: 0.375rem; padding: 0.625rem 1rem;">
<span wire:loading wire:target="do('stop')" class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-shut-down-line wire:loading.remove wire:target="do('stop')" class="size-4" />
<span wire:loading.remove wire:target="do('stop')">Power Off</span>
<span wire:loading wire:target="do('stop')">Powering Off...</span>
</button>
</div>
</div>
</div>
@if ($view !== 'statistics')
<!-- Preload Statistics/Telemetry data when entering the server page -->
<div
x-init="
if (window.fetch) {
// Preload statistics immediately for faster tab switching
setTimeout(function () {
try {
fetch('{{ route('proxmox.server.statistics', ['server' => $server->id]) }}', {
credentials: 'same-origin',
headers: { 'X-Proxmox-Preload': 'true' },
});
} catch (e) {}
}, 100);
// Also preload the telemetry stats data
setTimeout(function () {
try {
fetch('{{ route('proxmox.server.statistics', ['server' => $server->id]) }}?preload_charts=1', {
credentials: 'same-origin',
headers: { 'X-Proxmox-Preload': 'true', 'X-Preload-Charts': 'true' },
});
} catch (e) {}
}, 300);
}
"
class="hidden"
></div>
@endif
@php
// Build tabs dynamically with logical organization for speed and usability
$tabs = [];
// 1. Overview - Primary dashboard (always first, always visible)
$tabs[] = ['label' => 'Overview', 'view' => 'overview', 'route' => route('proxmox.server.overview', ['server' => $server->id]), 'icon' => 'ri-dashboard-line'];
// 2. Console - Quick access for remote management (if VNC enabled)
if($server->node->vnc_enabled) {
$tabs[] = [
'label' => 'Console',
'dropdown' => true,
'icon' => 'ri-terminal-box-line',
'items' => [
['label' => 'NoVNC Console', 'view' => 'vnc', 'route' => route('proxmox.server.vnc', ['server' => $server->id]), 'icon' => 'ri-computer-line'],
['label' => 'Xterm.js Terminal', 'view' => 'xterm', 'route' => route('proxmox.server.xterm', ['server' => $server->id]), 'icon' => 'ri-terminal-line'],
]
];
}
// 3. Monitoring - Statistics and telemetry (now prominently visible)
$tabs[] = ['label' => 'Monitoring', 'view' => 'statistics', 'route' => route('proxmox.server.statistics', ['server' => $server->id]), 'icon' => 'ri-line-chart-line'];
// 4. Networking - All network related (dropdown)
$tabs[] = [
'label' => 'Networking',
'dropdown' => true,
'icon' => 'ri-global-line',
'items' => [
['label' => 'IP Configuration', 'view' => 'network', 'route' => route('proxmox.server.network', ['server' => $server->id]), 'icon' => 'ri-router-line'],
['label' => 'Firewall Rules', 'view' => 'firewall', 'route' => route('proxmox.server.firewall', ['server' => $server->id]), 'icon' => 'ri-shield-line', 'advanced' => true],
['label' => 'Subdomain', 'view' => 'subdomain', 'route' => route('proxmox.server.subdomain', ['server' => $server->id]), 'icon' => 'ri-link', 'advanced' => true],
]
];
// 5. Storage - All storage related (dropdown)
$storageItems = [
['label' => 'Disk Management', 'view' => 'storage', 'route' => route('proxmox.server.storage', ['server' => $server->id]), 'icon' => 'ri-hard-drive-2-line', 'advanced' => true],
];
if($server->service->product->settings()->where('key', 'backups')->first()?->value > 0) {
$storageItems[] = ['label' => 'Backups', 'view' => 'backups', 'route' => route('proxmox.server.backups', ['server' => $server->id]), 'icon' => 'ri-folder-shield-line'];
}
$tabs[] = [
'label' => 'Storage',
'dropdown' => true,
'icon' => 'ri-hard-drive-line',
'items' => $storageItems
];
// 6. System - Hardware configuration (dropdown)
$tabs[] = [
'label' => 'System',
'dropdown' => true,
'icon' => 'ri-settings-3-line',
'items' => [
['label' => 'Boot Order', 'view' => 'boot-order', 'route' => route('proxmox.server.boot-order', ['server' => $server->id]), 'icon' => 'ri-restart-line', 'advanced' => true],
['label' => 'Display', 'view' => 'display', 'route' => route('proxmox.server.display', ['server' => $server->id]), 'icon' => 'ri-computer-line', 'advanced' => true],
['label' => 'Hardware Config', 'view' => 'system', 'route' => route('proxmox.server.system', ['server' => $server->id]), 'icon' => 'ri-cpu-line', 'advanced' => true],
['label' => 'Cloud-init', 'view' => 'cloud-init', 'route' => route('proxmox.server.cloud-init', ['server' => $server->id]), 'icon' => 'ri-cloud-line', 'advanced' => true],
]
];
// 7. Help - Documentation (always last)
$tabs[] = ['label' => 'Help', 'view' => 'help', 'route' => route('proxmox.server.help', ['server' => $server->id]), 'icon' => 'ri-question-line', 'advanced' => true];
@endphp
<div class="mb-10 mt-3">
<style>
/* TailAdmin Navigation Styles */
.proxmox-nav-container {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(226, 232, 240, 0.8);
border-radius: 16px;
padding: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
}
.dark .proxmox-nav-container {
background: rgba(30, 41, 59, 0.9);
border-color: rgba(51, 65, 85, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
.proxmox-nav-tabs {
display: flex;
gap: 2px;
flex-wrap: wrap;
flex: 1;
align-items: center;
}
.proxmox-tab-link {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.875rem;
font-weight: 500;
padding: 10px 16px;
border-radius: 10px;
white-space: nowrap;
transition: all 0.15s ease;
color: #64748b;
background: transparent;
}
.proxmox-tab-link:hover {
background: rgba(70, 95, 255, 0.06);
color: #475569;
}
.dark .proxmox-tab-link {
color: #94a3b8;
}
.dark .proxmox-tab-link:hover {
background: rgba(70, 95, 255, 0.1);
color: #cbd5e1;
}
.proxmox-tab-link.active {
background: linear-gradient(135deg, #465fff 0%, #6366f1 100%);
color: white;
box-shadow: 0 2px 8px rgba(70, 95, 255, 0.3);
}
.dark .proxmox-tab-link.active {
background: linear-gradient(135deg, #465fff 0%, #818cf8 100%);
box-shadow: 0 2px 12px rgba(70, 95, 255, 0.4);
}
.proxmox-tab-link .tab-icon {
width: 16px;
height: 16px;
opacity: 0.7;
transition: opacity 0.15s ease;
}
.proxmox-tab-link.active .tab-icon,
.proxmox-tab-link:hover .tab-icon {
opacity: 1;
}
/* TailAdmin Toggle Switch */
.modern-toggle-container {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 6px 14px;
background: rgba(241, 245, 249, 0.8);
border: 1px solid rgba(226, 232, 240, 0.8);
border-radius: 10px;
transition: all 0.2s ease;
}
.dark .modern-toggle-container {
background: rgba(30, 41, 59, 0.6);
border-color: rgba(51, 65, 85, 0.6);
}
.modern-toggle-label {
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.03em;
text-transform: uppercase;
transition: all 0.15s ease;
user-select: none;
}
.modern-toggle-label.inactive {
color: #94a3b8;
}
.modern-toggle-label.active {
color: #465fff;
}
.dark .modern-toggle-label.active {
color: #818cf8;
}
.dark .modern-toggle-label.inactive {
color: #64748b;
}
.modern-toggle-switch {
position: relative;
width: 44px;
height: 24px;
background: #e2e8f0;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.dark .modern-toggle-switch {
background: #334155;
}
.modern-toggle-switch.active {
background: linear-gradient(135deg, #465fff 0%, #6366f1 100%);
box-shadow: 0 0 0 3px rgba(70, 95, 255, 0.15);
}
.dark .modern-toggle-switch.active {
background: linear-gradient(135deg, #465fff 0%, #818cf8 100%);
box-shadow: 0 0 0 3px rgba(70, 95, 255, 0.2);
}
.modern-toggle-handle {
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.modern-toggle-switch.active .modern-toggle-handle {
transform: translateX(20px);
}
.modern-toggle-switch.disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* Animation for tab transitions */
@keyframes tabSlideIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.proxmox-tab-wrapper {
animation: tabSlideIn 0.2s ease-out;
display: flex;
align-items: center;
}
/* TailAdmin Dropdown Styles */
.dropdown-wrapper {
position: relative;
display: flex;
align-items: center;
}
.dropdown-trigger {
display: flex;
align-items: center;
gap: 4px;
}
.dropdown-icon {
transition: transform 0.15s ease;
width: 14px;
height: 14px;
opacity: 0.6;
}
.dropdown-wrapper.open .dropdown-icon {
transform: rotate(180deg);
}
.dropdown-menu {
position: absolute;
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
min-width: 200px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
z-index: 100;
overflow: hidden;
padding: 6px;
}
.dark .dropdown-menu {
background: #1e293b;
border-color: #334155;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.2);
}
.dropdown-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.15s ease;
border-radius: 8px;
color: #475569;
}
.dark .dropdown-item {
color: #94a3b8;
}
.dropdown-item:hover {
background: rgba(70, 95, 255, 0.08);
color: #465fff;
}
.dark .dropdown-item:hover {
background: rgba(70, 95, 255, 0.12);
color: #818cf8;
}
.dropdown-item.active {
background: linear-gradient(135deg, rgba(70, 95, 255, 0.12) 0%, rgba(99, 102, 241, 0.08) 100%);
color: #465fff;
}
.dark .dropdown-item.active {
background: linear-gradient(135deg, rgba(70, 95, 255, 0.18) 0%, rgba(129, 140, 248, 0.12) 100%);
color: #818cf8;
}
.dropdown-item .item-icon {
width: 16px;
height: 16px;
opacity: 0.7;
}
.dropdown-item:hover .item-icon,
.dropdown-item.active .item-icon {
opacity: 1;
}
.dropdown-divider {
height: 1px;
background: #e2e8f0;
margin: 6px 0;
}
.dark .dropdown-divider {
background: #334155;
}
</style>
<div class="hidden lg:block">
<div class="proxmox-nav-container">
<div class="flex items-center justify-between gap-4">
<div class="proxmox-nav-tabs">
@foreach($tabs as $tabIndex => $tab)
@php
$isAdvancedTab = $tab['advanced'] ?? false;
$isDropdown = $tab['dropdown'] ?? false;
$tabIcon = $tab['icon'] ?? null;
// Check if current view is in dropdown items
$isDropdownActive = false;
$hasVisibleItems = false;
if ($isDropdown && isset($tab['items'])) {
foreach ($tab['items'] as $item) {
if ($view === $item['view']) {
$isDropdownActive = true;
}
$itemAdvanced = $item['advanced'] ?? false;
if (!$itemAdvanced) {
$hasVisibleItems = true;
}
}
}
@endphp
@if($isDropdown)
<div class="proxmox-tab-wrapper dropdown-wrapper"
x-data="{ open: false }"
@click.outside="open = false"
@if(!$hasVisibleItems)
x-show="isAdvancedView"
x-transition
@endif>
<button @click="open = !open"
class="proxmox-tab-link {{ $isDropdownActive ? 'active' : '' }}">
<span class="dropdown-trigger">
@if($tabIcon)
<x-dynamic-component :component="$tabIcon" class="tab-icon" />
@endif
{{ $tab['label'] }}
<svg class="dropdown-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</span>
</button>
<div x-show="open"
x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0 scale-95 -translate-y-1"
x-transition:enter-end="opacity-100 scale-100 translate-y-0"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="dropdown-menu">
@foreach($tab['items'] as $itemIndex => $item)
@php
$itemAdvanced = $item['advanced'] ?? false;
$itemIcon = $item['icon'] ?? null;
@endphp
<div @if($itemAdvanced)
x-show="isAdvancedView"
@endif>
<a href="{{ $item['route'] }}" wire:navigate
class="dropdown-item {{ $view === $item['view'] ? 'active' : '' }}"
@click="open = false">
@if($itemIcon)
<x-dynamic-component :component="$itemIcon" class="item-icon" />
@endif
{{ $item['label'] }}
</a>
@if($itemIndex < count($tab['items']) - 1 && $itemAdvanced && !($tab['items'][$itemIndex + 1]['advanced'] ?? false))
<div x-show="isAdvancedView" class="dropdown-divider"></div>
@endif
</div>
@endforeach
</div>
</div>
@else
<div class="proxmox-tab-wrapper"
@if($isAdvancedTab)
x-show="isAdvancedView"
x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@endif>
<a href="{{ $tab['route'] }}" wire:navigate
class="proxmox-tab-link {{ $view === $tab['view'] ? 'active' : '' }}">
@if($tabIcon)
<x-dynamic-component :component="$tabIcon" class="tab-icon" />
@endif
<span>{{ $tab['label'] }}</span>
</a>
</div>
@endif
@endforeach
</div>
<div class="modern-toggle-container"
:class="isAdvancedOnlyView ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''">
<span class="modern-toggle-label"
:class="!isAdvancedView ? 'active' : 'inactive'">
Standard
</span>
<div class="modern-toggle-switch"
:class="{ 'active': isAdvancedView, 'disabled': isAdvancedOnlyView }"
@click="!isAdvancedOnlyView && toggleView()">
<div class="modern-toggle-handle"></div>
</div>
<span class="modern-toggle-label"
:class="isAdvancedView ? 'active' : 'inactive'">
Advanced
</span>
</div>
</div>
</div>
</div>
<div class="lg:hidden" x-data="{
open: false,
isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced',
isAdvancedOnlyView: {{ in_array($view, ['boot-order', 'cloud-init', 'display', 'system', 'storage', 'subdomain', 'statistics', 'help', 'firewall']) ? 'true' : 'false' }},
toggleView() {
if (!this.isAdvancedOnlyView) {
this.isAdvancedView = !this.isAdvancedView;
localStorage.setItem('proxmox-view-mode', this.isAdvancedView ? 'advanced' : 'basic');
window.dispatchEvent(new CustomEvent('proxmox-view-mode-changed', {
detail: { isAdvancedView: this.isAdvancedView }
}));
}
}
}" x-init="
if (!localStorage.getItem('proxmox-view-mode')) {
localStorage.setItem('proxmox-view-mode', 'basic');
isAdvancedView = false;
}
isAdvancedView = localStorage.getItem('proxmox-view-mode') === 'advanced';
window.addEventListener('proxmox-view-mode-changed', (e) => {
isAdvancedView = e.detail.isAdvancedView;
});
$watch('open', () => {
if (open) {
isAdvancedView = localStorage.getItem('proxmox-view-mode') === 'advanced';
}
});
">
<style>
.mobile-nav-button {
background: rgba(255, 255, 255, 0.9);
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 12px 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
transition: all 0.15s ease;
}
.dark .mobile-nav-button {
background: rgba(30, 41, 59, 0.95);
border-color: #334155;
}
.mobile-nav-button:active {
transform: scale(0.98);
}
.mobile-dropdown {
background: white;
border: 1px solid #e2e8f0;
border-radius: 14px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
}
.dark .mobile-dropdown {
background: #1e293b;
border-color: #334155;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
}
.mobile-tab-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
transition: all 0.15s ease;
border-radius: 8px;
margin: 2px 6px;
color: #475569;
}
.dark .mobile-tab-item {
color: #94a3b8;
}
.mobile-tab-item:hover {
background: rgba(70, 95, 255, 0.08);
color: #465fff;
}
.dark .mobile-tab-item:hover {
background: rgba(70, 95, 255, 0.12);
color: #818cf8;
}
.mobile-tab-item.active {
background: linear-gradient(135deg, rgba(70, 95, 255, 0.12) 0%, rgba(99, 102, 241, 0.08) 100%);
color: #465fff;
font-weight: 500;
}
.dark .mobile-tab-item.active {
background: linear-gradient(135deg, rgba(70, 95, 255, 0.18) 0%, rgba(129, 140, 248, 0.12) 100%);
color: #818cf8;
}
.mobile-toggle-compact {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 10px;
}
.dark .mobile-toggle-compact {
background: rgba(30, 41, 59, 0.6);
border-color: #334155;
}
.mobile-toggle-label-compact {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
color: #64748b;
}
.mobile-toggle-switch-compact {
width: 40px;
height: 22px;
background: #e2e8f0;
border-radius: 11px;
position: relative;
transition: all 0.2s ease;
}
.dark .mobile-toggle-switch-compact {
background: #334155;
}
.mobile-toggle-switch-compact.active {
background: linear-gradient(135deg, #465fff 0%, #6366f1 100%);
}
.dark .mobile-toggle-switch-compact.active {
background: linear-gradient(135deg,
rgb(59, 130, 246),
rgb(96, 165, 250));
}
.mobile-toggle-handle-compact {
position: absolute;
top: 1px;
left: 1px;
width: 18px;
height: 18px;
background: white;
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}
.mobile-toggle-switch-compact.active .mobile-toggle-handle-compact {
transform: translateX(18px);
}
.mobile-section-header {
background: linear-gradient(135deg,
rgba(37, 99, 235, 0.05),
rgba(59, 130, 246, 0.03));
border-bottom: 1px solid color-mix(in srgb, var(--color-neutral) 20%, transparent);
}
.dark .mobile-section-header {
background: linear-gradient(135deg,
rgba(59, 130, 246, 0.08),
rgba(37, 99, 235, 0.05));
}
</style>
<div class="flex gap-3">
@php
// Find the current tab label, checking both regular tabs and dropdown items
$currentLabel = 'Menu';
foreach ($tabs as $tab) {
if (isset($tab['view']) && $tab['view'] === $view) {
$currentLabel = $tab['label'];
break;
}
if (isset($tab['dropdown']) && $tab['dropdown'] && isset($tab['items'])) {
foreach ($tab['items'] as $item) {
if ($item['view'] === $view) {
$currentLabel = $item['label'];
break 2;
}
}
}
}
@endphp
<button @click="open = !open"
class="mobile-nav-button flex items-center gap-3 flex-1 justify-between font-semibold">
<span class="text-sm">{{ $currentLabel }}</span>
<x-ri-menu-line class="size-5" />
</button>
<div class="mobile-toggle-compact flex-shrink-0"
:class="isAdvancedOnlyView ? 'opacity-50 pointer-events-none' : ''">
<span class="mobile-toggle-label-compact"
:class="!isAdvancedView ? 'text-base' : 'text-base/40'">
Std
</span>
<div class="mobile-toggle-switch-compact"
:class="{ 'active': isAdvancedView }"
@click="!isAdvancedOnlyView && toggleView()">
<div class="mobile-toggle-handle-compact"></div>
</div>
<span class="mobile-toggle-label-compact"
:class="isAdvancedView ? 'text-base' : 'text-base/40'">
Adv
</span>
</div>
</div>
<div x-show="open"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@click.outside="open = false"
class="mobile-dropdown mt-3 overflow-hidden">
<div class="flex flex-col">
@foreach($tabs as $tab)
@php
$isAdvancedTab = $tab['advanced'] ?? false;
$isDropdown = $tab['dropdown'] ?? false;
@endphp
@if($isDropdown)
{{-- Render dropdown items as individual menu items for mobile --}}
<div class="mobile-section-header px-3 py-2 text-xs font-bold uppercase tracking-wider text-base/50">
{{ $tab['label'] }}
</div>
@foreach($tab['items'] as $item)
@php
$itemAdvanced = $item['advanced'] ?? false;
@endphp
<div @if($itemAdvanced)
x-show="isAdvancedView"
x-transition
@endif>
<a href="{{ $item['route'] }}" wire:navigate
class="mobile-tab-item flex items-center justify-between pl-8 {{ $view === $item['view'] ? 'active text-base' : 'text-base/70' }}"
@click="open = false">
<span>{{ $item['label'] }}</span>
@if($view === $item['view'])
<x-ri-check-line class="size-4 text-primary" />
@endif
</a>
</div>
@endforeach
@else
<div @if($isAdvancedTab)
x-show="isAdvancedView"
x-transition
@endif>
<a href="{{ $tab['route'] }}" wire:navigate
class="mobile-tab-item flex items-center justify-between {{ $view === $tab['view'] ? 'active text-base' : 'text-base/70' }}"
@click="open = false">
<span>{{ $tab['label'] }}</span>
@if($view === $tab['view'])
<x-ri-check-line class="size-4 text-primary" />
@endif
</a>
</div>
@endif
@endforeach
</div>
</div>
</div>
</div>
<div class="flex-1 flex flex-col">
<div class="flex-1 flex flex-col">
{{ $slot }}
</div>
</div>
</div>
@else
@php
$statuses = [
'suspended' => [
'color' => 'text-yellow-500',
'message' => 'This server is suspended. Please contact support for more information.'
],
'installing' => [
'color' => 'text-blue-500',
'message' => ''
],
];
@endphp
<div class="flex flex-col" wire:key='{{ $server->id }}-{{ $server->status }}' style="min-height: calc(100vh - 30rem);">
@if($server->status === 'installing')
<style>
@import url('https://fonts.googleapis.com/css?family=Fira+Mono:400');
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.installing-container {
min-height: calc(100vh - 30rem);
height: calc(100vh - 30rem);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.installing-body {
display: flex;
align-items: center;
justify-content: center;
font-family: "Inter", system-ui, sans-serif;
color: #1d2c3f;
width: 100%;
}
.loading-card {
width: min(460px, 92vw);
background: #ffffff;
border-radius: 20px;
padding: 34px 30px 38px;
box-shadow:
0 25px 50px rgba(86, 121, 168, 0.22),
0 0 0 1px rgba(109, 178, 255, 0.15);
position: relative;
overflow: hidden;
}
.dark .loading-card {
background: var(--background-secondary);
color: var(--base);
}
.loading-card::after {
content: "";
position: absolute;
inset: -2px;
border-radius: 22px;
background: linear-gradient(
90deg,
rgba(44, 185, 149, 0.15),
rgba(0, 163, 255, 0.15),
rgba(0, 84, 255, 0.15),
rgba(85, 0, 255, 0.15)
);
background-size: 200% 200%;
animation: border-glow 8s ease infinite;
z-index: -1;
pointer-events: none;
opacity: 0.3;
}
@keyframes border-glow {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 28px;
}
.logo-shell {
width: 58px;
height: 58px;
display: grid;
place-items: center;
}
.logo-shell img {
width: 42px;
height: auto;
display: block;
animation: pulse 2s infinite;
}
.header span {
text-transform: uppercase;
letter-spacing: 0.26em;
font-size: 0.75rem;
color: rgba(32, 56, 84, 0.6);
}
.dark .header span {
color: rgba(255, 255, 255, 0.6);
}
.header h1 {
margin: 4px 0 0;
font-size: clamp(1.4rem, 1vw, 1.65rem);
}
.line {
position: relative;
height: 6px;
border-radius: 999px;
background: rgba(37, 74, 179, 0.596);
overflow: hidden;
margin-bottom: 32px;
box-shadow:
inset 0 0 12px rgba(0, 0, 0, 0.05),
0 16px 32px rgba(109, 178, 255, 0.2);
}
.line::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
89deg,
#2cb995,
#00a3ff,
#0054ff,
#001cff,
#5500ff
);
background-size: 800% 800%;
animation: line-gradient 13s ease infinite;
}
.line::after {
content: "";
position: absolute;
top: 50%;
left: 0%;
width: 36px;
height: 12px;
border-radius: 999px;
border: 1px solid rgba(0, 20, 60, 0.25);
background: linear-gradient(90deg, rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0));
box-shadow:
0 4px 20px rgba(0, 90, 255, 0.25),
inset 0 0 8px rgba(255, 255, 255, 0.4);
transform: translate(-50%, -50%) skewX(-8deg);
animation: line-marker 2.8s cubic-bezier(0.55, 0.03, 0.35, 0.95) infinite;
}
.status-cycle {
margin-top: 24px;
height: 20px;
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: rgba(32, 56, 84, 0.65);
position: relative;
}
.dark .status-cycle {
color: rgba(255, 255, 255, 0.65);
}
.status-cycle ul {
list-style: none;
margin: 0;
padding: 0;
position: relative;
height: 20px;
}
.status-cycle li {
position: absolute;
inset: 0;
display: flex;
align-items: center;
gap: 10px;
opacity: 0;
animation: status-step 30s linear infinite;
}
.status-cycle li::before {
content: "";
width: 12px;
height: 2px;
background: linear-gradient(90deg, #18d4ff, transparent);
}
.status-cycle li:nth-child(2) {
animation-delay: 3s;
}
.status-cycle li:nth-child(3) {
animation-delay: 6s;
}
.status-cycle li:nth-child(4) {
animation-delay: 9s;
}
.status-cycle li:nth-child(5) {
animation-delay: 12s;
}
.status-cycle li:nth-child(6) {
animation-delay: 15s;
}
.status-cycle li:nth-child(7) {
animation-delay: 18s;
}
.status-cycle li:nth-child(8) {
animation-delay: 21s;
}
.status-cycle li:nth-child(9) {
animation-delay: 24s;
}
.status-cycle li:nth-child(10) {
animation-delay: 27s;
}
@keyframes line-gradient {
0% {
background-position: 0% 79%;
}
50% {
background-position: 100% 22%;
}
100% {
background-position: 0% 79%;
}
}
@keyframes line-marker {
0% {
left: 0%;
opacity: 0.15;
transform: translate(-50%, -50%) skewX(-8deg) scaleX(0.85);
}
10% {
opacity: 0.85;
}
50% {
opacity: 1;
transform: translate(-50%, -50%) skewX(-6deg) scaleX(1);
}
85% {
opacity: 0.4;
}
100% {
left: 100%;
opacity: 0.15;
transform: translate(-50%, -50%) skewX(-10deg) scaleX(0.75);
}
}
@keyframes status-step {
0%,
8% {
opacity: 1;
}
10%,
100% {
opacity: 0;
}
}
.watermark {
position: absolute;
bottom: 14px;
right: 24px;
font-size: 0.55rem;
letter-spacing: 0.22em;
color: rgba(32, 56, 84, 0.35);
cursor: default;
text-decoration: none;
}
.dark .watermark {
color: rgba(255, 255, 255, 0.35);
}
.audio-modal {
display: none;
position: fixed;
z-index: 10000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
animation: fadeIn 0.3s ease;
}
.audio-modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.audio-modal-content {
background: transparent;
border-radius: 20px;
padding: 0;
width: min(400px, 90vw);
max-width: 90vw;
box-shadow:
0 25px 50px rgba(86, 121, 168, 0.3),
0 0 0 1px rgba(109, 178, 255, 0.2);
animation: slideUp 0.3s ease;
overflow: hidden;
position: relative;
}
.audio-modal-content::before {
content: "";
position: absolute;
inset: 0;
background-image: url('https://billing.virtnet.bond/assets/images/glados.jpg');
background-size: cover;
background-position: center;
opacity: .95;
border-radius: 20px;
z-index: -1;
}
.audio-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 28px 10px;
position: relative;
background: rgba(255, 255, 255, 0.9);
}
.dark .audio-modal-header {
background: rgba(0, 0, 0, 0.9);
}
.audio-modal-header h2 {
margin: 0;
position: relative;
}
.audio-modal-header h2 .data-corruption {
display: inline-block;
font-family: 'Fira Mono', monospace;
font-size: 1.1rem;
font-weight: 400;
color: #1d2c3f;
letter-spacing: 0.1em;
position: relative;
}
.dark .audio-modal-header h2 .data-corruption {
color: var(--base);
}
.audio-modal-header h2 .data-corruption::before,
.audio-modal-header h2 .data-corruption::after {
content: attr(title);
position: absolute;
left: 0;
color: #1d2c3f;
}
.dark .audio-modal-header h2 .data-corruption::before,
.dark .audio-modal-header h2 .data-corruption::after {
color: var(--base);
}
.audio-modal-close {
background: none;
border: none;
font-size: 2rem;
color: rgba(32, 56, 84, 0.4);
cursor: pointer;
line-height: 1;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s ease;
}
.audio-modal-close:hover {
background: rgba(32, 56, 84, 0.1);
color: rgba(32, 56, 84, 0.7);
}
.dark .audio-modal-close {
color: rgba(255, 255, 255, 0.4);
}
.dark .audio-modal-close:hover {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.7);
}
.audio-modal-body {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 28px;
display: flex;
font-size: small;
flex-direction: column;
justify-content: center;
align-items: left;
gap: 16px;
position: relative;
background: rgba(255, 255, 255, 0.9);
}
.dark .audio-modal-body {
background: rgba(0, 0, 0, 0.9);
}
.audio-modal-body audio {
width: 100%;
outline: none;
}
.audio-modal-body a {
color: inherit;
text-decoration: none;
}
.audio-modal-body a:hover,
.audio-modal-body a:visited,
.audio-modal-body a:active {
color: inherit;
text-decoration: none;
}
.easter-egg-message {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 20px;
background: linear-gradient(135deg, rgba(118, 124, 128, 0.08), rgba(136, 136, 136, 0.445));
border-radius: 12px;
border-left: 3px solid rgba(54, 62, 66, 0.4);
margin-bottom: 8px;
}
.easter-egg-text {
margin: 0;
font-size: 0.75rem;
line-height: 1.6;
color: rgba(32, 56, 84, 0.85);
letter-spacing: 0.02em;
}
.dark .easter-egg-text {
color: rgba(255, 255, 255, 0.85);
}
.easter-egg-highlight {
font-weight: 700;
font-size: 1.1em;
background: linear-gradient(135deg, #00000070, #4e4d69);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: 0.05em;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>
<!-- Auto-refresh every 10s only in installing state -->
<script>
function scheduleInstallingRefresh() {
if (window.installingRefreshTimeout) {
clearTimeout(window.installingRefreshTimeout);
}
window.installingRefreshTimeout = setTimeout(function() {
window.location.reload();
}, 10000);
}
scheduleInstallingRefresh();
// Audio Modal Functions
function openAudioModal(e) {
e.preventDefault();
const modal = document.getElementById('audioModal');
const audio = document.getElementById('gladosAudio');
const watermark = document.querySelector('.watermark');
if (modal && audio) {
modal.classList.add('active');
audio.currentTime = 5;
audio.play().catch(function(error) {
console.log('Audio playback failed:', error);
});
if (window.installingRefreshTimeout) {
clearTimeout(window.installingRefreshTimeout);
window.installingRefreshTimeout = null;
}
audio.addEventListener('loadedmetadata', function() {
if (audio.currentTime < 5) {
audio.currentTime = 5;
}
}, { once: true });
if (watermark) {
watermark.textContent = 'Nice job breaking it, hero.';
}
}
return false;
}
function closeAudioModal() {
const modal = document.getElementById('audioModal');
const audio = document.getElementById('gladosAudio');
if (modal && audio) {
audio.pause();
audio.currentTime = 0;
modal.classList.remove('active');
scheduleInstallingRefresh();
}
}
window.addEventListener('click', function(event) {
const modal = document.getElementById('audioModal');
if (event.target === modal) {
closeAudioModal();
}
});
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeAudioModal();
}
});
</script>
<div class="installing-container">
<div class="installing-body">
<div class="loading-card">
<div class="header">
<div class="logo-shell" aria-hidden="true">
<img src="https://billing.virtnet.bond/assets/images/logofresh.png" alt="Virtnet.bond logo" width="46" height="34" />
</div>
<div>
<span>Virtnet.bond</span>
<h1>Creating Server</h1>
</div>
</div>
<div class="line" aria-hidden="true"></div>
<div class="status-cycle" aria-live="polite">
<ul>
<li>Telling the memory to try harder</li>
<li>Begging the network to behave (again)</li>
<li>Pretending the storage is organized</li>
<li>Asking the CPU to stop being dramatic</li>
<li>Informing telemetry it missed everything</li>
<li>Encouraging the firewall to chill out</li>
<li>Reminding the logs nobody reads them</li>
<li>Lecturing the cluster to stop gossiping</li>
<li>Asking the API politely to behave today</li>
<li>Calming down the swap space (it is stressed)</li>
</ul>
</div>
<a class="watermark" href="#" onclick="openAudioModal(event); return false;">
GLaDOS r4.32-03
</a>
</div>
</div>
</div>
<!-- Audio Modal -->
<div id="audioModal" class="audio-modal">
<div class="audio-modal-content">
<div class="audio-modal-header">
<h2>
<div class="data-corruption">
<b>GLaDOS</b> - <small><em>"Still Alive"</em></small>
</div>
</h2>
<button class="audio-modal-close" onclick="closeAudioModal()">&times;</button>
</div>
<div class="audio-modal-body">
<div class="easter-egg-message">
<p class="easter-egg-text">
<span class="easter-egg-highlight">Congrats!</span> You found a hidden Easter egg! Enjoy the tune and the same love Virtnet.bond has for the Portal series and all video games! We might be serious about work, but we're still gamers at heart.
</p>
</div>
<div>
<strong><a href="https://store.steampowered.com/app/400/Portal/">Portal</a></strong> -
<em>"Still Alive"</em> by Ellen McLain (voice of GLaDOS)
</div>
<audio id="gladosAudio" controls>
<source src="https://billing.virtnet.bond/assets/mp3/glados.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<div>
<small>
Virtnet.bond claims no rights to the "GLaDOS" name or the "Still Alive" audio content. This content is used under the doctrine of fair use (<b>17 U.S. Code § 107</b> – Limitations on exclusive rights: Fair use). It is provided for entertainment and cultural reference purposes only. All rights to the aforementioned content are retained by their respective copyright holders.
</small>
</div>
</div>
</div>
</div>
@else
<div class="text-center">
<h2 class="text-2xl font-bold mb-4">Oops!</h2>
<p class="{{ $statuses[$server->status]['color'] ?? 'text-black-100' }} font-semibold mb-4">
@php
$message = $statuses[$server->status]['message'] ?? "This server is currently inactive. Please view the Services tab in the Dashboard to see more information about the current state of this server. If more information is required, please contact support via the support portal under Tickets. This message can be displayed after a service is suspended, cancelled, revoked, or potentially in error. If you believe this message has been displayed in error, please contact us via a ticket and be sure to include the following information: VM{$server->vm_id}-ID{$server->id}-{$server->status}.";
@endphp
<p class="{{ $statuses[$server->status]['color'] ?? 'text-black-100' }}">
{!! $message !!}
</p>
</p>
</div>
@endif
</div>
@endif
</div>
<!-- Server Icon Customization Modal -->
<div id="serverIconModal" style="display: none;" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div class="bg-white dark:bg-background-secondary rounded-xl shadow-2xl p-6 w-full max-w-md relative">
<button onclick="closeServerIconModal()" class="absolute top-3 right-3 text-base/50 hover:text-base transition-colors">
<x-ri-close-line class="size-5" />
</button>
<h3 class="text-lg font-semibold mb-4">Customize Server Icon</h3>
<div class="flex items-center gap-4 mb-4">
<div id="iconPreview" class="size-12 rounded-xl border flex items-center justify-center text-3xl">
<i id="iconPreviewIcon" class="ri-server-line text-base/40"></i>
</div>
<input id="iconSearch" type="text" placeholder="Search icons..." class="border rounded px-2 py-1 text-sm w-40 dark:bg-background dark:border-base/20" oninput="filterIcons()" />
</div>
<div id="iconGrid" class="grid grid-cols-6 gap-2 mb-4 max-h-32 overflow-y-auto">
@php
$availableIcons = [
'server-line', 'database-2-line', 'computer-line', 'cpu-line', 'hard-drive-2-line',
'cloud-line', 'terminal-box-line', 'code-s-slash-line', 'braces-line',
'global-line', 'earth-line', 'wifi-line', 'signal-tower-line',
'shield-check-line', 'shield-keyhole-line', 'lock-line', 'lock-unlock-line', 'key-2-line',
'rocket-line', 'rocket-2-line', 'send-plane-line', 'flashlight-line', 'fire-line',
'sparkling-line', 'star-line', 'heart-line', 'home-line', 'home-2-line',
'building-line', 'building-2-line', 'store-line', 'box-3-line', 'archive-line',
'inbox-line', 'inbox-2-line', 'folder-line', 'folder-open-line',
'settings-3-line', 'settings-4-line', 'tools-line', 'hammer-line',
'flask-line', 'bug-line', 'puzzle-line', 'bar-chart-box-line', 'pie-chart-line',
'line-chart-line', 'money-dollar-circle-line', 'money-euro-circle-line',
'bank-card-line', 'wallet-line', 'user-line', 'user-3-line', 'group-line', 'team-line',
'mail-line', 'mail-open-line', 'chat-3-line', 'discuss-line',
'camera-line', 'vidicon-line', 'film-line', 'music-line',
'book-open-line', 'file-text-line', 'sun-line', 'moon-line',
'palette-line', 'brush-line', 'trophy-line', 'graduation-cap-line',
'lightbulb-line', 'fingerprint-line', 'eye-line', 'map-2-line', 'map-pin-line',
'time-line', 'calendar-line', 'gift-line', 'cake-2-line',
'gamepad-line', 'ghost-line', 'aliens-line', 'space-ship-line',
'android-line', 'apple-line', 'windows-line', 'ubuntu-line',
'github-line', 'gitlab-line', 'discord-line', 'slack-line'
];
@endphp
@foreach($availableIcons as $iconName)
<button onclick="selectIcon('{{ $iconName }}')" data-icon="{{ $iconName }}" class="icon-button rounded p-1 border flex items-center justify-center hover:bg-secondary/10 text-xl transition-all dark:border-base/20">
<i class="ri-{{ $iconName }}"></i>
</button>
@endforeach
</div>
<div class="flex flex-wrap gap-2 mb-4">
@php
$availableColors = [
'' => 'Default',
'#ef4444' => 'Red',
'#f97316' => 'Orange',
'#f59e0b' => 'Amber',
'#eab308' => 'Yellow',
'#84cc16' => 'Lime',
'#22c55e' => 'Green',
'#10b981' => 'Emerald',
'#14b8a6' => 'Teal',
'#06b6d4' => 'Cyan',
'#0ea5e9' => 'Sky',
'#3b82f6' => 'Blue',
'#6366f1' => 'Indigo',
'#8b5cf6' => 'Violet',
'#a855f7' => 'Purple',
'#d946ef' => 'Fuchsia',
'#ec4899' => 'Pink',
'#f43f5e' => 'Rose',
'#78716c' => 'Stone',
'#64748b' => 'Slate'
];
@endphp
@foreach($availableColors as $colorValue => $colorName)
<button onclick="selectColor('{{ $colorValue }}')" data-color="{{ $colorValue }}" title="{{ $colorName }}" class="color-button size-6 rounded-full border transition-all" style="background: {{ $colorValue ?: '#e5e7eb' }}"></button>
@endforeach
</div>
<div class="flex gap-2 justify-end">
<button onclick="resetServerIcon()" class="text-xs text-base/60 hover:text-danger transition-colors">Reset</button>
<button onclick="saveServerIcon()" class="px-3 py-1 rounded bg-primary text-white text-xs hover:bg-primary/80 transition-colors">Save</button>
</div>
<div id="iconMessage" class="mt-2 text-xs text-success hidden"></div>
</div>
</div>
<script>
let serverIconModalData = {
serverId: null,
selectedIcon: '',
selectedColor: ''
};
function openServerIconModal(serverId, currentIcon, currentColor) {
serverIconModalData.serverId = serverId;
serverIconModalData.selectedIcon = currentIcon || '';
serverIconModalData.selectedColor = currentColor || '';
updateIconPreview();
updateIconSelection();
updateColorSelection();
document.getElementById('serverIconModal').style.display = 'flex';
document.getElementById('iconSearch').value = '';
filterIcons();
}
function closeServerIconModal() {
document.getElementById('serverIconModal').style.display = 'none';
document.getElementById('iconMessage').classList.add('hidden');
}
function selectIcon(iconName) {
serverIconModalData.selectedIcon = iconName;
updateIconPreview();
updateIconSelection();
}
function selectColor(color) {
serverIconModalData.selectedColor = color;
updateIconPreview();
updateColorSelection();
}
function resetServerIcon() {
serverIconModalData.selectedIcon = '';
serverIconModalData.selectedColor = '';
updateIconPreview();
updateIconSelection();
updateColorSelection();
}
function updateIconPreview() {
const preview = document.getElementById('iconPreview');
const previewIcon = document.getElementById('iconPreviewIcon');
if (serverIconModalData.selectedIcon) {
previewIcon.className = 'ri-' + serverIconModalData.selectedIcon;
previewIcon.style.color = serverIconModalData.selectedColor || '';
preview.style.borderColor = serverIconModalData.selectedColor || '';
} else {
previewIcon.className = 'ri-server-line text-base/40';
previewIcon.style.color = '';
preview.style.borderColor = '';
}
}
function updateIconSelection() {
document.querySelectorAll('.icon-button').forEach(btn => {
if (btn.dataset.icon === serverIconModalData.selectedIcon) {
btn.classList.add('ring-2', 'ring-primary');
} else {
btn.classList.remove('ring-2', 'ring-primary');
}
});
}
function updateColorSelection() {
document.querySelectorAll('.color-button').forEach(btn => {
if (btn.dataset.color === serverIconModalData.selectedColor) {
btn.classList.add('ring-2', 'ring-primary');
} else {
btn.classList.remove('ring-2', 'ring-primary');
}
});
}
function filterIcons() {
const search = document.getElementById('iconSearch').value.toLowerCase();
document.querySelectorAll('.icon-button').forEach(btn => {
const iconName = btn.dataset.icon;
if (iconName.includes(search)) {
btn.style.display = 'flex';
} else {
btn.style.display = 'none';
}
});
}
function saveServerIcon() {
fetch(`/proxmox/server/${serverIconModalData.serverId}/icon`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content
},
body: JSON.stringify({
icon: serverIconModalData.selectedIcon,
color: serverIconModalData.selectedColor
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const messageEl = document.getElementById('iconMessage');
messageEl.textContent = 'Icon saved!';
messageEl.classList.remove('hidden');
setTimeout(() => {
closeServerIconModal();
location.reload();
}, 800);
}
})
.catch(error => {
console.error('Error saving icon:', error);
});
}
</script>