Private
Public Access
1
0

Your descriptive commit message

This commit is contained in:
2026-11-02 01:29:01 +01:00
parent 68043f0416
commit d4bf5b1b0a
4960 changed files with 102120 additions and 46287 deletions

View File

@@ -1,186 +1,192 @@
{{--
Credits Plus Enhanced Navigation Component
Created by: Exp (Discord: exp_yt)
Company: ExpHost - https://www.exphost.net/
This code is the intellectual property of Exp and ExpHost.
Unauthorized copying, modification, or distribution is prohibited.
--}}
<style>
.text-flow-effect {
background: linear-gradient(75deg,
hsl(178, 46.40%, 11.00%) 0%,
/* Tailwind slate-900 */
hsl(211, 57.80%, 17.60%) 30%,
hsl(220, 98%, 61%) 45%,
/* Tailwind blue-500 */
hsl(207, 53.00%, 50.80%) 55%,
/* Tailwind fuchsia-700 */
hsl(214, 82.50%, 31.40%) 70%,
hsl(222, 47%, 11%) 100%);
background-size: 400% auto;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: textFlowAnimation 8s linear infinite;
}
@keyframes textFlowAnimation {
to {
background-position: 400% center;
}
}
</style>
<nav
class="w-full px-4 lg:px-8 bg-background-secondary border-b border-neutral h-auto flex flex-col lg:flex-row justify-between fixed top-0 z-20">
<div x-data="{ slideOverOpen: false }"
x-init="$watch('slideOverOpen', value => { document.documentElement.style.overflow = value ? 'hidden' : '' })"
class="relative z-50 w-full h-auto">
<div class="flex flex-row items-center justify-between w-full h-16">
<!-- Logo and app name -->
<div class="flex flex-row items-center">
<a href="{{ route('home') }}" class="flex flex-row items-center h-10 gap-2" wire:navigate>
{!! $creditsLogoHtml ?? '' !!}
<span class="text-xl font-bold leading-none flex items-center text-flow-effect">
{{ config('app.name') }}
</span>
</a>
<!-- Desktop menu: show only lg and above -->
<div class="hidden lg:flex flex-row ml-7">
@foreach (\App\Classes\Navigation::getLinks() as $nav)
@if (isset($nav['children']) && count($nav['children']) > 0)
<div class="relative">
<x-dropdown>
<x-slot:trigger>
<div class="flex flex-col">
<span
class="flex flex-row items-center p-3 text-sm font-semibold whitespace-nowrap text-base hover:text-base/80">
{{ $nav['name'] }}
</span>
</div>
</x-slot:trigger>
<x-slot:content>
@foreach ($nav['children'] as $child)
<x-navigation.link :href="route($child['route'], $child['params'] ?? null)"
:spa="isset($child['spa']) ? $nav['spa'] : true">
{{ $child['name'] }}
</x-navigation.link>
@endforeach
</x-slot:content>
</x-dropdown>
</div>
@else
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)"
:spa="isset($nav['spa']) ? $nav['spa'] : true"
class="flex items-center p-3">
{{ $nav['name'] }}
</x-navigation.link>
@endif
@endforeach
</div>
</div>
<!-- Right-side controls -->
<div class="flex flex-row items-center">
<!-- Desktop-only controls -->
<div class="hidden lg:flex items-center mr-3 gap-3">
<!--<x-dropdown>
<x-slot:trigger>
<span class="text-sm font-semibold">
{{ strtoupper(app()->getLocale()) }} | {{ session('currency', config('settings.default_currency')) }}
</span>
</x-slot:trigger>
<x-slot:content>
<strong class="block p-2 text-xs font-semibold uppercase text-base/50"> Language </strong>
<livewire:components.language-switch />
<livewire:components.currency-switch />
</x-slot:content>
</x-dropdown>-->
@if(auth()->check() && isset($userCreditsDisplay))
<a href="{{ $creditsUrl ?? '/account/credits' }}" wire:navigate
class="text-sm text-base hover:text-primary transition-colors duration-200 px-3 py-2 whitespace-nowrap">
Credits: {{ $userCreditsDisplay }}
</a>
@endif
<x-theme-toggle />
</div>
<!-- Cart (always visible) -->
<livewire:components.cart />
<!-- Desktop account menu -->
@if(auth()->check())
<div class="hidden lg:flex">
<x-dropdown>
<x-slot:trigger>
<img src="{{ auth()->user()->avatar }}"
class="size-8 rounded-full border border-neutral bg-background" alt="avatar" />
</x-slot:trigger>
<x-slot:content>
<div class="flex flex-col p-2">
<span class="text-sm text-base break-words">{{ auth()->user()->name }}</span>
<span class="text-sm text-base break-words">{{ auth()->user()->email }}</span>
</div>
@foreach (\App\Classes\Navigation::getAccountDropdownLinks() as $nav)
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)"
:spa="isset($nav['spa']) ? $nav['spa'] : true">
{{ $nav['name'] }}
</x-navigation.link>
@endforeach
<livewire:auth.logout />
</x-slot:content>
</x-dropdown>
</div>
@else
<div class="hidden lg:flex flex-row gap-3">
<a href="{{ route('login') }}" wire:navigate>
<x-button.secondary>{{ __('navigation.login') }}</x-button.secondary>
</a>
@if(!config('settings.registration_disabled', false))
<a href="{{ route('register') }}" wire:navigate>
<x-button.primary>{{ __('navigation.register') }}</x-button.primary>
</a>
@endif
</div>
@endif
<!-- Mobile menu toggle (below lg) -->
<button @click="slideOverOpen = !slideOverOpen"
class="relative w-10 h-10 flex lg:hidden items-center justify-center rounded-lg hover:bg-neutral transition"
aria-label="Toggle Menu">
<span x-show="!slideOverOpen" x-transition class="absolute inset-0 flex items-center justify-center">
<x-ri-menu-fill class="size-5" />
</span>
<span x-show="slideOverOpen" x-transition class="absolute inset-0 flex items-center justify-center">
<x-ri-close-fill class="size-5" />
</span>
</button>
</div>
</div>
<!-- Mobile slide-over menu -->
<template x-teleport="body">
<div x-show="slideOverOpen" @keydown.window.escape="slideOverOpen=false" x-cloak
class="fixed left-0 right-0 top-16 w-full z-[99]" style="height:calc(100dvh - 4rem);" aria-modal="true">
<div x-show="slideOverOpen" @click.away="slideOverOpen = false"
x-transition.opacity.duration.300ms
class="absolute inset-0 bg-background-secondary/80 backdrop-blur-md border-t border-neutral/50 shadow-2xl overflow-y-auto flex flex-col rounded-t-2xl">
<div class="flex flex-col h-full p-6">
<div class="flex-1 min-h-0 overflow-y-auto">
@include('creditsplus::components.navigation.sidebar-links')
</div>
</div>
</div>
</div>
</template>
</div>
</nav>
{{--
Credits Plus Enhanced Navigation Component
Created by: Exp (Discord: exp_yt)
Company: ExpHost - https://www.exphost.net/
This code is the intellectual property of Exp and ExpHost.
Unauthorized copying, modification, or distribution is prohibited.
--}}
<style>
.text-flow-effect {
background: linear-gradient(75deg,
hsl(178, 46.40%, 11.00%) 0%,
/* Tailwind slate-900 */
hsl(211, 57.80%, 17.60%) 30%,
hsl(220, 98%, 61%) 45%,
/* Tailwind blue-500 */
hsl(207, 53.00%, 50.80%) 55%,
/* Tailwind fuchsia-700 */
hsl(214, 82.50%, 31.40%) 70%,
hsl(222, 47%, 11%) 100%);
background-size: 400% auto;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: textFlowAnimation 8s linear infinite;
}
@keyframes textFlowAnimation {
to {
background-position: 400% center;
}
}
</style>
<nav
class="w-full px-4 lg:px-8 bg-background-secondary border-b border-neutral h-auto flex flex-col lg:flex-row justify-between fixed top-0 z-20">
<div x-data="{ slideOverOpen: false }"
x-init="$watch('slideOverOpen', value => { document.documentElement.style.overflow = value ? 'hidden' : '' })"
class="relative z-50 w-full h-auto">
<div class="flex flex-row items-center justify-between w-full h-16">
<!-- Logo and app name -->
<div class="flex flex-row items-center">
<a href="{{ route('home') }}" class="flex flex-row items-center h-10 gap-2" wire:navigate>
{!! $creditsLogoHtml ?? '' !!}
<span class="text-xl font-bold leading-none flex items-center text-flow-effect">
{{ config('app.name') }}
</span>
</a>
<!-- Desktop menu: show only lg and above -->
<div class="hidden lg:flex flex-row ml-7">
@foreach (\App\Classes\Navigation::getLinks() as $nav)
@if (isset($nav['children']) && count($nav['children']) > 0)
<div class="relative">
<x-dropdown>
<x-slot:trigger>
<div class="flex flex-col">
<span
class="flex flex-row items-center p-3 text-sm font-semibold whitespace-nowrap text-base hover:text-base/80">
{{ $nav['name'] }}
</span>
</div>
</x-slot:trigger>
<x-slot:content>
@foreach ($nav['children'] as $child)
<x-navigation.link :href="route($child['route'], $child['params'] ?? null)"
:spa="isset($child['spa']) ? $nav['spa'] : true">
{{ $child['name'] }}
</x-navigation.link>
@endforeach
</x-slot:content>
</x-dropdown>
</div>
@else
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)"
:spa="isset($nav['spa']) ? $nav['spa'] : true"
class="flex items-center p-3">
{{ $nav['name'] }}
</x-navigation.link>
@endif
@endforeach
</div>
</div>
<!-- Right-side controls -->
<div class="flex flex-row items-center">
<!-- Desktop-only controls -->
<div class="hidden lg:flex items-center mr-3 gap-3">
<!--<x-dropdown>
<x-slot:trigger>
<span class="text-sm font-semibold">
{{ strtoupper(app()->getLocale()) }} | {{ session('currency', config('settings.default_currency')) }}
</span>
</x-slot:trigger>
<x-slot:content>
<strong class="block p-2 text-xs font-semibold uppercase text-base/50"> Language </strong>
<livewire:components.language-switch />
<livewire:components.currency-switch />
</x-slot:content>
</x-dropdown>-->
@if(auth()->check() && isset($userCreditsDisplay))
<a href="{{ $creditsUrl ?? '/account/credits' }}" wire:navigate
class="text-sm text-base hover:text-primary transition-colors duration-200 px-3 py-2 whitespace-nowrap">
Credits: {{ $userCreditsDisplay }}
</a>
@endif
</div>
<!-- Cart (always visible) -->
<livewire:components.cart />
<!-- Dark/Light mode toggle (left of user icon) -->
<button @click="darkMode = !darkMode" type="button"
class="w-10 h-10 flex items-center justify-center rounded-lg hover:bg-neutral/50 transition-colors duration-200"
:aria-label="darkMode ? 'Switch to light mode' : 'Switch to dark mode'">
<svg x-show="!darkMode" xmlns="http://www.w3.org/2000/svg" class="size-4 text-base" viewBox="0 0 24 24" fill="currentColor"><path d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z"/></svg>
<svg x-show="darkMode" x-cloak xmlns="http://www.w3.org/2000/svg" class="size-4 text-base" viewBox="0 0 24 24" fill="currentColor"><path d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7Z"/></svg>
</button>
<!-- Desktop account menu -->
@if(auth()->check())
<div class="hidden lg:flex">
<x-dropdown>
<x-slot:trigger>
<img src="{{ auth()->user()->avatar }}"
class="size-8 rounded-full border border-neutral bg-background" alt="avatar" />
</x-slot:trigger>
<x-slot:content>
<div class="flex flex-col p-2">
<span class="text-sm text-base break-words">{{ auth()->user()->name }}</span>
<span class="text-sm text-base break-words">{{ auth()->user()->email }}</span>
</div>
@foreach (\App\Classes\Navigation::getAccountDropdownLinks() as $nav)
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)"
:spa="isset($nav['spa']) ? $nav['spa'] : true">
{{ $nav['name'] }}
</x-navigation.link>
@endforeach
<livewire:auth.logout />
</x-slot:content>
</x-dropdown>
</div>
@else
<div class="hidden lg:flex flex-row gap-3">
<a href="{{ route('login') }}" wire:navigate>
<x-button.secondary>{{ __('navigation.login') }}</x-button.secondary>
</a>
@if(!config('settings.registration_disabled', false))
<a href="{{ route('register') }}" wire:navigate>
<x-button.primary>{{ __('navigation.register') }}</x-button.primary>
</a>
@endif
</div>
@endif
<!-- Mobile menu toggle (below lg) -->
<button @click="slideOverOpen = !slideOverOpen"
class="relative w-10 h-10 flex lg:hidden items-center justify-center rounded-lg hover:bg-neutral transition"
aria-label="Toggle Menu">
<span x-show="!slideOverOpen" x-transition class="absolute inset-0 flex items-center justify-center">
<x-ri-menu-fill class="size-5" />
</span>
<span x-show="slideOverOpen" x-transition class="absolute inset-0 flex items-center justify-center">
<x-ri-close-fill class="size-5" />
</span>
</button>
</div>
</div>
<!-- Mobile slide-over menu -->
<template x-teleport="body">
<div x-show="slideOverOpen" @keydown.window.escape="slideOverOpen=false" x-cloak
class="fixed left-0 right-0 top-16 w-full z-[99]" style="height:calc(100dvh - 4rem);" aria-modal="true">
<div x-show="slideOverOpen" @click.away="slideOverOpen = false"
x-transition.opacity.duration.300ms
class="absolute inset-0 bg-background-secondary/80 backdrop-blur-md border-t border-neutral/50 shadow-2xl overflow-y-auto flex flex-col rounded-t-2xl">
<div class="flex flex-col h-full p-6">
<div class="flex-1 min-h-0 overflow-y-auto">
@include('creditsplus::components.navigation.sidebar-links')
</div>
</div>
</div>
</div>
</template>
</div>
</nav>

View File

@@ -1,50 +1,51 @@
<?php
namespace Paymenter\Extensions\Others\ForceTheme;
use App\Classes\Extension\Extension;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\View;
use Illuminate\Support\HtmlString;
class ForceTheme extends Extension
{
public function getConfig($values = [])
{
return [
[
'name' => 'mode',
'label' => 'Theme Mode',
'type' => 'select',
'options' => ['light' => 'Light', 'dark' => 'Dark'],
'default' => 'light',
'required' => true,
'validation' => 'in:light,dark',
],
[
'name' => 'local_storage_key',
'label' => 'Local Storage Key',
'type' => 'text',
'default' => '_x_darkMode',
'required' => true,
'validation' => 'string',
],
];
}
public function boot()
{
View::prependLocation(__DIR__ . '/resources/views/overrides');
Event::listen('head', function () {
$mode = $this->config('mode') ?: 'light';
$key = $this->config('local_storage_key') ?: '_x_darkMode';
$modeJs = e($mode);
$keyJs = e($key);
$script = "<script>(function(){try{var m='" . $modeJs . "';var k='" . $keyJs . "';var def='_x_darkMode';var v=(m==='dark')?'true':'false';localStorage.setItem(k, v);if(k!==def){localStorage.setItem(def, v);} }catch(e){}})();</script>";
return ['view' => new HtmlString($script)];
});
}
}
<?php
namespace Paymenter\Extensions\Others\ForceTheme;
use App\Classes\Extension\Extension;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\View;
use Illuminate\Support\HtmlString;
class ForceTheme extends Extension
{
public function getConfig($values = [])
{
return [
[
'name' => 'mode',
'label' => 'Theme Mode',
'type' => 'select',
'options' => ['light' => 'Light', 'dark' => 'Dark'],
'default' => 'light',
'required' => true,
'validation' => 'in:light,dark',
],
[
'name' => 'local_storage_key',
'label' => 'Local Storage Key',
'type' => 'text',
'default' => '_x_darkMode',
'required' => true,
'validation' => 'string',
],
];
}
public function boot()
{
View::prependLocation(__DIR__ . '/resources/views/overrides');
Event::listen('head', function () {
$mode = $this->config('mode') ?: 'light';
$key = $this->config('local_storage_key') ?: '_x_darkMode';
$modeJs = e($mode);
$keyJs = e($key);
// Only set the default when the user has not yet made a choice; respect any existing selection so toggles persist.
$script = "<script>(function(){try{var m='" . $modeJs . "';var k='" . $keyJs . "';var def='_x_darkMode';var v=(m==='dark')?'true':'false';if(localStorage.getItem(def)===null){localStorage.setItem(def, v);}if(k!==def&&localStorage.getItem(k)===null){localStorage.setItem(k, v);} }catch(e){}})();</script>";
return ['view' => new HtmlString($script)];
});
}
}

View File

@@ -1,167 +1,167 @@
<div class="w-full">
<!-- Custom CSS for animations -->
<style>
@keyframes status-pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.1);
}
}
@keyframes status-ping {
0% {
transform: scale(1);
opacity: 1;
}
75%, 100% {
transform: scale(1.5);
opacity: 0;
}
}
.status-online {
animation: status-pulse 2s ease-in-out infinite;
box-shadow: 0 0 10px rgba(34, 197, 94, 0.5);
}
.status-offline {
animation: status-ping 1.5s ease-in-out infinite;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
}
</style>
<!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-8">
<div>
<h1 class="text-3xl font-bold ">Services Availability</h1>
</div>
<div class="mt-4 sm:mt-0">
<button
wire:click="refreshStatus"
wire:loading.attr="disabled"
class="inline-flex items-center px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors duration-200 disabled:opacity-50"
>
<svg wire:loading.remove class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<svg wire:loading fill="currentColor" stroke="currentColor" viewBox="0 0 128 128" class="w-4 h-4 mr-2"><g><path stroke-width="5" d="M31.6 8.98a4.02 4.02 0 0 0 1.7 5.3l6.12 3.22a52.56 52.56 0 1 0 63.13 10.73L96 33.97a43.88 43.88 0 1 1-53-8.5l-3.65 7.4a4.02 4.02 0 0 0 1.72 5.3 3.73 3.73 0 0 0 5.13-1.67l7.66-15.63a4.18 4.18 0 0 0-1.75-5.4L36.74 7.32a3.73 3.73 0 0 0-5.13 1.66z" fill="currentColor" stroke="currentColor"/><animateTransform attributeName="transform" type="rotate" from="0 64 64" to="360 64 64" dur="1000ms" repeatCount="indefinite"></animateTransform></g></svg>
Refresh
</button>
</div>
</div>
<!-- Server Status Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-6">
@forelse($servers as $server)
<div class="bg-background-secondary rounded-lg p-6 px-6 py-8">
<!-- Server Header -->
<div class="flex items-start justify-between ">
<div class="flex items-center">
<div class="w-3 h-3 rounded-full {{ $server->status === 'online' ? 'bg-green-500' : 'bg-red-500' }} mr-3 {{ $server->status === 'online' ? 'status-online' : 'status-offline' }}"></div>
<div>
<h3 class=" font-semibold text-sm">{{ $server->name }}</h3>
</div>
</div>
<span class="px-3 py-1 rounded-full text-xs font-medium {{ $server->status === 'online' ? 'bg-green-600 text-white' : 'bg-red-600 text-white' }}">
{{ ucfirst($server->status) }}
</span>
</div>
<!-- Server Metrics -->
<!-- Last Status Updates -->
<div class="space-y-2" style="margin-top: .5rem;">
<div class="flex items-center text-sm" style="align-items: center; ">
<style>
.svgclock {
height: 4vh;
width: auto;
animation: upDown 1s ease-in-out infinite;
}
.loader-circle,
.loader-pointer,
.loader-line {
fill: none;
stroke-width: 5px;
stroke: #4d6565;
}
.loader-center {
fill: #4d6565;
}
.loader-pointer {
transform-origin: bottom center;
animation: spin 4s linear infinite;
}
.loader-line {
transform-origin: bottom center;
animation: spin 60s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); transform-origin: 50% 50%; }
to { transform: rotate(360deg); transform-origin: 50% 50%; }
}
</style>
<svg class="svgclock" viewBox="0 0 240 240">
<line class="loader-pointer" x1="120" y1="120" x2="120" y2="97"/>
<line class="loader-line" x1="120" y1="120" x2="135" y2="120" />
<circle class="loader-circle" cx="120" cy="120" r="30" />
<circle class="loader-center" cx="120" cy="120" r="5" />
</svg>
<span>Updated:</span>
<span class="ml-2">
{{ \Carbon\Carbon::parse($server->last_check)->format('m/d/y H:i') }} -0 UTC (MM/DD/YY, HH:MM)
</span>
</div>
</div>
</div>
@empty
<div class="col-span-full text-center py-12">
<div class="mb-4">
<svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<h3 class="text-xl font-medium mb-2">No servers configured</h3>
<p class="">Contact your administrator to add servers for monitoring.</p>
</div>
@endforelse
</div>
<!-- Auto-refresh indicator -->
<div class="mt-3 text-center text-gray-500 text-sm">
<p>Status updates automatically every 30 seconds</p>
</div>
</div>
<script>
// Auto-refresh every 30 seconds
setInterval(function() {
@this.refreshStatus();
}, 30000);
</script>
<div class="container mx-auto px-4 py-8 sm:px-6 lg:px-8">
<!-- Custom CSS for animations -->
<style>
@keyframes status-pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.1);
}
}
@keyframes status-ping {
0% {
transform: scale(1);
opacity: 1;
}
75%, 100% {
transform: scale(1.5);
opacity: 0;
}
}
.status-online {
animation: status-pulse 2s ease-in-out infinite;
box-shadow: 0 0 10px rgba(34, 197, 94, 0.5);
}
.status-offline {
animation: status-ping 1.5s ease-in-out infinite;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
}
</style>
<!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-8">
<div>
<h1 class="text-3xl font-bold ">Services Availability</h1>
</div>
<div class="mt-4 sm:mt-0">
<button
wire:click="refreshStatus"
wire:loading.attr="disabled"
class="inline-flex items-center px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors duration-200 disabled:opacity-50"
>
<svg wire:loading.remove class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<svg wire:loading fill="currentColor" stroke="currentColor" viewBox="0 0 128 128" class="w-4 h-4 mr-2"><g><path stroke-width="5" d="M31.6 8.98a4.02 4.02 0 0 0 1.7 5.3l6.12 3.22a52.56 52.56 0 1 0 63.13 10.73L96 33.97a43.88 43.88 0 1 1-53-8.5l-3.65 7.4a4.02 4.02 0 0 0 1.72 5.3 3.73 3.73 0 0 0 5.13-1.67l7.66-15.63a4.18 4.18 0 0 0-1.75-5.4L36.74 7.32a3.73 3.73 0 0 0-5.13 1.66z" fill="currentColor" stroke="currentColor"/><animateTransform attributeName="transform" type="rotate" from="0 64 64" to="360 64 64" dur="1000ms" repeatCount="indefinite"></animateTransform></g></svg>
Refresh
</button>
</div>
</div>
<!-- Server Status Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-6">
@forelse($servers as $server)
<div class="bg-background-secondary rounded-lg p-6 px-6 py-8">
<!-- Server Header -->
<div class="flex items-start justify-between ">
<div class="flex items-center">
<div class="w-3 h-3 rounded-full {{ $server->status === 'online' ? 'bg-green-500' : 'bg-red-500' }} mr-3 {{ $server->status === 'online' ? 'status-online' : 'status-offline' }}"></div>
<div>
<h3 class=" font-semibold text-sm">{{ $server->name }}</h3>
</div>
</div>
<span class="px-3 py-1 rounded-full text-xs font-medium {{ $server->status === 'online' ? 'bg-green-600 text-white' : 'bg-red-600 text-white' }}">
{{ ucfirst($server->status) }}
</span>
</div>
<!-- Server Metrics -->
<!-- Last Status Updates -->
<div class="space-y-2" style="margin-top: .5rem;">
<div class="flex items-center text-sm" style="align-items: center; ">
<style>
.svgclock {
height: 4vh;
width: auto;
animation: upDown 1s ease-in-out infinite;
}
.loader-circle,
.loader-pointer,
.loader-line {
fill: none;
stroke-width: 5px;
stroke: #4d6565;
}
.loader-center {
fill: #4d6565;
}
.loader-pointer {
transform-origin: bottom center;
animation: spin 4s linear infinite;
}
.loader-line {
transform-origin: bottom center;
animation: spin 60s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); transform-origin: 50% 50%; }
to { transform: rotate(360deg); transform-origin: 50% 50%; }
}
</style>
<svg class="svgclock" viewBox="0 0 240 240">
<line class="loader-pointer" x1="120" y1="120" x2="120" y2="97"/>
<line class="loader-line" x1="120" y1="120" x2="135" y2="120" />
<circle class="loader-circle" cx="120" cy="120" r="30" />
<circle class="loader-center" cx="120" cy="120" r="5" />
</svg>
<span>Updated:</span>
<span class="ml-2">
{{ \Carbon\Carbon::parse($server->last_check)->format('m/d/y H:i') }} -0 UTC (MM/DD/YY, HH:MM)
</span>
</div>
</div>
</div>
@empty
<div class="col-span-full text-center py-12">
<div class="mb-4">
<svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<h3 class="text-xl font-medium mb-2">No servers configured</h3>
<p class="">Contact your administrator to add servers for monitoring.</p>
</div>
@endforelse
</div>
<!-- Auto-refresh indicator -->
<div class="mt-3 text-center text-gray-500 text-sm">
<p>Status updates automatically every 30 seconds</p>
</div>
</div>
<script>
// Auto-refresh every 30 seconds
setInterval(function() {
@this.refreshStatus();
}, 30000);
</script>