mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
b90f753890
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
153 lines
7.9 KiB
PHP
153 lines
7.9 KiB
PHP
<?php
|
|
// NovaCPX User Panel — End-user hosting dashboard
|
|
if (!defined('NOVACPX_ROOT')) define('NOVACPX_ROOT', dirname(__DIR__));
|
|
if (!defined('NOVACPX_VERSION')) define('NOVACPX_VERSION', trim(@file_get_contents(NOVACPX_ROOT . '/VERSION') ?: '1.0.0'));
|
|
$_v = fn($f) => '?v=' . @filemtime(dirname(__DIR__) . $f);
|
|
require_once dirname(__DIR__) . '/_branding.php';
|
|
$_pname = novacpx_panel_name('NovaCPX');
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title><?= $_pname ?> — My Hosting</title>
|
|
<link rel="icon" type="image/svg+xml" href="/assets/img/favicon.svg">
|
|
<link rel="stylesheet" href="/assets/css/nova.css<?= $_v('/assets/css/nova.css') ?>">
|
|
<?php novacpx_branding_head() ?>
|
|
<style>
|
|
/* ── User panel specific ─────────────────────────────── */
|
|
.feature-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
.feature-card {
|
|
background: var(--bg2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 1.25rem;
|
|
text-decoration: none;
|
|
color: var(--text);
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 1rem;
|
|
transition: border-color .15s, transform .1s;
|
|
cursor: pointer;
|
|
}
|
|
.feature-card:hover {
|
|
border-color: var(--primary);
|
|
transform: translateY(-1px);
|
|
}
|
|
.feature-icon {
|
|
width: 44px; height: 44px; flex-shrink: 0;
|
|
border-radius: 10px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.fi-purple { background: rgba(99,102,241,.15); color: var(--primary); }
|
|
.fi-sky { background: rgba(14,165,233,.15); color: var(--sky); }
|
|
.fi-green { background: rgba(16,185,129,.15); color: var(--green); }
|
|
.fi-yellow { background: rgba(245,158,11,.15); color: var(--yellow); }
|
|
.fi-red { background: rgba(239,68,68,.15); color: var(--red); }
|
|
.fi-pink { background: rgba(236,72,153,.15); color: #f472b6; }
|
|
.fi-teal { background: rgba(20,184,166,.15); color: #2dd4bf; }
|
|
.fi-orange { background: rgba(249,115,22,.15); color: #fb923c; }
|
|
.feature-icon svg { width: 22px; height: 22px; }
|
|
.feature-info { flex: 1; min-width: 0; }
|
|
.feature-name { font-weight: 600; font-size: .9rem; margin-bottom: .2rem; }
|
|
.feature-desc { font-size: .78rem; color: var(--text-muted); line-height: 1.4; }
|
|
.feature-meta { font-size: .75rem; color: var(--primary); margin-top: .3rem; }
|
|
|
|
/* Usage ring */
|
|
.usage-rings {
|
|
display: flex; gap: 2rem; align-items: center;
|
|
background: var(--bg2); border: 1px solid var(--border);
|
|
border-radius: 12px; padding: 1.25rem 1.5rem; margin-bottom: 1.5rem;
|
|
}
|
|
.ring-item { text-align: center; }
|
|
.ring-label { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); margin-top: .5rem; }
|
|
.ring-val { font-size: .85rem; font-weight: 600; margin-top: .15rem; }
|
|
svg.ring { transform: rotate(-90deg); }
|
|
svg.ring circle { transition: stroke-dashoffset .5s; }
|
|
|
|
/* Breadcrumb / section tabs */
|
|
.section-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.25rem; }
|
|
.section-header h2 { font-size: 1rem; font-weight: 700; flex: 1; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="panel-layout" id="main-layout" style="display:none">
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="sidebar-brand">
|
|
<?= novacpx_logo_html('<svg class="logo-icon" viewBox="0 0 40 40" fill="none"><circle cx="20" cy="20" r="18" stroke="url(#ulg1)" stroke-width="2"/><path d="M12 28 L20 8 L28 28" stroke="url(#ulg2)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 22 H26" stroke="url(#ulg2)" stroke-width="2" stroke-linecap="round"/><defs><linearGradient id="ulg1" x1="2" y1="2" x2="38" y2="38"><stop offset="0%" stop-color="#6366f1"/><stop offset="100%" stop-color="#0ea5e9"/></linearGradient><linearGradient id="ulg2" x1="12" y1="8" x2="28" y2="28"><stop offset="0%" stop-color="#6366f1"/><stop offset="100%" stop-color="#0ea5e9"/></linearGradient></defs></svg>') ?>
|
|
<span class="logo-text"><?= $_pname ?> <small style="font-size:.65rem;color:var(--text-muted)">My Hosting</small>
|
|
<span style="display:block;font-size:.6rem;color:var(--text-muted);font-weight:400;line-height:1;margin-top:2px">v<?= NOVACPX_VERSION ?></span>
|
|
</span>
|
|
</div>
|
|
<nav id="sidebar-nav"></nav>
|
|
<div class="sidebar-user">
|
|
<div class="sidebar-user-info">
|
|
<div class="avatar" id="user-avatar">U</div>
|
|
<div><div class="user-name" id="user-name">User</div><div class="user-role" id="user-domain">example.com</div></div>
|
|
<a href="#" id="logout-btn" class="btn btn-ghost btn-sm btn-icon" title="Logout" style="margin-left:auto">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<div class="main-content">
|
|
<header class="topbar">
|
|
<button class="btn btn-ghost btn-icon" id="sidebar-toggle" aria-label="Menu"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
|
|
<div class="topbar-title" id="page-title">My Hosting</div>
|
|
<div class="topbar-actions">
|
|
<span id="account-domain" class="text-muted text-sm"></span>
|
|
</div>
|
|
</header>
|
|
<div class="page-content" id="page-content"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="auth-check" style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--bg)">
|
|
<div style="width:100%;max-width:400px;padding:1.5rem">
|
|
<div style="text-align:center;margin-bottom:1.5rem">
|
|
<svg viewBox="0 0 40 40" fill="none" style="width:40px;height:40px;margin:0 auto 1rem">
|
|
<circle cx="20" cy="20" r="18" stroke="url(#ulg3)" stroke-width="2"/>
|
|
<path d="M12 28 L20 8 L28 28" stroke="url(#ulg4)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<path d="M14 22 H26" stroke="url(#ulg4)" stroke-width="2" stroke-linecap="round"/>
|
|
<defs>
|
|
<linearGradient id="ulg3" x1="2" y1="2" x2="38" y2="38"><stop offset="0%" stop-color="#6366f1"/><stop offset="100%" stop-color="#0ea5e9"/></linearGradient>
|
|
<linearGradient id="ulg4" x1="12" y1="8" x2="28" y2="28"><stop offset="0%" stop-color="#6366f1"/><stop offset="100%" stop-color="#0ea5e9"/></linearGradient>
|
|
</defs>
|
|
</svg>
|
|
<div style="font-size:1.4rem;font-weight:300"><?= htmlspecialchars($_pname, ENT_QUOTES, 'UTF-8') ?></div>
|
|
<div style="font-size:.78rem;color:var(--text-muted);margin-top:.25rem;text-transform:uppercase;letter-spacing:.1em">My Hosting</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div id="li-err" class="alert alert-error" style="display:none"></div>
|
|
<form id="login-form" onsubmit="event.preventDefault();doLogin()">
|
|
<div class="form-group"><label>Username or Email</label><input type="text" id="li-user" autofocus autocomplete="username"></div>
|
|
<div class="form-group"><label>Password</label><input type="password" id="li-pass" autocomplete="current-password"></div>
|
|
<button type="submit" class="btn btn-primary btn-full">Sign In</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
window.NOVACPX_BRANDING = <?= json_encode([
|
|
'panel_name' => novacpx_panel_name('NovaCPX'),
|
|
'support_email' => novacpx_get_branding()['support_email'] ?? null,
|
|
'support_url' => novacpx_get_branding()['support_url'] ?? null,
|
|
'hide_powered_by' => !novacpx_powered_by(),
|
|
]) ?>;
|
|
</script>
|
|
<script src="/assets/js/nova.js<?= $_v('/assets/js/nova.js') ?>"></script>
|
|
<script src="/assets/js/user.js<?= $_v('/assets/js/user.js') ?>"></script>
|
|
<!-- user.js boots via DOMContentLoaded and handles all auth/init -->
|
|
</body>
|
|
</html>
|