UI: sidebar fixed-header/scroll-mid/fixed-foot + mobile drawer + ChartJS
Sidebar layout (per user request: header+footer fix, only nav scrollable):
- Restructured clu-sidebar into 3 flex-children: clu-sidebar-head (brand +
ws-switch fixed at top), clu-sidebar-scroll (nav, overflow-y:auto in
middle), clu-sidebar-foot (plan-box fixed at bottom).
Mobile sidebar (per user request: drawer + hamburger):
- <920px: sidebar position fixed, translateX(-110%) hidden, .open class
slides in. clu-sidebar-backdrop overlay dimms main + click-to-close.
- New x-clu.burger component with hamburger icon, visible only <920px
via display:grid in media query. Injected into all 9 topbars.
- Alpine state sidebarOpen on <body>, nav-item click closes drawer
after navigation.
Linking (per user request: sites detail unreachable):
- Sites index table + dashboard top-sites table: domain cell now <a>
to route('sites.show', $site) with wire:navigate.
Charts (per user request: chart.js):
- composer/npm: chart.js ^4.5.
- resources/js/app.js: register all chart.js plugins, expose window.Chart
for inline page scripts.
- Dashboard: added Traffic-24h line chart (Chart.js line+gradient fill)
between metrics and 2-col row. Init re-runs on livewire:navigated for
wire:navigate compatibility.
Modal (per user request: wire-elements):
- @livewire('wire-elements-modal') already in layouts/app.blade.php
master layout per wire-elements/modal v3 docs (single mount). Open
pattern: wire:click="$dispatch('openModal', { component: 'name' })".
Full Pest regression: 47/47 green.
master
parent
4f0c787f89
commit
665ca4e40b
|
|
@ -5,6 +5,7 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chart.js": "^4.5.1",
|
||||||
"laravel-echo": "^2.3.4",
|
"laravel-echo": "^2.3.4",
|
||||||
"pusher-js": "^8.5.0"
|
"pusher-js": "^8.5.0"
|
||||||
},
|
},
|
||||||
|
|
@ -512,6 +513,12 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@kurkle/color": {
|
||||||
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.60.4",
|
"version": "4.60.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
|
||||||
|
|
@ -1369,6 +1376,18 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chart.js": {
|
||||||
|
"version": "4.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||||
|
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"pnpm": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"vite": "^7.0.7"
|
"vite": "^7.0.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chart.js": "^4.5.1",
|
||||||
"laravel-echo": "^2.3.4",
|
"laravel-echo": "^2.3.4",
|
||||||
"pusher-js": "^8.5.0"
|
"pusher-js": "^8.5.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -630,20 +630,55 @@
|
||||||
|
|
||||||
/* ───── App layout (sidebar + main) ───── */
|
/* ───── App layout (sidebar + main) ───── */
|
||||||
.clu-app-grid { display: grid; grid-template-columns: 236px 1fr; min-height: 100vh; }
|
.clu-app-grid { display: grid; grid-template-columns: 236px 1fr; min-height: 100vh; }
|
||||||
@media (max-width: 920px) { .clu-app-grid { grid-template-columns: 1fr; } .clu-sidebar { display: none; } }
|
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar — fixed brand+ws-switch header, scrollable nav, fixed plan-box footer */
|
||||||
.clu-sidebar {
|
.clu-sidebar {
|
||||||
position: sticky; top: 0; height: 100vh;
|
position: sticky; top: 0;
|
||||||
|
height: 100vh;
|
||||||
margin: 14px 0 14px 14px;
|
margin: 14px 0 14px 14px;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
padding: 18px 14px; gap: 18px;
|
padding: 18px 14px;
|
||||||
background: var(--color-glass-strong);
|
background: var(--color-glass-strong);
|
||||||
backdrop-filter: blur(30px) saturate(180%);
|
backdrop-filter: blur(30px) saturate(180%);
|
||||||
-webkit-backdrop-filter: blur(30px) saturate(180%);
|
-webkit-backdrop-filter: blur(30px) saturate(180%);
|
||||||
border: 1px solid var(--color-glass-border);
|
border: 1px solid var(--color-glass-border);
|
||||||
box-shadow: var(--shadow-glass), var(--shadow-glass-inset);
|
box-shadow: var(--shadow-glass), var(--shadow-glass-inset);
|
||||||
|
z-index: 30;
|
||||||
|
}
|
||||||
|
.clu-sidebar-head { flex: none; display: flex; flex-direction: column; gap: 18px; }
|
||||||
|
.clu-sidebar-scroll { flex: 1; min-height: 0; overflow-y: auto; margin-top: 18px; padding-right: 4px; }
|
||||||
|
.clu-sidebar-scroll::-webkit-scrollbar { width: 6px; }
|
||||||
|
.clu-sidebar-scroll::-webkit-scrollbar-thumb { background: rgba(30,35,60,0.15); border-radius: 3px; }
|
||||||
|
.clu-sidebar-foot { flex: none; margin-top: 14px; }
|
||||||
|
|
||||||
|
/* Sidebar backdrop (mobile only) */
|
||||||
|
.clu-sidebar-backdrop {
|
||||||
|
position: fixed; inset: 0;
|
||||||
|
background: rgba(15,18,30,0.45);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
z-index: 28;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hamburger button in topbar (mobile only) */
|
||||||
|
.clu-burger { display: none; }
|
||||||
|
|
||||||
|
@media (max-width: 920px) {
|
||||||
|
.clu-app-grid { grid-template-columns: 1fr; }
|
||||||
|
.clu-sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 256px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0 var(--radius) var(--radius) 0;
|
||||||
|
transform: translateX(-110%);
|
||||||
|
transition: transform .25s ease;
|
||||||
|
}
|
||||||
|
.clu-sidebar.open { transform: translateX(0); }
|
||||||
|
.clu-sidebar-backdrop.open { display: block; }
|
||||||
|
.clu-burger { display: grid; }
|
||||||
}
|
}
|
||||||
.clu-brand-row { display: flex; align-items: center; gap: 10px; padding: 2px 4px; }
|
.clu-brand-row { display: flex; align-items: center; gap: 10px; padding: 2px 4px; }
|
||||||
.clu-brand-name { font-weight: 600; font-size: 15px; letter-spacing: -0.01em; }
|
.clu-brand-name { font-weight: 600; font-size: 15px; letter-spacing: -0.01em; }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
import './login-effects';
|
import './login-effects';
|
||||||
import './register-effects';
|
import './register-effects';
|
||||||
|
import { Chart, registerables } from 'chart.js';
|
||||||
|
|
||||||
|
Chart.register(...registerables);
|
||||||
|
|
||||||
|
// Expose Chart globally so Blade inline <script> can create charts
|
||||||
|
// without needing a separate import per page.
|
||||||
|
window.Chart = Chart;
|
||||||
|
|
||||||
window.__clupilot = 1;
|
window.__clupilot = 1;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{{-- Mobile-only burger button (toggles sidebar via Alpine sidebarOpen). --}}
|
||||||
|
<button type="button"
|
||||||
|
class="clu-burger clu-icon-btn"
|
||||||
|
@click="sidebarOpen = !sidebarOpen"
|
||||||
|
aria-label="Menu öffnen">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
|
||||||
|
<path d="M3 6h18M3 12h18M3 18h18"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
@ -21,56 +21,62 @@
|
||||||
];
|
];
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<aside class="clu-sidebar">
|
<aside class="clu-sidebar" :class="{ 'open': sidebarOpen }">
|
||||||
{{-- Brand --}}
|
{{-- Fixed header --}}
|
||||||
<div class="clu-brand-row">
|
<div class="clu-sidebar-head">
|
||||||
<div class="clu-brand-mark" aria-hidden="true"></div>
|
<div class="clu-brand-row">
|
||||||
<div class="clu-brand-name">CluPilot<span>v2.4</span></div>
|
<div class="clu-brand-mark" aria-hidden="true"></div>
|
||||||
|
<div class="clu-brand-name">CluPilot<span>v2.4</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clu-ws-switch" role="button" tabindex="0">
|
||||||
|
<div class="clu-ws-avatar">AC</div>
|
||||||
|
<div class="clu-ws-meta">
|
||||||
|
<div class="clu-ws-name">Acme Cluster</div>
|
||||||
|
<div class="clu-ws-plan">PRO · 87/100 sites</div>
|
||||||
|
</div>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted-2) flex-none" aria-hidden="true">
|
||||||
|
<path d="M7 9l5-5 5 5M7 15l5 5 5-5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Workspace switch --}}
|
{{-- Scrollable nav --}}
|
||||||
<div class="clu-ws-switch" role="button" tabindex="0">
|
<div class="clu-sidebar-scroll">
|
||||||
<div class="clu-ws-avatar">AC</div>
|
<nav class="clu-nav">
|
||||||
<div class="clu-ws-meta">
|
<div class="clu-nav-title">Cluster</div>
|
||||||
<div class="clu-ws-name">Acme Cluster</div>
|
@foreach ($items as $item)
|
||||||
<div class="clu-ws-plan">PRO · 87/100 sites</div>
|
@php
|
||||||
</div>
|
$isActive = ($active === $item['route']) || ($active === $item['name']);
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted-2) flex-none" aria-hidden="true">
|
$href = \Illuminate\Support\Facades\Route::has($item['route']) ? route($item['route']) : '#';
|
||||||
<path d="M7 9l5-5 5 5M7 15l5 5 5-5"/>
|
@endphp
|
||||||
</svg>
|
<a href="{{ $href }}" wire:navigate
|
||||||
|
class="clu-nav-item @if($isActive) active @endif"
|
||||||
|
@click="sidebarOpen = false">
|
||||||
|
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||||
|
{!! $item['svg'] !!}
|
||||||
|
</svg>
|
||||||
|
{{ $item['label'] }}
|
||||||
|
@if (! empty($item['count']))
|
||||||
|
<span class="count">{{ $item['count'] }}</span>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Nav --}}
|
{{-- Fixed footer --}}
|
||||||
<nav class="clu-nav">
|
<div class="clu-sidebar-foot">
|
||||||
<div class="clu-nav-title">Cluster</div>
|
<div class="clu-plan-box">
|
||||||
@foreach ($items as $item)
|
<div class="clu-plan-row">
|
||||||
@php
|
<span class="clu-plan-name">PRO</span>
|
||||||
$isActive = ($active === $item['route']) || ($active === $item['name']);
|
<span class="clu-plan-tag">62% USED</span>
|
||||||
$href = \Illuminate\Support\Facades\Route::has($item['route']) ? route($item['route']) : '#';
|
</div>
|
||||||
@endphp
|
<div class="clu-plan-meter"><span style="width:62%;"></span></div>
|
||||||
<a href="{{ $href }}" wire:navigate
|
<div class="clu-plan-stats">
|
||||||
class="clu-nav-item @if($isActive) active @endif">
|
<span>87 / 100 sites</span>
|
||||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
<span>€249/mo</span>
|
||||||
{!! $item['svg'] !!}
|
</div>
|
||||||
</svg>
|
|
||||||
{{ $item['label'] }}
|
|
||||||
@if (! empty($item['count']))
|
|
||||||
<span class="count">{{ $item['count'] }}</span>
|
|
||||||
@endif
|
|
||||||
</a>
|
|
||||||
@endforeach
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{{-- Plan box --}}
|
|
||||||
<div class="clu-plan-box">
|
|
||||||
<div class="clu-plan-row">
|
|
||||||
<span class="clu-plan-name">PRO</span>
|
|
||||||
<span class="clu-plan-tag">62% USED</span>
|
|
||||||
</div>
|
|
||||||
<div class="clu-plan-meter"><span style="width:62%;"></span></div>
|
|
||||||
<div class="clu-plan-stats">
|
|
||||||
<span>87 / 100 sites</span>
|
|
||||||
<span>€249/mo</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,17 @@
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
@livewireStyles
|
@livewireStyles
|
||||||
</head>
|
</head>
|
||||||
<body class="clu-bg-aurora">
|
<body class="clu-bg-aurora" x-data="{ sidebarOpen: false }">
|
||||||
<div class="clu-app-grid">
|
|
||||||
|
|
||||||
{{-- ───── Sidebar ───── --}}
|
{{-- Mobile backdrop --}}
|
||||||
|
<div class="clu-sidebar-backdrop"
|
||||||
|
:class="{ 'open': sidebarOpen }"
|
||||||
|
@click="sidebarOpen = false"
|
||||||
|
aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<div class="clu-app-grid">
|
||||||
<x-clu.sidebar :active="$active ?? request()->route()?->getName()" />
|
<x-clu.sidebar :active="$active ?? request()->route()?->getName()" />
|
||||||
|
|
||||||
{{-- ───── Main ───── --}}
|
|
||||||
<main class="flex flex-col min-w-0" style="padding:14px 14px 0;">
|
<main class="flex flex-col min-w-0" style="padding:14px 14px 0;">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="clu-page-title">Backups</h1>
|
<h1 class="clu-page-title">Backups</h1>
|
||||||
<div class="clu-page-sub">{{ $recoveryPoints }} wiederherstellungspunkte · {{ $totalSites }} sites</div>
|
<div class="clu-page-sub">{{ $recoveryPoints }} wiederherstellungspunkte · {{ $totalSites }} sites</div>
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="clu-page-title">Cluster anlegen</h1>
|
<h1 class="clu-page-title">Cluster anlegen</h1>
|
||||||
<div class="clu-page-sub">schritt {{ $step }} von 4</div>
|
<div class="clu-page-sub">schritt {{ $step }} von 4</div>
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
<div>
|
<div>
|
||||||
{{-- ───── Topbar ───── --}}
|
{{-- ───── Topbar ───── --}}
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
<div class="crumb">Acme Cluster · eu-central-1</div>
|
<div class="crumb">Acme Cluster · eu-central-1</div>
|
||||||
|
|
@ -112,6 +113,64 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- ───── Traffic chart (ChartJS) ───── --}}
|
||||||
|
<div class="clu-card mb-[18px]" wire:ignore>
|
||||||
|
<div class="clu-card-head">
|
||||||
|
<h3>Traffic · letzte 24h</h3>
|
||||||
|
<span class="meta">requests / minute</span>
|
||||||
|
</div>
|
||||||
|
<div class="clu-card-body" style="position:relative;height:240px;">
|
||||||
|
<canvas id="clu-traffic-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
(function initTrafficChart() {
|
||||||
|
const el = document.getElementById('clu-traffic-chart');
|
||||||
|
if (!el || !window.Chart) return;
|
||||||
|
if (el._chart) el._chart.destroy();
|
||||||
|
const ctx = el.getContext('2d');
|
||||||
|
|
||||||
|
// 24 buckets, hour labels
|
||||||
|
const labels = Array.from({length: 24}, (_, i) => ((24 + new Date().getHours() - 23 + i) % 24).toString().padStart(2, '0') + ':00');
|
||||||
|
// mock 24h traffic curve (peak midday)
|
||||||
|
const data = [42, 38, 32, 28, 22, 19, 25, 48, 92, 134, 168, 192, 210, 218, 204, 185, 162, 148, 135, 118, 96, 78, 64, 52];
|
||||||
|
|
||||||
|
const grad = ctx.createLinearGradient(0, 0, 0, 240);
|
||||||
|
grad.addColorStop(0, 'rgba(59,111,242,0.35)');
|
||||||
|
grad.addColorStop(1, 'rgba(59,111,242,0.00)');
|
||||||
|
|
||||||
|
el._chart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels,
|
||||||
|
datasets: [{
|
||||||
|
data,
|
||||||
|
borderColor: '#3b6ff2',
|
||||||
|
borderWidth: 2,
|
||||||
|
backgroundColor: grad,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.35,
|
||||||
|
pointRadius: 0,
|
||||||
|
pointHoverRadius: 4,
|
||||||
|
pointHoverBackgroundColor: '#3b6ff2',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: { legend: { display: false } },
|
||||||
|
scales: {
|
||||||
|
x: { grid: { color: 'rgba(30,35,60,0.06)' }, ticks: { font: { family: 'JetBrains Mono', size: 10 }, color: '#9596a6' } },
|
||||||
|
y: { grid: { color: 'rgba(30,35,60,0.06)' }, ticks: { font: { family: 'JetBrains Mono', size: 10 }, color: '#9596a6' }, beginAtZero: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
document.addEventListener('livewire:navigated', () => initTrafficChart());
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
{{-- ───── 2-col: Sites table + Activity feed ───── --}}
|
{{-- ───── 2-col: Sites table + Activity feed ───── --}}
|
||||||
<div class="grid gap-[14px] mb-[18px]" style="grid-template-columns:minmax(0,2fr) minmax(280px,1fr);">
|
<div class="grid gap-[14px] mb-[18px]" style="grid-template-columns:minmax(0,2fr) minmax(280px,1fr);">
|
||||||
|
|
||||||
|
|
@ -136,13 +195,13 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
@foreach ($sites as $site)
|
@foreach ($sites as $site)
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class="clu-site-cell">
|
<a href="{{ route('sites.show', $site) }}" wire:navigate class="clu-site-cell" style="text-decoration:none;color:inherit;">
|
||||||
<div class="clu-favicon">{{ mb_substr($site->domain, 0, 1) }}</div>
|
<div class="clu-favicon">{{ mb_substr($site->domain, 0, 1) }}</div>
|
||||||
<div class="clu-site-meta">
|
<div class="clu-site-meta">
|
||||||
<span class="clu-site-domain">{{ $site->domain }}</span>
|
<span class="clu-site-domain">{{ $site->domain }}</span>
|
||||||
<span class="clu-site-server">{{ $site->server->name }} · {{ $site->server->region }}</span>
|
<span class="clu-site-server">{{ $site->server->name }} · {{ $site->server->region }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="clu-pill muted">WP {{ $site->wp_version }} · PHP {{ $site->php_version }}</span></td>
|
<td><span class="clu-pill muted">WP {{ $site->wp_version }} · PHP {{ $site->php_version }}</span></td>
|
||||||
<td class="font-mono">{{ number_format($site->visitors_24h, 0, ',', '.') }}</td>
|
<td class="font-mono">{{ number_format($site->visitors_24h, 0, ',', '.') }}</td>
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="clu-page-title">Sicherheit</h1>
|
<h1 class="clu-page-title">Sicherheit</h1>
|
||||||
<div class="clu-page-sub">{{ $blockedCount }} blockiert / 24h · {{ $advisories->count() }} advisories</div>
|
<div class="clu-page-sub">{{ $blockedCount }} blockiert / 24h · {{ $advisories->count() }} advisories</div>
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
<div>
|
<div>
|
||||||
{{-- ───── Topbar ───── --}}
|
{{-- ───── Topbar ───── --}}
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="clu-page-title">Server</h1>
|
<h1 class="clu-page-title">Server</h1>
|
||||||
<div class="clu-page-sub">{{ $totalServers }} nodes · cluster acme</div>
|
<div class="clu-page-sub">{{ $totalServers }} nodes · cluster acme</div>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="clu-page-title">Einstellungen</h1>
|
<h1 class="clu-page-title">Einstellungen</h1>
|
||||||
<div class="clu-page-sub">workspace · benachrichtigungen · billing · api</div>
|
<div class="clu-page-sub">workspace · benachrichtigungen · billing · api</div>
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
<div>
|
<div>
|
||||||
{{-- ───── Topbar ───── --}}
|
{{-- ───── Topbar ───── --}}
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="clu-page-title">Sites</h1>
|
<h1 class="clu-page-title">Sites</h1>
|
||||||
<div class="clu-page-sub">{{ $totalSites }} wordpress-installationen</div>
|
<div class="clu-page-sub">{{ $totalSites }} wordpress-installationen</div>
|
||||||
|
|
@ -174,13 +175,13 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
@forelse ($sites as $site)
|
@forelse ($sites as $site)
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class="clu-site-cell">
|
<a href="{{ route('sites.show', $site) }}" wire:navigate class="clu-site-cell" style="text-decoration:none;color:inherit;">
|
||||||
<div class="clu-favicon">{{ mb_substr($site->domain, 0, 1) }}</div>
|
<div class="clu-favicon">{{ mb_substr($site->domain, 0, 1) }}</div>
|
||||||
<div class="clu-site-meta">
|
<div class="clu-site-meta">
|
||||||
<span class="clu-site-domain">{{ $site->domain }}</span>
|
<span class="clu-site-domain">{{ $site->domain }}</span>
|
||||||
<span class="clu-site-server">ssl ✓ · {{ $site->cache_enabled ? 'cache an' : 'cache aus' }}</span>
|
<span class="clu-site-server">ssl ✓ · {{ $site->cache_enabled ? 'cache an' : 'cache aus' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="clu-pill muted">{{ $site->server->name }}</span></td>
|
<td><span class="clu-pill muted">{{ $site->server->name }}</span></td>
|
||||||
<td><span class="clu-pill @if($site->updates_pending > 0) warning @else success @endif">WP {{ $site->wp_version }}</span></td>
|
<td><span class="clu-pill @if($site->updates_pending > 0) warning @else success @endif">WP {{ $site->wp_version }}</span></td>
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div class="flex items-center gap-[12px]">
|
<div class="flex items-center gap-[12px]">
|
||||||
<a href="{{ route('sites.index') }}" wire:navigate class="clu-icon-btn" aria-label="Zurück">
|
<a href="{{ route('sites.index') }}" wire:navigate class="clu-icon-btn" aria-label="Zurück">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ new #[Layout('layouts.app')] class extends Component {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="clu-topbar">
|
<div class="clu-topbar">
|
||||||
|
<x-clu.burger />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="clu-page-title">Team</h1>
|
<h1 class="clu-page-title">Team</h1>
|
||||||
<div class="clu-page-sub">{{ $totalMembers }} mitglieder · {{ $pendingInvites }} einladungen offen</div>
|
<div class="clu-page-sub">{{ $totalMembers }} mitglieder · {{ $pendingInvites }} einladungen offen</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue