3198 lines
190 KiB
PHP
3198 lines
190 KiB
PHP
@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);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// Server Tags Manager - Simple tags stored in database
|
||
function serverTagsManager({serverId, tag1, tag1color, tag2, tag2color, tag3, tag3color}) {
|
||
return {
|
||
serverId: serverId,
|
||
show: false,
|
||
saving: false,
|
||
tag1: tag1 || '',
|
||
tag1color: tag1color || '#3498db',
|
||
tag2: tag2 || '',
|
||
tag2color: tag2color || '#2ecc71',
|
||
tag3: tag3 || '',
|
||
tag3color: tag3color || '#e74c3c',
|
||
colorPickerFor: null,
|
||
message: '',
|
||
messageType: 'success',
|
||
tagColors: [
|
||
'#e74c3c', '#e67e22', '#f1c40f', '#2ecc71', '#1abc9c', '#3498db',
|
||
'#9b59b6', '#e91e63', '#00bcd4', '#8bc34a', '#ff5722', '#607d8b',
|
||
'#795548', '#9e9e9e', '#673ab7', '#4caf50'
|
||
],
|
||
openModal() {
|
||
this.show = true;
|
||
this.message = '';
|
||
this.colorPickerFor = null;
|
||
},
|
||
closeModal() {
|
||
this.show = false;
|
||
this.colorPickerFor = null;
|
||
},
|
||
selectColorFor(tagNum) {
|
||
this.colorPickerFor = this.colorPickerFor === tagNum ? null : tagNum;
|
||
},
|
||
setColor(tagNum, color) {
|
||
if (tagNum === 1) this.tag1color = color;
|
||
else if (tagNum === 2) this.tag2color = color;
|
||
else if (tagNum === 3) this.tag3color = color;
|
||
this.colorPickerFor = null;
|
||
},
|
||
getColor(tagNum) {
|
||
if (tagNum === 1) return this.tag1color;
|
||
if (tagNum === 2) return this.tag2color;
|
||
if (tagNum === 3) return this.tag3color;
|
||
return '#3498db';
|
||
},
|
||
saveTags() {
|
||
this.saving = true;
|
||
this.message = '';
|
||
|
||
fetch(`/api/proxmox/tags/${this.serverId}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
||
},
|
||
body: JSON.stringify({
|
||
tag1: this.tag1.trim(),
|
||
tag1color: this.tag1color,
|
||
tag2: this.tag2.trim(),
|
||
tag2color: this.tag2color,
|
||
tag3: this.tag3.trim(),
|
||
tag3color: this.tag3color
|
||
})
|
||
})
|
||
.then(r => {
|
||
if (!r.ok) {
|
||
return r.text().then(text => {
|
||
throw new Error(`HTTP ${r.status}: ${text.substring(0, 100)}`);
|
||
});
|
||
}
|
||
return r.json();
|
||
})
|
||
.then(data => {
|
||
this.saving = false;
|
||
if (data.success) {
|
||
this.message = 'Tags saved!';
|
||
this.messageType = 'success';
|
||
setTimeout(() => { this.closeModal(); location.reload(); }, 1000);
|
||
} else {
|
||
this.message = data.error || 'Failed to save tags';
|
||
this.messageType = 'error';
|
||
}
|
||
})
|
||
.catch(err => {
|
||
this.saving = false;
|
||
this.message = 'Error: ' + err.message;
|
||
this.messageType = 'error';
|
||
console.error('Tag save error:', err);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
</div>
|
||
|
||
<!-- Server Tags Component -->
|
||
<div x-data="serverTagsManager({
|
||
serverId: '{{ $server->id }}',
|
||
tag1: '{{ $server->tag1 ?? '' }}',
|
||
tag1color: '{{ $server->tag1color ?? '#3498db' }}',
|
||
tag2: '{{ $server->tag2 ?? '' }}',
|
||
tag2color: '{{ $server->tag2color ?? '#2ecc71' }}',
|
||
tag3: '{{ $server->tag3 ?? '' }}',
|
||
tag3color: '{{ $server->tag3color ?? '#e74c3c' }}'
|
||
})" class="flex items-center gap-2">
|
||
<!-- Display Tags -->
|
||
<div class="flex items-center gap-1.5 flex-wrap">
|
||
<template x-if="tag1">
|
||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium text-white cursor-pointer hover:opacity-80 transition-opacity"
|
||
:style="'background-color: ' + tag1color"
|
||
@click="openModal()">
|
||
<span x-text="tag1"></span>
|
||
</span>
|
||
</template>
|
||
<template x-if="tag2">
|
||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium text-white cursor-pointer hover:opacity-80 transition-opacity"
|
||
:style="'background-color: ' + tag2color"
|
||
@click="openModal()">
|
||
<span x-text="tag2"></span>
|
||
</span>
|
||
</template>
|
||
<template x-if="tag3">
|
||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium text-white cursor-pointer hover:opacity-80 transition-opacity"
|
||
:style="'background-color: ' + tag3color"
|
||
@click="openModal()">
|
||
<span x-text="tag3"></span>
|
||
</span>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Add Tag Button -->
|
||
<button @click="openModal()"
|
||
class="inline-flex items-center justify-center size-6 rounded-full border border-dashed border-base/30 text-base/50 hover:border-primary hover:text-primary transition-colors"
|
||
title="Manage tags (up to 3)">
|
||
<x-ri-price-tag-3-line class="size-3.5" />
|
||
</button>
|
||
|
||
<!-- Tags 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" @click.away="closeModal()">
|
||
<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">Manage Server Tags</h3>
|
||
<p class="text-sm text-base/60 mb-4">Add up to 3 tags to organize and identify your server.</p>
|
||
|
||
<!-- Tag 1 -->
|
||
<div class="space-y-3 mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-xs text-base/50 w-12">Tag 1:</span>
|
||
<input type="text" x-model="tag1" placeholder="Tag 1"
|
||
class="flex-1 px-3 py-2 text-sm border border-neutral rounded-lg dark:bg-background dark:border-base/20 focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||
maxlength="32" />
|
||
<button @click="selectColorFor(1)"
|
||
class="size-8 rounded-full border-2 border-white shadow-md transition-transform hover:scale-110"
|
||
:style="'background-color: ' + tag1color"
|
||
title="Change color">
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tag 2 -->
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-xs text-base/50 w-12">Tag 2:</span>
|
||
<input type="text" x-model="tag2" placeholder="Tag 2"
|
||
class="flex-1 px-3 py-2 text-sm border border-neutral rounded-lg dark:bg-background dark:border-base/20 focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||
maxlength="32" />
|
||
<button @click="selectColorFor(2)"
|
||
class="size-8 rounded-full border-2 border-white shadow-md transition-transform hover:scale-110"
|
||
:style="'background-color: ' + tag2color"
|
||
title="Change color">
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tag 3 -->
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-xs text-base/50 w-12">Tag 3:</span>
|
||
<input type="text" x-model="tag3" placeholder="Tag 3"
|
||
class="flex-1 px-3 py-2 text-sm border border-neutral rounded-lg dark:bg-background dark:border-base/20 focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||
maxlength="32" />
|
||
<button @click="selectColorFor(3)"
|
||
class="size-8 rounded-full border-2 border-white shadow-md transition-transform hover:scale-110"
|
||
:style="'background-color: ' + tag3color"
|
||
title="Change color">
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Color Picker -->
|
||
<template x-if="colorPickerFor !== null">
|
||
<div class="mb-4 p-3 bg-background rounded-lg border border-neutral">
|
||
<p class="text-xs text-base/60 mb-2">Select color:</p>
|
||
<div class="flex flex-wrap gap-2">
|
||
<template x-for="c in tagColors" :key="c">
|
||
<button @click="setColor(colorPickerFor, c)"
|
||
:style="'background-color: ' + c"
|
||
:class="getColor(colorPickerFor) === c ? 'ring-2 ring-offset-2 ring-primary' : ''"
|
||
class="size-7 rounded-full border border-white shadow-sm hover:scale-110 transition-transform">
|
||
</button>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="flex gap-2 justify-end">
|
||
<button @click="closeModal()" class="px-4 py-2 text-sm text-base/60 hover:text-base">Cancel</button>
|
||
<button @click="saveTags()" class="px-4 py-2 rounded-lg bg-primary text-white text-sm hover:bg-primary/80 transition-colors">
|
||
<span x-show="!saving">Save Tags</span>
|
||
<span x-show="saving" class="flex items-center gap-2">
|
||
<svg class="animate-spin size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||
</svg>
|
||
Saving...
|
||
</span>
|
||
</button>
|
||
</div>
|
||
|
||
<template x-if="message">
|
||
<div class="mt-3 text-sm" :class="messageType === 'success' ? 'text-success' : 'text-danger'" x-text="message"></div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="hidden lg:flex flex-row gap-2 ml-auto justify-end items-center">
|
||
<x-button.primary wire:click="do('start')" class="disabled:cursor-not-allowed"
|
||
wire:loading.attr="disabled" wire:loading.class="opacity-50 cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'stopped'">
|
||
<x-loading target="do('start')" />
|
||
<x-ri-play-fill class="size-4" />
|
||
<div wire:loading.remove wire:target="do('start')">
|
||
Start
|
||
</div>
|
||
</x-button.primary>
|
||
<div x-show="isAdvancedView" x-transition>
|
||
<x-button.secondary wire:click="do('shutdown')" class="text-nowrap disabled:cursor-not-allowed"
|
||
wire:loading.attr="disabled" wire:loading.class="opacity-50 cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'running'">
|
||
<x-loading target="do('shutdown')" />
|
||
<x-ri-stop-fill class="size-4" />
|
||
<div wire:loading.remove wire:target="do('shutdown')">
|
||
Stop (ACPI)
|
||
</div>
|
||
</x-button.secondary>
|
||
</div>
|
||
<div x-show="isAdvancedView" x-transition>
|
||
<x-button.secondary wire:click="do('reboot')" class="text-nowrap disabled:cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'running'">
|
||
<x-loading target="do('reboot')" />
|
||
<x-ri-loop-left-line class="size-4" />
|
||
<div wire:loading.remove wire:target="do('reboot')">
|
||
Reboot (ACPI)
|
||
</div>
|
||
</x-button.secondary>
|
||
</div>
|
||
<x-button.danger wire:click="do('stop')" class="text-nowrap disabled:cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'running'">
|
||
<x-loading target="do('stop')" />
|
||
<x-ri-shut-down-line class="size-4" />
|
||
<div wire:loading.remove wire:target="do('stop')">
|
||
Power Off
|
||
</div>
|
||
</x-button.danger>
|
||
</div>
|
||
|
||
<div class="flex lg:hidden w-full flex-col gap-2">
|
||
<div class="flex flex-col gap-2 w-full">
|
||
<x-button.primary wire:click="do('start')" class="w-full disabled:cursor-not-allowed"
|
||
wire:loading.attr="disabled" wire:loading.class="opacity-50 cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'stopped'">
|
||
<x-loading target="do('start')" />
|
||
<x-ri-play-fill class="size-4" />
|
||
<div wire:loading.remove wire:target="do('start')">
|
||
Start
|
||
</div>
|
||
</x-button.primary>
|
||
<div x-show="isAdvancedView" x-transition>
|
||
<x-button.secondary wire:click="do('shutdown')" class="w-full text-nowrap disabled:cursor-not-allowed"
|
||
wire:loading.attr="disabled" wire:loading.class="opacity-50 cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'running'">
|
||
<x-loading target="do('shutdown')" />
|
||
<x-ri-stop-fill class="size-4" />
|
||
<div wire:loading.remove wire:target="do('shutdown')">
|
||
Stop (ACPI)
|
||
</div>
|
||
</x-button.secondary>
|
||
</div>
|
||
<div x-show="isAdvancedView" x-transition>
|
||
<x-button.secondary wire:click="do('reboot')" class="w-full text-nowrap disabled:cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'running'">
|
||
<x-loading target="do('reboot')" />
|
||
<x-ri-loop-left-line class="size-4" />
|
||
<div wire:loading.remove wire:target="do('reboot')">
|
||
Reboot (ACPI)
|
||
</div>
|
||
</x-button.secondary>
|
||
</div>
|
||
<x-button.danger wire:click="do('stop')"
|
||
class="text-nowrap w-full disabled:cursor-not-allowed"
|
||
:disabled="$vmStatus !== 'running'">
|
||
<x-loading target="do('stop')" />
|
||
<x-ri-shut-down-line class="size-4" />
|
||
<div wire:loading.remove wire:target="do('stop')">
|
||
Power Off
|
||
</div>
|
||
</x-button.danger>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@if ($view !== 'statistics')
|
||
<!-- Preload Statistics tab content when entering the server page -->
|
||
<div
|
||
x-init="
|
||
if (window.fetch) {
|
||
setTimeout(function () {
|
||
try {
|
||
fetch('{{ route('proxmox.server.statistics', ['server' => $server->id]) }}', {
|
||
credentials: 'same-origin',
|
||
headers: { 'X-Proxmox-Preload': 'true' },
|
||
});
|
||
} catch (e) {}
|
||
}, 500);
|
||
}
|
||
"
|
||
class="hidden"
|
||
></div>
|
||
@endif
|
||
|
||
@php
|
||
$tabs = [
|
||
['label' => 'Overview', 'view' => 'overview', 'route' => route('proxmox.server.overview', ['server' => $server->id])],
|
||
['label' => 'Statistics', 'view' => 'statistics', 'route' => route('proxmox.server.statistics', ['server' => $server->id]), 'advanced' => true],
|
||
[
|
||
'label' => 'Hardware',
|
||
'dropdown' => true,
|
||
'items' => [
|
||
['label' => 'Boot Settings', 'view' => 'boot-order', 'route' => route('proxmox.server.boot-order', ['server' => $server->id]), 'advanced' => true],
|
||
['label' => 'Display', 'view' => 'display', 'route' => route('proxmox.server.display', ['server' => $server->id]), 'advanced' => true],
|
||
['label' => 'Storage', 'view' => 'storage', 'route' => route('proxmox.server.storage', ['server' => $server->id]), 'advanced' => true],
|
||
['label' => 'System', 'view' => 'system', 'route' => route('proxmox.server.system', ['server' => $server->id]), 'advanced' => true],
|
||
]
|
||
],
|
||
[
|
||
'label' => 'Networking',
|
||
'dropdown' => true,
|
||
'items' => [
|
||
['label' => 'Configuration', 'view' => 'network', 'route' => route('proxmox.server.network', ['server' => $server->id])],
|
||
['label' => 'Firewall', 'view' => 'firewall', 'route' => route('proxmox.server.firewall', ['server' => $server->id]), 'advanced' => true],
|
||
['label' => 'Subdomain', 'view' => 'subdomain', 'route' => route('proxmox.server.subdomain', ['server' => $server->id]), 'advanced' => true],
|
||
]
|
||
],
|
||
['label' => 'Cloud-init', 'view' => 'cloud-init', 'route' => route('proxmox.server.cloud-init', ['server' => $server->id]), 'advanced' => true],
|
||
];
|
||
if($server->service->product->settings()->where('key', 'backups')->first()?->value > 0) {
|
||
$tabs[] = ['label' => 'Backups', 'view' => 'backups', 'route' => route('proxmox.server.backups', ['server' => $server->id])];
|
||
}
|
||
if($server->node->vnc_enabled) {
|
||
$tabs[] = [
|
||
'label' => 'Console',
|
||
'dropdown' => true,
|
||
'items' => [
|
||
['label' => 'NoVNC', 'view' => 'vnc', 'route' => route('proxmox.server.vnc', ['server' => $server->id])],
|
||
['label' => 'Xterm.js', 'view' => 'xterm', 'route' => route('proxmox.server.xterm', ['server' => $server->id])],
|
||
]
|
||
];
|
||
}
|
||
$tabs[] = ['label' => 'Help', 'view' => 'help', 'route' => route('proxmox.server.help', ['server' => $server->id]), 'advanced' => true];
|
||
@endphp
|
||
|
||
<div class="mb-10 mt-3">
|
||
<style>
|
||
/* Modern Navigation Styles */
|
||
.proxmox-nav-container {
|
||
background: color-mix(in srgb, var(--color-background-secondary) 60%, transparent);
|
||
backdrop-filter: blur(12px);
|
||
border: 1px solid color-mix(in srgb, var(--color-neutral) 30%, transparent);
|
||
border-radius: 14px;
|
||
padding: 8px;
|
||
box-shadow:
|
||
0 1px 3px rgba(0, 0, 0, 0.05),
|
||
0 0 0 1px rgba(0, 0, 0, 0.02);
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
position: relative;
|
||
z-index: 10;
|
||
}
|
||
|
||
.dark .proxmox-nav-container {
|
||
background: color-mix(in srgb, var(--color-background-secondary) 80%, transparent);
|
||
border-color: color-mix(in srgb, var(--color-neutral) 40%, transparent);
|
||
box-shadow:
|
||
0 1px 3px rgba(0, 0, 0, 0.3),
|
||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
.proxmox-nav-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
flex-wrap: wrap;
|
||
flex: 1;
|
||
}
|
||
|
||
.proxmox-tab-link {
|
||
position: relative;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
padding: 10px 16px;
|
||
border-radius: 10px;
|
||
letter-spacing: -0.01em;
|
||
white-space: nowrap;
|
||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||
border: 1px solid transparent;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.proxmox-tab-link::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(135deg,
|
||
rgba(37, 99, 235, 0.08),
|
||
rgba(59, 130, 246, 0.05));
|
||
opacity: 0;
|
||
transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||
z-index: 0;
|
||
}
|
||
|
||
.proxmox-tab-link:hover::before {
|
||
opacity: 1;
|
||
}
|
||
|
||
.proxmox-tab-link span {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.proxmox-tab-link:hover {
|
||
transform: translateY(-1px);
|
||
border-color: rgba(37, 99, 235, 0.15);
|
||
}
|
||
|
||
.proxmox-tab-link.active {
|
||
background: linear-gradient(135deg,
|
||
rgba(37, 99, 235, 0.12),
|
||
rgba(59, 130, 246, 0.08));
|
||
border-color: rgba(37, 99, 235, 0.25);
|
||
box-shadow:
|
||
0 2px 8px rgba(37, 99, 235, 0.15),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||
color: rgb(37, 99, 235);
|
||
}
|
||
|
||
.dark .proxmox-tab-link.active {
|
||
background: linear-gradient(135deg,
|
||
rgba(59, 130, 246, 0.18),
|
||
rgba(37, 99, 235, 0.12));
|
||
border-color: rgba(59, 130, 246, 0.35);
|
||
color: rgb(96, 165, 250);
|
||
}
|
||
|
||
.proxmox-tab-link:not(.active) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: color-mix(in srgb, var(--color-base) 65%, transparent);
|
||
}
|
||
|
||
.dark .proxmox-tab-link:not(.active) {
|
||
color: color-mix(in srgb, var(--color-base) 65%, transparent);
|
||
}
|
||
|
||
/* Modern Toggle Switch */
|
||
.modern-toggle-container {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 6px 12px;
|
||
background: color-mix(in srgb, var(--color-background) 50%, transparent);
|
||
border: 1px solid color-mix(in srgb, var(--color-neutral) 40%, transparent);
|
||
border-radius: 12px;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.dark .modern-toggle-container {
|
||
background: color-mix(in srgb, var(--color-background) 60%, transparent);
|
||
border-color: color-mix(in srgb, var(--color-neutral) 50%, transparent);
|
||
}
|
||
|
||
.modern-toggle-label {
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.02em;
|
||
text-transform: uppercase;
|
||
transition: all 0.2s ease;
|
||
user-select: none;
|
||
}
|
||
|
||
.modern-toggle-label.inactive {
|
||
color: color-mix(in srgb, var(--color-base) 40%, transparent);
|
||
}
|
||
|
||
.modern-toggle-label.active {
|
||
color: rgb(37, 99, 235);
|
||
}
|
||
|
||
.dark .modern-toggle-label.active {
|
||
color: rgb(96, 165, 250);
|
||
}
|
||
|
||
.dark .modern-toggle-label.inactive {
|
||
color: color-mix(in srgb, var(--color-base) 40%, transparent);
|
||
}
|
||
|
||
.modern-toggle-switch {
|
||
position: relative;
|
||
width: 48px;
|
||
height: 26px;
|
||
background: linear-gradient(135deg,
|
||
rgba(156, 163, 175, 0.4),
|
||
rgba(209, 213, 219, 0.4));
|
||
border-radius: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
border: 1px solid rgba(var(--neutral-rgb, 156, 163, 175), 0.2);
|
||
box-shadow:
|
||
inset 0 1px 3px rgba(0, 0, 0, 0.1),
|
||
0 1px 2px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.dark .modern-toggle-switch {
|
||
background: linear-gradient(135deg,
|
||
rgba(55, 65, 81, 0.6),
|
||
rgba(75, 85, 99, 0.6));
|
||
border-color: rgba(var(--neutral-rgb, 75, 85, 99), 0.4);
|
||
}
|
||
|
||
.modern-toggle-switch.active {
|
||
background: linear-gradient(135deg,
|
||
rgb(37, 99, 235),
|
||
rgb(59, 130, 246));
|
||
border-color: rgba(37, 99, 235, 0.3);
|
||
box-shadow:
|
||
0 0 0 3px rgba(37, 99, 235, 0.1),
|
||
inset 0 1px 3px rgba(0, 0, 0, 0.15),
|
||
0 2px 6px rgba(37, 99, 235, 0.25);
|
||
}
|
||
|
||
.dark .modern-toggle-switch.active {
|
||
background: linear-gradient(135deg,
|
||
rgb(59, 130, 246),
|
||
rgb(96, 165, 250));
|
||
box-shadow:
|
||
0 0 0 3px rgba(59, 130, 246, 0.15),
|
||
inset 0 1px 3px rgba(0, 0, 0, 0.2),
|
||
0 2px 8px rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
.modern-toggle-handle {
|
||
position: absolute;
|
||
top: 2px;
|
||
left: 2px;
|
||
width: 20px;
|
||
height: 20px;
|
||
background: linear-gradient(135deg,
|
||
rgb(255, 255, 255),
|
||
rgb(249, 250, 251));
|
||
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),
|
||
0 0 0 1px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.modern-toggle-switch.active .modern-toggle-handle {
|
||
transform: translateX(22px);
|
||
box-shadow:
|
||
0 3px 8px rgba(0, 0, 0, 0.2),
|
||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.modern-toggle-switch.disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* Animation for tab transitions */
|
||
@keyframes tabSlideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-8px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.proxmox-tab-wrapper {
|
||
animation: tabSlideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 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.2s ease;
|
||
width: 14px;
|
||
height: 14px;
|
||
}
|
||
|
||
.dropdown-wrapper.open .dropdown-icon {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.dropdown-menu {
|
||
position: absolute;
|
||
top: calc(100% + 8px);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
min-width: 180px;
|
||
background: rgba(255, 255, 255, 0.705);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(209, 213, 219, 0.3);
|
||
border-radius: 12px;
|
||
box-shadow:
|
||
0 4px 16px rgba(0, 0, 0, 0.15),
|
||
0 0 0 1px rgba(0, 0, 0, 0.05);
|
||
z-index: 100;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.dark .dropdown-menu {
|
||
background: rgba(30, 30, 35, 0.98);
|
||
border-color: rgba(75, 85, 99, 0.4);
|
||
box-shadow:
|
||
0 4px 20px rgba(0, 0, 0, 0.5),
|
||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.dropdown-item {
|
||
display: block;
|
||
padding: 10px 16px;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
transition: all 0.2s ease;
|
||
border-left: 3px solid transparent;
|
||
color: var(--color-base);
|
||
}
|
||
|
||
.dropdown-item:hover {
|
||
background: linear-gradient(135deg,
|
||
rgba(37, 99, 235, 0.15),
|
||
rgba(59, 130, 246, 0.1));
|
||
border-left-color: rgba(37, 99, 235, 0.6);
|
||
color: var(--color-base);
|
||
}
|
||
|
||
.dropdown-item.active {
|
||
background: linear-gradient(135deg,
|
||
rgba(37, 99, 235, 0.15),
|
||
rgba(59, 130, 246, 0.1));
|
||
border-left-color: rgb(37, 99, 235);
|
||
color: rgb(37, 99, 235);
|
||
}
|
||
|
||
.dark .dropdown-item.active {
|
||
background: linear-gradient(135deg,
|
||
rgba(59, 130, 246, 0.2),
|
||
rgba(37, 99, 235, 0.15));
|
||
border-left-color: rgb(96, 165, 250);
|
||
color: rgb(96, 165, 250);
|
||
}
|
||
|
||
.dropdown-divider {
|
||
height: 1px;
|
||
background: color-mix(in srgb, var(--color-neutral) 30%, transparent);
|
||
margin: 4px 0;
|
||
}
|
||
</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;
|
||
|
||
// 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">
|
||
{{ $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-200"
|
||
x-transition:enter-start="opacity-0 scale-95 -translate-y-2"
|
||
x-transition:enter-end="opacity-100 scale-100 translate-y-0"
|
||
x-transition:leave="transition ease-in duration-150"
|
||
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;
|
||
@endphp
|
||
<div @if($itemAdvanced)
|
||
x-show="isAdvancedView"
|
||
@endif>
|
||
<a href="{{ $item['route'] }}" wire:navigate
|
||
class="dropdown-item {{ $view === $item['view'] ? 'active' : '' }}"
|
||
@click="open = false">
|
||
{{ $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-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"
|
||
@endif>
|
||
<a href="{{ $tab['route'] }}" wire:navigate
|
||
class="proxmox-tab-link {{ $view === $tab['view'] ? 'active' : '' }}">
|
||
<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: color-mix(in srgb, var(--color-background-secondary) 60%, transparent);
|
||
backdrop-filter: blur(12px);
|
||
border: 1px solid color-mix(in srgb, var(--color-neutral) 30%, transparent);
|
||
border-radius: 12px;
|
||
padding: 12px 16px;
|
||
box-shadow:
|
||
0 1px 3px rgba(0, 0, 0, 0.05),
|
||
0 0 0 1px rgba(0, 0, 0, 0.02);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.dark .mobile-nav-button {
|
||
background: color-mix(in srgb, var(--color-background-secondary) 80%, transparent);
|
||
border-color: color-mix(in srgb, var(--color-neutral) 40%, transparent);
|
||
}
|
||
|
||
.mobile-nav-button:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.mobile-dropdown {
|
||
background: rgba(var(--background-secondary-rgb, 255, 255, 255), 0.95);
|
||
backdrop-filter: blur(16px);
|
||
border: 1px solid rgba(var(--neutral-rgb, 229, 231, 235), 0.3);
|
||
border-radius: 14px;
|
||
box-shadow:
|
||
0 4px 16px rgba(0, 0, 0, 0.1),
|
||
0 0 0 1px rgba(0, 0, 0, 0.03);
|
||
}
|
||
|
||
.dark .mobile-dropdown {
|
||
background: rgba(var(--background-secondary-rgb, 31, 41, 55), 0.95);
|
||
border-color: rgba(var(--neutral-rgb, 75, 85, 99), 0.4);
|
||
box-shadow:
|
||
0 4px 16px rgba(0, 0, 0, 0.3),
|
||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
.mobile-tab-item {
|
||
padding: 12px 16px;
|
||
transition: all 0.2s ease;
|
||
border-left: 3px solid transparent;
|
||
}
|
||
|
||
.mobile-tab-item:hover {
|
||
background: rgba(37, 99, 235, 0.08);
|
||
border-left-color: rgba(37, 99, 235, 0.3);
|
||
}
|
||
|
||
.mobile-tab-item.active {
|
||
background: rgba(37, 99, 235, 0.12);
|
||
border-left-color: rgb(37, 99, 235);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.dark .mobile-tab-item.active {
|
||
background: rgba(59, 130, 246, 0.15);
|
||
border-left-color: rgb(96, 165, 250);
|
||
}
|
||
|
||
.mobile-toggle-compact {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
background: rgba(var(--background-rgb, 255, 255, 255), 0.5);
|
||
border: 1px solid rgba(var(--neutral-rgb, 229, 231, 235), 0.4);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.dark .mobile-toggle-compact {
|
||
background: rgba(var(--background-rgb, 17, 24, 39), 0.6);
|
||
border-color: rgba(var(--neutral-rgb, 75, 85, 99), 0.5);
|
||
}
|
||
|
||
.mobile-toggle-label-compact {
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.03em;
|
||
}
|
||
|
||
.mobile-toggle-switch-compact {
|
||
width: 40px;
|
||
height: 22px;
|
||
background: linear-gradient(135deg,
|
||
rgba(156, 163, 175, 0.4),
|
||
rgba(209, 213, 219, 0.4));
|
||
border-radius: 11px;
|
||
position: relative;
|
||
transition: all 0.3s ease;
|
||
border: 1px solid rgba(var(--neutral-rgb, 156, 163, 175), 0.2);
|
||
}
|
||
|
||
.mobile-toggle-switch-compact.active {
|
||
background: linear-gradient(135deg,
|
||
rgb(37, 99, 235),
|
||
rgb(59, 130, 246));
|
||
}
|
||
|
||
.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()">×</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>
|