Foundation + Login Glass page from template
- Tailwind theme tokens rewritten to match template light-glass design
(color-fg / muted / accent / glass / hairline + radii + animations).
- Component classes: clu-glass, clu-brand-mark, clu-status-chip, clu-sso-btn,
clu-input, clu-btn-primary, clu-alt-btn, clu-checkbox, clu-stat, clu-term-*,
clu-live-tag, clu-pill.
- layouts/login.blade.php: clu-bg-aurora body, @stack('scripts') for page JS.
- pages/auth/login: full re-port of templates/akutell/CluPilot Login Glass.html:
two-col grid, SSO buttons, e-mail+password with eye toggle (Alpine),
remember checkbox, magic-link/passkey alt buttons, footer with TLS info,
right-side live cluster panel with stat strip, animated terminal, live tags.
- Pest installed (pest, pest-plugin-laravel, pest-plugin-livewire).
- tests/Pest.php with RefreshDatabase binding.
- tests/Feature/Auth/LoginPageTest.php: 5 tests, all green
(renders chrome, mounts Volt, rejects empty, authenticates, rejects invalid).
- 12 design templates committed to templates/akutell/.
master
parent
2c787a2523
commit
5287b0024d
|
|
@ -22,6 +22,9 @@
|
|||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"pestphp/pest": "^3.8",
|
||||
"pestphp/pest-plugin-laravel": "^3.2",
|
||||
"pestphp/pest-plugin-livewire": "^3.0",
|
||||
"phpunit/phpunit": "^11.5.50"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,331 +1,406 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
/* ────────────────────────────────────────────────────────────────────────
|
||||
CluPilot design tokens — sourced from templates/akutell/* (light glass).
|
||||
──────────────────────────────────────────────────────────────────────── */
|
||||
@theme {
|
||||
--color-bg: #0a0a0f;
|
||||
--color-s0: #0c0c12;
|
||||
--color-s1: #111117;
|
||||
--color-s2: #16161e;
|
||||
--color-s3: #1c1c26;
|
||||
--color-hover: #1a1a24;
|
||||
--color-surface: #101019;
|
||||
--color-surface2: #1d1d28;
|
||||
--color-b1: rgba(255,255,255,0.06);
|
||||
--color-b2: rgba(255,255,255,0.10);
|
||||
--color-t1: #f0f0f5;
|
||||
--color-t2: #8888a0;
|
||||
--color-t3: #44445a;
|
||||
--color-dim: #5c5c70;
|
||||
--color-akzent: #4f8ef7;
|
||||
--color-akzent2: #7c5cff;
|
||||
--color-success: #3ecf8e;
|
||||
--color-warning: #f5a623;
|
||||
--color-danger: #f05252;
|
||||
--color-fg: #1a1a22;
|
||||
--color-muted: #5c5c6b;
|
||||
--color-muted-2: #9596a6;
|
||||
--color-hairline: rgba(30,35,60,0.08);
|
||||
|
||||
--color-accent: #3b6ff2;
|
||||
--color-accent-2: #7c5cff;
|
||||
--color-success: #15a06a;
|
||||
--color-warning: #c2820f;
|
||||
--color-danger: #db3b3b;
|
||||
--color-purple: #c084fc;
|
||||
|
||||
--font-sans: "Inter", system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono", monospace;
|
||||
--color-accent-soft: rgba(59,111,242,0.12);
|
||||
--color-success-soft: rgba(21,160,106,0.12);
|
||||
--color-warning-soft: rgba(194,130,15,0.12);
|
||||
--color-danger-soft: rgba(219,59,59,0.12);
|
||||
|
||||
--color-glass: rgba(255,255,255,0.55);
|
||||
--color-glass-strong: rgba(255,255,255,0.72);
|
||||
--color-glass-soft: rgba(255,255,255,0.40);
|
||||
--color-glass-border: rgba(255,255,255,0.75);
|
||||
|
||||
--color-term-bg: rgba(14,15,24,0.92);
|
||||
--color-term-border: rgba(255,255,255,0.12);
|
||||
|
||||
--radius-xs: 11px;
|
||||
--radius-sm: 13px;
|
||||
--radius: 20px;
|
||||
|
||||
--font-sans: "Inter", system-ui, -apple-system, sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
|
||||
--shadow-glass: 0 10px 34px -10px rgba(40,52,92,0.20), 0 2px 8px -2px rgba(40,52,92,0.08);
|
||||
--shadow-glass-inset: inset 0 1px 0 rgba(255,255,255,0.95), inset 0 -1px 0 rgba(255,255,255,0.25);
|
||||
}
|
||||
|
||||
/* ───────── Base ───────── */
|
||||
@layer base {
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
color: var(--color-fg);
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13.5px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
min-height: 100vh;
|
||||
}
|
||||
a { color: var(--color-accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
button { font-family: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
|
||||
|
||||
body.clu-bg-aurora {
|
||||
background:
|
||||
radial-gradient(60% 55% at 12% 8%, rgba(99,140,255,0.30), transparent 60%),
|
||||
radial-gradient(55% 50% at 88% 0%, rgba(140,108,255,0.26), transparent 58%),
|
||||
radial-gradient(50% 60% at 78% 92%, rgba(64,196,210,0.22), transparent 60%),
|
||||
radial-gradient(45% 50% at 25% 100%, rgba(120,170,255,0.20), transparent 60%),
|
||||
linear-gradient(160deg,#eef1fb 0%,#f4f3fb 50%,#eaf2f8 100%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
body.clu-bg-app {
|
||||
background:
|
||||
radial-gradient(80% 60% at 0% 0%, rgba(99,140,255,0.16), transparent 60%),
|
||||
radial-gradient(60% 50% at 100% 0%, rgba(140,108,255,0.14), transparent 60%),
|
||||
linear-gradient(180deg,#f3f4fa 0%,#eef0f6 100%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Keyframes ── */
|
||||
@keyframes clu-pulse { 0%{transform:scale(.6);opacity:.5} 100%{transform:scale(2.4);opacity:0} }
|
||||
@keyframes clu-type-in { to{opacity:1;transform:translateY(0)} }
|
||||
@keyframes clu-blink { 50%{opacity:0} }
|
||||
@keyframes clu-spin { to{transform:rotate(360deg)} }
|
||||
@keyframes clu-dot-pulse { 0%,100%{opacity:1} 50%{opacity:.3} }
|
||||
@keyframes clu-fade { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:none} }
|
||||
|
||||
@layer utilities {
|
||||
.dot-pulse { animation: clu-dot-pulse 2s infinite; }
|
||||
.clu-reset { all: revert; }
|
||||
.clu-spin { animation: clu-spin .8s linear infinite; }
|
||||
.clu-fade-in { animation: clu-fade .25s ease-out both; }
|
||||
.dot-pulse { animation: clu-dot-pulse 2s infinite; }
|
||||
}
|
||||
|
||||
@layer components {
|
||||
|
||||
/* Login ambient background */
|
||||
.clu-login-body::before {
|
||||
content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
|
||||
background:
|
||||
radial-gradient(60% 50% at 12% 10%,rgba(79,142,247,.10),transparent 65%),
|
||||
radial-gradient(50% 50% at 88% 100%,rgba(124,92,255,.08),transparent 65%);
|
||||
/* Glass panel */
|
||||
.clu-glass {
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-glass-strong);
|
||||
backdrop-filter: blur(28px) saturate(175%);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(175%);
|
||||
border: 1px solid var(--color-glass-border);
|
||||
box-shadow: var(--shadow-glass), var(--shadow-glass-inset);
|
||||
}
|
||||
.clu-login-body::after {
|
||||
content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,.018) 1px,transparent 1px),
|
||||
linear-gradient(90deg,rgba(255,255,255,.018) 1px,transparent 1px);
|
||||
background-size:32px 32px;
|
||||
mask-image:radial-gradient(ellipse at center,#000 30%,transparent 75%);
|
||||
.clu-glass-sm {
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-glass);
|
||||
backdrop-filter: blur(14px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(160%);
|
||||
border: 1px solid var(--color-glass-border);
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
|
||||
}
|
||||
|
||||
/* Brand marks */
|
||||
/* Brand mark */
|
||||
.clu-brand-mark {
|
||||
background:linear-gradient(135deg,#4f8ef7 0%,#7c5cff 100%);
|
||||
box-shadow:0 0 0 1px rgba(255,255,255,.06) inset,0 6px 16px rgba(79,142,247,.30);
|
||||
width: 30px; height: 30px;
|
||||
border-radius: 9px;
|
||||
background: linear-gradient(135deg, #5a8bff 0%, #8a63ff 100%);
|
||||
display: grid; place-items: center;
|
||||
box-shadow: 0 0 0 1px rgba(255,255,255,0.4) inset, 0 6px 16px -4px rgba(90,139,255,0.6);
|
||||
flex: none;
|
||||
}
|
||||
.clu-brand-mark::before {
|
||||
content:"";width:11px;height:11px;border-radius:50%;
|
||||
background:#0a0a0f;box-shadow:inset 0 0 0 2px #fff;
|
||||
}
|
||||
.clu-brand-mark-sm {
|
||||
background:linear-gradient(135deg,#4f8ef7 0%,#7c5cff 100%);
|
||||
box-shadow:0 0 0 1px rgba(255,255,255,.06) inset,0 4px 12px rgba(79,142,247,.25);
|
||||
display:grid;place-items:center;
|
||||
}
|
||||
.clu-brand-mark-sm::before {
|
||||
content:"";width:10px;height:10px;border-radius:50%;
|
||||
background:#0c0c12;box-shadow:inset 0 0 0 2px #fff;
|
||||
content: "";
|
||||
width: 11px; height: 11px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.96);
|
||||
box-shadow: inset 0 0 0 2px #5a8bff;
|
||||
}
|
||||
|
||||
/* Pulse dot */
|
||||
.clu-pulse-dot { position:relative;flex:none; }
|
||||
/* Status chip */
|
||||
.clu-status-chip {
|
||||
display: inline-flex; align-items: center; gap: 7px;
|
||||
padding: 4px 11px 4px 9px;
|
||||
border: 1px solid rgba(21,160,106,0.28);
|
||||
background: var(--color-success-soft);
|
||||
color: var(--color-success);
|
||||
border-radius: 999px;
|
||||
font-size: 11px; font-weight: 600;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.clu-pulse-dot {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
position: relative; flex: none;
|
||||
}
|
||||
.clu-pulse-dot::after {
|
||||
content:"";position:absolute;inset:-3px;border-radius:50%;
|
||||
background:currentColor;opacity:.35;animation:clu-pulse 1.8s ease-out infinite;
|
||||
}
|
||||
|
||||
/* Input with :focus-within */
|
||||
.clu-input {
|
||||
display:flex;align-items:center;gap:10px;padding:0 12px;
|
||||
background:#101019;border:1px solid rgba(255,255,255,.06);border-radius:8px;
|
||||
transition:border-color .15s,background .15s,box-shadow .15s;height:40px;
|
||||
}
|
||||
.clu-input:hover { border-color:rgba(255,255,255,.10); }
|
||||
.clu-input:focus-within {
|
||||
border-color:rgba(79,142,247,.50);background:#12121c;
|
||||
box-shadow:0 0 0 3px rgba(79,142,247,.10);
|
||||
}
|
||||
.clu-input input {
|
||||
flex:1;background:none;border:0;outline:0;
|
||||
color:#f0f0f5;font-size:13.5px;font-family:inherit;min-width:0;height:100%;
|
||||
}
|
||||
.clu-input input::placeholder { color:#5c5c70; }
|
||||
|
||||
/* Divider */
|
||||
.clu-divider {
|
||||
display:flex;align-items:center;gap:12px;color:#5c5c70;
|
||||
font-size:11px;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;
|
||||
}
|
||||
.clu-divider::before,.clu-divider::after {
|
||||
content:"";flex:1;height:1px;background:rgba(255,255,255,.06);
|
||||
content: "";
|
||||
position: absolute; inset: -3px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
opacity: 0.35;
|
||||
animation: clu-pulse 1.8s ease-out infinite;
|
||||
}
|
||||
|
||||
/* SSO button */
|
||||
.clu-sso-btn {
|
||||
display:flex;align-items:center;justify-content:center;gap:8px;padding:9px 12px;
|
||||
background:#101019;border:1px solid rgba(255,255,255,.06);border-radius:8px;
|
||||
color:#f0f0f5;font-size:12.5px;font-weight:500;transition:all .15s;cursor:pointer;font-family:inherit;
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: rgba(255,255,255,0.55);
|
||||
border: 1px solid var(--color-hairline);
|
||||
border-radius: var(--radius-xs);
|
||||
color: var(--color-fg);
|
||||
font-size: 12.5px; font-weight: 600;
|
||||
transition: background .15s, border-color .15s;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
|
||||
}
|
||||
.clu-sso-btn:hover { background:#1d1d28;border-color:rgba(255,255,255,.10); }
|
||||
.clu-sso-btn:hover { background: rgba(255,255,255,0.9); border-color: var(--color-glass-border); }
|
||||
.clu-sso-btn svg { flex: none; }
|
||||
|
||||
/* Divider */
|
||||
.clu-divider {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
color: var(--color-muted-2);
|
||||
font-size: 11px;
|
||||
font-family: var(--font-mono);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.clu-divider::before, .clu-divider::after {
|
||||
content: "";
|
||||
flex: 1; height: 1px;
|
||||
background: var(--color-hairline);
|
||||
}
|
||||
|
||||
/* Input wrapper */
|
||||
.clu-input {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 0 13px;
|
||||
background: rgba(255,255,255,0.5);
|
||||
border: 1px solid var(--color-hairline);
|
||||
border-radius: var(--radius-xs);
|
||||
transition: border-color .15s, background .15s, box-shadow .15s;
|
||||
height: 42px;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.7);
|
||||
}
|
||||
.clu-input:hover { border-color: var(--color-glass-border); background: rgba(255,255,255,0.65); }
|
||||
.clu-input:focus-within {
|
||||
border-color: rgba(59,111,242,0.5);
|
||||
background: rgba(255,255,255,0.85);
|
||||
box-shadow: 0 0 0 3px rgba(59,111,242,0.12);
|
||||
}
|
||||
.clu-input svg { color: var(--color-muted-2); flex: none; }
|
||||
.clu-input input {
|
||||
flex: 1;
|
||||
background: none; border: 0; outline: 0;
|
||||
color: var(--color-fg);
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
min-width: 0; height: 100%;
|
||||
}
|
||||
.clu-input input::placeholder { color: var(--color-muted-2); }
|
||||
.clu-input .clu-eye {
|
||||
color: var(--color-muted);
|
||||
padding: 4px;
|
||||
border-radius: 6px;
|
||||
display: grid; place-items: center;
|
||||
transition: color .15s, background .15s;
|
||||
}
|
||||
.clu-input .clu-eye:hover { color: var(--color-fg); background: rgba(255,255,255,0.7); }
|
||||
|
||||
/* Primary button */
|
||||
.clu-btn-primary {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
||||
padding:10px 16px;background:#4f8ef7;color:#fff;border-radius:8px;
|
||||
font-weight:500;font-size:13.5px;height:40px;width:100%;
|
||||
transition:background .15s,transform .05s;border:none;cursor:pointer;font-family:inherit;
|
||||
box-shadow:0 1px 0 rgba(255,255,255,.08) inset,0 1px 3px rgba(79,142,247,.35);
|
||||
padding: 11px 16px;
|
||||
background: linear-gradient(180deg, #4f7dff, #3b6ff2);
|
||||
color: #fff;
|
||||
border-radius: var(--radius-xs);
|
||||
font-weight: 600; font-size: 13.5px;
|
||||
height: 44px;
|
||||
transition: filter .15s, transform .05s;
|
||||
box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset, 0 6px 16px -5px rgba(59,111,242,0.65);
|
||||
}
|
||||
.clu-btn-primary:hover { background:#5e98f7; }
|
||||
.clu-btn-primary:hover { filter: brightness(1.06); }
|
||||
.clu-btn-primary:active { transform: translateY(1px); }
|
||||
.clu-btn-primary:disabled { opacity:.7;cursor:not-allowed; }
|
||||
.clu-btn-primary svg.arrow { transition:transform .15s; }
|
||||
.clu-btn-primary:hover:not(:disabled) svg.arrow { transform:translateX(2px); }
|
||||
.clu-btn-primary:disabled { opacity: 0.7; cursor: progress; }
|
||||
.clu-btn-primary .arrow { transition: transform .15s; }
|
||||
.clu-btn-primary:hover .arrow { transform: translateX(2px); }
|
||||
|
||||
/* Alt button */
|
||||
/* Alt / ghost button */
|
||||
.clu-alt-btn {
|
||||
display:flex;align-items:center;gap:10px;padding:9px 12px;
|
||||
background:transparent;border:1px solid rgba(255,255,255,.06);border-radius:8px;
|
||||
font-size:12.5px;color:#f0f0f5;font-weight:500;transition:all .15s;
|
||||
width:100%;cursor:pointer;font-family:inherit;
|
||||
display: flex; align-items: center; gap: 11px;
|
||||
padding: 10px 13px;
|
||||
background: rgba(255,255,255,0.4);
|
||||
border: 1px solid var(--color-hairline);
|
||||
border-radius: var(--radius-xs);
|
||||
font-size: 12.5px; color: var(--color-fg); font-weight: 600;
|
||||
transition: background .15s, border-color .15s;
|
||||
}
|
||||
.clu-alt-btn:hover { border-color:rgba(255,255,255,.10);background:#101019; }
|
||||
.clu-alt-btn:hover { background: rgba(255,255,255,0.7); border-color: var(--color-glass-border); }
|
||||
|
||||
/* Live side glow */
|
||||
.clu-live-side::before {
|
||||
content:"";position:absolute;inset:0;pointer-events:none;
|
||||
background:radial-gradient(80% 50% at 100% 0%,rgba(79,142,247,.12),transparent 65%);
|
||||
/* Checkbox */
|
||||
.clu-checkbox {
|
||||
display: inline-flex; align-items: center; gap: 9px;
|
||||
font-size: 12.5px; color: var(--color-muted);
|
||||
cursor: pointer; user-select: none;
|
||||
}
|
||||
.clu-checkbox input { display: none; }
|
||||
.clu-checkbox .box {
|
||||
width: 16px; height: 16px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(30,35,60,0.2);
|
||||
background: rgba(255,255,255,0.7);
|
||||
display: grid; place-items: center;
|
||||
transition: background .15s, border-color .15s;
|
||||
flex: none;
|
||||
}
|
||||
.clu-checkbox input:checked + .box { background: var(--color-accent); border-color: var(--color-accent); }
|
||||
.clu-checkbox .box svg { opacity: 0; transform: scale(.6); transition: opacity .15s, transform .15s; color: #fff; }
|
||||
.clu-checkbox input:checked + .box svg { opacity: 1; transform: scale(1); }
|
||||
.clu-checkbox:hover { color: var(--color-fg); }
|
||||
|
||||
/* Terminal */
|
||||
/* Stat tile */
|
||||
.clu-stat {
|
||||
background: var(--color-glass);
|
||||
border: 1px solid var(--color-glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 13px;
|
||||
backdrop-filter: blur(14px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(160%);
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
|
||||
}
|
||||
.clu-stat-label {
|
||||
font-size: 10.5px;
|
||||
color: var(--color-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.clu-stat-value {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 19px; font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.clu-stat-value .unit {
|
||||
font-size: 12px; color: var(--color-muted-2); font-weight: 500; margin-left: 2px;
|
||||
}
|
||||
.clu-stat-trend {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
font-family: var(--font-mono); font-size: 10.5px;
|
||||
color: var(--color-success);
|
||||
margin-top: 5px;
|
||||
}
|
||||
.clu-stat-trend.warn { color: var(--color-warning); }
|
||||
.clu-stat-trend.bad { color: var(--color-danger); }
|
||||
|
||||
/* Terminal card */
|
||||
.clu-term-card {
|
||||
background: var(--color-term-bg);
|
||||
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--color-term-border);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
min-height: 280px;
|
||||
box-shadow: 0 14px 40px -12px rgba(20,24,50,0.45), inset 0 1px 0 rgba(255,255,255,0.08);
|
||||
}
|
||||
.clu-term-head {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.07);
|
||||
background: rgba(10,10,16,0.6);
|
||||
}
|
||||
.clu-term-dots { display: flex; gap: 5px; }
|
||||
.clu-term-dots span { width: 9px; height: 9px; border-radius: 50%; background: #2a2a36; }
|
||||
.clu-term-dots span:nth-child(1) { background: #f05252; }
|
||||
.clu-term-dots span:nth-child(2) { background: #f5a623; }
|
||||
.clu-term-dots span:nth-child(3) { background: #3ecf8e; }
|
||||
.clu-term-title { font-family: var(--font-mono); font-size: 11.5px; color: #8888a0; flex: 1; text-align: center; }
|
||||
.clu-term-meta { font-family: var(--font-mono); font-size: 10.5px; color: #5c5c70; }
|
||||
.clu-term-body {
|
||||
padding: 14px 16px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11.5px; line-height: 1.6;
|
||||
flex: 1; overflow: hidden;
|
||||
color: #cccddd;
|
||||
}
|
||||
.clu-term-line {
|
||||
display: flex; gap: 10px; align-items: flex-start;
|
||||
opacity:0;transform:translateY(4px);animation:clu-type-in .35s ease forwards;
|
||||
opacity: 0; transform: translateY(4px);
|
||||
animation: clu-type-in .35s ease forwards;
|
||||
}
|
||||
.clu-term-ok { color:#3ecf8e; }
|
||||
.clu-term-warn { color:#f5a623; }
|
||||
.clu-term-err { color:#f05252; }
|
||||
.clu-term-acc { color:#4f8ef7; }
|
||||
.clu-term-dim { color:#5c5c70; }
|
||||
.clu-term-pkg { color:#c084fc; }
|
||||
|
||||
/* Cursor */
|
||||
.clu-term-time { color: #5c5c70; flex: none; width: 60px; font-size: 10.5px; padding-top: 1px; }
|
||||
.clu-term-text { flex: 1; min-width: 0; word-break: break-word; }
|
||||
.clu-term-text .ok { color: #3ecf8e; }
|
||||
.clu-term-text .warn { color: #f5a623; }
|
||||
.clu-term-text .err { color: #f05252; }
|
||||
.clu-term-text .acc { color: #6f9bff; }
|
||||
.clu-term-text .dim { color: #8888a0; }
|
||||
.clu-term-text .pkg { color: #c084fc; }
|
||||
.clu-term-prompt { display: inline-flex; align-items: center; gap: 7px; color: #3ecf8e; }
|
||||
.clu-term-prompt::before { content: "$"; color: #6f9bff; font-weight: 600; }
|
||||
.clu-cursor {
|
||||
display:inline-block;width:7px;height:13px;background:#4f8ef7;
|
||||
vertical-align:-2px;animation:clu-blink 1s steps(1) infinite;margin-left:2px;
|
||||
display: inline-block;
|
||||
width: 7px; height: 13px;
|
||||
background: #6f9bff;
|
||||
vertical-align: -2px;
|
||||
animation: clu-blink 1s steps(1) infinite;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* Dashboard topbar */
|
||||
.clu-topbar {
|
||||
backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
|
||||
background:rgba(10,10,15,.85);
|
||||
/* Live tag */
|
||||
.clu-live-tag {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
padding: 4px 9px;
|
||||
font-size: 10.5px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-muted);
|
||||
background: var(--color-glass);
|
||||
border: 1px solid var(--color-glass-border);
|
||||
border-radius: 7px;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.7);
|
||||
}
|
||||
.clu-live-tag .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--color-success); }
|
||||
.clu-live-tag.warn .dot { background: var(--color-warning); }
|
||||
.clu-live-tag.bad .dot { background: var(--color-danger); }
|
||||
|
||||
/* Search */
|
||||
.clu-search {
|
||||
display:flex;align-items:center;gap:8px;padding:6px 10px;
|
||||
background:#101019;border:1px solid rgba(255,255,255,.06);border-radius:7px;
|
||||
color:#8888a0;transition:border-color .15s;min-width:280px;
|
||||
}
|
||||
.clu-search:hover { border-color:rgba(255,255,255,.10); }
|
||||
.clu-search:focus-within { border-color:rgba(79,142,247,.5);background:#12121c; }
|
||||
.clu-search input {
|
||||
flex:1;background:none;border:0;outline:0;
|
||||
color:#f0f0f5;font-size:12.5px;font-family:inherit;min-width:0;
|
||||
}
|
||||
.clu-search input::placeholder { color:#8888a0; }
|
||||
|
||||
/* Dashboard button */
|
||||
.clu-btn-dash {
|
||||
display:inline-flex;align-items:center;gap:6px;padding:7px 12px;
|
||||
background:#4f8ef7;color:#fff;border-radius:7px;font-weight:500;font-size:12.5px;
|
||||
transition:background .15s;border:none;cursor:pointer;font-family:inherit;
|
||||
box-shadow:0 1px 0 rgba(255,255,255,.08) inset,0 1px 3px rgba(79,142,247,.35);
|
||||
}
|
||||
.clu-btn-dash:hover { background:#5e98f7; }
|
||||
|
||||
/* Metric top stripe */
|
||||
.clu-metric { position:relative;overflow:hidden; }
|
||||
.clu-metric::before { content:"";position:absolute;top:0;left:0;right:0;height:2px;background:#4f8ef7; }
|
||||
.clu-metric-success::before { background:#3ecf8e; }
|
||||
.clu-metric-warning::before { background:#f5a623; }
|
||||
.clu-metric-danger::before { background:#f05252; }
|
||||
.clu-metric-neutral::before { background:#5c5c70; }
|
||||
|
||||
/* Nav active bar */
|
||||
.clu-nav-active { position:relative; }
|
||||
.clu-nav-active::before {
|
||||
content:"";position:absolute;left:-12px;top:6px;bottom:6px;
|
||||
width:2px;border-radius:2px;background:#4f8ef7;
|
||||
}
|
||||
|
||||
/* Row actions */
|
||||
.clu-row-actions { display:flex;gap:4px;justify-content:flex-end;opacity:0;transition:opacity .15s; }
|
||||
tr:hover .clu-row-actions { opacity:1; }
|
||||
|
||||
/* Plan box overlay */
|
||||
.clu-plan-box { position:relative;overflow:hidden; }
|
||||
.clu-plan-box::before {
|
||||
content:"";position:absolute;inset:0;pointer-events:none;
|
||||
background:radial-gradient(120% 80% at 0% 0%,rgba(79,142,247,.10),transparent 60%);
|
||||
}
|
||||
|
||||
/* Chart axis */
|
||||
.clu-chart-axis { font-family:var(--font-mono);font-size:9.5px;fill:#5c5c70; }
|
||||
|
||||
/* ── Stepper ── */
|
||||
.clu-stepper { display:flex;align-items:center;gap:10px;margin-top:28px;font-family:var(--font-mono);font-size:11px;color:#44445a; }
|
||||
.clu-step { display:flex;align-items:center;gap:7px; }
|
||||
.clu-step-num { width:20px;height:20px;border-radius:50%;border:1px solid rgba(255,255,255,.06);display:grid;place-items:center;font-size:10.5px;color:#8888a0;background:#101019; }
|
||||
.clu-step.active .clu-step-num { border-color:#4f8ef7;color:#4f8ef7;background:rgba(79,142,247,.12); }
|
||||
.clu-step.done .clu-step-num { border-color:#3ecf8e;color:#0a0a0f;background:#3ecf8e; }
|
||||
.clu-step-label { font-size:11px;color:#8888a0;text-transform:uppercase;letter-spacing:.06em;font-weight:500; }
|
||||
.clu-step.active .clu-step-label { color:#f0f0f5; }
|
||||
.clu-step-line { flex:1;height:1px;background:rgba(255,255,255,.06);min-width:14px;max-width:28px; }
|
||||
|
||||
/* ── Two-column field row ── */
|
||||
.clu-field-row { display:grid;grid-template-columns:1fr 1fr;gap:10px; }
|
||||
|
||||
/* ── Input suffix ── */
|
||||
.clu-input.with-suffix { padding-right:0; }
|
||||
.clu-input-suffix { height:100%;display:flex;align-items:center;padding:0 12px;border-left:1px solid rgba(255,255,255,.06);color:#8888a0;font-family:var(--font-mono);font-size:11.5px;background:rgba(255,255,255,.015);border-top-right-radius:8px;border-bottom-right-radius:8px; }
|
||||
|
||||
/* ── Input valid checkmark ── */
|
||||
.clu-input-check { color:#3ecf8e;display:none;flex:none; }
|
||||
.clu-input.valid .clu-input-check { display:inline-flex; }
|
||||
|
||||
/* ── Slug preview ── */
|
||||
.clu-slug-preview { font-family:var(--font-mono);font-size:11px;color:#8888a0;margin-top:4px; }
|
||||
.clu-slug-preview b { color:#4f8ef7;font-weight:500; }
|
||||
|
||||
/* ── Password strength meter ── */
|
||||
.clu-pw-meter { display:flex;gap:4px;margin-top:6px; }
|
||||
.clu-pw-meter span { flex:1;height:3px;border-radius:2px;background:#16161e;transition:background .2s; }
|
||||
.clu-pw-meter.s1 span:nth-child(-n+1) { background:#f05252; }
|
||||
.clu-pw-meter.s2 span:nth-child(-n+2) { background:#f5a623; }
|
||||
.clu-pw-meter.s3 span:nth-child(-n+3) { background:#4f8ef7; }
|
||||
.clu-pw-meter.s4 span { background:#3ecf8e; }
|
||||
.clu-pw-rules { display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:6px;font-family:var(--font-mono);font-size:10.5px;color:#5c5c70; }
|
||||
.clu-pw-rule { display:inline-flex;align-items:center;gap:5px; }
|
||||
.clu-pw-rule .clu-pw-bullet { width:5px;height:5px;border-radius:50%;background:#5c5c70;transition:background .15s; }
|
||||
.clu-pw-rule.ok { color:#3ecf8e; }
|
||||
.clu-pw-rule.ok .clu-pw-bullet { background:#3ecf8e; }
|
||||
|
||||
/* ── Checkbox ── */
|
||||
.clu-checkbox { display:flex;align-items:flex-start;gap:10px;font-size:12.5px;color:#8888a0;cursor:pointer;user-select:none;line-height:1.45; }
|
||||
.clu-checkbox .clu-check-box { width:15px;height:15px;border-radius:4px;border:1px solid rgba(255,255,255,.10);background:#101019;display:grid;place-items:center;transition:all .15s;flex:none;margin-top:1px; }
|
||||
.clu-checkbox input:checked ~ .clu-check-box { background:#4f8ef7;border-color:#4f8ef7; }
|
||||
.clu-checkbox input:checked ~ .clu-check-box svg { opacity:1;transform:scale(1); }
|
||||
.clu-checkbox .clu-check-box svg { opacity:0;transform:scale(.6);transition:all .15s;color:#fff; }
|
||||
.clu-checkbox:hover { color:#f0f0f5; }
|
||||
|
||||
/* ── Trial card (signup right panel) ── */
|
||||
.clu-trial-card { background:#16161e;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:18px 20px;position:relative;overflow:hidden; }
|
||||
.clu-trial-card::before { content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#4f8ef7,#7c5cff); }
|
||||
.clu-trial-row { display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px; }
|
||||
.clu-trial-tag { font-family:var(--font-mono);font-size:10.5px;color:#4f8ef7;background:rgba(79,142,247,.12);padding:3px 8px;border-radius:5px;border:1px solid rgba(79,142,247,.25);font-weight:500;text-transform:uppercase;letter-spacing:.05em; }
|
||||
.clu-trial-count { font-family:var(--font-mono);font-size:11px;color:#8888a0;display:inline-flex;align-items:center;gap:6px; }
|
||||
.clu-trial-title { font-size:22px;font-weight:600;letter-spacing:-.015em;line-height:1.25;margin-top:12px; }
|
||||
.clu-trial-title em { font-style:normal;background:linear-gradient(90deg,#4f8ef7,#a78bfa);-webkit-background-clip:text;background-clip:text;color:transparent;font-weight:700; }
|
||||
.clu-trial-sub { color:#8888a0;font-size:13px;margin-top:6px; }
|
||||
|
||||
/* ── Benefit list ── */
|
||||
.clu-benefit-list { list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:14px; }
|
||||
.clu-benefit { display:flex;gap:12px;align-items:flex-start; }
|
||||
.clu-benefit-ico { width:30px;height:30px;border-radius:8px;background:rgba(79,142,247,.12);border:1px solid rgba(79,142,247,.22);color:#4f8ef7;display:grid;place-items:center;flex:none; }
|
||||
.clu-benefit-ico.success { background:rgba(62,207,142,.12);border-color:rgba(62,207,142,.22);color:#3ecf8e; }
|
||||
.clu-benefit-ico.purple { background:rgba(124,92,255,.12);border-color:rgba(124,92,255,.25);color:#a78bfa; }
|
||||
.clu-benefit-ico.warn { background:rgba(245,166,35,.10);border-color:rgba(245,166,35,.22);color:#f5a623; }
|
||||
.clu-benefit-title { font-size:13.5px;font-weight:500;color:#f0f0f5; }
|
||||
.clu-benefit-desc { font-size:12.5px;color:#8888a0;margin-top:2px;line-height:1.5; }
|
||||
|
||||
/* ── Quote card ── */
|
||||
.clu-quote-card { background:#16161e;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:18px 20px;margin-top:auto; }
|
||||
.clu-quote-text { font-size:13.5px;color:#dcdce8;line-height:1.55; }
|
||||
.clu-quote-text::before { content:"\201E";color:#4f8ef7;font-size:24px;line-height:0;margin-right:4px;vertical-align:-6px;font-weight:600; }
|
||||
.clu-quote-text::after { content:"\201D";color:#4f8ef7;font-weight:600; }
|
||||
.clu-quote-author { display:flex;align-items:center;gap:10px;margin-top:12px; }
|
||||
.clu-avatar { width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#3ecf8e,#2a9d6f);display:grid;place-items:center;color:#0a0a0f;font-weight:600;font-size:12px;font-family:var(--font-mono); }
|
||||
.clu-author-name { font-size:12px;color:#f0f0f5;font-weight:500; }
|
||||
.clu-author-role { font-size:11px;color:#8888a0;font-family:var(--font-mono); }
|
||||
|
||||
/* ── Logo strip ── */
|
||||
.clu-logos { display:flex;align-items:center;gap:20px;flex-wrap:wrap;opacity:.55;font-family:var(--font-mono);font-size:11px;color:#8888a0;text-transform:uppercase;letter-spacing:.08em; }
|
||||
.clu-logo-pill { padding:5px 10px;border:1px solid rgba(255,255,255,.06);border-radius:5px;background:#101019; }
|
||||
|
||||
/* Login page responsive grid */
|
||||
.clu-login-grid {
|
||||
display:grid;
|
||||
grid-template-columns:minmax(420px,1fr) 1fr;
|
||||
min-height:100vh;
|
||||
min-height:100dvh;
|
||||
/* Auth two-column grid */
|
||||
.clu-auth-grid {
|
||||
position: relative;
|
||||
z-index:10;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(420px, 1fr) 1.05fr;
|
||||
gap: 14px;
|
||||
padding: 14px;
|
||||
}
|
||||
.clu-form-side { padding:32px 48px; }
|
||||
.clu-sso-row { display:grid;grid-template-columns:1fr 1fr;gap:8px; }
|
||||
@media (max-width: 1080px) {
|
||||
.clu-login-grid { grid-template-columns:1fr; }
|
||||
.clu-live-side { display:none !important; }
|
||||
.clu-form-side { padding:28px 36px; }
|
||||
.clu-auth-grid { grid-template-columns: 1fr; padding: 12px; }
|
||||
.clu-auth-grid > .clu-auth-side-right { display: none; }
|
||||
}
|
||||
@media(max-width:640px){
|
||||
.clu-form-side { padding:20px; }
|
||||
.clu-sso-row { grid-template-columns:1fr; }
|
||||
.clu-login-status { display:none !important; }
|
||||
.clu-field-row { grid-template-columns:1fr; }
|
||||
.clu-step-label { display:none; }
|
||||
}
|
||||
@media(max-width:390px){
|
||||
.clu-form-side { padding:16px; }
|
||||
|
||||
/* Pills */
|
||||
.clu-pill {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
padding: 2px 8px;
|
||||
font-size: 10.5px;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 600;
|
||||
border-radius: 999px;
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
.clu-pill.success { color: var(--color-success); background: var(--color-success-soft); border-color: rgba(21,160,106,0.28); }
|
||||
.clu-pill.warning { color: var(--color-warning); background: var(--color-warning-soft); border-color: rgba(194,130,15,0.28); }
|
||||
.clu-pill.danger { color: var(--color-danger); background: var(--color-danger-soft); border-color: rgba(219,59,59,0.28); }
|
||||
.clu-pill.accent { color: var(--color-accent); background: var(--color-accent-soft); border-color: rgba(59,111,242,0.28); }
|
||||
.clu-pill.muted { color: var(--color-muted); background: rgba(0,0,0,0.04); border-color: var(--color-hairline); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>Anmelden · {{ config('app.name', 'CluPilot') }}</title>
|
||||
<title>{{ $title ?? 'Anmelden' }} · {{ config('app.name', 'CluPilot') }}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="clu-login-body bg-bg text-t1 font-sans antialiased min-h-screen overflow-x-hidden" style="font-size:13.5px;line-height:1.5;">
|
||||
<body class="clu-bg-aurora">
|
||||
{{ $slot }}
|
||||
@livewireScripts
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ use Illuminate\Support\Facades\Session;
|
|||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('layouts.login')] class extends Component
|
||||
{
|
||||
new #[Layout('layouts.login')] class extends Component {
|
||||
public LoginForm $form;
|
||||
|
||||
public function login(): void
|
||||
|
|
@ -18,41 +17,38 @@ new #[Layout('layouts.login')] class extends Component
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="clu-login-grid">
|
||||
<div class="clu-auth-grid">
|
||||
|
||||
{{-- ===== LEFT: FORM ===== --}}
|
||||
<section class="clu-form-side flex flex-col min-h-screen">
|
||||
{{-- ============ LEFT: FORM ============ --}}
|
||||
<section class="clu-glass flex flex-col" style="padding:30px 44px;min-height:calc(100vh - 28px);">
|
||||
|
||||
{{-- Top row --}}
|
||||
<div class="flex items-center justify-between gap-4 flex-wrap gap-y-2">
|
||||
<div class="flex items-center gap-[10px] shrink-0">
|
||||
<div class="clu-brand-mark w-[30px] h-[30px] rounded-[8px]" aria-hidden="true"></div>
|
||||
{{-- Top row: brand + status chip --}}
|
||||
<div class="flex items-center justify-between gap-4 flex-wrap">
|
||||
<div class="flex items-center gap-[10px]">
|
||||
<div class="clu-brand-mark" aria-hidden="true"></div>
|
||||
<div class="font-semibold text-[15px] tracking-[-0.01em]">
|
||||
CluPilot<span class="text-t2 font-medium ml-[5px] text-[11.5px]">v2.4</span>
|
||||
CluPilot<span class="text-(--color-muted-2) font-medium ml-[5px] text-[11.5px]">v2.4</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="clu-login-status inline-flex items-center gap-[6px] px-[9px] py-1 border border-success/25 bg-success/12 text-success rounded-full text-[11px] font-medium font-mono">
|
||||
<span class="clu-pulse-dot w-[6px] h-[6px] rounded-full bg-current"></span>
|
||||
All systems operational
|
||||
</span>
|
||||
<span class="clu-status-chip"><span class="clu-pulse-dot"></span> Alle Systeme aktiv</span>
|
||||
</div>
|
||||
|
||||
{{-- Form wrap --}}
|
||||
<div class="my-auto flex flex-col gap-6 w-full py-6 sm:py-10 mx-auto" style="max-width:380px;">
|
||||
<div class="my-auto flex flex-col gap-6 w-full" style="max-width:380px;padding:44px 0;">
|
||||
|
||||
<header>
|
||||
<h1 class="m-0 text-[24px] font-semibold tracking-[-0.02em] leading-[1.2]">Willkommen zurück</h1>
|
||||
<p class="mt-[6px] text-[13.5px] text-t2">
|
||||
<h1 class="m-0 text-[25px] font-semibold tracking-[-0.02em] leading-[1.2]">Willkommen zurück</h1>
|
||||
<p class="mt-[6px] text-[13.5px] text-(--color-muted)">
|
||||
Bei CluPilot anmelden oder
|
||||
@if (Route::has('register'))
|
||||
<a href="{{ route('register') }}" wire:navigate class="text-akzent font-medium hover:underline">neues Konto erstellen</a>
|
||||
<a href="{{ route('register') }}" wire:navigate class="font-semibold">neues Konto erstellen</a>
|
||||
@endif
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{{-- SSO --}}
|
||||
<div class="clu-sso-row">
|
||||
<button class="clu-sso-btn" type="button">
|
||||
<div class="grid grid-cols-2 gap-[10px]">
|
||||
<button type="button" class="clu-sso-btn">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path fill="#4285F4" d="M22.5 12.27c0-.79-.07-1.54-.2-2.27H12v4.51h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.32z"/>
|
||||
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.99.66-2.25 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23z"/>
|
||||
|
|
@ -61,7 +57,7 @@ new #[Layout('layouts.login')] class extends Component
|
|||
</svg>
|
||||
Google
|
||||
</button>
|
||||
<button class="clu-sso-btn" type="button">
|
||||
<button type="button" class="clu-sso-btn">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.02c-3.2.7-3.87-1.36-3.87-1.36-.52-1.33-1.27-1.68-1.27-1.68-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.69 1.24 3.35.95.1-.74.4-1.24.73-1.53-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.28 1.18-3.09-.12-.29-.51-1.46.11-3.05 0 0 .97-.31 3.18 1.18.92-.26 1.91-.39 2.89-.39.98 0 1.97.13 2.89.39 2.20-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.12 3.05.74.81 1.18 1.83 1.18 3.09 0 4.42-2.69 5.40-5.25 5.68.41.36.78 1.06.78 2.14v3.18c0 .31.21.67.79.55C20.21 21.39 23.5 17.08 23.5 12c0-6.35-5.15-11.5-11.5-11.5z"/>
|
||||
</svg>
|
||||
|
|
@ -71,51 +67,44 @@ new #[Layout('layouts.login')] class extends Component
|
|||
|
||||
<div class="clu-divider">oder mit E-Mail</div>
|
||||
|
||||
{{-- Static dev hint --}}
|
||||
<div class="flex items-start gap-[9px] px-[12px] py-[10px] rounded-[8px] border border-akzent/20 bg-akzent/8 text-[12px] text-t2">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-akzent flex-none mt-[1px]" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
|
||||
</svg>
|
||||
<span>Testinstanz — <span class="font-mono text-t1">admin@clupilot.test</span> / <span class="font-mono text-t1">clupilot</span></span>
|
||||
</div>
|
||||
|
||||
{{-- Session status --}}
|
||||
{{-- Session status (Laravel default partial) --}}
|
||||
<x-auth-session-status :status="session('status')" />
|
||||
|
||||
{{-- Form --}}
|
||||
<form wire:submit="login" class="flex flex-col gap-[14px]">
|
||||
<form wire:submit="login" class="flex flex-col gap-[14px]" autocomplete="on" x-data="{ showPwd: false }">
|
||||
|
||||
{{-- Email --}}
|
||||
<div class="flex flex-col gap-[6px]">
|
||||
<label for="email" class="text-[11.5px] text-t2 font-medium">E-Mail-Adresse</label>
|
||||
<label for="email" class="text-[11.5px] text-(--color-muted) font-semibold">E-Mail-Adresse</label>
|
||||
<div class="clu-input">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-dim flex-none" aria-hidden="true">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/>
|
||||
</svg>
|
||||
<input wire:model="form.email" id="email" name="email" type="text"
|
||||
placeholder="du@firma.de" autocomplete="email" autofocus />
|
||||
<input wire:model="form.email" id="email" name="email" type="email"
|
||||
placeholder="du@firma.de" autocomplete="email" required autofocus />
|
||||
</div>
|
||||
<x-input-error :messages="$errors->get('form.email')" class="mt-1" />
|
||||
</div>
|
||||
|
||||
{{-- Password --}}
|
||||
<div class="flex flex-col gap-[6px]">
|
||||
<label for="password" class="flex items-center justify-between text-[11.5px] text-t2 font-medium">
|
||||
<label for="password" class="flex items-center justify-between text-[11.5px] text-(--color-muted) font-semibold">
|
||||
Passwort
|
||||
@if (Route::has('password.request'))
|
||||
<a href="{{ route('password.request') }}" wire:navigate
|
||||
class="text-[11.5px] text-t2 font-medium no-underline transition-colors hover:text-t1">Vergessen?</a>
|
||||
class="text-[11.5px] text-(--color-muted) font-medium hover:text-(--color-fg) no-underline">Vergessen?</a>
|
||||
@endif
|
||||
</label>
|
||||
<div class="clu-input">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-dim flex-none" aria-hidden="true">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>
|
||||
</svg>
|
||||
<input wire:model="form.password" id="password" name="password" type="password"
|
||||
placeholder="••••••••••••" autocomplete="current-password" />
|
||||
<button type="button" id="toggle-eye" aria-label="Passwort anzeigen"
|
||||
class="text-t2 p-[4px] rounded-[4px] grid place-items-center transition-colors hover:text-t1 hover:bg-surface2">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<input wire:model="form.password" id="password" name="password"
|
||||
:type="showPwd ? 'text' : 'password'" placeholder="••••••••••••"
|
||||
autocomplete="current-password" required />
|
||||
<button type="button" class="clu-eye" @click="showPwd = !showPwd"
|
||||
:aria-label="showPwd ? 'Passwort verbergen' : 'Passwort anzeigen'">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
|
@ -123,13 +112,12 @@ new #[Layout('layouts.login')] class extends Component
|
|||
<x-input-error :messages="$errors->get('form.password')" class="mt-1" />
|
||||
</div>
|
||||
|
||||
{{-- Remember (CSS peer toggle) --}}
|
||||
{{-- Remember --}}
|
||||
<div class="flex items-center mt-[2px]">
|
||||
<label class="flex items-center gap-2 text-[12.5px] text-t2 cursor-pointer select-none transition-colors hover:text-t1">
|
||||
<input wire:model="form.remember" type="checkbox" class="peer sr-only" />
|
||||
<span class="peer-checked:bg-akzent peer-checked:border-akzent flex-none w-[15px] h-[15px] rounded-[4px] border border-white/10 bg-surface grid place-items-center transition-all">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="opacity-0 scale-[.6] peer-checked:opacity-100 peer-checked:scale-100 transition-all duration-150">
|
||||
<label class="clu-checkbox">
|
||||
<input wire:model="form.remember" type="checkbox" />
|
||||
<span class="box">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12l5 5L20 7"/>
|
||||
</svg>
|
||||
</span>
|
||||
|
|
@ -150,28 +138,27 @@ new #[Layout('layouts.login')] class extends Component
|
|||
</button>
|
||||
|
||||
{{-- Alt options --}}
|
||||
<div class="flex flex-col gap-2 mt-1">
|
||||
<div class="flex flex-col gap-[9px] mt-[4px]">
|
||||
<button class="clu-alt-btn" type="button">
|
||||
<svg class="text-t2 flex-none" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted)" aria-hidden="true">
|
||||
<path d="M22 2L11 13"/><path d="M22 2l-7 20-4-9-9-4 20-7z"/>
|
||||
</svg>
|
||||
Magic-Link per E-Mail senden
|
||||
</button>
|
||||
<button class="clu-alt-btn" type="button">
|
||||
<svg class="text-t2 flex-none" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted)" aria-hidden="true">
|
||||
<path d="M21 12c0-4.97-4.03-9-9-9-2.5 0-4.76 1.02-6.39 2.67M3 12c0 4.97 4.03 9 9 9 2.5 0 4.76-1.02 6.39-2.67"/>
|
||||
<path d="M9 12a3 3 0 1 0 6 0 3 3 0 1 0-6 0M12 1v4M12 19v4"/>
|
||||
</svg>
|
||||
Mit Passkey anmelden
|
||||
<span class="ml-auto font-mono text-[10px] text-akzent bg-akzent/12 px-[6px] py-[2px] rounded-[4px] border border-akzent/25 font-medium">empfohlen</span>
|
||||
<span class="ml-auto font-mono text-[10px] text-(--color-accent) bg-(--color-accent-soft) px-[7px] py-[2px] rounded-[5px] border border-(--color-accent)/25 font-semibold">empfohlen</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Footer --}}
|
||||
<footer class="flex items-center justify-between gap-3 text-t2 text-[11.5px] font-mono pt-3 border-t border-white/6 flex-wrap">
|
||||
<footer class="flex items-center justify-between gap-3 text-(--color-muted) text-[11.5px] font-mono pt-[14px] border-t border-(--color-hairline) flex-wrap">
|
||||
<div class="flex items-center gap-[6px]">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
|
||||
<rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>
|
||||
|
|
@ -179,88 +166,110 @@ new #[Layout('layouts.login')] class extends Component
|
|||
TLS 1.3 · AES-256 · region eu-central-1
|
||||
</div>
|
||||
<div class="flex gap-[14px]">
|
||||
<a href="#" class="text-t2 hover:text-t1 no-underline transition-colors">Datenschutz</a>
|
||||
<a href="#" class="text-t2 hover:text-t1 no-underline transition-colors">AGB</a>
|
||||
<a href="#" class="text-t2 hover:text-t1 no-underline transition-colors">Status</a>
|
||||
<a href="#" class="text-t2 hover:text-t1 no-underline transition-colors">Hilfe</a>
|
||||
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">Datenschutz</a>
|
||||
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">AGB</a>
|
||||
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">Status</a>
|
||||
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">Hilfe</a>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
{{-- ===== RIGHT: LIVE PANEL ===== --}}
|
||||
<aside class="clu-live-side relative border-l border-white/6 overflow-hidden flex flex-col gap-[18px] min-h-screen"
|
||||
style="background:linear-gradient(180deg,#0c0c12 0%,#0a0a0f 100%);padding:32px 36px;"
|
||||
aria-hidden="true">
|
||||
{{-- ============ RIGHT: LIVE PANEL ============ --}}
|
||||
<aside class="clu-glass clu-auth-side-right relative overflow-hidden flex flex-col gap-[18px]"
|
||||
style="padding:30px 34px;min-height:calc(100vh - 28px);" aria-hidden="true">
|
||||
|
||||
{{-- ambient overlay --}}
|
||||
<div class="absolute inset-0 pointer-events-none"
|
||||
style="border-radius:inherit;background:radial-gradient(80% 50% at 100% 0%, rgba(99,140,255,0.18), transparent 60%);"></div>
|
||||
|
||||
{{-- head --}}
|
||||
<div class="relative flex items-center justify-between">
|
||||
<div class="text-[11px] text-t2 font-mono uppercase tracking-[.10em]">— live cluster</div>
|
||||
<div class="text-[11px] text-t2 font-mono">eu-central-1 · build #4f8a32c</div>
|
||||
<div class="text-[11px] text-(--color-muted) font-mono uppercase tracking-[0.10em]">— live cluster</div>
|
||||
<div class="text-[11px] text-(--color-muted) font-mono">eu-central-1 · build #4f8a32c</div>
|
||||
</div>
|
||||
|
||||
{{-- hero --}}
|
||||
<div class="relative">
|
||||
<p class="m-0 text-[22px] font-semibold tracking-[-0.015em] leading-[1.3]" style="max-width:480px;">
|
||||
Verwalte <em class="not-italic text-akzent">87 WordPress-Sites</em> auf 6 Servern. Ein Login. Volle Kontrolle.
|
||||
<p class="m-0 text-[23px] font-semibold tracking-[-0.015em] leading-[1.3]" style="max-width:480px;">
|
||||
Verwalte <em class="not-italic text-(--color-accent) font-semibold">87 WordPress-Sites</em> auf 6 Servern. Ein Login. Volle Kontrolle.
|
||||
</p>
|
||||
<p class="text-t2 text-[13px] mt-[6px]" style="max-width:460px;">
|
||||
<p class="text-(--color-muted) text-[13px] mt-[7px]" style="max-width:460px;">
|
||||
SSH-Tunnel, Backups, Updates und Security in einer Konsole — von Push-to-Deploy bis Brute-Force-Defense.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Stat strip --}}
|
||||
<div class="relative grid grid-cols-3 gap-[10px] mt-[6px]">
|
||||
<div class="bg-s2 border border-white/6 rounded-[9px] p-3">
|
||||
<div class="text-[10.5px] text-t2 uppercase tracking-[.06em] font-medium">Sites Online</div>
|
||||
<div class="font-mono text-[18px] font-semibold tracking-[-0.01em] mt-1">
|
||||
87<span class="text-[12px] text-t2 font-medium ml-[2px]">/87</span>
|
||||
<div class="relative grid grid-cols-3 gap-[12px] mt-[6px]">
|
||||
<div class="clu-stat">
|
||||
<div class="clu-stat-label">Sites Online</div>
|
||||
<div class="clu-stat-value">87<span class="unit">/87</span></div>
|
||||
<div class="clu-stat-trend">● 100% Uptime · 24h</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 font-mono text-[10.5px] text-success mt-1">● 100% Uptime · 24h</div>
|
||||
<div class="clu-stat">
|
||||
<div class="clu-stat-label">Ø Response</div>
|
||||
<div class="clu-stat-value">142<span class="unit">ms</span></div>
|
||||
<div class="clu-stat-trend">↓ 8,4% vs. 7d</div>
|
||||
</div>
|
||||
<div class="bg-s2 border border-white/6 rounded-[9px] p-3">
|
||||
<div class="text-[10.5px] text-t2 uppercase tracking-[.06em] font-medium">Ø Response</div>
|
||||
<div class="font-mono text-[18px] font-semibold tracking-[-0.01em] mt-1">
|
||||
142<span class="text-[12px] text-t2 font-medium ml-[2px]">ms</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 font-mono text-[10.5px] text-success mt-1">↓ 8.4% vs. 7d</div>
|
||||
</div>
|
||||
<div class="bg-s2 border border-white/6 rounded-[9px] p-3">
|
||||
<div class="text-[10.5px] text-t2 uppercase tracking-[.06em] font-medium">Blocked / 24h</div>
|
||||
<div class="font-mono text-[18px] font-semibold tracking-[-0.01em] mt-1">128</div>
|
||||
<div class="flex items-center gap-1 font-mono text-[10.5px] text-warning mt-1">↑ 23% Brute-force</div>
|
||||
<div class="clu-stat">
|
||||
<div class="clu-stat-label">Blocked / 24h</div>
|
||||
<div class="clu-stat-value">128</div>
|
||||
<div class="clu-stat-trend warn">↑ 23% Brute-force</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Terminal card --}}
|
||||
<div class="relative bg-s2 border border-white/6 rounded-[10px] overflow-hidden flex-1 flex flex-col" style="min-height:280px;">
|
||||
<div class="flex items-center gap-[10px] border-b border-white/6 bg-surface" style="padding:10px 14px;">
|
||||
<div class="flex gap-[5px]">
|
||||
<span class="w-[9px] h-[9px] rounded-full bg-danger"></span>
|
||||
<span class="w-[9px] h-[9px] rounded-full bg-warning"></span>
|
||||
<span class="w-[9px] h-[9px] rounded-full bg-success"></span>
|
||||
{{-- Terminal --}}
|
||||
<div class="relative clu-term-card flex-1">
|
||||
<div class="clu-term-head">
|
||||
<div class="clu-term-dots"><span></span><span></span><span></span></div>
|
||||
<div class="clu-term-title">ssh deploy@eu-prod-01</div>
|
||||
<div class="clu-term-meta">pts/0</div>
|
||||
</div>
|
||||
<div class="font-mono text-[11.5px] text-t2 flex-1 text-center">ssh deploy@eu-prod-01</div>
|
||||
<div class="font-mono text-[10.5px] text-dim">pts/0</div>
|
||||
</div>
|
||||
<div id="term-body" class="font-mono text-[11.5px] leading-[1.6] flex-1 overflow-hidden relative" style="padding:14px 16px;"></div>
|
||||
<div id="clu-term-body" class="clu-term-body" wire:ignore></div>
|
||||
</div>
|
||||
|
||||
{{-- Live tags --}}
|
||||
<div class="relative flex flex-wrap gap-[6px]">
|
||||
<span class="inline-flex items-center gap-[5px] px-[7px] py-[3px] text-[10.5px] font-mono text-t2 bg-surface border border-white/6 rounded-[4px]">
|
||||
<span class="w-[5px] h-[5px] rounded-full bg-success"></span>eu-prod-01 · 23 sites
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-[5px] px-[7px] py-[3px] text-[10.5px] font-mono text-t2 bg-surface border border-white/6 rounded-[4px]">
|
||||
<span class="w-[5px] h-[5px] rounded-full bg-success"></span>eu-prod-02 · 19 sites
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-[5px] px-[7px] py-[3px] text-[10.5px] font-mono text-t2 bg-surface border border-white/6 rounded-[4px]">
|
||||
<span class="w-[5px] h-[5px] rounded-full bg-success"></span>edge-ams · 14 sites
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-[5px] px-[7px] py-[3px] text-[10.5px] font-mono text-t2 bg-surface border border-white/6 rounded-[4px]">
|
||||
<span class="w-[5px] h-[5px] rounded-full bg-warning"></span>us-east-01 · CPU 78%
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-[5px] px-[7px] py-[3px] text-[10.5px] font-mono text-t2 bg-surface border border-white/6 rounded-[4px]">
|
||||
<span class="w-[5px] h-[5px] rounded-full bg-success"></span>staging · 6 sites
|
||||
</span>
|
||||
<div class="relative flex flex-wrap gap-[7px]">
|
||||
<span class="clu-live-tag"><span class="dot"></span>eu-prod-01 · 23 sites</span>
|
||||
<span class="clu-live-tag"><span class="dot"></span>eu-prod-02 · 19 sites</span>
|
||||
<span class="clu-live-tag"><span class="dot"></span>edge-ams · 14 sites</span>
|
||||
<span class="clu-live-tag warn"><span class="dot"></span>us-east-01 · CPU 78%</span>
|
||||
<span class="clu-live-tag"><span class="dot"></span>staging · 6 sites</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{{-- Terminal type-in script --}}
|
||||
@push('scripts')
|
||||
<script>
|
||||
(function () {
|
||||
const body = document.getElementById('clu-term-body');
|
||||
if (!body) return;
|
||||
const lines = [
|
||||
['09:42:18', '<span class="clu-term-prompt"></span> <span class="dim">clupilot</span> status --cluster=acme'],
|
||||
['09:42:18', '<span class="ok">✓</span> 87/87 sites <span class="dim">healthy</span> · <span class="acc">142ms</span> ø response'],
|
||||
['09:42:19', '<span class="ok">✓</span> backup eu-prod-02 → <span class="pkg">s3://clupilot-backups</span> <span class="dim">248 MB</span>'],
|
||||
['09:42:21', '<span class="acc">→</span> deploying <span class="pkg">Yoast-SEO@22.5</span> to <span class="dim">3 sites…</span>'],
|
||||
['09:42:23', '<span class="ok">✓</span> agentur-helio.com <span class="dim">(312ms)</span>'],
|
||||
['09:42:24', '<span class="ok">✓</span> blog.fintech-radar.io <span class="dim">(287ms)</span>'],
|
||||
['09:42:24', '<span class="ok">✓</span> docs.opencrate.dev <span class="dim">(298ms)</span>'],
|
||||
['09:42:26', '<span class="warn">!</span> classic.tonhoff-bau.de <span class="dim">PHP 7.4 EOL — migration queued</span>'],
|
||||
['09:42:28', '<span class="err">✗</span> fw <span class="dim">blocked</span> 17 login attempts from <span class="acc">185.220.101.18</span> <span class="dim">(TOR)</span>'],
|
||||
['09:42:30', '<span class="ok">✓</span> SSL renewed: <span class="pkg">*.kunde-mueller.de</span> <span class="dim">until 2026-08-25</span>'],
|
||||
['09:42:32', '<span class="clu-term-prompt"></span> <span class="dim">tail -f /var/log/clupilot/audit.log</span><span class="clu-cursor"></span>'],
|
||||
];
|
||||
let i = 0;
|
||||
function addLine() {
|
||||
if (i >= lines.length) { setTimeout(function () { body.innerHTML = ''; i = 0; addLine(); }, 5000); return; }
|
||||
const [t, html] = lines[i++];
|
||||
const el = document.createElement('div');
|
||||
el.className = 'clu-term-line';
|
||||
el.innerHTML = '<div class="clu-term-time">' + t + '</div><div class="clu-term-text">' + html + '</div>';
|
||||
body.appendChild(el);
|
||||
body.scrollTop = body.scrollHeight;
|
||||
while (body.children.length > 11) body.removeChild(body.firstChild);
|
||||
setTimeout(addLine, 600 + Math.random() * 450);
|
||||
}
|
||||
addLine();
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use function Pest\Laravel\get;
|
||||
use function Pest\Livewire\livewire;
|
||||
|
||||
it('renders the login page with template chrome', function () {
|
||||
$response = get(route('login'));
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertSeeText('Willkommen zurück')
|
||||
->assertSeeText('Anmelden')
|
||||
->assertSeeText('Magic-Link per E-Mail senden')
|
||||
->assertSeeText('Mit Passkey anmelden')
|
||||
->assertSeeText('Alle Systeme aktiv')
|
||||
->assertSeeText('Verwalte')
|
||||
->assertSeeText('WordPress-Sites')
|
||||
->assertSee('clu-glass', false)
|
||||
->assertSee('clu-brand-mark', false)
|
||||
->assertSee('clu-term-card', false)
|
||||
->assertSee('clu-stat', false);
|
||||
});
|
||||
|
||||
it('mounts the Volt component without errors', function () {
|
||||
livewire('pages.auth.login')
|
||||
->assertSet('form.email', '')
|
||||
->assertSet('form.password', '')
|
||||
->assertSet('form.remember', false);
|
||||
});
|
||||
|
||||
it('rejects empty credentials', function () {
|
||||
livewire('pages.auth.login')
|
||||
->set('form.email', '')
|
||||
->set('form.password', '')
|
||||
->call('login')
|
||||
->assertHasErrors(['form.email', 'form.password']);
|
||||
});
|
||||
|
||||
it('authenticates a user with valid credentials', function () {
|
||||
$user = User::factory()->create(['password' => bcrypt('hunter2-secret')]);
|
||||
|
||||
livewire('pages.auth.login')
|
||||
->set('form.email', $user->email)
|
||||
->set('form.password', 'hunter2-secret')
|
||||
->call('login')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('dashboard', absolute: false));
|
||||
|
||||
expect(auth()->check())->toBeTrue();
|
||||
});
|
||||
|
||||
it('rejects an invalid password', function () {
|
||||
$user = User::factory()->create(['password' => bcrypt('correct-password')]);
|
||||
|
||||
livewire('pages.auth.login')
|
||||
->set('form.email', $user->email)
|
||||
->set('form.password', 'wrong-password')
|
||||
->call('login')
|
||||
->assertHasErrors('form.email');
|
||||
|
||||
expect(auth()->check())->toBeFalse();
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(Tests\TestCase::class, RefreshDatabase::class)->in('Feature');
|
||||
uses(Tests\TestCase::class)->in('Unit');
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Backups</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand"><div class="brand-mark" aria-hidden="true"></div><div class="brand-name">CluPilot<span>v2.4</span></div></div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta"><div class="ws-name">Acme Cluster</div><div class="ws-plan">team · 14 user</div></div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg><span>Dashboard</span></a>
|
||||
<a class="nav-item" href="Server.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg><span>Server</span><span class="count">6</span></a>
|
||||
<a class="nav-item" href="Sites.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg><span>Sites</span><span class="count">87</span></a>
|
||||
<a class="nav-item active" href="Backups.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg><span>Backups</span></a>
|
||||
<a class="nav-item" href="Sicherheit.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg><span>Sicherheit</span><span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span></a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg><span>Team</span></a>
|
||||
<a class="nav-item" href="Einstellungen.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg><span>Einstellungen</span></a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row"><div class="plan-name">Team Plan</div><div class="plan-tag">PRO</div></div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats"><span>87 / 140 sites</span><span>62%</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main" data-screen-label="Backups">
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<div class="page-title">Backups</div>
|
||||
<div class="page-sub">automatisch · tägl. 03:00 utc</div>
|
||||
</div>
|
||||
<span class="status-chip"><span class="pulse-dot"></span> alle gesichert</span>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input placeholder="Backup suchen…" />
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
<button class="btn-primary">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg>
|
||||
Backup jetzt
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Letztes Backup</div><div class="m-ico success"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg></div></div>
|
||||
<div class="metric-value">12<span class="sub">min</span></div>
|
||||
<div class="metric-delta"><span class="chg up">erfolgreich</span> · 87/87 sites</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Speicher belegt</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg></div></div>
|
||||
<div class="metric-value">1,84<span class="sub">/ 4 TB</span></div>
|
||||
<div class="metric-delta"><span class="chg">46%</span> object storage</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Wiederherstellungspunkte</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-9 9M3 12H1m2-4l-2-1"/><path d="M12 7v5l3 2"/></svg></div></div>
|
||||
<div class="metric-value">2.310</div>
|
||||
<div class="metric-delta"><span class="chg">30 tage</span> aufbewahrung</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Fehlgeschlagen</div><div class="m-ico warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg></div></div>
|
||||
<div class="metric-value">1</div>
|
||||
<div class="metric-delta"><span class="chg warn">staging.opencrate</span> · retry geplant</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row2">
|
||||
<!-- recent backups -->
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg>Letzte Backups</h3>
|
||||
<div class="card-tabs"><button class="active">Alle</button><button>Auto</button><button>Manuell</button></div>
|
||||
</div>
|
||||
<div class="scroll-x">
|
||||
<table class="table">
|
||||
<thead><tr><th>Site</th><th>Typ</th><th class="num">Größe</th><th>Zeitpunkt</th><th>Status</th><th class="num"></th></tr></thead>
|
||||
<tbody id="bk-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- schedule + storage -->
|
||||
<div style="display:flex;flex-direction:column;gap:18px">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Zeitplan</h3></div>
|
||||
<div class="card-body" style="padding-top:4px">
|
||||
<div class="toggle-row">
|
||||
<div class="tr-body"><div class="tr-title">Tägliche Backups</div><div class="tr-desc">jeden Tag um 03:00 UTC</div></div>
|
||||
<div class="toggle on"></div>
|
||||
</div>
|
||||
<div class="toggle-row">
|
||||
<div class="tr-body"><div class="tr-title">Stündliche Snapshots</div><div class="tr-desc">nur Shop- & High-Traffic-Sites</div></div>
|
||||
<div class="toggle on"></div>
|
||||
</div>
|
||||
<div class="toggle-row">
|
||||
<div class="tr-body"><div class="tr-title">Off-site Replikation</div><div class="tr-desc">zweites Rechenzentrum (Helsinki)</div></div>
|
||||
<div class="toggle on"></div>
|
||||
</div>
|
||||
<div class="toggle-row">
|
||||
<div class="tr-body"><div class="tr-title">Vor Updates sichern</div><div class="tr-desc">automatischer Snapshot vor jedem Update</div></div>
|
||||
<div class="toggle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>Speicher</h3><span class="meta">eu-central · s3</span></div>
|
||||
<div class="card-body">
|
||||
<div class="plan-meter" style="height:9px"><span style="width:46%"></span></div>
|
||||
<div class="plan-stats" style="font-size:11.5px"><span>1,84 TB belegt</span><span>4 TB inkl.</span></div>
|
||||
<div style="margin-top:14px">
|
||||
<div class="kv"><span class="k">Voller Backup-Satz</span><span class="v">1,21 TB</span></div>
|
||||
<div class="kv"><span class="k">Inkrementell (30 t)</span><span class="v">0,63 TB</span></div>
|
||||
<div class="kv"><span class="k">Komprimierung</span><span class="v">zstd · 3,1×</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="foot">CluPilot · backups verschlüsselt · aes-256-gcm</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const Icon = {
|
||||
restore:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7M3 4v4h4"/></svg>',
|
||||
dl:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg>',
|
||||
more:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>',
|
||||
};
|
||||
const backups = [
|
||||
{site:'agentur-helio.com', type:'Auto', size:'248 MB', when:'vor 12 min', st:'ok', label:'OK'},
|
||||
{site:'shop.bergmann-coffee.com', type:'Auto', size:'1,9 GB', when:'vor 12 min', st:'ok', label:'OK'},
|
||||
{site:'kunde-mueller.de', type:'Auto', size:'412 MB', when:'vor 13 min', st:'ok', label:'OK'},
|
||||
{site:'rezepte.kochlust.de', type:'Stündl.', size:'2,4 GB', when:'vor 41 min', st:'ok', label:'OK'},
|
||||
{site:'classic.tonhoff-bau.de', type:'Manuell', size:'180 MB', when:'vor 2 std', st:'ok', label:'OK'},
|
||||
{site:'staging.opencrate.dev', type:'Auto', size:'—', when:'vor 12 min', st:'bad', label:'Fehler'},
|
||||
{site:'shop.nordlicht-mode.com', type:'Stündl.', size:'3,1 GB', when:'vor 1 std', st:'ok', label:'OK'},
|
||||
{site:'travelblog.fernweh.io', type:'Auto', size:'620 MB', when:'vor 13 min', st:'ok', label:'OK'},
|
||||
];
|
||||
const typePill = {Auto:'accent','Stündl.':'', Manuell:'warning'};
|
||||
const body = document.getElementById('bk-body');
|
||||
backups.forEach(b=>{
|
||||
const stCls = b.st==='ok'?'ok':b.st==='bad'?'bad':'warn';
|
||||
const pillCls = b.st==='ok'?'success':'danger';
|
||||
body.insertAdjacentHTML('beforeend',`
|
||||
<tr>
|
||||
<td><div class="site-name" style="max-width:200px">${b.site}</div></td>
|
||||
<td><span class="pill ${typePill[b.type]||''}">${b.type}</span></td>
|
||||
<td class="num">${b.size}</td>
|
||||
<td><span class="site-meta">${b.when}</span></td>
|
||||
<td><span class="health"><span class="health-dot ${stCls}"></span><span class="health-label">${b.label}</span></span></td>
|
||||
<td class="num"><div class="row-actions">
|
||||
<button title="Wiederherstellen">${Icon.restore}</button>
|
||||
<button title="Herunterladen">${Icon.dl}</button>
|
||||
<button title="Mehr">${Icon.more}</button>
|
||||
</div></td>
|
||||
</tr>`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,645 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Dashboard</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--fg:#1a1a22;
|
||||
--muted:#5c5c6b;
|
||||
--muted-2:#9596a6;
|
||||
--accent:#3b6ff2;
|
||||
--accent-2:#7c5cff;
|
||||
--success:#15a06a;
|
||||
--warning:#c2820f;
|
||||
--danger:#db3b3b;
|
||||
--success-soft:rgba(21,160,106,0.12);
|
||||
--warning-soft:rgba(194,130,15,0.12);
|
||||
--danger-soft:rgba(219,59,59,0.12);
|
||||
--accent-soft:rgba(59,111,242,0.12);
|
||||
|
||||
/* glass */
|
||||
--glass:rgba(255,255,255,0.55);
|
||||
--glass-strong:rgba(255,255,255,0.72);
|
||||
--glass-soft:rgba(255,255,255,0.38);
|
||||
--glass-border:rgba(255,255,255,0.75);
|
||||
--glass-shadow:0 10px 34px -10px rgba(40,52,92,0.20), 0 2px 8px -2px rgba(40,52,92,0.08);
|
||||
--glass-inset:inset 0 1px 0 rgba(255,255,255,0.95), inset 0 -1px 0 rgba(255,255,255,0.25);
|
||||
--hairline:rgba(30,35,60,0.08);
|
||||
|
||||
--radius:20px;
|
||||
--radius-sm:13px;
|
||||
--radius-xs:9px;
|
||||
--mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;color:var(--fg);font-family:'Inter',system-ui,-apple-system,sans-serif;font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased}
|
||||
a{color:inherit;text-decoration:none}
|
||||
button{font-family:inherit;color:inherit;background:none;border:0;cursor:pointer}
|
||||
|
||||
/* ---------- Aurora background ---------- */
|
||||
body{
|
||||
min-height:100vh;
|
||||
background:
|
||||
radial-gradient(60% 55% at 12% 8%, rgba(99,140,255,0.30), transparent 60%),
|
||||
radial-gradient(55% 50% at 88% 0%, rgba(140,108,255,0.26), transparent 58%),
|
||||
radial-gradient(50% 60% at 78% 92%, rgba(64,196,210,0.22), transparent 60%),
|
||||
radial-gradient(45% 50% at 25% 100%, rgba(120,170,255,0.20), transparent 60%),
|
||||
linear-gradient(160deg,#eef1fb 0%,#f4f3fb 50%,#eaf2f8 100%);
|
||||
background-attachment:fixed;
|
||||
}
|
||||
|
||||
/* ---------- App layout ---------- */
|
||||
.app{display:grid;grid-template-columns:236px 1fr;min-height:100vh;gap:0}
|
||||
|
||||
/* ---------- Glass primitive ---------- */
|
||||
.glass{
|
||||
background:var(--glass);
|
||||
backdrop-filter:blur(26px) saturate(170%);
|
||||
-webkit-backdrop-filter:blur(26px) saturate(170%);
|
||||
border:1px solid var(--glass-border);
|
||||
box-shadow:var(--glass-shadow), var(--glass-inset);
|
||||
}
|
||||
|
||||
/* ---------- Sidebar ---------- */
|
||||
.sidebar{
|
||||
position:sticky;top:0;height:100vh;margin:14px 0 14px 14px;
|
||||
border-radius:var(--radius);
|
||||
display:flex;flex-direction:column;padding:18px 14px;gap:18px;
|
||||
background:var(--glass-strong);
|
||||
backdrop-filter:blur(30px) saturate(180%);
|
||||
-webkit-backdrop-filter:blur(30px) saturate(180%);
|
||||
border:1px solid var(--glass-border);
|
||||
box-shadow:var(--glass-shadow), var(--glass-inset);
|
||||
}
|
||||
.brand{display:flex;align-items:center;gap:10px;padding:2px 4px}
|
||||
.brand-mark{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#5a8bff 0%,#8a63ff 100%);position:relative;display:grid;place-items:center;box-shadow:0 0 0 1px rgba(255,255,255,0.4) inset, 0 6px 16px -4px rgba(90,139,255,0.6)}
|
||||
.brand-mark::before{content:"";width:11px;height:11px;border-radius:50%;background:rgba(255,255,255,0.96);box-shadow:inset 0 0 0 2px #5a8bff}
|
||||
.brand-name{font-weight:600;font-size:15px;letter-spacing:-0.01em}
|
||||
.brand-name span{color:var(--muted-2);font-weight:500;margin-left:5px;font-size:11px}
|
||||
|
||||
.ws-switch{display:flex;align-items:center;gap:10px;padding:9px 10px;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--glass-soft);cursor:pointer;transition:background .18s, border-color .18s}
|
||||
.ws-switch:hover{background:rgba(255,255,255,0.6);border-color:var(--glass-border)}
|
||||
.ws-avatar{width:24px;height:24px;border-radius:7px;background:linear-gradient(135deg,#3ecf8e,#1f8f60);display:grid;place-items:center;color:#fff;font-weight:700;font-size:11px;flex:none;box-shadow:0 2px 6px -1px rgba(31,143,96,0.5)}
|
||||
.ws-meta{flex:1;min-width:0}
|
||||
.ws-name{font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.ws-plan{font-size:10.5px;color:var(--muted);font-family:var(--mono)}
|
||||
.ws-chev{color:var(--muted-2)}
|
||||
|
||||
.nav{display:flex;flex-direction:column;gap:2px;flex:1}
|
||||
.nav-title{font-size:10px;text-transform:uppercase;letter-spacing:0.09em;color:var(--muted-2);padding:14px 10px 5px;font-weight:600}
|
||||
.nav-item{display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:var(--radius-xs);color:#3a3a47;cursor:pointer;font-size:13px;font-weight:500;transition:background .15s,color .15s,box-shadow .15s;position:relative}
|
||||
.nav-item:hover{background:rgba(255,255,255,0.55);color:var(--fg)}
|
||||
.nav-item.active{
|
||||
color:var(--fg);
|
||||
background:linear-gradient(180deg,rgba(255,255,255,0.92),rgba(255,255,255,0.66));
|
||||
box-shadow:0 4px 14px -6px rgba(40,52,92,0.30), inset 0 1px 0 rgba(255,255,255,0.9);
|
||||
}
|
||||
.nav-item .ico{width:17px;height:17px;color:var(--muted);flex:none}
|
||||
.nav-item.active .ico{color:var(--accent)}
|
||||
.nav-item .count{margin-left:auto;font-size:10.5px;font-family:var(--mono);color:var(--muted);background:rgba(255,255,255,0.7);padding:1px 7px;border-radius:10px;border:1px solid var(--hairline)}
|
||||
|
||||
.plan-box{margin-top:8px;padding:13px;border:1px solid var(--glass-border);border-radius:var(--radius-sm);background:linear-gradient(180deg,rgba(255,255,255,0.7),rgba(255,255,255,0.4));position:relative;overflow:hidden}
|
||||
.plan-row{display:flex;align-items:center;justify-content:space-between;font-size:12px}
|
||||
.plan-name{font-weight:600}
|
||||
.plan-tag{font-family:var(--mono);font-size:10px;color:var(--accent);background:var(--accent-soft);padding:2px 7px;border-radius:5px;border:1px solid rgba(59,111,242,0.25)}
|
||||
.plan-meter{margin-top:10px;height:6px;border-radius:4px;background:rgba(30,35,60,0.08);overflow:hidden}
|
||||
.plan-meter span{display:block;height:100%;width:62%;background:linear-gradient(90deg,#3b6ff2,#7c5cff);border-radius:4px}
|
||||
.plan-stats{display:flex;justify-content:space-between;margin-top:7px;font-size:10.5px;color:var(--muted);font-family:var(--mono)}
|
||||
|
||||
/* ---------- Main ---------- */
|
||||
.main{display:flex;flex-direction:column;min-width:0;padding:14px 14px 0}
|
||||
|
||||
/* Topbar */
|
||||
.topbar{
|
||||
position:sticky;top:14px;z-index:20;display:flex;align-items:center;gap:14px;
|
||||
padding:12px 18px;border-radius:var(--radius);margin-bottom:18px;
|
||||
background:var(--glass-strong);
|
||||
backdrop-filter:blur(30px) saturate(180%);
|
||||
-webkit-backdrop-filter:blur(30px) saturate(180%);
|
||||
border:1px solid var(--glass-border);
|
||||
box-shadow:var(--glass-shadow), var(--glass-inset);
|
||||
}
|
||||
.page-title{font-size:16px;font-weight:600;letter-spacing:-0.015em}
|
||||
.status-chip{display:inline-flex;align-items:center;gap:7px;padding:4px 11px 4px 9px;border:1px solid rgba(21,160,106,0.28);background:var(--success-soft);color:var(--success);border-radius:20px;font-size:11px;font-weight:600;font-family:var(--mono)}
|
||||
.pulse-dot{width:6px;height:6px;border-radius:50%;background:currentColor;position:relative;flex:none}
|
||||
.pulse-dot::after{content:"";position:absolute;inset:-3px;border-radius:50%;background:currentColor;opacity:0.35;animation:pulse 1.8s ease-out infinite}
|
||||
@keyframes pulse{0%{transform:scale(0.6);opacity:.5}100%{transform:scale(2.2);opacity:0}}
|
||||
|
||||
.top-spacer{flex:1}
|
||||
.search{display:flex;align-items:center;gap:9px;padding:8px 12px;background:rgba(255,255,255,0.6);border:1px solid var(--hairline);border-radius:11px;min-width:260px;color:var(--muted);transition:border-color .15s,box-shadow .15s}
|
||||
.search:focus-within{border-color:rgba(59,111,242,0.5);box-shadow:0 0 0 3px rgba(59,111,242,0.1)}
|
||||
.search input{flex:1;background:none;border:0;outline:0;color:var(--fg);font-size:12.5px;font-family:inherit;min-width:0}
|
||||
.search input::placeholder{color:var(--muted)}
|
||||
.kbd{font-family:var(--mono);font-size:10.5px;color:var(--muted);padding:1px 6px;border:1px solid var(--hairline);border-radius:5px;background:rgba(255,255,255,0.5)}
|
||||
|
||||
.icon-btn{width:36px;height:36px;border:1px solid var(--hairline);border-radius:11px;display:grid;place-items:center;color:var(--muted);background:rgba(255,255,255,0.55);transition:all .15s;position:relative}
|
||||
.icon-btn:hover{color:var(--fg);border-color:var(--glass-border);background:rgba(255,255,255,0.85)}
|
||||
.icon-btn .badge{position:absolute;top:-4px;right:-4px;background:var(--danger);color:#fff;font-size:9.5px;font-weight:600;min-width:16px;height:16px;border-radius:8px;display:grid;place-items:center;padding:0 4px;border:2px solid #f1f2fb;font-family:var(--mono)}
|
||||
|
||||
.btn-primary{display:inline-flex;align-items:center;gap:7px;padding:9px 15px;background:linear-gradient(180deg,#4f7dff,#3b6ff2);color:#fff;border-radius:11px;font-weight:600;font-size:12.5px;transition:filter .15s;box-shadow:0 1px 0 rgba(255,255,255,0.3) inset, 0 6px 16px -5px rgba(59,111,242,0.65)}
|
||||
.btn-primary:hover{filter:brightness(1.06)}
|
||||
|
||||
/* Content */
|
||||
.content{display:flex;flex-direction:column;gap:18px;max-width:1500px;width:100%;margin:0 auto;padding-bottom:22px}
|
||||
|
||||
/* ---------- Metric tiles ---------- */
|
||||
.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
|
||||
.metric{
|
||||
position:relative;border-radius:var(--radius);padding:18px 18px 16px;overflow:hidden;
|
||||
background:var(--glass);
|
||||
backdrop-filter:blur(22px) saturate(160%);
|
||||
-webkit-backdrop-filter:blur(22px) saturate(160%);
|
||||
border:1px solid var(--glass-border);
|
||||
box-shadow:var(--glass-shadow), var(--glass-inset);
|
||||
transition:transform .18s, box-shadow .18s;
|
||||
}
|
||||
.metric:hover{transform:translateY(-2px);box-shadow:0 16px 40px -12px rgba(40,52,92,0.26), var(--glass-inset)}
|
||||
.metric::after{content:"";position:absolute;inset:0;border-radius:inherit;background:linear-gradient(135deg,rgba(255,255,255,0.5),transparent 42%);pointer-events:none}
|
||||
.metric-top{display:flex;align-items:center;justify-content:space-between}
|
||||
.metric-label{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px;font-weight:600}
|
||||
.m-ico{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;flex:none;color:var(--accent);background:var(--accent-soft);border:1px solid rgba(59,111,242,0.2)}
|
||||
.m-ico svg{width:15px;height:15px}
|
||||
.m-ico.success{color:var(--success);background:var(--success-soft);border-color:rgba(21,160,106,0.22)}
|
||||
.m-ico.warning{color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.22)}
|
||||
.m-ico.danger{color:var(--danger);background:var(--danger-soft);border-color:rgba(219,59,59,0.22)}
|
||||
.metric-value{margin-top:14px;font-family:var(--mono);font-size:30px;font-weight:600;letter-spacing:-0.02em;line-height:1;position:relative}
|
||||
.metric-value .sub{font-size:15px;color:var(--muted-2);font-weight:500;margin-left:3px}
|
||||
.metric-delta{margin-top:9px;font-size:11.5px;display:flex;align-items:center;gap:6px;color:var(--muted);position:relative}
|
||||
.chg{font-family:var(--mono);font-weight:600}
|
||||
.chg.up{color:var(--success)} .chg.down{color:var(--danger)} .chg.warn{color:var(--warning)}
|
||||
|
||||
/* ---------- Card ---------- */
|
||||
.card{
|
||||
border-radius:var(--radius);overflow:hidden;
|
||||
background:var(--glass);
|
||||
backdrop-filter:blur(24px) saturate(165%);
|
||||
-webkit-backdrop-filter:blur(24px) saturate(165%);
|
||||
border:1px solid var(--glass-border);
|
||||
box-shadow:var(--glass-shadow), var(--glass-inset);
|
||||
}
|
||||
.card-head{display:flex;align-items:center;justify-content:space-between;padding:15px 18px 14px;border-bottom:1px solid var(--hairline)}
|
||||
.card-head h3{margin:0;font-size:13.5px;font-weight:600;letter-spacing:-0.005em;display:flex;align-items:center;gap:9px}
|
||||
.card-head h3 .hico{color:var(--muted)}
|
||||
.card-head .meta{font-size:11.5px;color:var(--muted);font-family:var(--mono)}
|
||||
.live-tag{font-size:10.5px;color:var(--success);font-family:var(--mono);display:inline-flex;align-items:center;gap:5px;font-weight:600}
|
||||
.card-tabs{display:flex;gap:2px;background:rgba(255,255,255,0.45);border:1px solid var(--hairline);border-radius:9px;padding:2px}
|
||||
.card-tabs button{padding:5px 11px;font-size:11.5px;color:var(--muted);border-radius:7px;transition:all .15s;font-weight:500}
|
||||
.card-tabs button:hover{color:var(--fg)}
|
||||
.card-tabs button.active{color:var(--fg);background:#fff;box-shadow:0 2px 6px -2px rgba(40,52,92,0.2)}
|
||||
.ghost-btn{padding:6px 11px;font-size:11.5px;color:var(--muted);border:1px solid var(--hairline);border-radius:9px;background:rgba(255,255,255,0.5);transition:all .15s;font-weight:500}
|
||||
.ghost-btn:hover{color:var(--fg);border-color:var(--glass-border);background:rgba(255,255,255,0.85)}
|
||||
|
||||
/* ---------- 2-col row ---------- */
|
||||
.row2{display:grid;grid-template-columns:1.55fr 1fr;gap:18px}
|
||||
|
||||
/* Chart */
|
||||
.chart-wrap{padding:16px 18px 10px}
|
||||
.chart-head{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:8px}
|
||||
.chart-figure{font-family:var(--mono);font-size:26px;font-weight:600;letter-spacing:-0.02em}
|
||||
.chart-figure .unit{font-size:14px;color:var(--muted-2);margin-left:4px;font-weight:500}
|
||||
.chart-sub{font-size:11.5px;color:var(--muted);margin-top:3px}
|
||||
.legend{display:flex;gap:14px;font-size:11px;color:var(--muted);font-family:var(--mono)}
|
||||
.legend i{display:inline-block;width:8px;height:8px;border-radius:3px;margin-right:5px;vertical-align:middle}
|
||||
.svg-chart{width:100%;height:172px;display:block}
|
||||
.chart-axis{font-family:var(--mono);font-size:9.5px;fill:var(--muted-2)}
|
||||
|
||||
/* Activity feed */
|
||||
.feed{padding:4px 0;max-height:340px;overflow-y:auto}
|
||||
.feed-row{display:flex;gap:12px;padding:11px 18px;align-items:flex-start;transition:background .12s}
|
||||
.feed-row:hover{background:rgba(255,255,255,0.45)}
|
||||
.feed-ico{width:28px;height:28px;border-radius:9px;display:grid;place-items:center;flex:none;color:var(--muted);background:rgba(255,255,255,0.6);border:1px solid var(--hairline)}
|
||||
.feed-ico.success{color:var(--success);background:var(--success-soft);border-color:rgba(21,160,106,0.22)}
|
||||
.feed-ico.warning{color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.22)}
|
||||
.feed-ico.danger{color:var(--danger);background:var(--danger-soft);border-color:rgba(219,59,59,0.22)}
|
||||
.feed-ico.accent{color:var(--accent);background:var(--accent-soft);border-color:rgba(59,111,242,0.22)}
|
||||
.feed-body{flex:1;min-width:0;font-size:12.5px}
|
||||
.feed-body b{font-weight:600;color:var(--fg)}
|
||||
.feed-body .target{font-family:var(--mono);color:var(--fg);background:rgba(255,255,255,0.7);padding:1px 6px;border-radius:5px;font-size:11px;border:1px solid var(--hairline)}
|
||||
.feed-meta{color:var(--muted-2);font-size:10.5px;margin-top:3px;font-family:var(--mono)}
|
||||
|
||||
/* ---------- Sites table ---------- */
|
||||
.table{width:100%;border-collapse:collapse;font-size:12.5px}
|
||||
.table th{text-align:left;font-weight:600;color:var(--muted-2);font-size:10.5px;letter-spacing:0.05em;text-transform:uppercase;padding:11px 18px;border-bottom:1px solid var(--hairline)}
|
||||
.table th.num{text-align:right}
|
||||
.table td{padding:13px 18px;border-bottom:1px solid var(--hairline);vertical-align:middle}
|
||||
.table tbody tr{transition:background .12s}
|
||||
.table tbody tr:hover{background:rgba(255,255,255,0.5)}
|
||||
.table tbody tr:last-child td{border-bottom:0}
|
||||
.table td.num{text-align:right;font-family:var(--mono)}
|
||||
|
||||
.site-cell{display:flex;align-items:center;gap:11px;min-width:0}
|
||||
.favicon{width:30px;height:30px;border-radius:8px;display:grid;place-items:center;font-weight:600;font-size:12px;color:#fff;flex:none;font-family:var(--mono);box-shadow:0 3px 8px -2px rgba(40,52,92,0.35)}
|
||||
.site-name{font-weight:600;color:var(--fg);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
|
||||
.site-meta{font-family:var(--mono);font-size:10.5px;color:var(--muted-2)}
|
||||
|
||||
.pill{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;font-size:10.5px;font-weight:600;border-radius:7px;font-family:var(--mono);border:1px solid var(--hairline);background:rgba(255,255,255,0.6);color:var(--muted);line-height:1.4}
|
||||
.pill.success{color:var(--success);background:var(--success-soft);border-color:rgba(21,160,106,0.25)}
|
||||
.pill.warning{color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.25)}
|
||||
.pill.danger{color:var(--danger);background:var(--danger-soft);border-color:rgba(219,59,59,0.25)}
|
||||
|
||||
.health{display:inline-flex;align-items:center;gap:8px}
|
||||
.health-dot{width:8px;height:8px;border-radius:50%;flex:none;box-shadow:0 0 0 3px rgba(255,255,255,0.5)}
|
||||
.health-dot.ok{background:var(--success)} .health-dot.warn{background:var(--warning)} .health-dot.bad{background:var(--danger)}
|
||||
.health-label{font-size:11.5px;color:var(--muted)}
|
||||
|
||||
.row-actions{display:flex;gap:4px;justify-content:flex-end;opacity:0;transition:opacity .15s}
|
||||
tr:hover .row-actions{opacity:1}
|
||||
.row-actions button{width:28px;height:28px;border-radius:8px;color:var(--muted);display:grid;place-items:center;transition:all .15s}
|
||||
.row-actions button:hover{color:var(--fg);background:rgba(255,255,255,0.85);box-shadow:0 2px 6px -2px rgba(40,52,92,0.2)}
|
||||
|
||||
.foot{text-align:center;color:var(--muted-2);font-size:11px;font-family:var(--mono);padding:4px 0 8px;letter-spacing:0.04em}
|
||||
|
||||
/* scrollbars */
|
||||
.feed::-webkit-scrollbar{width:7px}
|
||||
.feed::-webkit-scrollbar-thumb{background:rgba(30,35,60,0.14);border-radius:4px}
|
||||
|
||||
/* ---------- Responsive ---------- */
|
||||
@media (max-width:1240px){
|
||||
.metrics{grid-template-columns:repeat(2,1fr)}
|
||||
.row2{grid-template-columns:1fr}
|
||||
}
|
||||
@media (max-width:920px){
|
||||
.app{grid-template-columns:1fr}
|
||||
.sidebar{display:none}
|
||||
.main{padding:12px}
|
||||
}
|
||||
@media (max-width:640px){
|
||||
.metrics{grid-template-columns:1fr}
|
||||
.search{display:none}
|
||||
.scroll-x{overflow-x:auto}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<!-- ============ SIDEBAR ============ -->
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true"></div>
|
||||
<div class="brand-name">CluPilot<span>v2.4</span></div>
|
||||
</div>
|
||||
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta">
|
||||
<div class="ws-name">Acme Cluster</div>
|
||||
<div class="ws-plan">team · 14 user</div>
|
||||
</div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
|
||||
<nav class="nav">
|
||||
<a class="nav-item active" href="CluPilot Dashboard Glass.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Server.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg>
|
||||
<span>Server</span>
|
||||
<span class="count">6</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Sites.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
|
||||
<span>Sites</span>
|
||||
<span class="count">87</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Backups.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg>
|
||||
<span>Backups</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Sicherheit.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg>
|
||||
<span>Sicherheit</span>
|
||||
<span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span>
|
||||
</a>
|
||||
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg>
|
||||
<span>Team</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Einstellungen.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>
|
||||
<span>Einstellungen</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="plan-box">
|
||||
<div class="plan-row">
|
||||
<div class="plan-name">Team Plan</div>
|
||||
<div class="plan-tag">PRO</div>
|
||||
</div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats">
|
||||
<span>87 / 140 sites</span>
|
||||
<span>62%</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ============ MAIN ============ -->
|
||||
<main class="main" data-screen-label="Dashboard">
|
||||
<!-- Topbar -->
|
||||
<div class="topbar">
|
||||
<span class="page-title">Dashboard</span>
|
||||
<span class="status-chip"><span class="pulse-dot"></span> Alle Systeme aktiv</span>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input placeholder="Suchen…" />
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
<button class="icon-btn" aria-label="Benachrichtigungen">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg>
|
||||
<span class="badge">3</span>
|
||||
</button>
|
||||
<a class="btn-primary" href="Cluster anlegen.html">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6"><path d="M12 5v14M5 12h14"/></svg>
|
||||
Cluster anlegen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<!-- ============ METRICS ============ -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">Sites Online</div>
|
||||
<div class="m-ico success">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-value">87<span class="sub">/ 87</span></div>
|
||||
<div class="metric-delta"><span class="chg up">100%</span> Verfügbarkeit · 24 h</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">Ø Response</div>
|
||||
<div class="m-ico">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-value">142<span class="sub">ms</span></div>
|
||||
<div class="metric-delta"><span class="chg up">↓ 8,4%</span> vs. letzte Woche</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">Updates offen</div>
|
||||
<div class="m-ico warning">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-value">24</div>
|
||||
<div class="metric-delta"><span class="chg warn">7 kritisch</span> · 17 minor</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">Security Events</div>
|
||||
<div class="m-ico danger">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/><path d="M12 8v5M12 16h.01"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-value">128</div>
|
||||
<div class="metric-delta"><span class="chg down">↑ 23%</span> Angriffe blockiert</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ ROW: CHART + FEED ============ -->
|
||||
<div class="row2">
|
||||
<!-- Traffic chart -->
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h3>
|
||||
<svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 17l5-5 4 4 7-8M14 8h6v6"/></svg>
|
||||
Traffic
|
||||
<span class="live-tag" style="margin-left:2px"><span class="pulse-dot" style="color:var(--success)"></span>live</span>
|
||||
</h3>
|
||||
<div class="card-tabs">
|
||||
<button>15m</button>
|
||||
<button class="active">60m</button>
|
||||
<button>24h</button>
|
||||
<button>7d</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-wrap">
|
||||
<div class="chart-head">
|
||||
<div>
|
||||
<div class="chart-figure">14,2k<span class="unit">req/min</span></div>
|
||||
<div class="chart-sub">Peak vor 12 min · 16,8k req/min</div>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<span><i style="background:#3b6ff2"></i>Requests</span>
|
||||
<span><i style="background:#7c5cff"></i>Cache hit</span>
|
||||
</div>
|
||||
</div>
|
||||
<svg class="svg-chart" viewBox="0 0 600 172" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="grad-req" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="#3b6ff2" stop-opacity="0.28"/>
|
||||
<stop offset="100%" stop-color="#3b6ff2" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="grad-cache" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="#7c5cff" stop-opacity="0.16"/>
|
||||
<stop offset="100%" stop-color="#7c5cff" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g stroke="rgba(30,35,60,0.06)" stroke-width="1">
|
||||
<line x1="30" y1="34" x2="595" y2="34"/>
|
||||
<line x1="30" y1="74" x2="595" y2="74"/>
|
||||
<line x1="30" y1="114" x2="595" y2="114"/>
|
||||
</g>
|
||||
<text class="chart-axis" x="4" y="38">20k</text>
|
||||
<text class="chart-axis" x="4" y="78">15k</text>
|
||||
<text class="chart-axis" x="4" y="118">10k</text>
|
||||
<path id="cache-area" d="" fill="url(#grad-cache)" />
|
||||
<path id="cache-line" d="" stroke="#7c5cff" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="req-area" d="" fill="url(#grad-req)" />
|
||||
<path id="req-line" d="" stroke="#3b6ff2" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle id="live-dot" cx="0" cy="0" r="3.5" fill="#3b6ff2" stroke="#ffffff" stroke-width="2"/>
|
||||
<text class="chart-axis" x="34" y="166">-60m</text>
|
||||
<text class="chart-axis" x="300" y="166">-30m</text>
|
||||
<text class="chart-axis" x="560" y="166">jetzt</text>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Activity feed -->
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h3>
|
||||
<svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
|
||||
Aktivität
|
||||
<span class="live-tag" style="margin-left:2px"><span class="pulse-dot" style="color:var(--success)"></span>live</span>
|
||||
</h3>
|
||||
<span class="meta">30 min</span>
|
||||
</div>
|
||||
<div class="feed" id="feed-body"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ============ SITES TABLE ============ -->
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h3>
|
||||
<svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
|
||||
WordPress-Sites
|
||||
<span class="pill" style="margin-left:4px">8 von 87</span>
|
||||
</h3>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="ghost-btn">Filter</button>
|
||||
<button class="ghost-btn">Alle anzeigen →</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll-x">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Site</th>
|
||||
<th>Server</th>
|
||||
<th>WordPress</th>
|
||||
<th class="num">Besucher / 24h</th>
|
||||
<th>Status</th>
|
||||
<th class="num"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sites-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="foot">CluPilot · region eu-central-1 · uptime 99,984%</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const Icon = {
|
||||
terminal:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>',
|
||||
ext:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 3h7v7M21 3l-9 9M19 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"/></svg>',
|
||||
more:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>',
|
||||
};
|
||||
|
||||
/* ============ SITES ============ */
|
||||
const palette = ['#3b6ff2','#15a06a','#c2820f','#7c5cff','#db3b3b','#0ea5b7','#c026d3','#0d9488'];
|
||||
const sites = [
|
||||
{url:'kunde-mueller.de', server:'eu-prod-01', wp:'6.5.2', wpOk:true, visitors:'12,4k', health:'ok', label:'Optimal'},
|
||||
{url:'shop.bergmann-coffee.com', server:'eu-prod-01', wp:'6.4.3', wpOk:false, visitors:'48,7k', health:'ok', label:'3 Updates'},
|
||||
{url:'blog.fintech-radar.io', server:'eu-prod-02', wp:'6.5.2', wpOk:true, visitors:'9,1k', health:'ok', label:'Optimal'},
|
||||
{url:'praxis-dr-schwarz.de', server:'eu-prod-02', wp:'6.2.1', wpOk:false, visitors:'1,2k', health:'warn', label:'Wartung nötig'},
|
||||
{url:'agentur-helio.com', server:'eu-edge-amsterdam', wp:'6.5.2', wpOk:true, visitors:'18,2k', health:'ok', label:'Optimal'},
|
||||
{url:'docs.opencrate.dev', server:'us-east-prod-01', wp:'6.5.1', wpOk:true, visitors:'7,8k', health:'ok', label:'2 Updates'},
|
||||
{url:'staging.opencrate.dev', server:'eu-staging', wp:'6.6-RC1', wpOk:true, visitors:'82', health:'warn', label:'Backup spät'},
|
||||
{url:'classic.tonhoff-bau.de', server:'us-east-prod-01', wp:'5.9.7', wpOk:false, visitors:'3,4k', health:'bad', label:'Kritisch'},
|
||||
];
|
||||
const sitesBody = document.getElementById('sites-body');
|
||||
sites.forEach((s,i)=>{
|
||||
const initial = s.url[0].toUpperCase();
|
||||
const color = palette[i % palette.length];
|
||||
const wpPill = s.wpOk ? 'success' : 'warning';
|
||||
sitesBody.insertAdjacentHTML('beforeend',`
|
||||
<tr>
|
||||
<td>
|
||||
<div class="site-cell">
|
||||
<div class="favicon" style="background:${color}">${initial}</div>
|
||||
<div style="min-width:0">
|
||||
<a class="site-name" href="Site Details.html" style="display:block">${s.url}</a>
|
||||
<div class="site-meta">ssl ✓ · php 8.2</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill">${s.server}</span></td>
|
||||
<td><span class="pill ${wpPill}">WP ${s.wp}</span></td>
|
||||
<td class="num"><span style="color:var(--fg)">${s.visitors}</span></td>
|
||||
<td>
|
||||
<span class="health">
|
||||
<span class="health-dot ${s.health}"></span>
|
||||
<span class="health-label">${s.label}</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="num">
|
||||
<div class="row-actions">
|
||||
<button title="Site öffnen">${Icon.ext}</button>
|
||||
<button title="WP-Admin">${Icon.terminal}</button>
|
||||
<button title="Mehr">${Icon.more}</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`);
|
||||
});
|
||||
|
||||
/* ============ ACTIVITY FEED ============ */
|
||||
const feed = [
|
||||
{ic:'success', icon:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M5 12l5 5L20 7"/></svg>',
|
||||
html:'Backup erstellt für <span class="target">agentur-helio.com</span>', meta:'vor 12 sek · 248 MB'},
|
||||
{ic:'danger', icon:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg>',
|
||||
html:'<b>17 Login-Versuche</b> blockiert auf <span class="target">classic.tonhoff-bau.de</span>', meta:'vor 1 min · TOR exit'},
|
||||
{ic:'accent', icon:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>',
|
||||
html:'Plugin-Update <span class="target">Yoast SEO 22.5</span> auf 3 Sites', meta:'vor 4 min · von m.weber'},
|
||||
{ic:'warning', icon:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 9v4M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg>',
|
||||
html:'CPU > 80% auf <span class="target">us-east-prod-01</span>', meta:'vor 7 min · 4m 12s'},
|
||||
{ic:'success', icon:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>',
|
||||
html:'SSL-Zertifikat erneuert · <span class="target">*.kunde-mueller.de</span>', meta:'vor 14 min · gültig bis 08/26'},
|
||||
{ic:'accent', icon:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>',
|
||||
html:'SSH-Session geöffnet auf <span class="target">eu-prod-01</span>', meta:'vor 19 min · user j.koch'},
|
||||
{ic:'success', icon:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M5 12l5 5L20 7"/></svg>',
|
||||
html:'Health-Check OK · <b>87/87 Sites</b>', meta:'vor 23 min · run #4421'},
|
||||
];
|
||||
const feedBody = document.getElementById('feed-body');
|
||||
feed.forEach(f=>{
|
||||
feedBody.insertAdjacentHTML('beforeend',`
|
||||
<div class="feed-row">
|
||||
<div class="feed-ico ${f.ic}">${f.icon}</div>
|
||||
<div class="feed-body">
|
||||
<div>${f.html}</div>
|
||||
<div class="feed-meta">${f.meta}</div>
|
||||
</div>
|
||||
</div>`);
|
||||
});
|
||||
|
||||
/* ============ TRAFFIC CHART ============ */
|
||||
(function(){
|
||||
const W = 600, H = 172, padL = 30, padR = 8, padT = 12, padB = 30;
|
||||
const N = 60;
|
||||
const reqData = [], cacheData = [];
|
||||
let v = 11;
|
||||
for(let i=0;i<N;i++){
|
||||
v += (Math.random()-0.45)*1.6;
|
||||
if(i>40 && i<52) v += Math.sin((i-40)/12*Math.PI)*3.5;
|
||||
v = Math.max(5, Math.min(19, v));
|
||||
reqData.push(v);
|
||||
cacheData.push(v * (0.55 + Math.random()*0.15));
|
||||
}
|
||||
const x = i => padL + (W-padL-padR) * (i/(N-1));
|
||||
const y = vv => { const t = vv/20; return H - padB - t*(H-padT-padB); };
|
||||
function pathFor(arr){
|
||||
let d = `M ${x(0)} ${y(arr[0])}`;
|
||||
for(let i=1;i<arr.length;i++){
|
||||
const xc = (x(i-1)+x(i))/2;
|
||||
d += ` Q ${x(i-1)} ${y(arr[i-1])} ${xc} ${(y(arr[i-1])+y(arr[i]))/2}`;
|
||||
}
|
||||
d += ` T ${x(arr.length-1)} ${y(arr[arr.length-1])}`;
|
||||
return d;
|
||||
}
|
||||
const areaFor = arr => pathFor(arr) + ` L ${x(arr.length-1)} ${H-padB} L ${x(0)} ${H-padB} Z`;
|
||||
document.getElementById('req-line').setAttribute('d', pathFor(reqData));
|
||||
document.getElementById('req-area').setAttribute('d', areaFor(reqData));
|
||||
document.getElementById('cache-line').setAttribute('d', pathFor(cacheData));
|
||||
document.getElementById('cache-area').setAttribute('d', areaFor(cacheData));
|
||||
const dot = document.getElementById('live-dot');
|
||||
dot.setAttribute('cx', x(N-1));
|
||||
dot.setAttribute('cy', y(reqData[N-1]));
|
||||
let t0 = 0;
|
||||
(function tick(){ t0 += 0.04; dot.setAttribute('r', 3.5 + Math.sin(t0)*0.8); requestAnimationFrame(tick); })();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Anmelden · CluPilot</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--fg:#1a1a22;
|
||||
--muted:#5c5c6b;
|
||||
--muted-2:#9596a6;
|
||||
--accent:#3b6ff2;
|
||||
--accent-2:#7c5cff;
|
||||
--success:#15a06a;
|
||||
--warning:#c2820f;
|
||||
--danger:#db3b3b;
|
||||
--success-soft:rgba(21,160,106,0.12);
|
||||
--accent-soft:rgba(59,111,242,0.12);
|
||||
|
||||
--glass:rgba(255,255,255,0.55);
|
||||
--glass-strong:rgba(255,255,255,0.72);
|
||||
--glass-soft:rgba(255,255,255,0.4);
|
||||
--glass-border:rgba(255,255,255,0.75);
|
||||
--glass-shadow:0 10px 34px -10px rgba(40,52,92,0.20), 0 2px 8px -2px rgba(40,52,92,0.08);
|
||||
--glass-inset:inset 0 1px 0 rgba(255,255,255,0.95), inset 0 -1px 0 rgba(255,255,255,0.25);
|
||||
--hairline:rgba(30,35,60,0.08);
|
||||
--border-focus:rgba(59,111,242,0.5);
|
||||
|
||||
--radius:20px;
|
||||
--radius-sm:13px;
|
||||
--radius-xs:11px;
|
||||
--mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;color:var(--fg);font-family:'Inter',system-ui,-apple-system,sans-serif;font-size:13.5px;line-height:1.5;-webkit-font-smoothing:antialiased;min-height:100vh}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
button{font-family:inherit;color:inherit;background:none;border:0;cursor:pointer}
|
||||
|
||||
body{
|
||||
min-height:100vh;
|
||||
background:
|
||||
radial-gradient(60% 55% at 12% 8%, rgba(99,140,255,0.30), transparent 60%),
|
||||
radial-gradient(55% 50% at 88% 0%, rgba(140,108,255,0.26), transparent 58%),
|
||||
radial-gradient(50% 60% at 78% 92%, rgba(64,196,210,0.22), transparent 60%),
|
||||
radial-gradient(45% 50% at 25% 100%, rgba(120,170,255,0.20), transparent 60%),
|
||||
linear-gradient(160deg,#eef1fb 0%,#f4f3fb 50%,#eaf2f8 100%);
|
||||
background-attachment:fixed;
|
||||
}
|
||||
|
||||
/* ===== layout: two floating glass panels ===== */
|
||||
.page{position:relative;z-index:1;min-height:100vh;display:grid;grid-template-columns:minmax(420px,1fr) 1.05fr;gap:14px;padding:14px}
|
||||
|
||||
.glass-panel{
|
||||
border-radius:var(--radius);
|
||||
background:var(--glass-strong);
|
||||
backdrop-filter:blur(28px) saturate(175%);
|
||||
-webkit-backdrop-filter:blur(28px) saturate(175%);
|
||||
border:1px solid var(--glass-border);
|
||||
box-shadow:var(--glass-shadow), var(--glass-inset);
|
||||
}
|
||||
|
||||
/* ===== left: form ===== */
|
||||
.form-side{display:flex;flex-direction:column;padding:30px 44px;min-height:calc(100vh - 28px)}
|
||||
.top-row{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
||||
.brand{display:flex;align-items:center;gap:10px}
|
||||
.brand-mark{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#5a8bff 0%,#8a63ff 100%);position:relative;display:grid;place-items:center;box-shadow:0 0 0 1px rgba(255,255,255,0.4) inset, 0 6px 16px -4px rgba(90,139,255,0.6)}
|
||||
.brand-mark::before{content:"";width:11px;height:11px;border-radius:50%;background:rgba(255,255,255,0.96);box-shadow:inset 0 0 0 2px #5a8bff}
|
||||
.brand-name{font-weight:600;font-size:15px;letter-spacing:-0.01em}
|
||||
.brand-name span{color:var(--muted-2);font-weight:500;margin-left:5px;font-size:11.5px}
|
||||
|
||||
.status-chip{display:inline-flex;align-items:center;gap:7px;padding:4px 11px 4px 9px;border:1px solid rgba(21,160,106,0.28);background:var(--success-soft);color:var(--success);border-radius:20px;font-size:11px;font-weight:600;font-family:var(--mono)}
|
||||
.pulse-dot{width:6px;height:6px;border-radius:50%;background:currentColor;position:relative;flex:none}
|
||||
.pulse-dot::after{content:"";position:absolute;inset:-3px;border-radius:50%;background:currentColor;opacity:0.35;animation:pulse 1.8s ease-out infinite}
|
||||
@keyframes pulse{0%{transform:scale(0.6);opacity:.5}100%{transform:scale(2.4);opacity:0}}
|
||||
|
||||
.form-wrap{margin:auto 0;display:flex;flex-direction:column;gap:24px;max-width:380px;width:100%;padding:44px 0}
|
||||
h1{margin:0;font-size:25px;font-weight:600;letter-spacing:-0.02em;line-height:1.2}
|
||||
.sub{margin:6px 0 0;font-size:13.5px;color:var(--muted)}
|
||||
.sub a{font-weight:600}
|
||||
|
||||
/* SSO row */
|
||||
.sso-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.sso-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:10px 12px;background:rgba(255,255,255,0.55);border:1px solid var(--hairline);border-radius:var(--radius-xs);color:var(--fg);font-size:12.5px;font-weight:600;transition:all .15s;box-shadow:inset 0 1px 0 rgba(255,255,255,0.8)}
|
||||
.sso-btn:hover{background:rgba(255,255,255,0.9);border-color:var(--glass-border)}
|
||||
.sso-btn svg{flex:none}
|
||||
|
||||
.divider{display:flex;align-items:center;gap:12px;color:var(--muted-2);font-size:11px;font-family:var(--mono);text-transform:uppercase;letter-spacing:0.08em}
|
||||
.divider::before,.divider::after{content:"";flex:1;height:1px;background:var(--hairline)}
|
||||
|
||||
/* Inputs */
|
||||
form{display:flex;flex-direction:column;gap:14px}
|
||||
.field{display:flex;flex-direction:column;gap:6px}
|
||||
.field-label{display:flex;align-items:center;justify-content:space-between;font-size:11.5px;color:var(--muted);font-weight:600}
|
||||
.field-label a{font-size:11.5px;color:var(--muted);font-weight:500}
|
||||
.field-label a:hover{color:var(--fg);text-decoration:none}
|
||||
.input{display:flex;align-items:center;gap:10px;padding:0 13px;background:rgba(255,255,255,0.5);border:1px solid var(--hairline);border-radius:var(--radius-xs);transition:border-color .15s, background .15s, box-shadow .15s;height:42px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.7)}
|
||||
.input:hover{border-color:var(--glass-border);background:rgba(255,255,255,0.65)}
|
||||
.input:focus-within{border-color:var(--border-focus);background:rgba(255,255,255,0.85);box-shadow:0 0 0 3px rgba(59,111,242,0.12)}
|
||||
.input svg{color:var(--muted-2);flex:none}
|
||||
.input input{flex:1;background:none;border:0;outline:0;color:var(--fg);font-size:13.5px;font-family:inherit;min-width:0;height:100%}
|
||||
.input input::placeholder{color:var(--muted-2)}
|
||||
.input .eye{color:var(--muted);padding:4px;border-radius:6px;display:grid;place-items:center;transition:color .15s,background .15s}
|
||||
.input .eye:hover{color:var(--fg);background:rgba(255,255,255,0.7)}
|
||||
|
||||
/* Remember + submit */
|
||||
.row-between{display:flex;align-items:center;justify-content:space-between;margin-top:2px}
|
||||
.checkbox{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--muted);cursor:pointer;user-select:none}
|
||||
.checkbox input{display:none}
|
||||
.checkbox .box{width:16px;height:16px;border-radius:5px;border:1px solid rgba(30,35,60,0.2);background:rgba(255,255,255,0.7);display:grid;place-items:center;transition:all .15s}
|
||||
.checkbox input:checked + .box{background:var(--accent);border-color:var(--accent)}
|
||||
.checkbox input:checked + .box svg{opacity:1;transform:scale(1)}
|
||||
.checkbox .box svg{opacity:0;transform:scale(0.6);transition:all .15s;color:#fff}
|
||||
.checkbox:hover{color:var(--fg)}
|
||||
|
||||
.btn-primary{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:11px 16px;background:linear-gradient(180deg,#4f7dff,#3b6ff2);color:#fff;border-radius:var(--radius-xs);font-weight:600;font-size:13.5px;height:44px;transition:filter .15s,transform .05s;box-shadow:0 1px 0 rgba(255,255,255,0.3) inset, 0 6px 16px -5px rgba(59,111,242,0.65)}
|
||||
.btn-primary:hover{filter:brightness(1.06)}
|
||||
.btn-primary:active{transform:translateY(1px)}
|
||||
.btn-primary .arrow{transition:transform .15s}
|
||||
.btn-primary:hover .arrow{transform:translateX(2px)}
|
||||
|
||||
/* Magic link / passkey */
|
||||
.alt-options{display:flex;flex-direction:column;gap:9px;margin-top:4px}
|
||||
.alt-btn{display:flex;align-items:center;gap:11px;padding:10px 13px;background:rgba(255,255,255,0.4);border:1px solid var(--hairline);border-radius:var(--radius-xs);font-size:12.5px;color:var(--fg);font-weight:600;transition:all .15s}
|
||||
.alt-btn:hover{border-color:var(--glass-border);background:rgba(255,255,255,0.7)}
|
||||
.alt-btn .badge{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--accent);background:var(--accent-soft);padding:2px 7px;border-radius:5px;border:1px solid rgba(59,111,242,0.25);font-weight:600}
|
||||
.alt-btn .ico{color:var(--muted)}
|
||||
|
||||
/* footer */
|
||||
.form-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--muted);font-size:11.5px;font-family:var(--mono);padding-top:14px;border-top:1px solid var(--hairline);flex-wrap:wrap}
|
||||
.form-footer .links{display:flex;gap:14px}
|
||||
.form-footer .links a{color:var(--muted)}
|
||||
.form-footer .links a:hover{color:var(--fg);text-decoration:none}
|
||||
.form-footer .lock{display:flex;align-items:center;gap:6px}
|
||||
|
||||
/* ===== right: live panel ===== */
|
||||
.live-side{position:relative;padding:30px 34px;overflow:hidden;display:flex;flex-direction:column;gap:18px;min-height:calc(100vh - 28px)}
|
||||
.live-side::before{
|
||||
content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
|
||||
background:radial-gradient(80% 50% at 100% 0%, rgba(99,140,255,0.18), transparent 60%);
|
||||
}
|
||||
.live-side > *{position:relative}
|
||||
|
||||
.live-head{display:flex;align-items:center;justify-content:space-between}
|
||||
.live-head .label{font-size:11px;color:var(--muted);font-family:var(--mono);text-transform:uppercase;letter-spacing:0.10em}
|
||||
.live-head .meta{font-size:11px;color:var(--muted);font-family:var(--mono)}
|
||||
|
||||
.hero-quote{margin:8px 0 0;font-size:23px;font-weight:600;letter-spacing:-0.015em;line-height:1.3;max-width:480px}
|
||||
.hero-quote em{font-style:normal;color:var(--accent);font-weight:600}
|
||||
.hero-sub{color:var(--muted);font-size:13px;max-width:460px;margin-top:7px}
|
||||
|
||||
/* Stat strip */
|
||||
.stat-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:6px}
|
||||
.stat{background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius-sm);padding:13px;backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);box-shadow:inset 0 1px 0 rgba(255,255,255,0.8)}
|
||||
.stat-label{font-size:10.5px;color:var(--muted);text-transform:uppercase;letter-spacing:0.06em;font-weight:600}
|
||||
.stat-value{font-family:var(--mono);font-size:19px;font-weight:600;letter-spacing:-0.01em;margin-top:5px}
|
||||
.stat-value .unit{font-size:12px;color:var(--muted-2);font-weight:500;margin-left:2px}
|
||||
.stat-trend{display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:10.5px;color:var(--success);margin-top:5px}
|
||||
.stat-trend.warn{color:var(--warning)}
|
||||
|
||||
/* terminal card — stays dark for code aesthetic */
|
||||
.term-card{background:rgba(14,15,24,0.92);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.12);border-radius:var(--radius-sm);overflow:hidden;flex:1;display:flex;flex-direction:column;min-height:280px;box-shadow:0 14px 40px -12px rgba(20,24,50,0.45), inset 0 1px 0 rgba(255,255,255,0.08)}
|
||||
.term-head{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid rgba(255,255,255,0.07);background:rgba(10,10,16,0.6)}
|
||||
.term-dots{display:flex;gap:5px}
|
||||
.term-dots span{width:9px;height:9px;border-radius:50%;background:#2a2a36}
|
||||
.term-dots span:nth-child(1){background:#f05252}
|
||||
.term-dots span:nth-child(2){background:#f5a623}
|
||||
.term-dots span:nth-child(3){background:#3ecf8e}
|
||||
.term-title{font-family:var(--mono);font-size:11.5px;color:#8888a0;flex:1;text-align:center}
|
||||
.term-meta{font-family:var(--mono);font-size:10.5px;color:#5c5c70}
|
||||
|
||||
.term-body{padding:14px 16px;font-family:var(--mono);font-size:11.5px;line-height:1.6;flex:1;overflow:hidden;position:relative}
|
||||
.term-line{display:flex;gap:10px;align-items:flex-start;opacity:0;transform:translateY(4px);animation:typeIn .35s ease forwards}
|
||||
.term-time{color:#5c5c70;flex:none;width:60px;font-size:10.5px;padding-top:1px}
|
||||
.term-text{color:#cccddd;flex:1;min-width:0;word-break:break-word}
|
||||
.term-text .ok{color:#3ecf8e}
|
||||
.term-text .warn{color:#f5a623}
|
||||
.term-text .err{color:#f05252}
|
||||
.term-text .acc{color:#6f9bff}
|
||||
.term-text .dim{color:#8888a0}
|
||||
.term-text .pkg{color:#c084fc}
|
||||
.term-prompt{display:inline-flex;align-items:center;gap:7px;color:#3ecf8e}
|
||||
.term-prompt::before{content:"$";color:#6f9bff;font-weight:600}
|
||||
@keyframes typeIn{to{opacity:1;transform:translateY(0)}}
|
||||
|
||||
.cursor{display:inline-block;width:7px;height:13px;background:#6f9bff;vertical-align:-2px;animation:blink 1s steps(1) infinite;margin-left:2px}
|
||||
@keyframes blink{50%{opacity:0}}
|
||||
|
||||
/* mini activity row */
|
||||
.live-tags{display:flex;flex-wrap:wrap;gap:7px}
|
||||
.live-tag{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;font-size:10.5px;font-family:var(--mono);color:var(--muted);background:var(--glass);border:1px solid var(--glass-border);border-radius:7px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.7)}
|
||||
.live-tag .dot{width:5px;height:5px;border-radius:50%;background:var(--success)}
|
||||
.live-tag.warn .dot{background:var(--warning)}
|
||||
|
||||
/* responsive */
|
||||
@media (max-width:1080px){
|
||||
.page{grid-template-columns:1fr;padding:12px}
|
||||
.live-side{display:none}
|
||||
.form-side{padding:26px;min-height:calc(100vh - 24px)}
|
||||
}
|
||||
@media (max-width:520px){
|
||||
.form-side{padding:20px}
|
||||
.sso-row{grid-template-columns:1fr}
|
||||
.form-footer{justify-content:flex-start}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ============ LEFT: FORM ============ -->
|
||||
<section class="form-side glass-panel" data-screen-label="Login">
|
||||
<div class="top-row">
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true"></div>
|
||||
<div class="brand-name">CluPilot<span>v2.4</span></div>
|
||||
</div>
|
||||
<span class="status-chip"><span class="pulse-dot"></span> Alle Systeme aktiv</span>
|
||||
</div>
|
||||
|
||||
<div class="form-wrap">
|
||||
<header>
|
||||
<h1>Willkommen zurück</h1>
|
||||
<p class="sub">Bei CluPilot anmelden oder <a href="#">neues Konto erstellen</a></p>
|
||||
</header>
|
||||
|
||||
<div class="sso-row">
|
||||
<button class="sso-btn" type="button">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.5 12.27c0-.79-.07-1.54-.2-2.27H12v4.51h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.32z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.99.66-2.25 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23z"/><path fill="#FBBC05" d="M5.84 14.10A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.44.34-2.10V7.06H2.18A11 11 0 0 0 1 12c0 1.77.42 3.45 1.18 4.94l3.66-2.84z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.07.56 4.21 1.64l3.15-3.15C17.45 2.10 14.96 1 12 1A11 11 0 0 0 2.18 7.06l3.66 2.84C6.71 7.30 9.14 5.38 12 5.38z"/></svg>
|
||||
Google
|
||||
</button>
|
||||
<button class="sso-btn" type="button">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.02c-3.2.7-3.87-1.36-3.87-1.36-.52-1.33-1.27-1.68-1.27-1.68-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.69 1.24 3.35.95.1-.74.4-1.24.73-1.53-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.28 1.18-3.09-.12-.29-.51-1.46.11-3.05 0 0 .97-.31 3.18 1.18.92-.26 1.91-.39 2.89-.39.98 0 1.97.13 2.89.39 2.20-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.12 3.05.74.81 1.18 1.83 1.18 3.09 0 4.42-2.69 5.40-5.25 5.68.41.36.78 1.06.78 2.14v3.18c0 .31.21.67.79.55C20.21 21.39 23.5 17.08 23.5 12c0-6.35-5.15-11.5-11.5-11.5z"/></svg>
|
||||
GitHub
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="divider">oder mit E-Mail</div>
|
||||
|
||||
<form id="login-form" autocomplete="on">
|
||||
<div class="field">
|
||||
<label class="field-label" for="email">E-Mail-Adresse</label>
|
||||
<div class="input">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>
|
||||
<input id="email" name="email" type="email" placeholder="du@firma.de" autocomplete="email" required value="m.weber@acme-cluster.de" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label" for="password">
|
||||
Passwort
|
||||
<a href="#">Vergessen?</a>
|
||||
</label>
|
||||
<div class="input">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
|
||||
<input id="password" name="password" type="password" placeholder="••••••••••••" autocomplete="current-password" required value="hunter2-very-secure" />
|
||||
<button type="button" class="eye" id="toggle-eye" aria-label="Passwort anzeigen">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-between">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" checked />
|
||||
<span class="box">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>
|
||||
</span>
|
||||
<span>30 Tage angemeldet bleiben</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="btn-primary" type="submit">
|
||||
Anmelden
|
||||
<svg class="arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
|
||||
</button>
|
||||
|
||||
<div class="alt-options">
|
||||
<button class="alt-btn" type="button">
|
||||
<svg class="ico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2l-7 20-4-9-9-4 20-7z"/></svg>
|
||||
Magic-Link per E-Mail senden
|
||||
</button>
|
||||
<button class="alt-btn" type="button">
|
||||
<svg class="ico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12c0-4.97-4.03-9-9-9-2.5 0-4.76 1.02-6.39 2.67M3 12c0 4.97 4.03 9 9 9 2.5 0 4.76-1.02 6.39-2.67"/><path d="M9 12a3 3 0 1 0 6 0 3 3 0 1 0-6 0M12 1v4M12 19v4"/></svg>
|
||||
Mit Passkey anmelden
|
||||
<span class="badge">empfohlen</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<footer class="form-footer">
|
||||
<div class="lock">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
|
||||
TLS 1.3 · AES-256 · region eu-central-1
|
||||
</div>
|
||||
<div class="links">
|
||||
<a href="#">Datenschutz</a>
|
||||
<a href="#">AGB</a>
|
||||
<a href="#">Status</a>
|
||||
<a href="#">Hilfe</a>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<!-- ============ RIGHT: LIVE PANEL ============ -->
|
||||
<aside class="live-side glass-panel" aria-hidden="true">
|
||||
<div class="live-head">
|
||||
<div class="label">— live cluster</div>
|
||||
<div class="meta">eu-central-1 · build #4f8a32c</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="hero-quote">Verwalte <em>87 WordPress-Sites</em> auf 6 Servern. Ein Login. Volle Kontrolle.</p>
|
||||
<p class="hero-sub">SSH-Tunnel, Backups, Updates und Security in einer Konsole — von Push-to-Deploy bis Brute-Force-Defense.</p>
|
||||
</div>
|
||||
|
||||
<div class="stat-strip">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Sites Online</div>
|
||||
<div class="stat-value">87<span class="unit">/87</span></div>
|
||||
<div class="stat-trend">● 100% Uptime · 24h</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Ø Response</div>
|
||||
<div class="stat-value">142<span class="unit">ms</span></div>
|
||||
<div class="stat-trend">↓ 8,4% vs. 7d</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Blocked / 24h</div>
|
||||
<div class="stat-value">128</div>
|
||||
<div class="stat-trend warn">↑ 23% Brute-force</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="term-card">
|
||||
<div class="term-head">
|
||||
<div class="term-dots"><span></span><span></span><span></span></div>
|
||||
<div class="term-title">ssh <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="92fde2e1d2f7e7bfe2e0fdf6bfa2a3bcf1fee7e2fbfefde6bcfbfd">[email protected]</a></div>
|
||||
<div class="term-meta">pts/0</div>
|
||||
</div>
|
||||
<div class="term-body" id="term-body"></div>
|
||||
</div>
|
||||
|
||||
<div class="live-tags">
|
||||
<span class="live-tag"><span class="dot"></span>eu-prod-01 · 23 sites</span>
|
||||
<span class="live-tag"><span class="dot"></span>eu-prod-02 · 19 sites</span>
|
||||
<span class="live-tag"><span class="dot"></span>edge-ams · 14 sites</span>
|
||||
<span class="live-tag warn"><span class="dot"></span>us-east-01 · CPU 78%</span>
|
||||
<span class="live-tag"><span class="dot"></span>staging · 6 sites</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script>
|
||||
const eyeBtn = document.getElementById('toggle-eye');
|
||||
const pwd = document.getElementById('password');
|
||||
eyeBtn.addEventListener('click', () => { pwd.type = pwd.type === 'password' ? 'text' : 'password'; });
|
||||
|
||||
document.getElementById('login-form').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const btn = e.target.querySelector('.btn-primary');
|
||||
const orig = btn.innerHTML;
|
||||
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="animation:spin 0.8s linear infinite"><path d="M21 12a9 9 0 1 1-6.2-8.55"/></svg> Authentifiziere…';
|
||||
btn.disabled = true;
|
||||
setTimeout(() => { btn.innerHTML = orig; btn.disabled = false; }, 1800);
|
||||
});
|
||||
|
||||
const lines = [
|
||||
['09:42:18', '<span class="term-prompt"></span> <span class="dim">clupilot</span> status --cluster=acme'],
|
||||
['09:42:18', '<span class="ok">✓</span> 87/87 sites <span class="dim">healthy</span> · <span class="acc">142ms</span> ø response'],
|
||||
['09:42:19', '<span class="ok">✓</span> backup eu-prod-02 → <span class="pkg">s3://clupilot-backups</span> <span class="dim">248 MB</span>'],
|
||||
['09:42:21', '<span class="acc">→</span> deploying <span class="pkg">Yoast-SEO@22.5</span> to <span class="dim">3 sites…</span>'],
|
||||
['09:42:23', '<span class="ok">✓</span> agentur-helio.com <span class="dim">(312ms)</span>'],
|
||||
['09:42:24', '<span class="ok">✓</span> blog.fintech-radar.io <span class="dim">(287ms)</span>'],
|
||||
['09:42:24', '<span class="ok">✓</span> docs.opencrate.dev <span class="dim">(298ms)</span>'],
|
||||
['09:42:26', '<span class="warn">!</span> classic.tonhoff-bau.de <span class="dim">PHP 7.4 EOL — migration queued</span>'],
|
||||
['09:42:28', '<span class="err">✗</span> fw <span class="dim">blocked</span> 17 login attempts from <span class="acc">185.220.101.18</span> <span class="dim">(TOR)</span>'],
|
||||
['09:42:30', '<span class="ok">✓</span> SSL renewed: <span class="pkg">*.kunde-mueller.de</span> <span class="dim">until 2026-08-25</span>'],
|
||||
['09:42:32', '<span class="term-prompt"></span> <span class="dim">tail -f /var/log/clupilot/audit.log</span><span class="cursor"></span>'],
|
||||
];
|
||||
const body = document.getElementById('term-body');
|
||||
let i = 0;
|
||||
function addLine(){
|
||||
if(i >= lines.length){ setTimeout(() => { body.innerHTML = ''; i = 0; addLine(); }, 5000); return; }
|
||||
const [t, html] = lines[i++];
|
||||
const el = document.createElement('div');
|
||||
el.className = 'term-line';
|
||||
el.innerHTML = '<div class="term-time">'+t+'</div><div class="term-text">'+html+'</div>';
|
||||
body.appendChild(el);
|
||||
body.scrollTop = body.scrollHeight;
|
||||
while(body.children.length > 11) body.removeChild(body.firstChild);
|
||||
setTimeout(addLine, 600 + Math.random()*450);
|
||||
}
|
||||
addLine();
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = '@keyframes spin{to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(style);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,474 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Konto erstellen · CluPilot</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--fg:#1a1a22;
|
||||
--muted:#5c5c6b;
|
||||
--muted-2:#9596a6;
|
||||
--accent:#3b6ff2;
|
||||
--accent-2:#7c5cff;
|
||||
--success:#15a06a;
|
||||
--warning:#c2820f;
|
||||
--danger:#db3b3b;
|
||||
--success-soft:rgba(21,160,106,0.12);
|
||||
--accent-soft:rgba(59,111,242,0.12);
|
||||
|
||||
--glass:rgba(255,255,255,0.55);
|
||||
--glass-strong:rgba(255,255,255,0.72);
|
||||
--glass-soft:rgba(255,255,255,0.4);
|
||||
--glass-border:rgba(255,255,255,0.75);
|
||||
--glass-shadow:0 10px 34px -10px rgba(40,52,92,0.20), 0 2px 8px -2px rgba(40,52,92,0.08);
|
||||
--glass-inset:inset 0 1px 0 rgba(255,255,255,0.95), inset 0 -1px 0 rgba(255,255,255,0.25);
|
||||
--hairline:rgba(30,35,60,0.08);
|
||||
--border-focus:rgba(59,111,242,0.5);
|
||||
--surface-2:rgba(30,35,60,0.08);
|
||||
|
||||
--radius:20px;
|
||||
--radius-sm:13px;
|
||||
--radius-xs:11px;
|
||||
--mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;color:var(--fg);font-family:'Inter',system-ui,-apple-system,sans-serif;font-size:13.5px;line-height:1.5;-webkit-font-smoothing:antialiased;min-height:100vh}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
button{font-family:inherit;color:inherit;background:none;border:0;cursor:pointer}
|
||||
|
||||
body{
|
||||
min-height:100vh;
|
||||
background:
|
||||
radial-gradient(60% 55% at 12% 8%, rgba(99,140,255,0.30), transparent 60%),
|
||||
radial-gradient(55% 50% at 88% 0%, rgba(140,108,255,0.26), transparent 58%),
|
||||
radial-gradient(50% 60% at 78% 92%, rgba(64,196,210,0.22), transparent 60%),
|
||||
radial-gradient(45% 50% at 25% 100%, rgba(120,170,255,0.20), transparent 60%),
|
||||
linear-gradient(160deg,#eef1fb 0%,#f4f3fb 50%,#eaf2f8 100%);
|
||||
background-attachment:fixed;
|
||||
}
|
||||
|
||||
.page{position:relative;z-index:1;min-height:100vh;display:grid;grid-template-columns:minmax(440px,1fr) 1fr;gap:14px;padding:14px}
|
||||
|
||||
.glass-panel{
|
||||
border-radius:var(--radius);
|
||||
background:var(--glass-strong);
|
||||
backdrop-filter:blur(28px) saturate(175%);
|
||||
-webkit-backdrop-filter:blur(28px) saturate(175%);
|
||||
border:1px solid var(--glass-border);
|
||||
box-shadow:var(--glass-shadow), var(--glass-inset);
|
||||
}
|
||||
|
||||
/* ===== left: form ===== */
|
||||
.form-side{display:flex;flex-direction:column;padding:30px 44px;min-height:calc(100vh - 28px)}
|
||||
.top-row{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
||||
.brand{display:flex;align-items:center;gap:10px}
|
||||
.brand-mark{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#5a8bff 0%,#8a63ff 100%);position:relative;display:grid;place-items:center;box-shadow:0 0 0 1px rgba(255,255,255,0.4) inset, 0 6px 16px -4px rgba(90,139,255,0.6)}
|
||||
.brand-mark::before{content:"";width:11px;height:11px;border-radius:50%;background:rgba(255,255,255,0.96);box-shadow:inset 0 0 0 2px #5a8bff}
|
||||
.brand-name{font-weight:600;font-size:15px;letter-spacing:-0.01em}
|
||||
.brand-name span{color:var(--muted-2);font-weight:500;margin-left:5px;font-size:11.5px}
|
||||
|
||||
.top-link{font-size:12.5px;color:var(--muted)}
|
||||
.top-link a{margin-left:6px;font-weight:600}
|
||||
|
||||
/* Stepper */
|
||||
.stepper{display:flex;align-items:center;gap:10px;margin-top:30px;font-family:var(--mono);font-size:11px;color:var(--muted-2)}
|
||||
.step{display:flex;align-items:center;gap:7px}
|
||||
.step-num{width:22px;height:22px;border-radius:50%;border:1px solid var(--hairline);display:grid;place-items:center;font-size:10.5px;color:var(--muted);background:rgba(255,255,255,0.6)}
|
||||
.step.active .step-num{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
|
||||
.step.done .step-num{border-color:var(--success);color:#ffffff;background:var(--success)}
|
||||
.step-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:0.06em;font-weight:600}
|
||||
.step.active .step-label{color:var(--fg)}
|
||||
.step-line{flex:1;height:1px;background:var(--hairline);min-width:14px;max-width:28px}
|
||||
|
||||
.form-wrap{margin:auto 0;display:flex;flex-direction:column;gap:22px;max-width:420px;width:100%;padding:30px 0}
|
||||
h1{margin:0;font-size:25px;font-weight:600;letter-spacing:-0.02em;line-height:1.2}
|
||||
.sub{margin:6px 0 0;font-size:13.5px;color:var(--muted)}
|
||||
.sub a{font-weight:600}
|
||||
|
||||
/* SSO row */
|
||||
.sso-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.sso-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:10px 12px;background:rgba(255,255,255,0.55);border:1px solid var(--hairline);border-radius:var(--radius-xs);color:var(--fg);font-size:12.5px;font-weight:600;transition:all .15s;box-shadow:inset 0 1px 0 rgba(255,255,255,0.8)}
|
||||
.sso-btn:hover{background:rgba(255,255,255,0.9);border-color:var(--glass-border)}
|
||||
|
||||
.divider{display:flex;align-items:center;gap:12px;color:var(--muted-2);font-size:11px;font-family:var(--mono);text-transform:uppercase;letter-spacing:0.08em}
|
||||
.divider::before,.divider::after{content:"";flex:1;height:1px;background:var(--hairline)}
|
||||
|
||||
/* Inputs */
|
||||
form{display:flex;flex-direction:column;gap:14px}
|
||||
.field{display:flex;flex-direction:column;gap:6px}
|
||||
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.field-label{display:flex;align-items:center;justify-content:space-between;font-size:11.5px;color:var(--muted);font-weight:600}
|
||||
.field-label .hint{font-family:var(--mono);font-size:10.5px;color:var(--muted-2);font-weight:400}
|
||||
.input{display:flex;align-items:center;gap:10px;padding:0 13px;background:rgba(255,255,255,0.5);border:1px solid var(--hairline);border-radius:var(--radius-xs);transition:border-color .15s, background .15s, box-shadow .15s;height:42px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.7)}
|
||||
.input:hover{border-color:var(--glass-border);background:rgba(255,255,255,0.65)}
|
||||
.input:focus-within{border-color:var(--border-focus);background:rgba(255,255,255,0.85);box-shadow:0 0 0 3px rgba(59,111,242,0.12)}
|
||||
.input svg{color:var(--muted-2);flex:none}
|
||||
.input input{flex:1;background:none;border:0;outline:0;color:var(--fg);font-size:13.5px;font-family:inherit;min-width:0;height:100%}
|
||||
.input input::placeholder{color:var(--muted-2)}
|
||||
.input.with-suffix{padding-right:0}
|
||||
.input-suffix{height:100%;display:flex;align-items:center;padding:0 13px;border-left:1px solid var(--hairline);color:var(--muted);font-family:var(--mono);font-size:11.5px;background:rgba(255,255,255,0.5);border-top-right-radius:var(--radius-xs);border-bottom-right-radius:var(--radius-xs)}
|
||||
.input .eye{color:var(--muted);padding:4px;border-radius:6px;display:grid;place-items:center;transition:color .15s,background .15s;margin-right:4px}
|
||||
.input .eye:hover{color:var(--fg);background:rgba(255,255,255,0.7)}
|
||||
.input-check{color:var(--success);margin-right:8px;display:none}
|
||||
.input.valid .input-check{display:inline-flex}
|
||||
|
||||
/* Slug preview */
|
||||
.slug-preview{font-family:var(--mono);font-size:11px;color:var(--muted);margin-top:2px}
|
||||
.slug-preview b{color:var(--accent);font-weight:500}
|
||||
|
||||
/* Password strength */
|
||||
.pw-meter{display:flex;gap:4px;margin-top:6px}
|
||||
.pw-meter span{flex:1;height:4px;border-radius:2px;background:var(--surface-2);transition:background .2s}
|
||||
.pw-meter.s1 span:nth-child(-n+1){background:var(--danger)}
|
||||
.pw-meter.s2 span:nth-child(-n+2){background:var(--warning)}
|
||||
.pw-meter.s3 span:nth-child(-n+3){background:var(--accent)}
|
||||
.pw-meter.s4 span{background:var(--success)}
|
||||
.pw-rules{display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:7px;font-family:var(--mono);font-size:10.5px;color:var(--muted-2)}
|
||||
.pw-rule{display:inline-flex;align-items:center;gap:5px}
|
||||
.pw-rule .bullet{width:5px;height:5px;border-radius:50%;background:var(--muted-2);transition:background .15s}
|
||||
.pw-rule.ok{color:var(--success)}
|
||||
.pw-rule.ok .bullet{background:var(--success)}
|
||||
|
||||
/* Checkbox + submit */
|
||||
.checkbox{display:flex;align-items:flex-start;gap:10px;font-size:12.5px;color:var(--muted);cursor:pointer;user-select:none;line-height:1.45}
|
||||
.checkbox input{display:none}
|
||||
.checkbox .box{width:16px;height:16px;border-radius:5px;border:1px solid rgba(30,35,60,0.2);background:rgba(255,255,255,0.7);display:grid;place-items:center;transition:all .15s;flex:none;margin-top:1px}
|
||||
.checkbox input:checked + .box{background:var(--accent);border-color:var(--accent)}
|
||||
.checkbox input:checked + .box svg{opacity:1;transform:scale(1)}
|
||||
.checkbox .box svg{opacity:0;transform:scale(0.6);transition:all .15s;color:#fff}
|
||||
.checkbox:hover{color:var(--fg)}
|
||||
|
||||
.btn-primary{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:11px 16px;background:linear-gradient(180deg,#4f7dff,#3b6ff2);color:#fff;border-radius:var(--radius-xs);font-weight:600;font-size:13.5px;height:44px;transition:filter .15s,transform .05s;box-shadow:0 1px 0 rgba(255,255,255,0.3) inset, 0 6px 16px -5px rgba(59,111,242,0.65)}
|
||||
.btn-primary:hover{filter:brightness(1.06)}
|
||||
.btn-primary:active{transform:translateY(1px)}
|
||||
.btn-primary .arrow{transition:transform .15s}
|
||||
.btn-primary:hover .arrow{transform:translateX(2px)}
|
||||
|
||||
/* footer */
|
||||
.form-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--muted);font-size:11.5px;font-family:var(--mono);padding-top:14px;border-top:1px solid var(--hairline);flex-wrap:wrap}
|
||||
.form-footer .links{display:flex;gap:14px}
|
||||
.form-footer .links a{color:var(--muted)}
|
||||
.form-footer .links a:hover{color:var(--fg);text-decoration:none}
|
||||
.form-footer .lock{display:flex;align-items:center;gap:6px}
|
||||
|
||||
/* ===== right: benefits panel ===== */
|
||||
.live-side{position:relative;padding:32px 36px;overflow:hidden;display:flex;flex-direction:column;gap:20px;min-height:calc(100vh - 28px)}
|
||||
.live-side::before{
|
||||
content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
|
||||
background:radial-gradient(70% 50% at 100% 0%, rgba(99,140,255,0.18), transparent 60%);
|
||||
}
|
||||
.live-side > *{position:relative}
|
||||
|
||||
.trial-card{background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius-sm);padding:18px 20px;position:relative;overflow:hidden;backdrop-filter:blur(16px) saturate(160%);-webkit-backdrop-filter:blur(16px) saturate(160%);box-shadow:inset 0 1px 0 rgba(255,255,255,0.85)}
|
||||
.trial-card::before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b6ff2,#7c5cff)}
|
||||
.trial-row{display:flex;align-items:center;justify-content:space-between}
|
||||
.trial-tag{font-family:var(--mono);font-size:10.5px;color:var(--accent);background:var(--accent-soft);padding:3px 8px;border-radius:6px;border:1px solid rgba(59,111,242,0.28);font-weight:600;text-transform:uppercase;letter-spacing:0.05em}
|
||||
.trial-count{font-family:var(--mono);font-size:11px;color:var(--muted);display:inline-flex;align-items:center;gap:6px}
|
||||
.pulse-dot{width:6px;height:6px;border-radius:50%;background:var(--success);position:relative;flex:none}
|
||||
.pulse-dot::after{content:"";position:absolute;inset:-3px;border-radius:50%;background:currentColor;color:var(--success);opacity:0.4;animation:pulse 1.8s ease-out infinite}
|
||||
@keyframes pulse{0%{transform:scale(0.6);opacity:.5}100%{transform:scale(2.4);opacity:0}}
|
||||
|
||||
.trial-title{font-size:23px;font-weight:600;letter-spacing:-0.015em;line-height:1.25;margin-top:12px;max-width:420px}
|
||||
.trial-title em{font-style:normal;background:linear-gradient(90deg,#3b6ff2,#7c5cff);-webkit-background-clip:text;background-clip:text;color:transparent;font-weight:700}
|
||||
.trial-sub{color:var(--muted);font-size:13px;margin-top:7px;max-width:440px}
|
||||
|
||||
.benefit-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:15px}
|
||||
.benefit{display:flex;gap:13px;align-items:flex-start}
|
||||
.benefit-ico{width:32px;height:32px;border-radius:9px;background:var(--accent-soft);border:1px solid rgba(59,111,242,0.25);color:var(--accent);display:grid;place-items:center;flex:none;box-shadow:inset 0 1px 0 rgba(255,255,255,0.5)}
|
||||
.benefit-ico.success{background:var(--success-soft);border-color:rgba(21,160,106,0.25);color:var(--success)}
|
||||
.benefit-ico.purple{background:rgba(124,92,255,0.12);border-color:rgba(124,92,255,0.25);color:#7c5cff}
|
||||
.benefit-ico.warn{background:rgba(194,130,15,0.12);border-color:rgba(194,130,15,0.25);color:var(--warning)}
|
||||
.benefit-body{flex:1;min-width:0}
|
||||
.benefit-title{font-size:13.5px;font-weight:600;color:var(--fg)}
|
||||
.benefit-desc{font-size:12.5px;color:var(--muted);margin-top:2px;line-height:1.5}
|
||||
|
||||
/* Quote */
|
||||
.quote-card{margin-top:auto;background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius-sm);padding:18px 20px;backdrop-filter:blur(16px) saturate(160%);-webkit-backdrop-filter:blur(16px) saturate(160%);box-shadow:inset 0 1px 0 rgba(255,255,255,0.85)}
|
||||
.quote-text{font-size:13.5px;color:#34343f;line-height:1.55;font-weight:400}
|
||||
.quote-text::before{content:"„";color:var(--accent);font-size:24px;line-height:0;margin-right:4px;vertical-align:-6px;font-weight:600}
|
||||
.quote-text::after{content:"“";color:var(--accent);font-weight:600}
|
||||
.quote-author{display:flex;align-items:center;gap:10px;margin-top:12px}
|
||||
.avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#16a368,#0c7a4e);display:grid;place-items:center;color:#ffffff;font-weight:600;font-size:12px;font-family:var(--mono)}
|
||||
.author-meta{font-size:12px}
|
||||
.author-name{color:var(--fg);font-weight:600}
|
||||
.author-role{color:var(--muted);font-size:11px;font-family:var(--mono)}
|
||||
|
||||
/* Logo strip */
|
||||
.logos{display:flex;align-items:center;gap:20px;flex-wrap:wrap;opacity:0.8;font-family:var(--mono);font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:0.08em}
|
||||
.logo-pill{padding:5px 11px;border:1px solid var(--glass-border);border-radius:7px;background:var(--glass);box-shadow:inset 0 1px 0 rgba(255,255,255,0.7)}
|
||||
|
||||
/* responsive */
|
||||
@media (max-width:1080px){
|
||||
.page{grid-template-columns:1fr;padding:12px}
|
||||
.live-side{display:none}
|
||||
.form-side{padding:26px;min-height:calc(100vh - 24px)}
|
||||
}
|
||||
@media (max-width:520px){
|
||||
.form-side{padding:20px}
|
||||
.sso-row{grid-template-columns:1fr}
|
||||
.field-row{grid-template-columns:1fr}
|
||||
.form-footer{justify-content:flex-start}
|
||||
.stepper .step-label{display:none}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ============ LEFT: FORM ============ -->
|
||||
<section class="form-side glass-panel" data-screen-label="Signup">
|
||||
<div class="top-row">
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true"></div>
|
||||
<div class="brand-name">CluPilot<span>v2.4</span></div>
|
||||
</div>
|
||||
<div class="top-link">Schon Konto?<a href="#">Anmelden →</a></div>
|
||||
</div>
|
||||
|
||||
<div class="stepper" aria-label="Registrierungs-Schritte">
|
||||
<div class="step active">
|
||||
<div class="step-num">1</div>
|
||||
<div class="step-label">Konto</div>
|
||||
</div>
|
||||
<div class="step-line"></div>
|
||||
<div class="step">
|
||||
<div class="step-num">2</div>
|
||||
<div class="step-label">Workspace</div>
|
||||
</div>
|
||||
<div class="step-line"></div>
|
||||
<div class="step">
|
||||
<div class="step-num">3</div>
|
||||
<div class="step-label">Erster Server</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-wrap">
|
||||
<header>
|
||||
<h1>14 Tage kostenlos testen</h1>
|
||||
<p class="sub">Keine Kreditkarte. Volle Funktionen. Jederzeit kündbar.</p>
|
||||
</header>
|
||||
|
||||
<div class="sso-row">
|
||||
<button class="sso-btn" type="button">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.5 12.27c0-.79-.07-1.54-.2-2.27H12v4.51h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.32z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.99.66-2.25 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23z"/><path fill="#FBBC05" d="M5.84 14.10A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.44.34-2.10V7.06H2.18A11 11 0 0 0 1 12c0 1.77.42 3.45 1.18 4.94l3.66-2.84z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.07.56 4.21 1.64l3.15-3.15C17.45 2.10 14.96 1 12 1A11 11 0 0 0 2.18 7.06l3.66 2.84C6.71 7.30 9.14 5.38 12 5.38z"/></svg>
|
||||
Mit Google fortfahren
|
||||
</button>
|
||||
<button class="sso-btn" type="button">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.02c-3.2.7-3.87-1.36-3.87-1.36-.52-1.33-1.27-1.68-1.27-1.68-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.69 1.24 3.35.95.1-.74.4-1.24.73-1.53-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.28 1.18-3.09-.12-.29-.51-1.46.11-3.05 0 0 .97-.31 3.18 1.18.92-.26 1.91-.39 2.89-.39.98 0 1.97.13 2.89.39 2.20-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.12 3.05.74.81 1.18 1.83 1.18 3.09 0 4.42-2.69 5.40-5.25 5.68.41.36.78 1.06.78 2.14v3.18c0 .31.21.67.79.55C20.21 21.39 23.5 17.08 23.5 12c0-6.35-5.15-11.5-11.5-11.5z"/></svg>
|
||||
Mit GitHub fortfahren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="divider">oder mit E-Mail</div>
|
||||
|
||||
<form id="signup-form" autocomplete="on">
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label" for="firstname">Vorname</label>
|
||||
<div class="input">
|
||||
<input id="firstname" type="text" placeholder="Marie" autocomplete="given-name" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label" for="lastname">Nachname</label>
|
||||
<div class="input">
|
||||
<input id="lastname" type="text" placeholder="Weber" autocomplete="family-name" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label" for="email">Arbeits-E-Mail</label>
|
||||
<div class="input" id="email-wrap">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>
|
||||
<input id="email" type="email" placeholder="du@firma.de" autocomplete="email" required />
|
||||
<svg class="input-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12l5 5L20 7"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label" for="workspace">
|
||||
Workspace-Name
|
||||
<span class="hint">URL für dein Team</span>
|
||||
</label>
|
||||
<div class="input with-suffix">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg>
|
||||
<input id="workspace" type="text" placeholder="Acme Cluster" required />
|
||||
<span class="input-suffix" id="slug-suffix">.clupilot.io</span>
|
||||
</div>
|
||||
<div class="slug-preview">Erreichbar unter <b id="slug-preview">dein-team</b>.clupilot.io</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label" for="password">
|
||||
Passwort
|
||||
<span class="hint">min. 10 Zeichen</span>
|
||||
</label>
|
||||
<div class="input">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
|
||||
<input id="password" type="password" placeholder="••••••••••••" autocomplete="new-password" required />
|
||||
<button type="button" class="eye" id="toggle-eye" aria-label="Passwort anzeigen">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pw-meter" id="pw-meter"><span></span><span></span><span></span><span></span></div>
|
||||
<div class="pw-rules">
|
||||
<span class="pw-rule" data-rule="len"><span class="bullet"></span>10+ Zeichen</span>
|
||||
<span class="pw-rule" data-rule="upper"><span class="bullet"></span>Großbuchstabe</span>
|
||||
<span class="pw-rule" data-rule="num"><span class="bullet"></span>Zahl</span>
|
||||
<span class="pw-rule" data-rule="sym"><span class="bullet"></span>Sonderzeichen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="agree" required />
|
||||
<span class="box">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>
|
||||
</span>
|
||||
<span>Ich akzeptiere die <a href="#">AGB</a> und stimme der <a href="#">Datenschutzerklärung</a> zu. CluPilot speichert SSH-Keys verschlüsselt in der EU.</span>
|
||||
</label>
|
||||
|
||||
<button class="btn-primary" type="submit">
|
||||
Konto erstellen
|
||||
<svg class="arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<footer class="form-footer">
|
||||
<div class="lock">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
|
||||
TLS 1.3 · AES-256 · region eu-central-1
|
||||
</div>
|
||||
<div class="links">
|
||||
<a href="#">Datenschutz</a>
|
||||
<a href="#">AGB</a>
|
||||
<a href="#">Status</a>
|
||||
<a href="#">Hilfe</a>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<!-- ============ RIGHT: TRIAL / BENEFITS ============ -->
|
||||
<aside class="live-side glass-panel" aria-hidden="true">
|
||||
|
||||
<div class="trial-card">
|
||||
<div class="trial-row">
|
||||
<span class="trial-tag">14 Tage Pro · gratis</span>
|
||||
<span class="trial-count"><span class="pulse-dot"></span> 47 Teams diese Woche</span>
|
||||
</div>
|
||||
<div class="trial-title">In <em>60 Sekunden</em> deinen ersten Server verbunden.</div>
|
||||
<div class="trial-sub">SSH-Schlüssel hochladen, Site importieren, fertig. Wir kümmern uns ums Monitoring, Backups und Updates — du um dein Business.</div>
|
||||
</div>
|
||||
|
||||
<ul class="benefit-list">
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">SSH-First Setup</div>
|
||||
<div class="benefit-desc">Verbinde Hetzner, Hostinger oder deinen eigenen Server in 2 Klicks — keine Agents, kein Custom-DNS.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico success">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">Stündliche Backups inklusive</div>
|
||||
<div class="benefit-desc">Verschlüsselt nach Frankfurt-S3, 30 Tage Retention, One-Click-Restore — auch für die WP-Datenbank.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico purple">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">Bulk-Updates ohne Drama</div>
|
||||
<div class="benefit-desc">WordPress, Plugins und Themes auf 100 Sites gleichzeitig — mit Staging-Diff und automatischem Rollback bei Fehler.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico warn">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/><path d="M12 8v5M12 16h.01"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">Brute-Force-Defense aktiv</div>
|
||||
<div class="benefit-desc">Geo-Fencing, Rate-Limits und WP-User-Lockout — schon im Trial dabei. Letzte 24h: 12.488 Versuche blockiert.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="quote-card">
|
||||
<div class="quote-text">Statt 18 SSH-Sessions in Terminal-Tabs habe ich jetzt eine Konsole. Mein Agency-Team hostet 87 Sites — und schläft endlich wieder durch.</div>
|
||||
<div class="quote-author">
|
||||
<div class="avatar">JK</div>
|
||||
<div class="author-meta">
|
||||
<div class="author-name">Julian Koch</div>
|
||||
<div class="author-role">CTO · Helio Digital · 87 Sites</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logos">
|
||||
<span>vertrauen uns</span>
|
||||
<span class="logo-pill">helio.</span>
|
||||
<span class="logo-pill">acme▲</span>
|
||||
<span class="logo-pill">opencrate</span>
|
||||
<span class="logo-pill">bergmann</span>
|
||||
<span class="logo-pill">+ 2.1k</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ws = document.getElementById('workspace');
|
||||
const slug = document.getElementById('slug-preview');
|
||||
const slugify = s => s.toLowerCase().trim().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'').slice(0,32) || 'dein-team';
|
||||
ws.addEventListener('input', () => { slug.textContent = slugify(ws.value); });
|
||||
|
||||
const email = document.getElementById('email');
|
||||
const emailWrap = document.getElementById('email-wrap');
|
||||
email.addEventListener('input', () => {
|
||||
emailWrap.classList.toggle('valid', /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value));
|
||||
});
|
||||
|
||||
const pwd = document.getElementById('password');
|
||||
const meter = document.getElementById('pw-meter');
|
||||
pwd.addEventListener('input', () => {
|
||||
const v = pwd.value;
|
||||
const tests = { len: v.length >= 10, upper: /[A-Z]/.test(v), num: /[0-9]/.test(v), sym: /[^A-Za-z0-9]/.test(v) };
|
||||
let score = 0;
|
||||
Object.keys(tests).forEach(k => {
|
||||
const ok = tests[k];
|
||||
document.querySelector(`.pw-rule[data-rule="${k}"]`).classList.toggle('ok', ok);
|
||||
if(ok) score++;
|
||||
});
|
||||
meter.className = 'pw-meter' + (v.length ? ' s'+score : '');
|
||||
});
|
||||
|
||||
document.getElementById('toggle-eye').addEventListener('click', () => {
|
||||
pwd.type = pwd.type === 'password' ? 'text' : 'password';
|
||||
});
|
||||
|
||||
document.getElementById('signup-form').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const btn = e.target.querySelector('.btn-primary');
|
||||
const orig = btn.innerHTML;
|
||||
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="animation:spin 0.8s linear infinite"><path d="M21 12a9 9 0 1 1-6.2-8.55"/></svg> Konto wird angelegt…';
|
||||
btn.disabled = true;
|
||||
setTimeout(() => { btn.innerHTML = orig; btn.disabled = false; }, 1800);
|
||||
});
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = '@keyframes spin{to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(style);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,477 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Konto erstellen · CluPilot</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f7f7f9;
|
||||
--bg-2:#fbfbfd;
|
||||
--card:#ffffff;
|
||||
--card-hover:#f5f5f8;
|
||||
--surface:#f1f1f5;
|
||||
--surface-2:#ebebf0;
|
||||
--border:rgba(17,17,27,0.07);
|
||||
--border-strong:rgba(17,17,27,0.12);
|
||||
--border-focus:rgba(59,122,242,0.5);
|
||||
--fg:#18181b;
|
||||
--muted:#71717a;
|
||||
--muted-2:#a1a1ad;
|
||||
--accent:#3b7af2;
|
||||
--accent-2:#6b46ff;
|
||||
--accent-soft:rgba(59,122,242,0.10);
|
||||
--success:#16a368;
|
||||
--warning:#c87f10;
|
||||
--danger:#dc3a3a;
|
||||
--success-soft:rgba(22,163,104,0.10);
|
||||
--mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--fg);font-family:'Inter',system-ui,-apple-system,sans-serif;font-size:13.5px;line-height:1.5;-webkit-font-smoothing:antialiased;min-height:100vh}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
button{font-family:inherit;color:inherit;background:none;border:0;cursor:pointer}
|
||||
|
||||
body::before{
|
||||
content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
|
||||
background:
|
||||
radial-gradient(60% 50% at 12% 10%, rgba(59,122,242,0.08), transparent 65%),
|
||||
radial-gradient(50% 50% at 88% 100%, rgba(107,70,255,0.06), transparent 65%);
|
||||
}
|
||||
body::after{
|
||||
content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
|
||||
background-image:
|
||||
linear-gradient(rgba(17,17,27,0.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(17,17,27,0.025) 1px, transparent 1px);
|
||||
background-size:32px 32px;
|
||||
mask-image:radial-gradient(ellipse at center, #000 30%, transparent 75%);
|
||||
}
|
||||
|
||||
.page{position:relative;z-index:1;min-height:100vh;display:grid;grid-template-columns:minmax(440px,1fr) 1fr;gap:0}
|
||||
|
||||
/* ===== left: form ===== */
|
||||
.form-side{display:flex;flex-direction:column;padding:32px 48px;min-height:100vh}
|
||||
.top-row{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
||||
.brand{display:flex;align-items:center;gap:10px}
|
||||
.brand-mark{width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,#4f8ef7 0%,#7c5cff 100%);position:relative;display:grid;place-items:center;box-shadow:0 0 0 1px rgba(255,255,255,0.20) inset, 0 6px 16px rgba(79,142,247,0.32)}
|
||||
.brand-mark::before{content:"";width:11px;height:11px;border-radius:50%;background:rgba(255,255,255,0.95);box-shadow:inset 0 0 0 2px #4f8ef7}
|
||||
.brand-name{font-weight:600;font-size:15px;letter-spacing:-0.01em}
|
||||
.brand-name span{color:var(--muted);font-weight:500;margin-left:5px;font-size:11.5px}
|
||||
|
||||
.top-link{font-size:12.5px;color:var(--muted)}
|
||||
.top-link a{margin-left:6px;font-weight:500}
|
||||
|
||||
/* Stepper */
|
||||
.stepper{display:flex;align-items:center;gap:10px;margin-top:32px;font-family:var(--mono);font-size:11px;color:var(--muted-2)}
|
||||
.step{display:flex;align-items:center;gap:7px}
|
||||
.step-num{width:20px;height:20px;border-radius:50%;border:1px solid var(--border);display:grid;place-items:center;font-size:10.5px;color:var(--muted);background:var(--surface)}
|
||||
.step.active .step-num{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
|
||||
.step.done .step-num{border-color:var(--success);color:#ffffff;background:var(--success)}
|
||||
.step-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:0.06em;font-weight:500}
|
||||
.step.active .step-label{color:var(--fg)}
|
||||
.step-line{flex:1;height:1px;background:var(--border);min-width:14px;max-width:28px}
|
||||
|
||||
.form-wrap{margin:auto 0;display:flex;flex-direction:column;gap:22px;max-width:420px;width:100%;padding:32px 0}
|
||||
h1{margin:0;font-size:24px;font-weight:600;letter-spacing:-0.02em;line-height:1.2}
|
||||
.sub{margin:6px 0 0;font-size:13.5px;color:var(--muted)}
|
||||
.sub a{font-weight:500}
|
||||
|
||||
/* SSO row */
|
||||
.sso-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.sso-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:9px 12px;background:#fff;border:1px solid var(--border);border-radius:8px;color:var(--fg);font-size:12.5px;font-weight:500;transition:all .15s}
|
||||
.sso-btn:hover{background:var(--surface);border-color:var(--border-strong)}
|
||||
|
||||
.divider{display:flex;align-items:center;gap:12px;color:var(--muted-2);font-size:11px;font-family:var(--mono);text-transform:uppercase;letter-spacing:0.08em}
|
||||
.divider::before,.divider::after{content:"";flex:1;height:1px;background:var(--border)}
|
||||
|
||||
/* Inputs */
|
||||
form{display:flex;flex-direction:column;gap:14px}
|
||||
.field{display:flex;flex-direction:column;gap:6px}
|
||||
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.field-label{display:flex;align-items:center;justify-content:space-between;font-size:11.5px;color:var(--muted);font-weight:500}
|
||||
.field-label .hint{font-family:var(--mono);font-size:10.5px;color:var(--muted-2);font-weight:400}
|
||||
.input{display:flex;align-items:center;gap:10px;padding:0 12px;background:#fff;border:1px solid var(--border);border-radius:8px;transition:border-color .15s, background .15s, box-shadow .15s;height:40px}
|
||||
.input:hover{border-color:var(--border-strong)}
|
||||
.input:focus-within{border-color:var(--border-focus);background:#fff;box-shadow:0 0 0 3px rgba(59,122,242,0.10)}
|
||||
.input svg{color:var(--muted-2);flex:none}
|
||||
.input input{flex:1;background:none;border:0;outline:0;color:var(--fg);font-size:13.5px;font-family:inherit;min-width:0;height:100%}
|
||||
.input input::placeholder{color:var(--muted-2)}
|
||||
.input.with-suffix{padding-right:0}
|
||||
.input-suffix{height:100%;display:flex;align-items:center;padding:0 12px;border-left:1px solid var(--border);color:var(--muted);font-family:var(--mono);font-size:11.5px;background:var(--surface);border-top-right-radius:8px;border-bottom-right-radius:8px}
|
||||
.input .eye{color:var(--muted);padding:4px;border-radius:4px;display:grid;place-items:center;transition:color .15s,background .15s;margin-right:4px}
|
||||
.input .eye:hover{color:var(--fg);background:var(--surface)}
|
||||
.input-check{color:var(--success);margin-right:8px;display:none}
|
||||
.input.valid .input-check{display:inline-flex}
|
||||
|
||||
/* Slug preview */
|
||||
.slug-preview{font-family:var(--mono);font-size:11px;color:var(--muted);margin-top:2px}
|
||||
.slug-preview b{color:var(--accent);font-weight:500}
|
||||
|
||||
/* Password strength */
|
||||
.pw-meter{display:flex;gap:4px;margin-top:6px}
|
||||
.pw-meter span{flex:1;height:3px;border-radius:2px;background:var(--surface-2);transition:background .2s}
|
||||
.pw-meter.s1 span:nth-child(-n+1){background:var(--danger)}
|
||||
.pw-meter.s2 span:nth-child(-n+2){background:var(--warning)}
|
||||
.pw-meter.s3 span:nth-child(-n+3){background:var(--accent)}
|
||||
.pw-meter.s4 span{background:var(--success)}
|
||||
.pw-rules{display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:6px;font-family:var(--mono);font-size:10.5px;color:var(--muted-2)}
|
||||
.pw-rule{display:inline-flex;align-items:center;gap:5px}
|
||||
.pw-rule .bullet{width:5px;height:5px;border-radius:50%;background:var(--muted-2);transition:background .15s}
|
||||
.pw-rule.ok{color:var(--success)}
|
||||
.pw-rule.ok .bullet{background:var(--success)}
|
||||
|
||||
/* Checkbox + submit */
|
||||
.checkbox{display:flex;align-items:flex-start;gap:10px;font-size:12.5px;color:var(--muted);cursor:pointer;user-select:none;line-height:1.45}
|
||||
.checkbox input{display:none}
|
||||
.checkbox .box{width:15px;height:15px;border-radius:4px;border:1px solid var(--border-strong);background:#fff;display:grid;place-items:center;transition:all .15s;flex:none;margin-top:1px}
|
||||
.checkbox input:checked + .box{background:var(--accent);border-color:var(--accent)}
|
||||
.checkbox input:checked + .box svg{opacity:1;transform:scale(1)}
|
||||
.checkbox .box svg{opacity:0;transform:scale(0.6);transition:all .15s;color:#fff}
|
||||
.checkbox:hover{color:var(--fg)}
|
||||
|
||||
.btn-primary{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 16px;background:var(--accent);color:#fff;border-radius:8px;font-weight:500;font-size:13.5px;height:40px;transition:background .15s,transform .05s;box-shadow:0 1px 0 rgba(255,255,255,0.18) inset, 0 1px 3px rgba(59,122,242,0.30)}
|
||||
.btn-primary:hover{background:#2c6ce6}
|
||||
.btn-primary:active{transform:translateY(1px)}
|
||||
.btn-primary .arrow{transition:transform .15s}
|
||||
.btn-primary:hover .arrow{transform:translateX(2px)}
|
||||
|
||||
/* footer */
|
||||
.form-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--muted);font-size:11.5px;font-family:var(--mono);padding-top:12px;border-top:1px solid var(--border);flex-wrap:wrap}
|
||||
.form-footer .links{display:flex;gap:14px}
|
||||
.form-footer .links a{color:var(--muted)}
|
||||
.form-footer .links a:hover{color:var(--fg);text-decoration:none}
|
||||
.form-footer .lock{display:flex;align-items:center;gap:6px}
|
||||
|
||||
/* ===== right: benefits panel ===== */
|
||||
.live-side{position:relative;border-left:1px solid var(--border);background:linear-gradient(180deg,#fbfbfd 0%, #f3f3f7 100%);padding:36px 40px;overflow:hidden;display:flex;flex-direction:column;gap:22px;min-height:100vh}
|
||||
.live-side::before{
|
||||
content:"";position:absolute;inset:0;pointer-events:none;
|
||||
background:radial-gradient(70% 50% at 100% 0%, rgba(59,122,242,0.10), transparent 65%);
|
||||
}
|
||||
.live-side > *{position:relative}
|
||||
|
||||
.trial-card{background:#ffffff;border:1px solid var(--border);border-radius:12px;padding:18px 20px;position:relative;overflow:hidden;box-shadow:0 4px 16px rgba(17,17,27,0.05)}
|
||||
.trial-card::before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b7af2,#6b46ff)}
|
||||
.trial-row{display:flex;align-items:center;justify-content:space-between}
|
||||
.trial-tag{font-family:var(--mono);font-size:10.5px;color:var(--accent);background:var(--accent-soft);padding:3px 8px;border-radius:5px;border:1px solid rgba(59,122,242,0.28);font-weight:500;text-transform:uppercase;letter-spacing:0.05em}
|
||||
.trial-count{font-family:var(--mono);font-size:11px;color:var(--muted);display:inline-flex;align-items:center;gap:6px}
|
||||
.pulse-dot{width:6px;height:6px;border-radius:50%;background:var(--success);position:relative;flex:none}
|
||||
.pulse-dot::after{content:"";position:absolute;inset:-3px;border-radius:50%;background:currentColor;color:var(--success);opacity:0.4;animation:pulse 1.8s ease-out infinite}
|
||||
@keyframes pulse{0%{transform:scale(0.6);opacity:.5}100%{transform:scale(2.4);opacity:0}}
|
||||
|
||||
.trial-title{font-size:22px;font-weight:600;letter-spacing:-0.015em;line-height:1.25;margin-top:12px;max-width:420px}
|
||||
.trial-title em{font-style:normal;background:linear-gradient(90deg,#3b7af2,#6b46ff);-webkit-background-clip:text;background-clip:text;color:transparent;font-weight:700}
|
||||
.trial-sub{color:var(--muted);font-size:13px;margin-top:6px;max-width:440px}
|
||||
|
||||
.benefit-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:14px}
|
||||
.benefit{display:flex;gap:12px;align-items:flex-start}
|
||||
.benefit-ico{width:30px;height:30px;border-radius:8px;background:var(--accent-soft);border:1px solid rgba(59,122,242,0.25);color:var(--accent);display:grid;place-items:center;flex:none}
|
||||
.benefit-ico.success{background:var(--success-soft);border-color:rgba(22,163,104,0.25);color:var(--success)}
|
||||
.benefit-ico.purple{background:rgba(107,70,255,0.10);border-color:rgba(107,70,255,0.25);color:#6b46ff}
|
||||
.benefit-ico.warn{background:rgba(200,127,16,0.10);border-color:rgba(200,127,16,0.25);color:var(--warning)}
|
||||
.benefit-body{flex:1;min-width:0}
|
||||
.benefit-title{font-size:13.5px;font-weight:500;color:var(--fg)}
|
||||
.benefit-desc{font-size:12.5px;color:var(--muted);margin-top:2px;line-height:1.5}
|
||||
|
||||
/* Quote */
|
||||
.quote-card{margin-top:auto;background:#ffffff;border:1px solid var(--border);border-radius:12px;padding:18px 20px;box-shadow:0 4px 16px rgba(17,17,27,0.05)}
|
||||
.quote-text{font-size:13.5px;color:#3f3f47;line-height:1.55;font-weight:400}
|
||||
.quote-text::before{content:"„";color:var(--accent);font-size:24px;line-height:0;margin-right:4px;vertical-align:-6px;font-weight:600}
|
||||
.quote-text::after{content:""";color:var(--accent);font-weight:600}
|
||||
.quote-author{display:flex;align-items:center;gap:10px;margin-top:12px}
|
||||
.avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#16a368,#0c7a4e);display:grid;place-items:center;color:#ffffff;font-weight:600;font-size:12px;font-family:var(--mono)}
|
||||
.avatar.alt{background:linear-gradient(135deg,#6b46ff,#3b7af2);color:#fff}
|
||||
.author-meta{font-size:12px}
|
||||
.author-name{color:var(--fg);font-weight:500}
|
||||
.author-role{color:var(--muted);font-size:11px;font-family:var(--mono)}
|
||||
|
||||
/* Logo strip */
|
||||
.logos{display:flex;align-items:center;gap:20px;flex-wrap:wrap;opacity:0.75;font-family:var(--mono);font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:0.08em}
|
||||
.logo-pill{padding:5px 10px;border:1px solid var(--border);border-radius:5px;background:#ffffff}
|
||||
|
||||
/* responsive */
|
||||
@media (max-width:1080px){
|
||||
.page{grid-template-columns:1fr}
|
||||
.live-side{display:none}
|
||||
.form-side{padding:28px}
|
||||
}
|
||||
@media (max-width:520px){
|
||||
.form-side{padding:22px}
|
||||
.sso-row{grid-template-columns:1fr}
|
||||
.field-row{grid-template-columns:1fr}
|
||||
.form-footer{justify-content:flex-start}
|
||||
.stepper .step-label{display:none}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ============ LEFT: FORM ============ -->
|
||||
<section class="form-side">
|
||||
<div class="top-row">
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true"></div>
|
||||
<div class="brand-name">CluPilot<span>v2.4</span></div>
|
||||
</div>
|
||||
<div class="top-link">Schon Konto?<a href="#">Anmelden →</a></div>
|
||||
</div>
|
||||
|
||||
<div class="stepper" aria-label="Registrierungs-Schritte">
|
||||
<div class="step active">
|
||||
<div class="step-num">1</div>
|
||||
<div class="step-label">Konto</div>
|
||||
</div>
|
||||
<div class="step-line"></div>
|
||||
<div class="step">
|
||||
<div class="step-num">2</div>
|
||||
<div class="step-label">Workspace</div>
|
||||
</div>
|
||||
<div class="step-line"></div>
|
||||
<div class="step">
|
||||
<div class="step-num">3</div>
|
||||
<div class="step-label">Erster Server</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-wrap">
|
||||
<header>
|
||||
<h1>14 Tage kostenlos testen</h1>
|
||||
<p class="sub">Keine Kreditkarte. Volle Funktionen. Jederzeit kündbar.</p>
|
||||
</header>
|
||||
|
||||
<div class="sso-row">
|
||||
<button class="sso-btn" type="button">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.5 12.27c0-.79-.07-1.54-.2-2.27H12v4.51h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.32z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.99.66-2.25 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23z"/><path fill="#FBBC05" d="M5.84 14.10A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.44.34-2.10V7.06H2.18A11 11 0 0 0 1 12c0 1.77.42 3.45 1.18 4.94l3.66-2.84z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.07.56 4.21 1.64l3.15-3.15C17.45 2.10 14.96 1 12 1A11 11 0 0 0 2.18 7.06l3.66 2.84C6.71 7.30 9.14 5.38 12 5.38z"/></svg>
|
||||
Mit Google fortfahren
|
||||
</button>
|
||||
<button class="sso-btn" type="button">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.02c-3.2.7-3.87-1.36-3.87-1.36-.52-1.33-1.27-1.68-1.27-1.68-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.69 1.24 3.35.95.1-.74.4-1.24.73-1.53-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.28 1.18-3.09-.12-.29-.51-1.46.11-3.05 0 0 .97-.31 3.18 1.18.92-.26 1.91-.39 2.89-.39.98 0 1.97.13 2.89.39 2.20-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.12 3.05.74.81 1.18 1.83 1.18 3.09 0 4.42-2.69 5.40-5.25 5.68.41.36.78 1.06.78 2.14v3.18c0 .31.21.67.79.55C20.21 21.39 23.5 17.08 23.5 12c0-6.35-5.15-11.5-11.5-11.5z"/></svg>
|
||||
Mit GitHub fortfahren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="divider">oder mit E-Mail</div>
|
||||
|
||||
<form id="signup-form" autocomplete="on">
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label" for="firstname">Vorname</label>
|
||||
<div class="input">
|
||||
<input id="firstname" type="text" placeholder="Marie" autocomplete="given-name" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label" for="lastname">Nachname</label>
|
||||
<div class="input">
|
||||
<input id="lastname" type="text" placeholder="Weber" autocomplete="family-name" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label" for="email">Arbeits-E-Mail</label>
|
||||
<div class="input" id="email-wrap">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>
|
||||
<input id="email" type="email" placeholder="du@firma.de" autocomplete="email" required />
|
||||
<svg class="input-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12l5 5L20 7"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label" for="workspace">
|
||||
Workspace-Name
|
||||
<span class="hint">URL für dein Team</span>
|
||||
</label>
|
||||
<div class="input with-suffix">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg>
|
||||
<input id="workspace" type="text" placeholder="Acme Cluster" required />
|
||||
<span class="input-suffix" id="slug-suffix">.clupilot.io</span>
|
||||
</div>
|
||||
<div class="slug-preview">Erreichbar unter <b id="slug-preview">dein-team</b>.clupilot.io</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label" for="password">
|
||||
Passwort
|
||||
<span class="hint">min. 10 Zeichen</span>
|
||||
</label>
|
||||
<div class="input">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
|
||||
<input id="password" type="password" placeholder="••••••••••••" autocomplete="new-password" required />
|
||||
<button type="button" class="eye" id="toggle-eye" aria-label="Passwort anzeigen">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pw-meter" id="pw-meter"><span></span><span></span><span></span><span></span></div>
|
||||
<div class="pw-rules">
|
||||
<span class="pw-rule" data-rule="len"><span class="bullet"></span>10+ Zeichen</span>
|
||||
<span class="pw-rule" data-rule="upper"><span class="bullet"></span>Großbuchstabe</span>
|
||||
<span class="pw-rule" data-rule="num"><span class="bullet"></span>Zahl</span>
|
||||
<span class="pw-rule" data-rule="sym"><span class="bullet"></span>Sonderzeichen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="agree" required />
|
||||
<span class="box">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>
|
||||
</span>
|
||||
<span>Ich akzeptiere die <a href="#">AGB</a> und stimme der <a href="#">Datenschutzerklärung</a> zu. CluPilot speichert SSH-Keys verschlüsselt in der EU.</span>
|
||||
</label>
|
||||
|
||||
<button class="btn-primary" type="submit">
|
||||
Konto erstellen
|
||||
<svg class="arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<footer class="form-footer">
|
||||
<div class="lock">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
|
||||
TLS 1.3 · AES-256 · region eu-central-1
|
||||
</div>
|
||||
<div class="links">
|
||||
<a href="#">Datenschutz</a>
|
||||
<a href="#">AGB</a>
|
||||
<a href="#">Status</a>
|
||||
<a href="#">Hilfe</a>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<!-- ============ RIGHT: TRIAL / BENEFITS ============ -->
|
||||
<aside class="live-side" aria-hidden="true">
|
||||
|
||||
<div class="trial-card">
|
||||
<div class="trial-row">
|
||||
<span class="trial-tag">14 Tage Pro · gratis</span>
|
||||
<span class="trial-count"><span class="pulse-dot"></span> 47 Teams diese Woche</span>
|
||||
</div>
|
||||
<div class="trial-title">In <em>60 Sekunden</em> deinen ersten Server verbunden.</div>
|
||||
<div class="trial-sub">SSH-Schlüssel hochladen, Site importieren, fertig. Wir kümmern uns ums Monitoring, Backups und Updates — du um dein Business.</div>
|
||||
</div>
|
||||
|
||||
<ul class="benefit-list">
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">SSH-First Setup</div>
|
||||
<div class="benefit-desc">Verbinde Hetzner, Hostinger oder deinen eigenen Server in 2 Klicks — keine Agents, kein Custom-DNS.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico success">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">Stündliche Backups inklusive</div>
|
||||
<div class="benefit-desc">Verschlüsselt nach Frankfurt-S3, 30 Tage Retention, One-Click-Restore — auch für die WP-Datenbank.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico purple">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">Bulk-Updates ohne Drama</div>
|
||||
<div class="benefit-desc">WordPress, Plugins und Themes auf 100 Sites gleichzeitig — mit Staging-Diff und automatischem Rollback bei Fehler.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="benefit">
|
||||
<div class="benefit-ico warn">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/><path d="M12 8v5M12 16h.01"/></svg>
|
||||
</div>
|
||||
<div class="benefit-body">
|
||||
<div class="benefit-title">Brute-Force-Defense aktiv</div>
|
||||
<div class="benefit-desc">Geo-Fencing, Rate-Limits und WP-User-Lockout — schon im Trial dabei. Letzte 24h: 12.488 Versuche blockiert.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="quote-card">
|
||||
<div class="quote-text">Statt 18 SSH-Sessions in Terminal-Tabs habe ich jetzt eine Konsole. Mein Agency-Team hostet 87 Sites — und schläft endlich wieder durch.</div>
|
||||
<div class="quote-author">
|
||||
<div class="avatar">JK</div>
|
||||
<div class="author-meta">
|
||||
<div class="author-name">Julian Koch</div>
|
||||
<div class="author-role">CTO · Helio Digital · 87 Sites</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logos">
|
||||
<span>vertrauen uns</span>
|
||||
<span class="logo-pill">helio.</span>
|
||||
<span class="logo-pill">acme▲</span>
|
||||
<span class="logo-pill">opencrate</span>
|
||||
<span class="logo-pill">bergmann</span>
|
||||
<span class="logo-pill">+ 2.1k</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ---- workspace slug preview ---- */
|
||||
const ws = document.getElementById('workspace');
|
||||
const slug = document.getElementById('slug-preview');
|
||||
const slugify = s => s.toLowerCase().trim().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'').slice(0,32) || 'dein-team';
|
||||
ws.addEventListener('input', () => { slug.textContent = slugify(ws.value); });
|
||||
|
||||
/* ---- email valid checkmark ---- */
|
||||
const email = document.getElementById('email');
|
||||
const emailWrap = document.getElementById('email-wrap');
|
||||
email.addEventListener('input', () => {
|
||||
emailWrap.classList.toggle('valid', /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value));
|
||||
});
|
||||
|
||||
/* ---- pw strength + rules ---- */
|
||||
const pwd = document.getElementById('password');
|
||||
const meter = document.getElementById('pw-meter');
|
||||
const rules = document.querySelectorAll('.pw-rule');
|
||||
pwd.addEventListener('input', () => {
|
||||
const v = pwd.value;
|
||||
const tests = {
|
||||
len: v.length >= 10,
|
||||
upper: /[A-Z]/.test(v),
|
||||
num: /[0-9]/.test(v),
|
||||
sym: /[^A-Za-z0-9]/.test(v),
|
||||
};
|
||||
let score = 0;
|
||||
Object.keys(tests).forEach(k => {
|
||||
const ok = tests[k];
|
||||
const el = document.querySelector(`.pw-rule[data-rule="${k}"]`);
|
||||
el.classList.toggle('ok', ok);
|
||||
if(ok) score++;
|
||||
});
|
||||
meter.className = 'pw-meter' + (v.length ? ' s'+score : '');
|
||||
});
|
||||
|
||||
/* ---- eye toggle ---- */
|
||||
document.getElementById('toggle-eye').addEventListener('click', () => {
|
||||
pwd.type = pwd.type === 'password' ? 'text' : 'password';
|
||||
});
|
||||
|
||||
/* ---- submit (demo) ---- */
|
||||
document.getElementById('signup-form').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const btn = e.target.querySelector('.btn-primary');
|
||||
const orig = btn.innerHTML;
|
||||
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="animation:spin 0.8s linear infinite"><path d="M21 12a9 9 0 1 1-6.2-8.55"/></svg> Konto wird angelegt…';
|
||||
btn.disabled = true;
|
||||
setTimeout(() => { btn.innerHTML = orig; btn.disabled = false; }, 1800);
|
||||
});
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = '@keyframes spin{to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(style);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Cluster anlegen</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
<style>
|
||||
.wizard{display:grid;grid-template-columns:1fr 320px;gap:18px;align-items:start}
|
||||
.step-pane{display:none;animation:fade .25s ease}
|
||||
.step-pane.active{display:block}
|
||||
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
||||
.summary{position:sticky;top:84px;display:flex;flex-direction:column;gap:0}
|
||||
.sum-price{font-family:var(--mono);font-size:26px;font-weight:600;letter-spacing:-0.02em}
|
||||
.sum-price .unit{font-size:13px;color:var(--muted-2);font-weight:500}
|
||||
.slider-row{display:flex;align-items:center;gap:14px}
|
||||
.slider-row input[type=range]{flex:1;accent-color:var(--accent);height:5px}
|
||||
.node-val{font-family:var(--mono);font-weight:600;font-size:18px;min-width:24px;text-align:center}
|
||||
.wizard-foot{display:flex;align-items:center;justify-content:space-between;margin-top:6px}
|
||||
@media (max-width:1100px){.wizard{grid-template-columns:1fr}.summary{position:static}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand"><div class="brand-mark" aria-hidden="true"></div><div class="brand-name">CluPilot<span>v2.4</span></div></div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta"><div class="ws-name">Acme Cluster</div><div class="ws-plan">team · 14 user</div></div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg><span>Dashboard</span></a>
|
||||
<a class="nav-item active" href="Server.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg><span>Server</span><span class="count">6</span></a>
|
||||
<a class="nav-item" href="Sites.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg><span>Sites</span><span class="count">87</span></a>
|
||||
<a class="nav-item" href="Backups.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg><span>Backups</span></a>
|
||||
<a class="nav-item" href="Sicherheit.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg><span>Sicherheit</span><span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span></a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg><span>Team</span></a>
|
||||
<a class="nav-item" href="Einstellungen.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg><span>Einstellungen</span></a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row"><div class="plan-name">Team Plan</div><div class="plan-tag">PRO</div></div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats"><span>87 / 140 sites</span><span>62%</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main" data-screen-label="Cluster anlegen">
|
||||
<div class="topbar">
|
||||
<a class="icon-btn" href="Server.html" aria-label="Zurück">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
|
||||
</a>
|
||||
<div>
|
||||
<div class="page-title">Cluster anlegen</div>
|
||||
<div class="page-sub">neuer server-cluster · acme</div>
|
||||
</div>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="stepper" id="stepper">
|
||||
<div class="step active" data-step="0"><span class="num">1</span>Basis</div>
|
||||
<span class="step-line"></span>
|
||||
<div class="step" data-step="1"><span class="num">2</span>Region & Hardware</div>
|
||||
<span class="step-line"></span>
|
||||
<div class="step" data-step="2"><span class="num">3</span>WordPress</div>
|
||||
<span class="step-line"></span>
|
||||
<div class="step" data-step="3"><span class="num">4</span>Überprüfen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="wizard">
|
||||
<!-- ============ FORM ============ -->
|
||||
<div>
|
||||
<!-- STEP 1 -->
|
||||
<div class="step-pane active" data-pane="0">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Basis-Konfiguration</h3><span class="meta">schritt 1 / 4</span></div>
|
||||
<div class="card-body">
|
||||
<div class="field">
|
||||
<label>Cluster-Name</label>
|
||||
<input class="input" id="f-name" value="eu-prod-04" placeholder="z. B. eu-prod-04" />
|
||||
<span class="hint">Wird in Listen, SSH und im Audit-Log angezeigt.</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Slug / Hostname</label>
|
||||
<div class="input-group"><input id="f-slug" value="eu-prod-04" /><span class="suffix">.clupilot.net</span></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Umgebung</label>
|
||||
<div class="opt-grid cols-3" data-radio="env">
|
||||
<div class="opt selected" data-val="Produktion"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">Produktion</div><div class="opt-desc">live · monitoring</div></div></div>
|
||||
<div class="opt" data-val="Staging"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">Staging</div><div class="opt-desc">test · isoliert</div></div></div>
|
||||
<div class="opt" data-val="Entwicklung"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">Entwicklung</div><div class="opt-desc">sandbox</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Beschreibung <span class="hint" style="font-weight:400">(optional)</span></label>
|
||||
<textarea class="input" id="f-desc" placeholder="Wofür ist dieser Cluster gedacht?"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- STEP 2 -->
|
||||
<div class="step-pane" data-pane="1">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Region</h3><span class="meta">schritt 2 / 4</span></div>
|
||||
<div class="card-body">
|
||||
<div class="opt-grid cols-2" data-radio="region">
|
||||
<div class="opt selected" data-val="Frankfurt · de-fra"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">🇩🇪 Frankfurt</div><div class="opt-desc">de-fra · 4 ms</div></div></div>
|
||||
<div class="opt" data-val="Amsterdam · nl-ams"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">🇳🇱 Amsterdam</div><div class="opt-desc">nl-ams · 11 ms</div></div></div>
|
||||
<div class="opt" data-val="Helsinki · fi-hel"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">🇫🇮 Helsinki</div><div class="opt-desc">fi-hel · 24 ms</div></div></div>
|
||||
<div class="opt" data-val="Ashburn · us-east"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">🇺🇸 Ashburn</div><div class="opt-desc">us-east · 92 ms</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card" style="margin-top:18px">
|
||||
<div class="card-head"><h3>Server-Größe</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="opt-grid" data-radio="size" data-price>
|
||||
<div class="opt" data-val="Starter" data-amt="29"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">Starter</div><div class="opt-desc">2 vCPU · 8 GB · 160 GB NVMe · ~15 sites</div></div><div class="opt-price">29 €/mo</div></div>
|
||||
<div class="opt selected" data-val="Standard" data-amt="59"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">Standard <span class="pill accent" style="margin-left:4px">beliebt</span></div><div class="opt-desc">4 vCPU · 16 GB · 320 GB NVMe · ~40 sites</div></div><div class="opt-price">59 €/mo</div></div>
|
||||
<div class="opt" data-val="Performance" data-amt="119"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">Performance</div><div class="opt-desc">8 vCPU · 32 GB · 640 GB NVMe · ~90 sites</div></div><div class="opt-price">119 €/mo</div></div>
|
||||
<div class="opt" data-val="Dedicated" data-amt="249"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">Dedicated</div><div class="opt-desc">16 vCPU · 64 GB · 1,2 TB NVMe · ~200 sites</div></div><div class="opt-price">249 €/mo</div></div>
|
||||
</div>
|
||||
<div class="field" style="margin-top:18px;margin-bottom:0">
|
||||
<label>Anzahl Nodes</label>
|
||||
<div class="slider-row">
|
||||
<input type="range" id="f-nodes" min="1" max="6" value="1" />
|
||||
<span class="node-val" id="nodes-val">1</span>
|
||||
</div>
|
||||
<span class="hint">Ab 2 Nodes wird automatisch ein Load-Balancer & Failover eingerichtet.</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- STEP 3 -->
|
||||
<div class="step-pane" data-pane="2">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>WordPress-Voreinstellungen</h3><span class="meta">schritt 3 / 4</span></div>
|
||||
<div class="card-body">
|
||||
<div class="grid-2">
|
||||
<div class="field"><label>PHP-Version</label>
|
||||
<div class="opt-grid cols-3" data-radio="php">
|
||||
<div class="opt" data-val="8.1"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">8.1</div></div></div>
|
||||
<div class="opt selected" data-val="8.2"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">8.2</div></div></div>
|
||||
<div class="opt" data-val="8.3"><span class="opt-radio"></span><div class="opt-main"><div class="opt-title">8.3</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field"><label>WordPress-Version</label><input class="input input-mono" value="6.5.2 (aktuell)" readonly /></div>
|
||||
</div>
|
||||
<div style="margin-top:6px">
|
||||
<div class="toggle-row" data-toggle="autoupdate"><div class="tr-body"><div class="tr-title">Automatische Updates</div><div class="tr-desc">core- & sichere plugin-updates</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row" data-toggle="ssl"><div class="tr-body"><div class="tr-title">SSL automatisch (Let's Encrypt)</div><div class="tr-desc">zertifikate ausstellen & erneuern</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row" data-toggle="cdn"><div class="tr-body"><div class="tr-title">Edge-CDN & Cache</div><div class="tr-desc">globaler cache für statische inhalte</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row" data-toggle="backup"><div class="tr-body"><div class="tr-title">Tägliche Backups</div><div class="tr-desc">03:00 utc · 30 tage aufbewahrung</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row" data-toggle="waf"><div class="tr-body"><div class="tr-title">WAF & Brute-Force-Schutz</div><div class="tr-desc">managed firewall-regelsatz</div></div><div class="toggle on"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- STEP 4 -->
|
||||
<div class="step-pane" data-pane="3">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12l5 5L20 7"/></svg>Überprüfen & anlegen</h3><span class="meta">schritt 4 / 4</span></div>
|
||||
<div class="card-body">
|
||||
<div class="kv"><span class="k">Cluster-Name</span><span class="v" data-review="name">eu-prod-04</span></div>
|
||||
<div class="kv"><span class="k">Hostname</span><span class="v" data-review="host">eu-prod-04.clupilot.net</span></div>
|
||||
<div class="kv"><span class="k">Umgebung</span><span class="v" data-review="env">Produktion</span></div>
|
||||
<div class="kv"><span class="k">Region</span><span class="v" data-review="region">Frankfurt · de-fra</span></div>
|
||||
<div class="kv"><span class="k">Server-Größe</span><span class="v" data-review="size">Standard</span></div>
|
||||
<div class="kv"><span class="k">Nodes</span><span class="v" data-review="nodes">1</span></div>
|
||||
<div class="kv"><span class="k">PHP / WordPress</span><span class="v" data-review="stack">8.2 · WP 6.5.2</span></div>
|
||||
<div class="kv"><span class="k">Funktionen</span><span class="v" data-review="features">5 aktiv</span></div>
|
||||
</div>
|
||||
<div class="card-body" style="border-top:1px solid var(--hairline);display:flex;align-items:center;gap:12px">
|
||||
<div class="feed-ico accent" style="width:34px;height:34px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 8v5M12 16h.01"/><circle cx="12" cy="12" r="9"/></svg></div>
|
||||
<div class="tr-desc" style="flex:1">Die Bereitstellung dauert i. d. R. <b>2–4 Minuten</b>. Du wirst benachrichtigt, sobald der Cluster bereit ist.</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- nav buttons -->
|
||||
<div class="wizard-foot">
|
||||
<button class="btn-ghost" id="btn-back" style="visibility:hidden">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M15 18l-6-6 6-6"/></svg>Zurück
|
||||
</button>
|
||||
<button class="btn-primary lg" id="btn-next">
|
||||
Weiter
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M9 6l6 6-6 6"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ SUMMARY ============ -->
|
||||
<section class="card summary">
|
||||
<div class="card-head"><h3>Zusammenfassung</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="kv"><span class="k">Name</span><span class="v" id="s-name">eu-prod-04</span></div>
|
||||
<div class="kv"><span class="k">Umgebung</span><span class="v" id="s-env">Produktion</span></div>
|
||||
<div class="kv"><span class="k">Region</span><span class="v" id="s-region">Frankfurt</span></div>
|
||||
<div class="kv"><span class="k">Größe</span><span class="v" id="s-size">Standard</span></div>
|
||||
<div class="kv"><span class="k">Nodes</span><span class="v" id="s-nodes">1×</span></div>
|
||||
</div>
|
||||
<div class="card-body" style="border-top:1px solid var(--hairline)">
|
||||
<div style="display:flex;align-items:flex-end;justify-content:space-between">
|
||||
<div>
|
||||
<div class="tr-desc">Geschätzt / Monat</div>
|
||||
<div class="sum-price"><span id="s-price">59</span> €<span class="unit">/mo</span></div>
|
||||
</div>
|
||||
<span class="pill success">inkl. backup</span>
|
||||
</div>
|
||||
<div class="hint" style="margin-top:10px">Abgerechnet pro Stunde · jederzeit kündbar. Preis exkl. MwSt.</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="foot">CluPilot · bereitstellung über terraform · region eu-central-1</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const state = {
|
||||
step:0, total:4,
|
||||
name:'eu-prod-04', env:'Produktion', region:'Frankfurt · de-fra',
|
||||
size:'Standard', amt:59, nodes:1, php:'8.2',
|
||||
features:{autoupdate:true,ssl:true,cdn:true,backup:true,waf:true},
|
||||
};
|
||||
|
||||
/* ---- radio option groups ---- */
|
||||
document.querySelectorAll('[data-radio]').forEach(group=>{
|
||||
group.querySelectorAll('.opt').forEach(opt=>{
|
||||
opt.addEventListener('click',()=>{
|
||||
group.querySelectorAll('.opt').forEach(o=>o.classList.remove('selected'));
|
||||
opt.classList.add('selected');
|
||||
const key = group.dataset.radio;
|
||||
const val = opt.dataset.val;
|
||||
if(key==='env') state.env = val;
|
||||
if(key==='region') state.region = val;
|
||||
if(key==='php') state.php = val;
|
||||
if(key==='size'){ state.size = val; state.amt = +opt.dataset.amt; }
|
||||
render();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* ---- toggles ---- */
|
||||
document.querySelectorAll('[data-toggle]').forEach(row=>{
|
||||
const t = row.querySelector('.toggle');
|
||||
row.addEventListener('click',()=>{
|
||||
t.classList.toggle('on');
|
||||
state.features[row.dataset.toggle] = t.classList.contains('on');
|
||||
render();
|
||||
});
|
||||
});
|
||||
|
||||
/* ---- inputs ---- */
|
||||
const nameEl = document.getElementById('f-name');
|
||||
const slugEl = document.getElementById('f-slug');
|
||||
nameEl.addEventListener('input',()=>{ state.name = nameEl.value || '—'; render(); });
|
||||
const nodesEl = document.getElementById('f-nodes');
|
||||
nodesEl.addEventListener('input',()=>{ state.nodes = +nodesEl.value; render(); });
|
||||
|
||||
/* ---- stepper ---- */
|
||||
const panes = [...document.querySelectorAll('.step-pane')];
|
||||
const steps = [...document.querySelectorAll('#stepper .step')];
|
||||
const btnNext = document.getElementById('btn-next');
|
||||
const btnBack = document.getElementById('btn-back');
|
||||
|
||||
function gotoStep(n){
|
||||
state.step = Math.max(0, Math.min(state.total-1, n));
|
||||
panes.forEach((p,i)=>p.classList.toggle('active', i===state.step));
|
||||
steps.forEach((s,i)=>{
|
||||
s.classList.toggle('active', i===state.step);
|
||||
s.classList.toggle('done', i<state.step);
|
||||
});
|
||||
btnBack.style.visibility = state.step===0 ? 'hidden':'visible';
|
||||
if(state.step===state.total-1){
|
||||
btnNext.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M5 12l5 5L20 7"/></svg> Cluster anlegen';
|
||||
}else{
|
||||
btnNext.innerHTML = 'Weiter <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M9 6l6 6-6 6"/></svg>';
|
||||
}
|
||||
render();
|
||||
}
|
||||
btnNext.addEventListener('click',()=>{
|
||||
if(state.step===state.total-1){
|
||||
btnNext.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" style="animation:spin 1s linear infinite"><path d="M21 12a9 9 0 1 1-6.2-8.5"/></svg> Wird angelegt…';
|
||||
btnNext.disabled = true;
|
||||
setTimeout(()=>{ window.location.href = 'Server.html'; }, 1400);
|
||||
return;
|
||||
}
|
||||
gotoStep(state.step+1);
|
||||
});
|
||||
btnBack.addEventListener('click',()=>gotoStep(state.step-1));
|
||||
steps.forEach(s=>s.addEventListener('click',()=>gotoStep(+s.dataset.step)));
|
||||
|
||||
/* ---- render derived values ---- */
|
||||
function render(){
|
||||
slugify();
|
||||
const lb = state.nodes>1 ? state.nodes*state.amt + 9 : state.amt;
|
||||
// summary card
|
||||
document.getElementById('s-name').textContent = state.name;
|
||||
document.getElementById('s-env').textContent = state.env;
|
||||
document.getElementById('s-region').textContent = state.region.split(' · ')[0];
|
||||
document.getElementById('s-size').textContent = state.size;
|
||||
document.getElementById('s-nodes').textContent = state.nodes+'×';
|
||||
document.getElementById('s-price').textContent = lb;
|
||||
document.getElementById('nodes-val').textContent = state.nodes;
|
||||
// review step
|
||||
const feats = Object.values(state.features).filter(Boolean).length;
|
||||
const set=(k,v)=>{const el=document.querySelector(`[data-review="${k}"]`); if(el) el.textContent=v;};
|
||||
set('name', state.name);
|
||||
set('host', (slugEl.value||state.name)+'.clupilot.net');
|
||||
set('env', state.env);
|
||||
set('region', state.region);
|
||||
set('size', state.size+' · '+state.amt+' €/mo');
|
||||
set('nodes', state.nodes + (state.nodes>1?' (+ load-balancer)':''));
|
||||
set('stack', state.php+' · WP 6.5.2');
|
||||
set('features', feats+' aktiv');
|
||||
}
|
||||
function slugify(){
|
||||
if(document.activeElement!==slugEl){
|
||||
slugEl.value = state.name.toLowerCase().replace(/[^a-z0-9-]+/g,'-').replace(/^-+|-+$/g,'');
|
||||
}
|
||||
}
|
||||
const style=document.createElement('style');
|
||||
style.textContent='@keyframes spin{to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(style);
|
||||
gotoStep(0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Einstellungen</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
<style>
|
||||
.settings{display:grid;grid-template-columns:212px 1fr;gap:18px;align-items:start}
|
||||
.set-nav{display:flex;flex-direction:column;gap:2px;padding:10px;position:sticky;top:84px}
|
||||
.set-nav a{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:var(--radius-xs);color:#3a3a47;font-size:12.5px;font-weight:500;cursor:pointer;transition:background .15s,color .15s}
|
||||
.set-nav a:hover{background:rgba(255,255,255,0.55);color:var(--fg)}
|
||||
.set-nav a.active{color:var(--fg);background:linear-gradient(180deg,rgba(255,255,255,0.92),rgba(255,255,255,0.66));box-shadow:0 4px 14px -6px rgba(40,52,92,0.3), inset 0 1px 0 rgba(255,255,255,0.9)}
|
||||
.set-nav a .ico{width:16px;height:16px;color:var(--muted)}
|
||||
.set-nav a.active .ico{color:var(--accent)}
|
||||
.set-col{display:flex;flex-direction:column;gap:18px;min-width:0}
|
||||
.form-actions{display:flex;justify-content:flex-end;gap:10px;padding:14px 18px;border-top:1px solid var(--hairline)}
|
||||
@media (max-width:920px){.settings{grid-template-columns:1fr}.set-nav{position:static;flex-direction:row;flex-wrap:wrap}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand"><div class="brand-mark" aria-hidden="true"></div><div class="brand-name">CluPilot<span>v2.4</span></div></div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta"><div class="ws-name">Acme Cluster</div><div class="ws-plan">team · 14 user</div></div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg><span>Dashboard</span></a>
|
||||
<a class="nav-item" href="Server.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg><span>Server</span><span class="count">6</span></a>
|
||||
<a class="nav-item" href="Sites.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg><span>Sites</span><span class="count">87</span></a>
|
||||
<a class="nav-item" href="Backups.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg><span>Backups</span></a>
|
||||
<a class="nav-item" href="Sicherheit.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg><span>Sicherheit</span><span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span></a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg><span>Team</span></a>
|
||||
<a class="nav-item active" href="Einstellungen.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg><span>Einstellungen</span></a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row"><div class="plan-name">Team Plan</div><div class="plan-tag">PRO</div></div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats"><span>87 / 140 sites</span><span>62%</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main" data-screen-label="Einstellungen">
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<div class="page-title">Einstellungen</div>
|
||||
<div class="page-sub">workspace · acme cluster</div>
|
||||
</div>
|
||||
<div class="top-spacer"></div>
|
||||
<button class="btn-ghost">Verwerfen</button>
|
||||
<button class="btn-primary"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M5 12l5 5L20 7"/></svg>Speichern</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="settings">
|
||||
<!-- sub nav -->
|
||||
<aside class="card set-nav">
|
||||
<a class="active"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18"/></svg>Workspace</a>
|
||||
<a><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a6 6 0 0 1 12 0v1"/></svg>Profil</a>
|
||||
<a><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg>Benachrichtigungen</a>
|
||||
<a><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>Abrechnung</a>
|
||||
<a><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 17l5-5-5-5M13 19h7"/></svg>API & Tokens</a>
|
||||
<a><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>Regionen</a>
|
||||
</aside>
|
||||
|
||||
<!-- content -->
|
||||
<div class="set-col">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Workspace-Details</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="grid-2">
|
||||
<div class="field"><label>Workspace-Name</label><input class="input" value="Acme Cluster" /></div>
|
||||
<div class="field"><label>Workspace-Slug</label>
|
||||
<div class="input-group"><span class="suffix" style="border-left:0;border-right:1px solid var(--hairline)">clupilot.io/</span><input value="acme-cluster" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field"><label>Beschreibung</label><textarea class="input" placeholder="Wofür wird dieser Workspace genutzt?">Verwaltung aller WordPress-Kundenprojekte der Acme GmbH.</textarea></div>
|
||||
<div class="grid-2">
|
||||
<div class="field"><label>Standard-Region</label>
|
||||
<input class="input" value="eu-central-1 · Frankfurt" /></div>
|
||||
<div class="field"><label>Zeitzone</label>
|
||||
<input class="input" value="Europe/Berlin (UTC+1)" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-ghost">Verwerfen</button><button class="btn-primary">Speichern</button></div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Standard-Verhalten</h3><span class="meta">gilt für neue sites</span></div>
|
||||
<div class="card-body" style="padding-top:4px">
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Automatische WordPress-Core-Updates</div><div class="tr-desc">minor-versionen automatisch einspielen</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Plugin-Updates automatisch</div><div class="tr-desc">nur als sicher markierte plugins</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Staging vor Deploy erzwingen</div><div class="tr-desc">änderungen erst auf staging testen</div></div><div class="toggle"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">CDN für neue Sites aktivieren</div><div class="tr-desc">global edge cache standardmäßig an</div></div><div class="toggle on"></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>Abrechnung</h3><span class="meta">nächste rechnung 01.06.2026</span></div>
|
||||
<div class="card-body">
|
||||
<div class="kv"><span class="k">Aktueller Plan</span><span class="v">Team · PRO</span></div>
|
||||
<div class="kv"><span class="k">Sites-Kontingent</span><span class="v">87 / 140</span></div>
|
||||
<div class="kv"><span class="k">Monatlich</span><span class="v">249,00 €</span></div>
|
||||
<div class="kv"><span class="k">Zahlungsmethode</span><span class="v">VISA · **** 4072</span></div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-ghost">Rechnungen</button><button class="btn-primary">Plan ändern</button></div>
|
||||
</section>
|
||||
|
||||
<section class="card" style="border-color:rgba(219,59,59,0.3)">
|
||||
<div class="card-head"><h3 style="color:var(--danger)"><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--danger)" stroke-width="2"><path d="M12 9v4M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg>Gefahrenzone</h3></div>
|
||||
<div class="card-body" style="display:flex;align-items:center;justify-content:space-between;gap:14px">
|
||||
<div><div style="font-weight:600;font-size:12.5px">Workspace löschen</div><div class="tr-desc">Alle Sites, Backups und Daten werden unwiderruflich entfernt.</div></div>
|
||||
<button class="btn-ghost" style="color:var(--danger);border-color:rgba(219,59,59,0.35)">Löschen</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="foot">CluPilot · änderungen werden im audit-log protokolliert</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.toggle').forEach(t=>t.addEventListener('click',()=>t.classList.toggle('on')));
|
||||
document.querySelectorAll('.set-nav a').forEach(a=>a.addEventListener('click',()=>{
|
||||
document.querySelectorAll('.set-nav a').forEach(x=>x.classList.remove('active'));
|
||||
a.classList.add('active');
|
||||
}));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Server</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<!-- ============ SIDEBAR ============ -->
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true"></div>
|
||||
<div class="brand-name">CluPilot<span>v2.4</span></div>
|
||||
</div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta">
|
||||
<div class="ws-name">Acme Cluster</div>
|
||||
<div class="ws-plan">team · 14 user</div>
|
||||
</div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a class="nav-item active" href="Server.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg>
|
||||
<span>Server</span>
|
||||
<span class="count">6</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Sites.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
|
||||
<span>Sites</span>
|
||||
<span class="count">87</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Backups.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg>
|
||||
<span>Backups</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Sicherheit.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg>
|
||||
<span>Sicherheit</span>
|
||||
<span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span>
|
||||
</a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg>
|
||||
<span>Team</span>
|
||||
</a>
|
||||
<a class="nav-item" href="Einstellungen.html">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>
|
||||
<span>Einstellungen</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row">
|
||||
<div class="plan-name">Team Plan</div>
|
||||
<div class="plan-tag">PRO</div>
|
||||
</div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats">
|
||||
<span>87 / 140 sites</span>
|
||||
<span>62%</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ============ MAIN ============ -->
|
||||
<main class="main" data-screen-label="Server">
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<div class="page-title">Server</div>
|
||||
<div class="page-sub">6 nodes · 3 regionen</div>
|
||||
</div>
|
||||
<span class="status-chip"><span class="pulse-dot"></span> 5 aktiv · 1 wartung</span>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input placeholder="Server suchen…" />
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
<a class="btn-primary" href="Cluster anlegen.html">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6"><path d="M12 5v14M5 12h14"/></svg>
|
||||
Cluster anlegen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- metrics -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">Aktive Nodes</div>
|
||||
<div class="m-ico success"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/></svg></div>
|
||||
</div>
|
||||
<div class="metric-value">5<span class="sub">/ 6</span></div>
|
||||
<div class="metric-delta"><span class="chg up">99,98%</span> uptime · 30 d</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">Ø CPU-Last</div>
|
||||
<div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9zM2 9h2M2 15h2M20 9h2M20 15h2M9 2v2M15 2v2M9 20v2M15 20v2"/></svg></div>
|
||||
</div>
|
||||
<div class="metric-value">38<span class="sub">%</span></div>
|
||||
<div class="metric-delta"><span class="chg up">↓ 4,1%</span> vs. gestern</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">RAM gesamt</div>
|
||||
<div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="10" rx="2"/><path d="M7 7v10M11 7v10M15 7v10"/></svg></div>
|
||||
</div>
|
||||
<div class="metric-value">112<span class="sub">/ 192 GB</span></div>
|
||||
<div class="metric-delta"><span class="chg">58%</span> belegt</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top">
|
||||
<div class="metric-label">Wartung</div>
|
||||
<div class="m-ico warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a4 4 0 0 0-5.4 5.4L3 18v3h3l6.3-6.3a4 4 0 0 0 5.4-5.4l-2.6 2.6-2-2 2.6-2.6z"/></svg></div>
|
||||
</div>
|
||||
<div class="metric-value">1</div>
|
||||
<div class="metric-delta"><span class="chg warn">eu-staging</span> · kernel-update</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- toolbar -->
|
||||
<div class="section-head">
|
||||
<h2>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/></svg>
|
||||
Alle Server
|
||||
</h2>
|
||||
<div class="toolbar">
|
||||
<div class="seg">
|
||||
<button class="active">Alle</button>
|
||||
<button>Produktion</button>
|
||||
<button>Edge</button>
|
||||
<button>Staging</button>
|
||||
</div>
|
||||
<button class="chip-filter">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 5h18M6 12h12M10 19h4"/></svg>
|
||||
Region
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- server cards -->
|
||||
<div class="grid-3" id="srv-grid"></div>
|
||||
|
||||
<div class="foot">CluPilot · region eu-central-1 · uptime 99,984%</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const servers = [
|
||||
{name:'eu-prod-01', region:'Frankfurt · de-fra', flag:'🇩🇪', specs:'8 vCPU · 32 GB · 640 GB NVMe', sites:24, status:'ok', cpu:42, ram:61, disk:48, ip:'94.130.12.4'},
|
||||
{name:'eu-prod-02', region:'Frankfurt · de-fra', flag:'🇩🇪', specs:'8 vCPU · 32 GB · 640 GB NVMe', sites:19, status:'ok', cpu:31, ram:54, disk:55, ip:'94.130.12.9'},
|
||||
{name:'eu-edge-amsterdam', region:'Amsterdam · nl-ams', flag:'🇳🇱', specs:'4 vCPU · 16 GB · 320 GB NVMe', sites:14, status:'ok', cpu:58, ram:47, disk:39, ip:'37.97.8.22'},
|
||||
{name:'us-east-prod-01', region:'Ashburn · us-east', flag:'🇺🇸', specs:'8 vCPU · 32 GB · 640 GB NVMe', sites:21, status:'warn', cpu:81, ram:73, disk:62, ip:'162.55.4.18'},
|
||||
{name:'eu-staging', region:'Nürnberg · de-nbg', flag:'🇩🇪', specs:'4 vCPU · 16 GB · 240 GB NVMe', sites:6, status:'maint', cpu:12, ram:28, disk:33, ip:'88.99.7.41'},
|
||||
{name:'eu-prod-03', region:'Helsinki · fi-hel', flag:'🇫🇮', specs:'8 vCPU · 32 GB · 640 GB NVMe', sites:3, status:'ok', cpu:19, ram:24, disk:21, ip:'95.216.3.7'},
|
||||
];
|
||||
const statusMap = {
|
||||
ok: {cls:'success', dot:'ok', label:'Online'},
|
||||
warn: {cls:'warning', dot:'warn', label:'Hohe Last'},
|
||||
maint: {cls:'', dot:'warn', label:'Wartung'},
|
||||
};
|
||||
function lvl(v){ return v>=80?'bad':v>=65?'warn':''; }
|
||||
const grid = document.getElementById('srv-grid');
|
||||
servers.forEach(s=>{
|
||||
const st = statusMap[s.status];
|
||||
grid.insertAdjacentHTML('beforeend',`
|
||||
<div class="srv-card">
|
||||
<div class="srv-head">
|
||||
<div class="srv-flag">${s.flag}</div>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div class="srv-name">${s.name}</div>
|
||||
<div class="srv-region">${s.region}</div>
|
||||
</div>
|
||||
<span class="pill ${st.cls}"><span class="health-dot ${st.dot}" style="box-shadow:none"></span>${st.label}</span>
|
||||
</div>
|
||||
<div class="srv-spec">
|
||||
<span><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/></svg>${s.specs}</span>
|
||||
</div>
|
||||
<div class="srv-spec" style="margin-top:6px">
|
||||
<span>${s.sites} sites</span>
|
||||
<span>· ${s.ip}</span>
|
||||
</div>
|
||||
<div class="gauges">
|
||||
<div class="gauge-row"><span class="lbl">CPU</span><span class="gauge-track"><span class="gauge-fill ${lvl(s.cpu)}" style="width:${s.cpu}%"></span></span><span class="val">${s.cpu}%</span></div>
|
||||
<div class="gauge-row"><span class="lbl">RAM</span><span class="gauge-track"><span class="gauge-fill ${lvl(s.ram)}" style="width:${s.ram}%"></span></span><span class="val">${s.ram}%</span></div>
|
||||
<div class="gauge-row"><span class="lbl">Disk</span><span class="gauge-track"><span class="gauge-fill ${lvl(s.disk)}" style="width:${s.disk}%"></span></span><span class="val">${s.disk}%</span></div>
|
||||
</div>
|
||||
</div>`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Sicherheit</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand"><div class="brand-mark" aria-hidden="true"></div><div class="brand-name">CluPilot<span>v2.4</span></div></div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta"><div class="ws-name">Acme Cluster</div><div class="ws-plan">team · 14 user</div></div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg><span>Dashboard</span></a>
|
||||
<a class="nav-item" href="Server.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg><span>Server</span><span class="count">6</span></a>
|
||||
<a class="nav-item" href="Sites.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg><span>Sites</span><span class="count">87</span></a>
|
||||
<a class="nav-item" href="Backups.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg><span>Backups</span></a>
|
||||
<a class="nav-item active" href="Sicherheit.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg><span>Sicherheit</span><span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span></a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg><span>Team</span></a>
|
||||
<a class="nav-item" href="Einstellungen.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg><span>Einstellungen</span></a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row"><div class="plan-name">Team Plan</div><div class="plan-tag">PRO</div></div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats"><span>87 / 140 sites</span><span>62%</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main" data-screen-label="Sicherheit">
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<div class="page-title">Sicherheit</div>
|
||||
<div class="page-sub">waf aktiv · 3 hinweise</div>
|
||||
</div>
|
||||
<span class="status-chip warn"><span class="pulse-dot"></span> 3 offene hinweise</span>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input placeholder="Ereignis suchen…" />
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
<button class="btn-primary">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/><path d="M9 12l2 2 4-4"/></svg>
|
||||
Scan starten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Angriffe blockiert</div><div class="m-ico danger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/><path d="M12 8v5M12 16h.01"/></svg></div></div>
|
||||
<div class="metric-value">128</div>
|
||||
<div class="metric-delta"><span class="chg down">↑ 23%</span> letzte 24 h</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Firewall-Regeln</div><div class="m-ico success"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/><path d="M9 12l2 2 4-4"/></svg></div></div>
|
||||
<div class="metric-value">342</div>
|
||||
<div class="metric-delta"><span class="chg up">aktiv</span> · managed ruleset</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">SSL-Zertifikate</div><div class="m-ico warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg></div></div>
|
||||
<div class="metric-value">85<span class="sub">/ 87</span></div>
|
||||
<div class="metric-delta"><span class="chg warn">2 laufen ab</span> in < 14 t</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">2FA-Abdeckung</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></div></div>
|
||||
<div class="metric-value">79<span class="sub">%</span></div>
|
||||
<div class="metric-delta"><span class="chg warn">3 user</span> ohne 2FA</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- open advisories -->
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg>Offene Hinweise <span class="pill warning" style="margin-left:4px">3</span></h3>
|
||||
<button class="ghost-btn">Alle erledigen</button>
|
||||
</div>
|
||||
<div id="adv-body"></div>
|
||||
</section>
|
||||
|
||||
<div class="row2">
|
||||
<!-- threat feed -->
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>Bedrohungs-Feed <span class="live-tag" style="margin-left:2px"><span class="pulse-dot" style="color:var(--success)"></span>live</span></h3>
|
||||
<span class="meta">24 h</span>
|
||||
</div>
|
||||
<div class="feed" id="threat-feed"></div>
|
||||
</section>
|
||||
|
||||
<!-- SSL certs -->
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>SSL-Zertifikate</h3><span class="meta">auto-renew</span></div>
|
||||
<div class="scroll-x">
|
||||
<table class="table">
|
||||
<thead><tr><th>Domain</th><th>Aussteller</th><th>Läuft ab</th></tr></thead>
|
||||
<tbody id="ssl-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="foot">CluPilot · waf managed ruleset v2026.05 · ddos-schutz aktiv</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* advisories */
|
||||
const advisories = [
|
||||
{sev:'danger', title:'Kritisches Plugin-CVE: WP File Manager 7.0', sites:'classic.tonhoff-bau.de', cve:'CVE-2026-1184', action:'Patchen'},
|
||||
{sev:'warning', title:'SSL-Zertifikat läuft in 9 Tagen ab', sites:'praxis-dr-schwarz.de', cve:'auto-renew fehlgeschlagen', action:'Erneuern'},
|
||||
{sev:'warning', title:'3 Team-Mitglieder ohne 2-Faktor-Auth', sites:'workspace · Acme Cluster', cve:'richtlinie', action:'Erinnern'},
|
||||
];
|
||||
const advBody = document.getElementById('adv-body');
|
||||
advisories.forEach((a,i)=>{
|
||||
const last = i===advisories.length-1;
|
||||
advBody.insertAdjacentHTML('beforeend',`
|
||||
<div style="display:flex;align-items:center;gap:14px;padding:14px 18px;${last?'':'border-bottom:1px solid var(--hairline)'}">
|
||||
<div class="feed-ico ${a.sev}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 9v4M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg></div>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-weight:600;font-size:12.5px">${a.title}</div>
|
||||
<div class="feed-meta">${a.sites} · ${a.cve}</div>
|
||||
</div>
|
||||
<button class="btn-primary" style="padding:7px 13px;font-size:12px">${a.action}</button>
|
||||
</div>`);
|
||||
});
|
||||
|
||||
/* threat feed */
|
||||
const threats = [
|
||||
{ic:'danger', t:'<b>17 Login-Versuche</b> blockiert · <span class="target">classic.tonhoff-bau.de</span>', m:'vor 1 min · 185.220.* (TOR)'},
|
||||
{ic:'danger', t:'SQL-Injection geblockt auf <span class="target">/wp-json</span>', m:'vor 6 min · shop.bergmann-coffee.com'},
|
||||
{ic:'warning', t:'Rate-Limit ausgelöst · <span class="target">xmlrpc.php</span>', m:'vor 14 min · 42 req/s'},
|
||||
{ic:'success', t:'WAF-Regelsatz aktualisiert · <b>v2026.05</b>', m:'vor 38 min · 12 neue Signaturen'},
|
||||
{ic:'danger', t:'Bekannter Bot-Range gesperrt · <span class="target">/8 CIDR</span>', m:'vor 52 min · 1.240 IPs'},
|
||||
{ic:'accent', t:'Malware-Scan abgeschlossen · <b>0 Funde</b>', m:'vor 1 std · 87 sites'},
|
||||
{ic:'success', t:'2FA aktiviert für <span class="target">m.weber</span>', m:'vor 2 std · TOTP'},
|
||||
];
|
||||
const tf = document.getElementById('threat-feed');
|
||||
threats.forEach(f=>{
|
||||
tf.insertAdjacentHTML('beforeend',`
|
||||
<div class="feed-row">
|
||||
<div class="feed-ico ${f.ic}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg></div>
|
||||
<div class="feed-body"><div>${f.t}</div><div class="feed-meta">${f.m}</div></div>
|
||||
</div>`);
|
||||
});
|
||||
|
||||
/* ssl */
|
||||
const certs = [
|
||||
{dom:'*.kunde-mueller.de', iss:"Let's Encrypt", days:71, st:'ok'},
|
||||
{dom:'shop.bergmann-coffee.com', iss:"Let's Encrypt", days:54, st:'ok'},
|
||||
{dom:'praxis-dr-schwarz.de', iss:"Let's Encrypt", days:9, st:'warn'},
|
||||
{dom:'agentur-helio.com', iss:'ZeroSSL', days:122, st:'ok'},
|
||||
{dom:'classic.tonhoff-bau.de', iss:"Let's Encrypt", days:12, st:'warn'},
|
||||
{dom:'rezepte.kochlust.de', iss:"Let's Encrypt", days:88, st:'ok'},
|
||||
];
|
||||
const sslBody = document.getElementById('ssl-body');
|
||||
certs.forEach(c=>{
|
||||
const cls = c.st==='warn'?'warn':'ok';
|
||||
const lbl = c.st==='warn'?`${c.days} t`:`${c.days} t`;
|
||||
sslBody.insertAdjacentHTML('beforeend',`
|
||||
<tr>
|
||||
<td><div class="site-name" style="max-width:200px">${c.dom}</div></td>
|
||||
<td><span class="site-meta">${c.iss}</span></td>
|
||||
<td><span class="health"><span class="health-dot ${cls}"></span><span class="health-label">in ${lbl}</span></span></td>
|
||||
</tr>`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Site Details</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
<style>
|
||||
/* ---- site header ---- */
|
||||
.site-hero{display:flex;align-items:center;gap:14px}
|
||||
.site-hero .favicon{width:42px;height:42px;border-radius:11px;font-size:16px}
|
||||
.site-hero h1{margin:0;font-size:18px;font-weight:600;letter-spacing:-0.02em}
|
||||
.site-hero .sub{font-family:var(--mono);font-size:11.5px;color:var(--muted);margin-top:3px;display:flex;gap:10px;flex-wrap:wrap}
|
||||
|
||||
/* ---- detail tab bar ---- */
|
||||
.dtabs{display:flex;gap:3px;padding:5px;border-radius:15px;overflow-x:auto;
|
||||
background:var(--glass-strong);backdrop-filter:blur(30px) saturate(180%);-webkit-backdrop-filter:blur(30px) saturate(180%);
|
||||
border:1px solid var(--glass-border);box-shadow:var(--glass-shadow), var(--glass-inset)}
|
||||
.dtab{display:flex;align-items:center;gap:8px;padding:9px 14px;border-radius:10px;color:#3a3a47;font-weight:600;font-size:12.5px;white-space:nowrap;transition:all .15s}
|
||||
.dtab:hover{background:rgba(255,255,255,0.55)}
|
||||
.dtab.active{color:var(--fg);background:linear-gradient(180deg,#fff,rgba(255,255,255,0.8));box-shadow:0 4px 12px -5px rgba(40,52,92,0.3)}
|
||||
.dtab .ico{width:16px;height:16px;color:var(--muted)}
|
||||
.dtab.active .ico{color:var(--accent)}
|
||||
.dtab .count{font-family:var(--mono);font-size:10px;background:rgba(30,35,60,0.08);padding:1px 6px;border-radius:8px;color:var(--muted)}
|
||||
|
||||
.pane{display:none}
|
||||
.pane.active{display:flex;flex-direction:column;gap:18px;animation:fade .25s ease}
|
||||
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
||||
|
||||
/* ---- file manager ---- */
|
||||
.fm{display:grid;grid-template-columns:268px 1fr;gap:18px;align-items:start}
|
||||
.tree{padding:10px;max-height:600px;overflow:auto}
|
||||
.tree-head{display:flex;align-items:center;justify-content:space-between;padding:6px 8px 10px;border-bottom:1px solid var(--hairline);margin-bottom:6px}
|
||||
.tree-head .path{font-family:var(--mono);font-size:11px;color:var(--muted)}
|
||||
.tree-item{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:8px;font-size:12.5px;cursor:pointer;font-family:var(--mono);color:#3a3a47;white-space:nowrap;transition:background .12s}
|
||||
.tree-item:hover{background:rgba(255,255,255,0.6)}
|
||||
.tree-item.active{background:var(--accent-soft);color:var(--accent)}
|
||||
.tree-item .ti-ico{width:15px;height:15px;flex:none;color:var(--muted-2)}
|
||||
.tree-item.dir{font-weight:600;color:var(--fg)}
|
||||
.tree-item.active .ti-ico{color:var(--accent)}
|
||||
.tree-children{margin-left:9px;border-left:1px solid var(--hairline);padding-left:5px}
|
||||
.tree-children.collapsed{display:none}
|
||||
.chev{transition:transform .15s;color:var(--muted-2)}
|
||||
.tree-item.open .chev{transform:rotate(90deg)}
|
||||
|
||||
.editor{display:flex;flex-direction:column;height:600px;overflow:hidden}
|
||||
.editor-head{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--hairline)}
|
||||
.crumb{font-family:var(--mono);font-size:12px;color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.crumb b{color:var(--fg)}
|
||||
.editor-tagline{font-family:var(--mono);font-size:10.5px;color:var(--muted-2)}
|
||||
.code-wrap{flex:1;display:flex;overflow:hidden;background:rgba(255,255,255,0.35)}
|
||||
.gutter{padding:14px 0;text-align:right;color:var(--muted-2);background:rgba(30,35,60,0.04);user-select:none;font-family:var(--mono);font-size:12px;line-height:1.65;border-right:1px solid var(--hairline);overflow:hidden}
|
||||
.gutter div{padding:0 11px}
|
||||
.code-area{flex:1;border:0;outline:0;resize:none;padding:14px 16px;font-family:var(--mono);font-size:12.5px;line-height:1.65;background:transparent;color:var(--fg);white-space:pre;overflow:auto;tab-size:4}
|
||||
.editor-foot{display:flex;align-items:center;justify-content:space-between;padding:11px 16px;border-top:1px solid var(--hairline)}
|
||||
.editor-empty{flex:1;display:grid;place-items:center;color:var(--muted-2);font-family:var(--mono);font-size:12px;text-align:center;gap:10px;padding:40px}
|
||||
|
||||
/* ---- console / logs ---- */
|
||||
.console{border-radius:var(--radius);background:linear-gradient(180deg,rgba(24,26,42,0.96),rgba(17,19,32,0.96));border:1px solid rgba(255,255,255,0.09);padding:6px 0;font-family:var(--mono);font-size:12px;line-height:1.7;color:#cfd3e6;max-height:540px;overflow:auto;box-shadow:inset 0 1px 0 rgba(255,255,255,0.06), 0 10px 34px -10px rgba(40,52,92,0.3)}
|
||||
.log-line{display:flex;gap:12px;padding:1px 16px;white-space:pre-wrap}
|
||||
.log-line:hover{background:rgba(255,255,255,0.04)}
|
||||
.log-time{color:#6b7191;flex:none}
|
||||
.log-lvl{font-weight:600;flex:none;width:50px}
|
||||
.lvl-INFO{color:#5aa9ff}.lvl-WARN{color:#e7b94e}.lvl-ERROR{color:#ff7373}.lvl-OK{color:#4cd6a0}.lvl-DEBUG{color:#a98bff}
|
||||
.log-msg{color:#cfd3e6}
|
||||
.console::-webkit-scrollbar{width:9px}.console::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.14);border-radius:5px}
|
||||
|
||||
.switch-line{display:flex;align-items:center;gap:9px;font-size:12px;color:var(--muted);font-weight:500;cursor:pointer}
|
||||
|
||||
/* settings two-column forms */
|
||||
.set-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 26px}
|
||||
@media (max-width:1100px){.fm{grid-template-columns:1fr}.set-grid{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand"><div class="brand-mark" aria-hidden="true"></div><div class="brand-name">CluPilot<span>v2.4</span></div></div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta"><div class="ws-name">Acme Cluster</div><div class="ws-plan">team · 14 user</div></div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg><span>Dashboard</span></a>
|
||||
<a class="nav-item" href="Server.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg><span>Server</span><span class="count">6</span></a>
|
||||
<a class="nav-item active" href="Sites.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg><span>Sites</span><span class="count">87</span></a>
|
||||
<a class="nav-item" href="Backups.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg><span>Backups</span></a>
|
||||
<a class="nav-item" href="Sicherheit.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg><span>Sicherheit</span><span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span></a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg><span>Team</span></a>
|
||||
<a class="nav-item" href="Einstellungen.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg><span>Einstellungen</span></a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row"><div class="plan-name">Team Plan</div><div class="plan-tag">PRO</div></div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats"><span>87 / 140 sites</span><span>62%</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main" data-screen-label="Site Details">
|
||||
<!-- topbar -->
|
||||
<div class="topbar">
|
||||
<a class="icon-btn" href="Sites.html" aria-label="Zurück zu Sites">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
|
||||
</a>
|
||||
<div class="site-hero">
|
||||
<div class="favicon" style="background:#15a06a">S</div>
|
||||
<div>
|
||||
<h1>shop.bergmann-coffee.com</h1>
|
||||
<div class="sub"><span>eu-prod-01</span><span>· wp 6.4.3</span><span>· php 8.1</span><span>· ssl ✓</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="status-chip"><span class="pulse-dot"></span> online</span>
|
||||
<div class="top-spacer"></div>
|
||||
<button class="btn-ghost"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 3h7v7M21 3l-9 9M19 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"/></svg>Besuchen</button>
|
||||
<button class="btn-primary"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>WP-Admin</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- detail tabs -->
|
||||
<div class="dtabs" id="dtabs">
|
||||
<button class="dtab active" data-tab="overview"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>Übersicht</button>
|
||||
<button class="dtab" data-tab="wp"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>WordPress</button>
|
||||
<button class="dtab" data-tab="php"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M4 7h16M4 12h16M4 17h10"/></svg>PHP / ini</button>
|
||||
<button class="dtab" data-tab="files"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Datei-Manager</button>
|
||||
<button class="dtab" data-tab="cron"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Cronjobs<span class="count">5</span></button>
|
||||
<button class="dtab" data-tab="logs"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M4 5h16M4 10h16M4 15h10M4 20h7"/></svg>Logs</button>
|
||||
</div>
|
||||
|
||||
<!-- ============ ÜBERSICHT ============ -->
|
||||
<div class="pane active" id="tab-overview">
|
||||
<div class="metrics">
|
||||
<div class="metric"><div class="metric-top"><div class="metric-label">Besucher / 24h</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div></div><div class="metric-value">48,7<span class="sub">k</span></div><div class="metric-delta"><span class="chg up">↑ 9,2%</span> vs. gestern</div></div>
|
||||
<div class="metric"><div class="metric-top"><div class="metric-label">Speicher</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg></div></div><div class="metric-value">14,2<span class="sub">/ 40 GB</span></div><div class="metric-delta"><span class="chg">36%</span> belegt</div></div>
|
||||
<div class="metric"><div class="metric-top"><div class="metric-label">PageSpeed</div><div class="m-ico success"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3a9 9 0 1 0 9 9M12 12l5-5"/></svg></div></div><div class="metric-value">92<span class="sub">/ 100</span></div><div class="metric-delta"><span class="chg up">core web vitals ✓</span></div></div>
|
||||
<div class="metric"><div class="metric-top"><div class="metric-label">Updates</div><div class="m-ico warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg></div></div><div class="metric-value">3</div><div class="metric-delta"><span class="chg warn">1 core</span> · 2 plugins</div></div>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Installation</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="kv"><span class="k">Primäre Domain</span><span class="v">shop.bergmann-coffee.com</span></div>
|
||||
<div class="kv"><span class="k">Dokument-Root</span><span class="v">/var/www/bergmann/htdocs</span></div>
|
||||
<div class="kv"><span class="k">WordPress</span><span class="v">6.4.3</span></div>
|
||||
<div class="kv"><span class="k">PHP</span><span class="v">8.1.27 · FPM</span></div>
|
||||
<div class="kv"><span class="k">Datenbank</span><span class="v">MariaDB 10.11 · 312 MB</span></div>
|
||||
<div class="kv"><span class="k">SSL</span><span class="v">Let's Encrypt · 54 t</span></div>
|
||||
<div class="kv"><span class="k">Aktives Theme</span><span class="v">Storefront Child</span></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Schnellaktionen</h3></div>
|
||||
<div class="card-body" style="padding-top:4px">
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Wartungsmodus</div><div class="tr-desc">site für besucher sperren</div></div><div class="toggle"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Objekt-Cache (Redis)</div><div class="tr-desc">datenbankabfragen zwischenspeichern</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Edge-CDN</div><div class="tr-desc">statische inhalte global cachen</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">HTTPS erzwingen</div><div class="tr-desc">alle anfragen auf ssl umleiten</div></div><div class="toggle on"></div></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ WORDPRESS ============ -->
|
||||
<div class="pane" id="tab-wp">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M4 12h16M4 17h10"/></svg>wp-config.php</h3><span class="meta">/htdocs/wp-config.php</span></div>
|
||||
<div class="card-body">
|
||||
<div class="set-grid">
|
||||
<div class="field"><label>Tabellen-Präfix</label><input class="input input-mono" value="wp_bg_" /></div>
|
||||
<div class="field"><label>WP_MEMORY_LIMIT</label><div class="input-group"><input class="input-mono" value="256" style="font-family:var(--mono)"/><span class="suffix">MB</span></div></div>
|
||||
<div class="field"><label>WP_MAX_MEMORY_LIMIT</label><div class="input-group"><input class="input-mono" value="512" style="font-family:var(--mono)"/><span class="suffix">MB</span></div></div>
|
||||
<div class="field"><label>AUTOSAVE_INTERVAL</label><div class="input-group"><input class="input-mono" value="60" style="font-family:var(--mono)"/><span class="suffix">sek</span></div></div>
|
||||
<div class="field"><label>WP_POST_REVISIONS</label><input class="input input-mono" value="10" /></div>
|
||||
<div class="field"><label>Sprache (WPLANG)</label><input class="input input-mono" value="de_DE" /></div>
|
||||
</div>
|
||||
<div style="margin-top:8px">
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">WP_DEBUG</div><div class="tr-desc">debug-modus & fehlerprotokoll</div></div><div class="toggle"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">WP_CACHE</div><div class="tr-desc">persistentes caching aktiv</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">DISALLOW_FILE_EDIT</div><div class="tr-desc">datei-editor im wp-admin sperren</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">AUTOMATIC_UPDATER_DISABLED</div><div class="tr-desc">automatische core-updates deaktiviert</div></div><div class="toggle"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">FORCE_SSL_ADMIN</div><div class="tr-desc">wp-admin nur über https</div></div><div class="toggle on"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-ghost">In Editor öffnen</button><button class="btn-primary">wp-config speichern</button></div>
|
||||
</section>
|
||||
|
||||
<div class="grid-2">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Datenbank</h3><span class="pill success">verbunden</span></div>
|
||||
<div class="card-body">
|
||||
<div class="field"><label>DB_NAME</label><input class="input input-mono" value="bergmann_prod" /></div>
|
||||
<div class="field"><label>DB_USER</label><input class="input input-mono" value="bergmann_wp" /></div>
|
||||
<div class="field"><label>DB_HOST</label><input class="input input-mono" value="127.0.0.1:3306" /></div>
|
||||
<div class="field" style="margin-bottom:0"><label>DB_PASSWORD</label><div class="input-group"><input class="input-mono" type="password" value="************" style="font-family:var(--mono)"/><span class="suffix" style="cursor:pointer">anzeigen</span></div></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Sicherheits-Schlüssel (Salts)</h3></div>
|
||||
<div class="card-body">
|
||||
<p class="tr-desc" style="margin:0 0 14px">AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY & zugehörige Salts. Neu generieren meldet alle Benutzer ab.</p>
|
||||
<div class="kv"><span class="k">Zuletzt rotiert</span><span class="v">14.03.2026</span></div>
|
||||
<div class="kv"><span class="k">Schlüssel-Länge</span><span class="v">64 zeichen</span></div>
|
||||
<div class="kv" style="border-bottom:0"><span class="k">Status</span><span class="v" style="color:var(--success)">stark</span></div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-ghost" style="color:var(--warning);border-color:rgba(194,130,15,0.35)">Salts neu generieren</button></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Allgemeine WordPress-Einstellungen</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="set-grid">
|
||||
<div class="field"><label>Seitentitel</label><input class="input" value="Bergmann Coffee Shop" /></div>
|
||||
<div class="field"><label>Untertitel</label><input class="input" value="Rösterei & Onlineshop" /></div>
|
||||
<div class="field"><label>WordPress-Adresse (URL)</label><input class="input input-mono" value="https://shop.bergmann-coffee.com" /></div>
|
||||
<div class="field"><label>Permalink-Struktur</label><input class="input input-mono" value="/%postname%/" /></div>
|
||||
</div>
|
||||
<div style="margin-top:8px">
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Suchmaschinen-Sichtbarkeit</div><div class="tr-desc">von google & co. indexieren lassen</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">Registrierung erlauben</div><div class="tr-desc">neue benutzer können sich registrieren</div></div><div class="toggle"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-primary">Einstellungen speichern</button></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ============ PHP / INI ============ -->
|
||||
<div class="pane" id="tab-php">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M4 12h16M4 17h10"/></svg>php.ini</h3><span class="meta">php 8.1 · fpm pool: bergmann</span></div>
|
||||
<div class="card-body">
|
||||
<div class="set-grid">
|
||||
<div class="field"><label>memory_limit</label><div class="input-group"><input class="input-mono" value="256" style="font-family:var(--mono)"/><span class="suffix">M</span></div></div>
|
||||
<div class="field"><label>max_execution_time</label><div class="input-group"><input class="input-mono" value="120" style="font-family:var(--mono)"/><span class="suffix">sek</span></div></div>
|
||||
<div class="field"><label>upload_max_filesize</label><div class="input-group"><input class="input-mono" value="128" style="font-family:var(--mono)"/><span class="suffix">M</span></div></div>
|
||||
<div class="field"><label>post_max_size</label><div class="input-group"><input class="input-mono" value="128" style="font-family:var(--mono)"/><span class="suffix">M</span></div></div>
|
||||
<div class="field"><label>max_input_vars</label><input class="input input-mono" value="3000" /></div>
|
||||
<div class="field"><label>max_input_time</label><div class="input-group"><input class="input-mono" value="120" style="font-family:var(--mono)"/><span class="suffix">sek</span></div></div>
|
||||
<div class="field"><label>default_charset</label><input class="input input-mono" value="UTF-8" /></div>
|
||||
<div class="field"><label>date.timezone</label><input class="input input-mono" value="Europe/Berlin" /></div>
|
||||
</div>
|
||||
<div style="margin-top:8px">
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">display_errors</div><div class="tr-desc">php-fehler im browser anzeigen (nur dev)</div></div><div class="toggle"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">opcache.enable</div><div class="tr-desc">bytecode-cache aktiv</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">log_errors</div><div class="tr-desc">fehler in error.log schreiben</div></div><div class="toggle on"></div></div>
|
||||
<div class="toggle-row"><div class="tr-body"><div class="tr-title">allow_url_fopen</div><div class="tr-desc">externe urls als datei öffnen</div></div><div class="toggle on"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-ghost">PHP-FPM neu starten</button><button class="btn-primary">php.ini speichern</button></div>
|
||||
</section>
|
||||
|
||||
<div class="grid-2">
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>OPcache</h3><span class="pill success">aktiv</span></div>
|
||||
<div class="card-body">
|
||||
<div class="kv"><span class="k">Trefferquote</span><span class="v" style="color:var(--success)">99,2%</span></div>
|
||||
<div class="kv"><span class="k">Speicher</span><span class="v">142 / 256 MB</span></div>
|
||||
<div class="kv"><span class="k">Zwischengesp. Skripte</span><span class="v">4.218</span></div>
|
||||
<div class="kv" style="border-bottom:0"><span class="k">opcache.revalidate_freq</span><span class="v">60 s</span></div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-ghost">OPcache leeren</button></div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Aktive Erweiterungen</h3><span class="meta">28 module</span></div>
|
||||
<div class="card-body" style="display:flex;flex-wrap:wrap;gap:8px">
|
||||
<span class="pill">curl</span><span class="pill">gd</span><span class="pill">imagick</span><span class="pill">intl</span><span class="pill">mbstring</span><span class="pill">mysqli</span><span class="pill">opcache</span><span class="pill">redis</span><span class="pill">soap</span><span class="pill">zip</span><span class="pill">bcmath</span><span class="pill">exif</span><span class="pill accent">+ 16</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ DATEI-MANAGER ============ -->
|
||||
<div class="pane" id="tab-files">
|
||||
<div class="section-head">
|
||||
<h2><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Datei-Manager</h2>
|
||||
<div class="toolbar">
|
||||
<button class="chip-filter"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>Neue Datei</button>
|
||||
<button class="chip-filter"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 16V4M7 9l5-5 5 5M4 20h16"/></svg>Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fm">
|
||||
<section class="card tree">
|
||||
<div class="tree-head">
|
||||
<span class="path">/var/www/bergmann</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--muted-2)" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7M3 4v4h4"/></svg>
|
||||
</div>
|
||||
<div id="file-tree"></div>
|
||||
</section>
|
||||
<section class="card editor">
|
||||
<div class="editor-head">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
|
||||
<span class="crumb" id="crumb">Wähle eine Datei aus dem Baum</span>
|
||||
<span class="editor-tagline" id="editor-tagline"></span>
|
||||
</div>
|
||||
<div class="code-wrap" id="code-wrap" style="display:none">
|
||||
<div class="gutter" id="gutter"></div>
|
||||
<textarea class="code-area" id="code-area" spellcheck="false"></textarea>
|
||||
</div>
|
||||
<div class="editor-empty" id="editor-empty">
|
||||
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="var(--muted-2)" stroke-width="1.5"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
|
||||
<div>Klicke links auf eine Datei,<br>um sie hier zu bearbeiten.</div>
|
||||
</div>
|
||||
<div class="editor-foot">
|
||||
<span class="editor-tagline" id="editor-status">bereit</span>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn-ghost" id="btn-revert" style="padding:7px 13px;font-size:12px">Verwerfen</button>
|
||||
<button class="btn-primary" id="btn-save" style="padding:7px 13px;font-size:12px"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8M7 3v5h8"/></svg>Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ CRONJOBS ============ -->
|
||||
<div class="pane" id="tab-cron">
|
||||
<div class="section-head">
|
||||
<h2><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Cronjobs</h2>
|
||||
<button class="btn-primary" id="btn-add-cron"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M12 5v14M5 12h14"/></svg>Cronjob anlegen</button>
|
||||
</div>
|
||||
|
||||
<!-- inline create form -->
|
||||
<section class="card" id="cron-form" style="display:none">
|
||||
<div class="card-head"><h3>Neuer Cronjob</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="set-grid">
|
||||
<div class="field"><label>Name</label><input class="input" id="cf-name" placeholder="z. B. WooCommerce Lagerabgleich" /></div>
|
||||
<div class="field"><label>Zeitplan</label>
|
||||
<select class="input" id="cf-preset">
|
||||
<option value="*/5 * * * *">Alle 5 Minuten</option>
|
||||
<option value="0 * * * *">Stündlich</option>
|
||||
<option value="0 3 * * *" selected>Täglich 03:00</option>
|
||||
<option value="0 4 * * 1">Wöchentlich (Mo 04:00)</option>
|
||||
<option value="custom">Eigener Ausdruck…</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-grid">
|
||||
<div class="field"><label>Cron-Ausdruck</label><input class="input input-mono" id="cf-expr" value="0 3 * * *" /></div>
|
||||
<div class="field"><label>Typ</label>
|
||||
<select class="input" id="cf-type"><option>WP-CLI</option><option>URL-Aufruf</option><option>Shell-Befehl</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" style="margin-bottom:0"><label>Befehl</label><input class="input input-mono" id="cf-cmd" value="wp cron event run --due-now --path=/var/www/bergmann/htdocs" /></div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn-ghost" id="cf-cancel">Abbrechen</button><button class="btn-primary" id="cf-save">Cronjob speichern</button></div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head"><h3>Geplante Aufgaben <span class="pill" id="cron-count" style="margin-left:4px">5</span></h3><span class="meta">system + wp-cron</span></div>
|
||||
<div class="scroll-x">
|
||||
<table class="table">
|
||||
<thead><tr><th>Name</th><th>Zeitplan</th><th>Befehl</th><th>Letzter Lauf</th><th>Status</th><th class="num"></th></tr></thead>
|
||||
<tbody id="cron-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ============ LOGS ============ -->
|
||||
<div class="pane" id="tab-logs">
|
||||
<div class="section-head">
|
||||
<h2><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 5h16M4 10h16M4 15h10M4 20h7"/></svg>Logs</h2>
|
||||
<div class="toolbar">
|
||||
<div class="seg" id="log-seg">
|
||||
<button class="active" data-log="access">access.log</button>
|
||||
<button data-log="error">error.log</button>
|
||||
<button data-log="php">php-fpm</button>
|
||||
<button data-log="wp">wp-debug</button>
|
||||
</div>
|
||||
<label class="switch-line"><span class="toggle on" id="log-follow" style="width:34px;height:20px"></span>Live folgen</label>
|
||||
<button class="chip-filter" id="log-clear"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg>Leeren</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
<div class="card-head"><h3 id="log-title">access.log</h3><span class="live-tag"><span class="pulse-dot" style="color:var(--success)"></span>live</span></div>
|
||||
<div class="console" id="console"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="foot">CluPilot · shop.bergmann-coffee.com · region eu-central-1</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="sitedetails.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Sites</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true"></div>
|
||||
<div class="brand-name">CluPilot<span>v2.4</span></div>
|
||||
</div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta">
|
||||
<div class="ws-name">Acme Cluster</div>
|
||||
<div class="ws-plan">team · 14 user</div>
|
||||
</div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg><span>Dashboard</span></a>
|
||||
<a class="nav-item" href="Server.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg><span>Server</span><span class="count">6</span></a>
|
||||
<a class="nav-item active" href="Sites.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg><span>Sites</span><span class="count">87</span></a>
|
||||
<a class="nav-item" href="Backups.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg><span>Backups</span></a>
|
||||
<a class="nav-item" href="Sicherheit.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg><span>Sicherheit</span><span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span></a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item" href="Team.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg><span>Team</span></a>
|
||||
<a class="nav-item" href="Einstellungen.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg><span>Einstellungen</span></a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row"><div class="plan-name">Team Plan</div><div class="plan-tag">PRO</div></div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats"><span>87 / 140 sites</span><span>62%</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main" data-screen-label="Sites">
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<div class="page-title">Sites</div>
|
||||
<div class="page-sub">87 wordpress-installationen</div>
|
||||
</div>
|
||||
<span class="status-chip"><span class="pulse-dot"></span> 87 online</span>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input placeholder="Domain suchen…" />
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
<button class="btn-primary">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6"><path d="M12 5v14M5 12h14"/></svg>
|
||||
Site hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Sites Online</div><div class="m-ico success"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg></div></div>
|
||||
<div class="metric-value">87<span class="sub">/ 87</span></div>
|
||||
<div class="metric-delta"><span class="chg up">100%</span> erreichbar</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Updates offen</div><div class="m-ico warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg></div></div>
|
||||
<div class="metric-value">24</div>
|
||||
<div class="metric-delta"><span class="chg warn">7 kritisch</span> · 17 minor</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Ø PageSpeed</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3a9 9 0 1 0 9 9M12 12l5-5"/></svg></div></div>
|
||||
<div class="metric-value">94<span class="sub">/ 100</span></div>
|
||||
<div class="metric-delta"><span class="chg up">↑ 3</span> core web vitals</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Besucher / 24h</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div></div>
|
||||
<div class="metric-value">182<span class="sub">k</span></div>
|
||||
<div class="metric-delta"><span class="chg up">↑ 12%</span> vs. vorwoche</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<div class="toolbar">
|
||||
<div class="seg">
|
||||
<button class="active">Alle <span style="opacity:.6">87</span></button>
|
||||
<button>Updates <span style="opacity:.6">12</span></button>
|
||||
<button>Warnungen <span style="opacity:.6">2</span></button>
|
||||
<button>Kritisch <span style="opacity:.6">1</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="ghost-btn">Server: Alle ▾</button>
|
||||
<button class="ghost-btn">Sortieren ▾</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll-x">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Site</th>
|
||||
<th>Server</th>
|
||||
<th>WordPress</th>
|
||||
<th>PHP</th>
|
||||
<th class="num">Besucher / 24h</th>
|
||||
<th>Status</th>
|
||||
<th class="num"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sites-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:13px 18px;border-top:1px solid var(--hairline)">
|
||||
<span class="site-meta">1–15 von 87</span>
|
||||
<div style="display:flex;gap:6px">
|
||||
<button class="ghost-btn">← Zurück</button>
|
||||
<button class="ghost-btn">Weiter →</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="foot">CluPilot · region eu-central-1 · uptime 99,984%</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const Icon = {
|
||||
terminal:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>',
|
||||
ext:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 3h7v7M21 3l-9 9M19 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"/></svg>',
|
||||
more:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>',
|
||||
};
|
||||
const palette = ['#3b6ff2','#15a06a','#c2820f','#7c5cff','#db3b3b','#0ea5b7','#c026d3','#0d9488','#e0772e','#2563eb'];
|
||||
const sites = [
|
||||
{url:'kunde-mueller.de', server:'eu-prod-01', wp:'6.5.2', wpOk:true, php:'8.2', visitors:'12,4k', health:'ok', label:'Optimal'},
|
||||
{url:'shop.bergmann-coffee.com', server:'eu-prod-01', wp:'6.4.3', wpOk:false, php:'8.1', visitors:'48,7k', health:'ok', label:'3 Updates'},
|
||||
{url:'blog.fintech-radar.io', server:'eu-prod-02', wp:'6.5.2', wpOk:true, php:'8.2', visitors:'9,1k', health:'ok', label:'Optimal'},
|
||||
{url:'praxis-dr-schwarz.de', server:'eu-prod-02', wp:'6.2.1', wpOk:false, php:'8.0', visitors:'1,2k', health:'warn', label:'Wartung nötig'},
|
||||
{url:'agentur-helio.com', server:'eu-edge-amsterdam', wp:'6.5.2', wpOk:true, php:'8.2', visitors:'18,2k', health:'ok', label:'Optimal'},
|
||||
{url:'docs.opencrate.dev', server:'us-east-prod-01', wp:'6.5.1', wpOk:true, php:'8.3', visitors:'7,8k', health:'ok', label:'2 Updates'},
|
||||
{url:'staging.opencrate.dev', server:'eu-staging', wp:'6.6-RC1', wpOk:true, php:'8.3', visitors:'82', health:'warn', label:'Backup spät'},
|
||||
{url:'classic.tonhoff-bau.de', server:'us-east-prod-01', wp:'5.9.7', wpOk:false, php:'7.4', visitors:'3,4k', health:'bad', label:'Kritisch'},
|
||||
{url:'rezepte.kochlust.de', server:'eu-prod-03', wp:'6.5.2', wpOk:true, php:'8.2', visitors:'31,5k', health:'ok', label:'Optimal'},
|
||||
{url:'verein-sv-eiche.de', server:'eu-prod-02', wp:'6.5.0', wpOk:true, php:'8.1', visitors:'640', health:'ok', label:'Optimal'},
|
||||
{url:'shop.nordlicht-mode.com', server:'eu-edge-amsterdam', wp:'6.4.3', wpOk:false, php:'8.2', visitors:'22,9k', health:'ok', label:'1 Update'},
|
||||
{url:'kanzlei-roth-partner.de', server:'eu-prod-01', wp:'6.5.2', wpOk:true, php:'8.2', visitors:'2,1k', health:'ok', label:'Optimal'},
|
||||
{url:'travelblog.fernweh.io', server:'eu-prod-03', wp:'6.5.1', wpOk:true, php:'8.3', visitors:'14,7k', health:'ok', label:'Optimal'},
|
||||
{url:'app.leadmagnet.co', server:'us-east-prod-01', wp:'6.5.2', wpOk:true, php:'8.3', visitors:'5,3k', health:'ok', label:'Optimal'},
|
||||
{url:'alt.stadtwerke-lohr.de', server:'eu-prod-02', wp:'6.1.4', wpOk:false, php:'7.4', visitors:'910', health:'warn', label:'PHP veraltet'},
|
||||
];
|
||||
const body = document.getElementById('sites-body');
|
||||
sites.forEach((s,i)=>{
|
||||
const initial = s.url[0].toUpperCase();
|
||||
const color = palette[i % palette.length];
|
||||
const wpPill = s.wpOk ? 'success' : 'warning';
|
||||
const phpOld = (s.php==='7.4');
|
||||
body.insertAdjacentHTML('beforeend',`
|
||||
<tr>
|
||||
<td>
|
||||
<div class="site-cell">
|
||||
<div class="favicon" style="background:${color}">${initial}</div>
|
||||
<div style="min-width:0">
|
||||
<a class="site-name" href="Site Details.html" style="display:block">${s.url}</a>
|
||||
<div class="site-meta">ssl ✓ · cdn an</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill">${s.server}</span></td>
|
||||
<td><span class="pill ${wpPill}">WP ${s.wp}</span></td>
|
||||
<td><span class="pill ${phpOld?'warning':''}">php ${s.php}</span></td>
|
||||
<td class="num"><span style="color:var(--fg)">${s.visitors}</span></td>
|
||||
<td><span class="health"><span class="health-dot ${s.health}"></span><span class="health-label">${s.label}</span></span></td>
|
||||
<td class="num"><div class="row-actions">
|
||||
<button title="Site öffnen">${Icon.ext}</button>
|
||||
<button title="WP-Admin">${Icon.terminal}</button>
|
||||
<button title="Mehr">${Icon.more}</button>
|
||||
</div></td>
|
||||
</tr>`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>CluPilot — Team</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="clupilot.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="sidebar" data-screen-label="Sidebar">
|
||||
<div class="brand"><div class="brand-mark" aria-hidden="true"></div><div class="brand-name">CluPilot<span>v2.4</span></div></div>
|
||||
<button class="ws-switch" aria-label="Workspace wechseln">
|
||||
<div class="ws-avatar">AC</div>
|
||||
<div class="ws-meta"><div class="ws-name">Acme Cluster</div><div class="ws-plan">team · 14 user</div></div>
|
||||
<svg class="ws-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l4-4 4 4M8 15l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<nav class="nav">
|
||||
<a class="nav-item" href="CluPilot Dashboard Glass.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg><span>Dashboard</span></a>
|
||||
<a class="nav-item" href="Server.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg><span>Server</span><span class="count">6</span></a>
|
||||
<a class="nav-item" href="Sites.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg><span>Sites</span><span class="count">87</span></a>
|
||||
<a class="nav-item" href="Backups.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg><span>Backups</span></a>
|
||||
<a class="nav-item" href="Sicherheit.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg><span>Sicherheit</span><span class="count" style="color:var(--warning);background:var(--warning-soft);border-color:rgba(194,130,15,0.3)">3</span></a>
|
||||
<div class="nav-title">Konto</div>
|
||||
<a class="nav-item active" href="Team.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg><span>Team</span></a>
|
||||
<a class="nav-item" href="Einstellungen.html"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg><span>Einstellungen</span></a>
|
||||
</nav>
|
||||
<div class="plan-box">
|
||||
<div class="plan-row"><div class="plan-name">Team Plan</div><div class="plan-tag">PRO</div></div>
|
||||
<div class="plan-meter"><span></span></div>
|
||||
<div class="plan-stats"><span>87 / 140 sites</span><span>62%</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main" data-screen-label="Team">
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<div class="page-title">Team</div>
|
||||
<div class="page-sub">14 mitglieder · 2 einladungen offen</div>
|
||||
</div>
|
||||
<span class="status-chip"><span class="pulse-dot"></span> 6 gerade aktiv</span>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input placeholder="Mitglied suchen…" />
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
<button class="btn-primary">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M12 5v14M5 12h14"/></svg>
|
||||
Einladen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Mitglieder</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div></div>
|
||||
<div class="metric-value">14<span class="sub">/ 20</span></div>
|
||||
<div class="metric-delta"><span class="chg up">6 sitze frei</span> im Team-Plan</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Admins</div><div class="m-ico success"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg></div></div>
|
||||
<div class="metric-value">3</div>
|
||||
<div class="metric-delta"><span class="chg">voller zugriff</span></div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">Einladungen offen</div><div class="m-ico warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M4 7l8 6 8-6"/></svg></div></div>
|
||||
<div class="metric-value">2</div>
|
||||
<div class="metric-delta"><span class="chg warn">ausstehend</span> · > 2 tage</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-top"><div class="metric-label">2FA aktiv</div><div class="m-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></div></div>
|
||||
<div class="metric-value">11<span class="sub">/ 14</span></div>
|
||||
<div class="metric-delta"><span class="chg warn">3 ohne 2FA</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>Mitglieder</h3>
|
||||
<div class="card-tabs"><button class="active">Alle</button><button>Admins</button><button>Developer</button><button>Nur Lesen</button></div>
|
||||
</div>
|
||||
<div class="scroll-x">
|
||||
<table class="table">
|
||||
<thead><tr><th>Mitglied</th><th>Rolle</th><th>Zugriff</th><th>2FA</th><th>Zuletzt aktiv</th><th class="num"></th></tr></thead>
|
||||
<tbody id="team-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head"><h3><svg class="hico" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M4 7l8 6 8-6"/></svg>Offene Einladungen</h3></div>
|
||||
<div class="scroll-x">
|
||||
<table class="table">
|
||||
<thead><tr><th>E-Mail</th><th>Rolle</th><th>Eingeladen von</th><th>Gesendet</th><th class="num"></th></tr></thead>
|
||||
<tbody id="inv-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="foot">CluPilot · rollenbasierte zugriffskontrolle · audit-log aktiv</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const Icon = {
|
||||
more:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>',
|
||||
resend:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7M3 4v4h4"/></svg>',
|
||||
x:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 6l12 12M18 6L6 18"/></svg>',
|
||||
};
|
||||
const avColors = ['#3b6ff2','#15a06a','#7c5cff','#c2820f','#db3b3b','#0ea5b7','#c026d3','#0d9488','#e0772e','#2563eb'];
|
||||
const roleMap = {
|
||||
Owner: {cls:'accent'},
|
||||
Admin: {cls:'success'},
|
||||
Developer:{cls:''},
|
||||
Editor: {cls:''},
|
||||
'Nur Lesen':{cls:''},
|
||||
};
|
||||
const team = [
|
||||
{name:'Julia Koch', email:'j.koch@acme.io', role:'Owner', scope:'Alle Server & Sites', tfa:true, last:'gerade eben'},
|
||||
{name:'Marc Weber', email:'m.weber@acme.io', role:'Admin', scope:'Alle Server & Sites', tfa:true, last:'vor 4 min'},
|
||||
{name:'Sina Brandt', email:'s.brandt@acme.io', role:'Admin', scope:'eu-prod-* · Sites', tfa:true, last:'vor 22 min'},
|
||||
{name:'Tobias Reuter', email:'t.reuter@acme.io', role:'Developer', scope:'eu-prod-01, eu-prod-02', tfa:true, last:'vor 1 std'},
|
||||
{name:'Nadine Frei', email:'n.frei@acme.io', role:'Developer', scope:'us-east-prod-01', tfa:false, last:'vor 3 std'},
|
||||
{name:'Leon Hartmann', email:'l.hartmann@acme.io',role:'Editor', scope:'12 Sites (Content)', tfa:true, last:'vor 5 std'},
|
||||
{name:'Petra Lang', email:'p.lang@acme.io', role:'Editor', scope:'shop.* Sites', tfa:false, last:'gestern'},
|
||||
{name:'Ömer Yıldız', email:'o.yildiz@acme.io', role:'Developer', scope:'eu-edge-amsterdam', tfa:true, last:'gestern'},
|
||||
{name:'Clara Voss', email:'c.voss@acme.io', role:'Nur Lesen', scope:'Reporting · alle', tfa:true, last:'vor 2 tagen'},
|
||||
{name:'David Mertens', email:'d.mertens@acme.io', role:'Nur Lesen', scope:'Reporting · eu', tfa:false, last:'vor 4 tagen'},
|
||||
];
|
||||
const tbody = document.getElementById('team-body');
|
||||
team.forEach((m,i)=>{
|
||||
const initials = m.name.split(' ').map(w=>w[0]).join('').slice(0,2).toUpperCase();
|
||||
const color = avColors[i % avColors.length];
|
||||
const r = roleMap[m.role] || {cls:''};
|
||||
tbody.insertAdjacentHTML('beforeend',`
|
||||
<tr>
|
||||
<td><div class="site-cell">
|
||||
<div class="avatar" style="background:linear-gradient(135deg,${color},${color}cc)">${initials}</div>
|
||||
<div style="min-width:0"><div class="site-name">${m.name}</div><div class="site-meta">${m.email}</div></div>
|
||||
</div></td>
|
||||
<td><span class="pill ${r.cls}">${m.role}</span></td>
|
||||
<td><span class="health-label">${m.scope}</span></td>
|
||||
<td>${m.tfa
|
||||
? '<span class="health"><span class="health-dot ok"></span><span class="health-label">aktiv</span></span>'
|
||||
: '<span class="health"><span class="health-dot warn"></span><span class="health-label">fehlt</span></span>'}</td>
|
||||
<td><span class="site-meta">${m.last}</span></td>
|
||||
<td class="num"><div class="row-actions"><button title="Mehr">${Icon.more}</button></div></td>
|
||||
</tr>`);
|
||||
});
|
||||
|
||||
const invites = [
|
||||
{email:'a.schmidt@acme.io', role:'Developer', by:'Julia Koch', sent:'vor 3 tagen'},
|
||||
{email:'freelance@studio-nord.de', role:'Editor', by:'Marc Weber', sent:'vor 2 tagen'},
|
||||
];
|
||||
const ibody = document.getElementById('inv-body');
|
||||
invites.forEach(v=>{
|
||||
ibody.insertAdjacentHTML('beforeend',`
|
||||
<tr>
|
||||
<td><div class="site-cell">
|
||||
<div class="avatar sm" style="background:rgba(30,35,60,0.12);color:var(--muted)"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M4 7l8 6 8-6"/></svg></div>
|
||||
<div class="site-name">${v.email}</div>
|
||||
</div></td>
|
||||
<td><span class="pill">${v.role}</span></td>
|
||||
<td><span class="health-label">${v.by}</span></td>
|
||||
<td><span class="site-meta">${v.sent}</span></td>
|
||||
<td class="num"><div class="row-actions"><button title="Erneut senden">${Icon.resend}</button><button title="Zurückziehen">${Icon.x}</button></div></td>
|
||||
</tr>`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
/* ===================================================================
|
||||
CluPilot — Site Details (Datei-Manager, Editor, Cronjobs, Logs)
|
||||
=================================================================== */
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
/* ---------- generic toggles ---------- */
|
||||
document.addEventListener('click', e=>{
|
||||
const t = e.target.closest('.toggle');
|
||||
if(t && !t.closest('[data-toggle]')) t.classList.toggle('on');
|
||||
});
|
||||
|
||||
/* ---------- detail tab switching ---------- */
|
||||
const tabs = document.querySelectorAll('#dtabs .dtab');
|
||||
const panes = {
|
||||
overview:'tab-overview', wp:'tab-wp', php:'tab-php',
|
||||
files:'tab-files', cron:'tab-cron', logs:'tab-logs'
|
||||
};
|
||||
tabs.forEach(tab=>tab.addEventListener('click',()=>{
|
||||
tabs.forEach(t=>t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
Object.values(panes).forEach(id=>document.getElementById(id).classList.remove('active'));
|
||||
document.getElementById(panes[tab.dataset.tab]).classList.add('active');
|
||||
if(tab.dataset.tab==='logs') renderLog(currentLog);
|
||||
}));
|
||||
|
||||
/* =====================================================================
|
||||
FILE MANAGER + EDITOR
|
||||
===================================================================== */
|
||||
const fileContents = {
|
||||
'htdocs/wp-config.php':
|
||||
`<?php
|
||||
/**
|
||||
* Basis-Konfiguration für WordPress — shop.bergmann-coffee.com
|
||||
*/
|
||||
|
||||
// ** Datenbank-Einstellungen ** //
|
||||
define( 'DB_NAME', 'bergmann_prod' );
|
||||
define( 'DB_USER', 'bergmann_wp' );
|
||||
define( 'DB_PASSWORD', getenv('WP_DB_PASS') );
|
||||
define( 'DB_HOST', '127.0.0.1:3306' );
|
||||
define( 'DB_CHARSET', 'utf8mb4' );
|
||||
define( 'DB_COLLATE', '' );
|
||||
|
||||
$table_prefix = 'wp_bg_';
|
||||
|
||||
// ** Speicher & Performance ** //
|
||||
define( 'WP_MEMORY_LIMIT', '256M' );
|
||||
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
|
||||
define( 'AUTOSAVE_INTERVAL', 60 );
|
||||
define( 'WP_POST_REVISIONS', 10 );
|
||||
define( 'WP_CACHE', true );
|
||||
|
||||
// ** Sicherheit ** //
|
||||
define( 'DISALLOW_FILE_EDIT', true );
|
||||
define( 'FORCE_SSL_ADMIN', true );
|
||||
define( 'AUTOMATIC_UPDATER_DISABLED', false );
|
||||
|
||||
// ** Debug ** //
|
||||
define( 'WP_DEBUG', false );
|
||||
define( 'WP_DEBUG_LOG', '/var/www/bergmann/logs/wp-debug.log' );
|
||||
define( 'WP_DEBUG_DISPLAY', false );
|
||||
|
||||
// ** Authentifizierungs-Schlüssel (rotiert 14.03.2026) ** //
|
||||
define( 'AUTH_KEY', getenv('WP_AUTH_KEY') );
|
||||
define( 'SECURE_AUTH_KEY', getenv('WP_SECURE_AUTH_KEY') );
|
||||
define( 'LOGGED_IN_KEY', getenv('WP_LOGGED_IN_KEY') );
|
||||
define( 'NONCE_KEY', getenv('WP_NONCE_KEY') );
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) define( 'ABSPATH', __DIR__ . '/' );
|
||||
require_once ABSPATH . 'wp-settings.php';
|
||||
`,
|
||||
'htdocs/.htaccess':
|
||||
`# BEGIN WordPress
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
RewriteRule ^index\\.php$ - [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule . /index.php [L]
|
||||
</IfModule>
|
||||
# END WordPress
|
||||
|
||||
# HTTPS erzwingen
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Sicherheits-Header
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
Header set X-Frame-Options "SAMEORIGIN"
|
||||
Header set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
</IfModule>
|
||||
|
||||
# Browser-Caching
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresByType image/webp "access plus 1 year"
|
||||
ExpiresByType text/css "access plus 1 month"
|
||||
</IfModule>
|
||||
`,
|
||||
'htdocs/robots.txt':
|
||||
`User-agent: *
|
||||
Disallow: /wp-admin/
|
||||
Allow: /wp-admin/admin-ajax.php
|
||||
Disallow: /cart/
|
||||
Disallow: /checkout/
|
||||
Disallow: /my-account/
|
||||
|
||||
Sitemap: https://shop.bergmann-coffee.com/sitemap_index.xml
|
||||
`,
|
||||
'htdocs/index.php':
|
||||
`<?php
|
||||
/**
|
||||
* Front-Controller. Lädt die WordPress-Umgebung und das Template.
|
||||
*/
|
||||
define( 'WP_USE_THEMES', true );
|
||||
require __DIR__ . '/wp-blog-header.php';
|
||||
`,
|
||||
'htdocs/wp-content/themes/storefront-child/functions.php':
|
||||
`<?php
|
||||
/**
|
||||
* Storefront Child — Funktionen
|
||||
*/
|
||||
add_action( 'wp_enqueue_scripts', function () {
|
||||
wp_enqueue_style( 'storefront-parent', get_template_directory_uri() . '/style.css' );
|
||||
wp_enqueue_style( 'storefront-child', get_stylesheet_uri(), array('storefront-parent'), '1.4.2' );
|
||||
}, 20 );
|
||||
|
||||
// WooCommerce: 4 Produkte pro Reihe
|
||||
add_filter( 'loop_shop_columns', fn() => 4 );
|
||||
|
||||
// Checkout: Telefon optional
|
||||
add_filter( 'woocommerce_checkout_fields', function ( $fields ) {
|
||||
$fields['billing']['billing_phone']['required'] = false;
|
||||
return $fields;
|
||||
});
|
||||
|
||||
// Heartbeat im Admin drosseln
|
||||
add_filter( 'heartbeat_settings', function ( $s ) { $s['interval'] = 60; return $s; });
|
||||
`,
|
||||
'htdocs/wp-content/themes/storefront-child/style.css':
|
||||
`/*
|
||||
Theme Name: Storefront Child
|
||||
Template: storefront
|
||||
Version: 1.4.2
|
||||
*/
|
||||
:root{ --bg-accent:#6f4e37; --bg-cream:#f5efe6; }
|
||||
|
||||
.site-header{ background:var(--bg-cream); border-bottom:3px solid var(--bg-accent); }
|
||||
.button.alt{ background:var(--bg-accent); }
|
||||
.price{ font-family:"JetBrains Mono", monospace; font-weight:600; }
|
||||
`,
|
||||
'php.ini':
|
||||
`; php.ini — pool: bergmann (php 8.1-fpm)
|
||||
memory_limit = 256M
|
||||
max_execution_time = 120
|
||||
max_input_time = 120
|
||||
max_input_vars = 3000
|
||||
upload_max_filesize = 128M
|
||||
post_max_size = 128M
|
||||
default_charset = "UTF-8"
|
||||
date.timezone = Europe/Berlin
|
||||
|
||||
display_errors = Off
|
||||
log_errors = On
|
||||
error_log = /var/www/bergmann/logs/php-error.log
|
||||
allow_url_fopen = On
|
||||
|
||||
[opcache]
|
||||
opcache.enable = 1
|
||||
opcache.memory_consumption = 256
|
||||
opcache.max_accelerated_files = 10000
|
||||
opcache.revalidate_freq = 60
|
||||
`,
|
||||
'logs/access.log':
|
||||
`192.0.2.41 - - [31/May/2026:09:12:04] "GET /shop/ HTTP/2.0" 200 18421
|
||||
203.0.113.7 - - [31/May/2026:09:12:05] "GET /wp-json/wc/store/cart HTTP/2.0" 200 842
|
||||
192.0.2.41 - - [31/May/2026:09:12:09] "POST /?wc-ajax=add_to_cart HTTP/2.0" 200 311
|
||||
`,
|
||||
};
|
||||
|
||||
const tree = [
|
||||
{name:'htdocs', type:'dir', open:true, children:[
|
||||
{name:'wp-admin', type:'dir', children:[]},
|
||||
{name:'wp-content', type:'dir', open:true, children:[
|
||||
{name:'themes', type:'dir', open:true, children:[
|
||||
{name:'storefront-child', type:'dir', open:true, children:[
|
||||
{name:'functions.php', type:'file', lang:'PHP'},
|
||||
{name:'style.css', type:'file', lang:'CSS'},
|
||||
]},
|
||||
]},
|
||||
{name:'plugins', type:'dir', children:[]},
|
||||
{name:'uploads', type:'dir', children:[]},
|
||||
]},
|
||||
{name:'wp-includes', type:'dir', children:[]},
|
||||
{name:'.htaccess', type:'file', lang:'APACHE'},
|
||||
{name:'index.php', type:'file', lang:'PHP'},
|
||||
{name:'robots.txt', type:'file', lang:'TEXT'},
|
||||
{name:'wp-config.php', type:'file', lang:'PHP'},
|
||||
]},
|
||||
{name:'php.ini', type:'file', lang:'INI'},
|
||||
{name:'logs', type:'dir', open:true, children:[
|
||||
{name:'access.log', type:'file', lang:'LOG'},
|
||||
{name:'error.log', type:'file', lang:'LOG'},
|
||||
]},
|
||||
];
|
||||
|
||||
const ICO = {
|
||||
dirClosed:'<svg class="ti-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>',
|
||||
file:'<svg class="ti-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>',
|
||||
chev:'<svg class="chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6"/></svg>',
|
||||
};
|
||||
|
||||
const treeEl = document.getElementById('file-tree');
|
||||
function renderTree(nodes, parentPath, container){
|
||||
nodes.forEach(node=>{
|
||||
const path = parentPath ? parentPath+'/'+node.name : node.name;
|
||||
const row = document.createElement('div');
|
||||
row.className = 'tree-item '+(node.type==='dir'?'dir':'') + (node.open?' open':'');
|
||||
if(node.type==='dir'){
|
||||
row.innerHTML = ICO.chev + ICO.dirClosed + '<span>'+node.name+'</span>';
|
||||
}else{
|
||||
row.innerHTML = '<span style="width:13px;flex:none"></span>' + ICO.file + '<span>'+node.name+'</span>';
|
||||
}
|
||||
container.appendChild(row);
|
||||
if(node.type==='dir'){
|
||||
const kids = document.createElement('div');
|
||||
kids.className = 'tree-children'+(node.open?'':' collapsed');
|
||||
container.appendChild(kids);
|
||||
if(node.children && node.children.length){
|
||||
renderTree(node.children, path, kids);
|
||||
}else{
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'tree-item';
|
||||
empty.style.cssText = 'color:var(--muted-2);font-style:italic;cursor:default';
|
||||
empty.innerHTML = '<span style="width:13px"></span><span>leer</span>';
|
||||
kids.appendChild(empty);
|
||||
}
|
||||
row.addEventListener('click',()=>{
|
||||
row.classList.toggle('open');
|
||||
kids.classList.toggle('collapsed');
|
||||
});
|
||||
}else{
|
||||
row.addEventListener('click',()=>openFile(path, node.lang, row));
|
||||
}
|
||||
});
|
||||
}
|
||||
renderTree(tree, '', treeEl);
|
||||
|
||||
/* ---- editor ---- */
|
||||
const codeWrap = document.getElementById('code-wrap');
|
||||
const editorEmpty = document.getElementById('editor-empty');
|
||||
const gutter = document.getElementById('gutter');
|
||||
const codeArea = document.getElementById('code-area');
|
||||
const crumb = document.getElementById('crumb');
|
||||
const tagline = document.getElementById('editor-tagline');
|
||||
const status = document.getElementById('editor-status');
|
||||
let currentPath = null, original = '';
|
||||
|
||||
function openFile(path, lang, row){
|
||||
document.querySelectorAll('.tree-item').forEach(r=>r.classList.remove('active'));
|
||||
if(row) row.classList.add('active');
|
||||
const content = fileContents[path] !== undefined ? fileContents[path]
|
||||
: '# '+path+'\n# (Inhalt wird vom Server geladen…)\n';
|
||||
currentPath = path; original = content;
|
||||
codeArea.value = content;
|
||||
editorEmpty.style.display = 'none';
|
||||
codeWrap.style.display = 'flex';
|
||||
const parts = path.split('/');
|
||||
crumb.innerHTML = parts.map((p,i)=> i===parts.length-1 ? '<b>'+p+'</b>' : p).join(' / ');
|
||||
tagline.textContent = lang || '';
|
||||
status.textContent = 'bereit';
|
||||
buildGutter();
|
||||
}
|
||||
function buildGutter(){
|
||||
const lines = codeArea.value.split('\n').length;
|
||||
let html = '';
|
||||
for(let i=1;i<=lines;i++) html += '<div>'+i+'</div>';
|
||||
gutter.innerHTML = html;
|
||||
gutter.scrollTop = codeArea.scrollTop;
|
||||
}
|
||||
codeArea.addEventListener('input',()=>{ buildGutter(); status.textContent='● ungespeichert'; status.style.color='var(--warning)'; });
|
||||
codeArea.addEventListener('scroll',()=>{ gutter.scrollTop = codeArea.scrollTop; });
|
||||
document.getElementById('btn-save').addEventListener('click',()=>{
|
||||
if(!currentPath) return;
|
||||
fileContents[currentPath] = codeArea.value; original = codeArea.value;
|
||||
status.textContent='✓ gespeichert'; status.style.color='var(--success)';
|
||||
});
|
||||
document.getElementById('btn-revert').addEventListener('click',()=>{
|
||||
codeArea.value = original; buildGutter();
|
||||
status.textContent='zurückgesetzt'; status.style.color='var(--muted)';
|
||||
});
|
||||
|
||||
/* open wp-config.php by default */
|
||||
(function(){
|
||||
const rows = [...document.querySelectorAll('.tree-item')];
|
||||
const cfg = rows.find(r=>r.textContent.trim()==='wp-config.php');
|
||||
if(cfg) openFile('htdocs/wp-config.php','PHP',cfg);
|
||||
})();
|
||||
|
||||
/* =====================================================================
|
||||
CRONJOBS
|
||||
===================================================================== */
|
||||
const cronBody = document.getElementById('cron-body');
|
||||
const cronCount = document.getElementById('cron-count');
|
||||
let crons = [
|
||||
{name:'WP-Cron Hauptlauf', expr:'*/5 * * * *', cmd:'wp cron event run --due-now', last:'vor 2 min', st:'ok'},
|
||||
{name:'WooCommerce Lagerabgleich', expr:'0 * * * *', cmd:'wp wc tool run regenerate_stock', last:'vor 41 min', st:'ok'},
|
||||
{name:'Tägliches Backup', expr:'0 3 * * *', cmd:'clupilot backup create --site=bergmann', last:'heute 03:00', st:'ok'},
|
||||
{name:'Sitemap neu aufbauen', expr:'0 4 * * *', cmd:'wp yoast index --reindex', last:'heute 04:00', st:'ok'},
|
||||
{name:'Transients aufräumen', expr:'0 2 * * 0', cmd:'wp transient delete --expired', last:'vor 3 tagen', st:'warn'},
|
||||
];
|
||||
const moreIco = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>';
|
||||
const runIco = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 4l14 8-14 8z"/></svg>';
|
||||
function renderCron(){
|
||||
cronBody.innerHTML = '';
|
||||
crons.forEach(c=>{
|
||||
const st = c.st==='ok'?'ok':c.st==='bad'?'bad':'warn';
|
||||
const lbl = c.st==='ok'?'aktiv':c.st==='bad'?'fehler':'übersprungen';
|
||||
cronBody.insertAdjacentHTML('beforeend',`
|
||||
<tr>
|
||||
<td><div class="site-name" style="max-width:220px">${c.name}</div></td>
|
||||
<td><span class="pill accent">${c.expr}</span></td>
|
||||
<td><span class="site-meta" style="max-width:280px;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom">${c.cmd}</span></td>
|
||||
<td><span class="site-meta">${c.last}</span></td>
|
||||
<td><span class="health"><span class="health-dot ${st}"></span><span class="health-label">${lbl}</span></span></td>
|
||||
<td class="num"><div class="row-actions"><button title="Jetzt ausführen">${runIco}</button><button title="Mehr">${moreIco}</button></div></td>
|
||||
</tr>`);
|
||||
});
|
||||
cronCount.textContent = crons.length;
|
||||
document.querySelector('.dtab[data-tab="cron"] .count').textContent = crons.length;
|
||||
}
|
||||
renderCron();
|
||||
|
||||
const cronForm = document.getElementById('cron-form');
|
||||
document.getElementById('btn-add-cron').addEventListener('click',()=>{
|
||||
cronForm.style.display = cronForm.style.display==='none'?'':'none';
|
||||
});
|
||||
document.getElementById('cf-cancel').addEventListener('click',()=>{ cronForm.style.display='none'; });
|
||||
document.getElementById('cf-preset').addEventListener('change',e=>{
|
||||
const v = e.target.value;
|
||||
if(v!=='custom') document.getElementById('cf-expr').value = v;
|
||||
});
|
||||
document.getElementById('cf-save').addEventListener('click',()=>{
|
||||
const name = document.getElementById('cf-name').value.trim() || 'Neuer Cronjob';
|
||||
const expr = document.getElementById('cf-expr').value.trim() || '0 3 * * *';
|
||||
const cmd = document.getElementById('cf-cmd').value.trim() || '—';
|
||||
crons.unshift({name, expr, cmd, last:'noch nicht gelaufen', st:'warn'});
|
||||
renderCron();
|
||||
cronForm.style.display='none';
|
||||
document.getElementById('cf-name').value='';
|
||||
});
|
||||
|
||||
/* =====================================================================
|
||||
LOGS
|
||||
===================================================================== */
|
||||
const logData = {
|
||||
access:[
|
||||
['09:12:04','INFO','192.0.2.41 "GET /shop/" 200 18421 — 142ms'],
|
||||
['09:12:05','INFO','203.0.113.7 "GET /wp-json/wc/store/cart" 200 842 — 38ms'],
|
||||
['09:12:09','INFO','192.0.2.41 "POST /?wc-ajax=add_to_cart" 200 311 — 96ms'],
|
||||
['09:12:14','INFO','198.51.100.2 "GET /produkt/espresso-blend/" 200 21044 — 158ms'],
|
||||
['09:12:21','WARN','45.155.* "GET /wp-login.php" 403 — rate-limit'],
|
||||
['09:12:33','INFO','192.0.2.88 "GET /warenkorb/" 200 9920 — 71ms'],
|
||||
['09:12:40','INFO','Googlebot "GET /sitemap_index.xml" 200 4210 — 22ms'],
|
||||
['09:12:48','INFO','192.0.2.41 "POST /checkout/" 302 0 — 210ms'],
|
||||
],
|
||||
error:[
|
||||
['09:05:11','WARN','PHP Notice: Undefined index "shipping_zone" in cart.php:88'],
|
||||
['09:07:42','ERROR','Uncaught TypeError: count(): Argument must be array — class-wc-cart.php:412'],
|
||||
['09:09:03','INFO','Cache geleert: object-cache (redis) — 18421 keys'],
|
||||
['08:58:19','WARN','Slow query (2,1s): wp_bg_postmeta — SELECT meta_value …'],
|
||||
],
|
||||
php:[
|
||||
['09:00:01','OK','php-fpm pool "bergmann" reloaded — 8 worker'],
|
||||
['09:03:55','INFO','OPcache: 4218 scripts cached, 99.2% hit rate'],
|
||||
['09:08:12','WARN','Worker 3: memory peak 214M / 256M'],
|
||||
['09:11:40','INFO','request /checkout/ — 210ms, mem 41M'],
|
||||
],
|
||||
wp:[
|
||||
['09:01:00','DEBUG','WC_Cart::calculate_totals() — 12 items'],
|
||||
['09:04:22','DEBUG','do_action("woocommerce_payment_complete", 48211)'],
|
||||
['09:06:30','WARN','Plugin "Smart Coupons" deprecated hook: woocommerce_add_to_cart'],
|
||||
['09:10:05','DEBUG','wp_mail() → kunde@example.com — Bestellbestätigung #48211'],
|
||||
],
|
||||
};
|
||||
const consoleEl = document.getElementById('console');
|
||||
let currentLog = 'access';
|
||||
function line(t,lvl,msg){
|
||||
return `<div class="log-line"><span class="log-time">${t}</span><span class="log-lvl lvl-${lvl}">${lvl}</span><span class="log-msg">${msg}</span></div>`;
|
||||
}
|
||||
function renderLog(type){
|
||||
currentLog = type;
|
||||
consoleEl.innerHTML = logData[type].map(l=>line(l[0],l[1],l[2])).join('');
|
||||
consoleEl.scrollTop = consoleEl.scrollHeight;
|
||||
document.getElementById('log-title').textContent =
|
||||
({access:'access.log',error:'error.log',php:'php-fpm',wp:'wp-debug'})[type];
|
||||
}
|
||||
document.querySelectorAll('#log-seg button').forEach(b=>b.addEventListener('click',()=>{
|
||||
document.querySelectorAll('#log-seg button').forEach(x=>x.classList.remove('active'));
|
||||
b.classList.add('active');
|
||||
renderLog(b.dataset.log);
|
||||
}));
|
||||
document.getElementById('log-clear').addEventListener('click',()=>{ logData[currentLog]=[]; renderLog(currentLog); });
|
||||
|
||||
const followBtn = document.getElementById('log-follow');
|
||||
followBtn.addEventListener('click',()=>followBtn.classList.toggle('on'));
|
||||
const sampleMsgs = {
|
||||
access:['INFO','"GET /shop/" 200 — 130ms'],
|
||||
error:['INFO','Cache hit ratio 99.3%'],
|
||||
php:['INFO','request handled — 88ms'],
|
||||
wp:['DEBUG','transient set: wc_products_onsale'],
|
||||
};
|
||||
function tick(){
|
||||
if(followBtn.classList.contains('on') && document.getElementById('tab-logs').classList.contains('active')){
|
||||
const now = new Date();
|
||||
const t = String(now.getHours()).padStart(2,'0')+':'+String(now.getMinutes()).padStart(2,'0')+':'+String(now.getSeconds()).padStart(2,'0');
|
||||
const s = sampleMsgs[currentLog];
|
||||
const ip = currentLog==='access' ? '192.0.2.'+(10+Math.floor(Math.random()*240))+' ' : '';
|
||||
logData[currentLog].push([t, s[0], ip+s[1]]);
|
||||
if(logData[currentLog].length>200) logData[currentLog].shift();
|
||||
consoleEl.insertAdjacentHTML('beforeend', line(t,s[0],ip+s[1]));
|
||||
consoleEl.scrollTop = consoleEl.scrollHeight;
|
||||
}
|
||||
}
|
||||
setInterval(tick, 2600);
|
||||
|
||||
})();
|
||||
Loading…
Reference in New Issue