<!DOCTYPE html>
<?php
/* Owner-only REPLICATION & FAILOVER dashboard.
   Vars: $baseUrl, $assetsUrl, $user, $report (from ReplicationReport::gather()).
   Self-contained; theme-aware via --pp-* tokens; NO external library / CDN. */
use PpPhp\Core\Replication\ReplicationReport as RR;

if (!isset($baseUrl)) $baseUrl = '';
if (!isset($assetsUrl)) $assetsUrl = rtrim($baseUrl, '/') . '/assets';
$b = rtrim($baseUrl, '/');
$r = $report ?? RR::gather();
$now = (int) ($r['now'] ?? time());
$role = (string) ($r['role'] ?? 'main');
$isMain = $role === 'main';
$e = static fn ($s) => htmlspecialchars((string) $s, ENT_QUOTES);

/* absolute + a live-updating relative span (JS ticks it from data-ts). */
$when = static function (int $ts) use ($now): string {
    if ($ts <= 0) return '<span class="repl-muted">never</span>';
    $abs = gmdate('j M Y, H:i', $ts) . ' UTC';
    return '<span class="repl-when"><span class="repl-abs">' . htmlspecialchars($abs)
        . '</span> <span class="repl-rel" data-ts="' . $ts . '">' . htmlspecialchars(RR::ago($ts, $now)) . '</span></span>';
};
$dur = static function (int $secs): string {
    if ($secs <= 0) return 'now';
    if ($secs < 60) return $secs . 's';
    if ($secs < 3600) return round($secs / 60) . ' min';
    if ($secs < 86400) return round($secs / 3600, 1) . ' h';
    return round($secs / 86400, 1) . ' d';
};
$est = $r['estate'] ?? [];
?>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="robots" content="noindex,nofollow">
    <title><?= $e(\PpPhp\Core\Brand::name()) ?> · Replication</title>
    <?php include constant('BASE_PATH') . '/core/views/favicon.php'; ?>
    <link href="<?= $e($assetsUrl) ?>/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="<?= $e($assetsUrl) ?>/icons/bootstrap-icons.css" rel="stylesheet">
    <script src="<?= $e($assetsUrl) ?>/bootstrap/js/bootstrap.bundle.min.js" defer></script>
    <?php include constant('BASE_PATH') . '/core/views/theme-head.php'; ?>
    <style>
        body { background: var(--pp-app-bg, #f4f6fb); color: var(--pp-text, #172033); padding-top: 56px; }
        .rp-wrap { max-width: 1160px; margin: 0 auto; padding: 1.2rem 1rem 4rem; }
        .rp-top { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
        .rp-back { color: var(--pp-text-muted, #64748b); text-decoration: none; font-weight: 600; font-size: .9rem; }
        .rp-back:hover { color: var(--pp-accent, #4f46e5); }
        .rp-spacer { flex: 1; }
        .rp-btn { border: 1px solid var(--pp-border, #e2e8f0); background: var(--pp-surface, #fff); color: var(--pp-text, #172033);
                  border-radius: 10px; padding: .4rem .8rem; font-size: .85rem; font-weight: 600; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: .4rem; }
        .rp-btn:hover { border-color: var(--pp-accent, #4f46e5); color: var(--pp-accent, #4f46e5); }
        .rp-hero { background: linear-gradient(135deg, var(--pp-accent, #4f46e5), color-mix(in srgb, var(--pp-accent, #4f46e5) 60%, #0ea5e9));
                   color: #fff; border-radius: 18px; padding: 1.5rem 1.7rem; box-shadow: 0 12px 34px rgba(15,23,42,.12); }
        .rp-hero h1 { font-size: 1.5rem; font-weight: 800; margin: .3rem 0 .1rem; letter-spacing: -.01em; }
        .rp-role { display: inline-flex; align-items: center; gap: .4rem; font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .08em;
                   background: rgba(255,255,255,.18); padding: .25rem .7rem; border-radius: 999px; }
        .rp-hero .sub { opacity: .92; font-size: .95rem; }
        .rp-hero code { background: rgba(255,255,255,.16); color: #fff; padding: .05rem .4rem; border-radius: 6px; }
        .rp-live { font-size: .78rem; opacity: .9; display: inline-flex; align-items: center; gap: .4rem; }
        .rp-dot { width: 8px; height: 8px; border-radius: 50%; background: #4ade80; box-shadow: 0 0 0 0 rgba(74,222,128,.7); animation: rp-pulse 2s infinite; }
        @keyframes rp-pulse { 0% { box-shadow: 0 0 0 0 rgba(74,222,128,.6);} 70% { box-shadow: 0 0 0 7px rgba(74,222,128,0);} 100% { box-shadow: 0 0 0 0 rgba(74,222,128,0);} }
        @media (prefers-reduced-motion: reduce) { .rp-dot { animation: none; } }

        .rp-grid { display: grid; gap: 1rem; margin-top: 1.1rem; }
        .rp-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
        .rp-stat { background: var(--pp-surface, #fff); border: 1px solid var(--pp-border, #e6e9f0); border-radius: 14px; padding: 1rem 1.1rem; }
        .rp-stat b { display: block; font-size: 1.7rem; font-weight: 800; line-height: 1.05; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
        .rp-stat span { color: var(--pp-text-muted, #64748b); font-size: .82rem; font-weight: 600; }
        .rp-stat .ic { float: right; font-size: 1.1rem; color: var(--pp-accent, #4f46e5); opacity: .8; }

        .rp-card { background: var(--pp-surface, #fff); border: 1px solid var(--pp-border, #e6e9f0); border-radius: 16px; padding: 1.3rem 1.4rem; }
        .rp-card h2 { font-size: 1.1rem; font-weight: 800; margin: 0 0 .2rem; display: flex; align-items: center; gap: .5rem; }
        .rp-card .lead { color: var(--pp-text-muted, #64748b); font-size: .9rem; margin: 0 0 1rem; }
        .rp-sect-title { font-size: .74rem; font-weight: 800; text-transform: uppercase; letter-spacing: .07em; color: var(--pp-text-muted, #94a3b8);
                         margin: 1.8rem .2rem .7rem; display: flex; align-items: center; gap: .5rem; }
        .rp-sect-title::after { content: ''; flex: 1; height: 1px; background: var(--pp-border, #e6e9f0); }

        .rp-chans { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
        .rp-chan { background: var(--pp-surface, #fff); border: 1px solid var(--pp-border, #e6e9f0); border-radius: 16px; padding: 1.2rem 1.3rem; }
        .rp-chan .ch-h { display: flex; align-items: center; gap: .55rem; font-weight: 800; font-size: 1rem; margin-bottom: .2rem; }
        .rp-chan .ch-cad { color: var(--pp-text-muted, #64748b); font-size: .8rem; }
        .rp-row { display: flex; justify-content: space-between; gap: 1rem; padding: .5rem 0; border-top: 1px dashed var(--pp-border, #e6e9f0); font-size: .9rem; }
        .rp-row:first-of-type { border-top: 0; }
        .rp-row .k { color: var(--pp-text-muted, #64748b); }
        .rp-row .v { font-weight: 600; text-align: right; }

        table.rp-t { width: 100%; border-collapse: collapse; font-size: .9rem; }
        table.rp-t th { text-align: left; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; color: var(--pp-text-muted, #94a3b8);
                        padding: .5rem .6rem; border-bottom: 2px solid var(--pp-border, #e6e9f0); white-space: nowrap; }
        table.rp-t td { padding: .7rem .6rem; border-bottom: 1px solid var(--pp-border, #eef1f6); vertical-align: middle; }
        table.rp-t tr:last-child td { border-bottom: 0; }
        .rp-flow { font-family: ui-monospace, Menlo, monospace; font-size: .8rem; color: var(--pp-text-muted, #64748b); }
        .rp-pill { display: inline-flex; align-items: center; gap: .35rem; font-size: .74rem; font-weight: 700; padding: .18rem .55rem; border-radius: 999px; white-space: nowrap; }
        .rp-pill.ok        { background: color-mix(in srgb, #16a34a 15%, transparent); color: #16a34a; }
        .rp-pill.lagging   { background: color-mix(in srgb, #d97706 16%, transparent); color: #b45309; }
        .rp-pill.stale,
        .rp-pill.revoked   { background: color-mix(in srgb, #dc2626 14%, transparent); color: #dc2626; }
        .rp-pill.suspended { background: color-mix(in srgb, #64748b 16%, transparent); color: #475569; }
        .rp-pill.pending,
        .rp-pill.new       { background: color-mix(in srgb, #2563eb 14%, transparent); color: #2563eb; }
        .rp-when { display: inline-flex; flex-direction: column; }
        .rp-abs { font-variant-numeric: tabular-nums; }
        .rp-rel { color: var(--pp-text-muted, #94a3b8); font-size: .78rem; }
        .rp-muted { color: var(--pp-text-muted, #94a3b8); }
        .rp-note { display: flex; gap: .6rem; align-items: flex-start; background: var(--pp-surface-muted, #f1f5f9); border: 1px solid var(--pp-border, #e6e9f0);
                   border-radius: 12px; padding: .8rem 1rem; font-size: .88rem; color: var(--pp-text-muted, #475569); }
        .rp-empty { text-align: center; padding: 2.5rem 1rem; color: var(--pp-text-muted, #64748b); }
        .rp-empty i { font-size: 2.2rem; opacity: .5; display: block; margin-bottom: .5rem; }
        .rp-ok  { color: #16a34a; } .rp-bad { color: #dc2626; } .rp-warn { color: #b45309; }
        .rp-two { grid-template-columns: 1fr; }
        @media (min-width: 860px) { .rp-two { grid-template-columns: 1.3fr 1fr; } }
    </style>
</head>
<body>
<?php
    // Uniform platform navbar — brand, back link, and the standard right cluster
    // (notifications, theme switcher, account menu). Needs $baseUrl + $user in scope.
    $navBrandLabel = 'Replication';
    $navBrandIcon  = 'hdd-network';
    $navBack = ['url' => $b . '/licensing/server', 'label' => 'Licence server', 'icon' => 'arrow-left'];
    include constant('BASE_PATH') . '/core/views/console-navbar.php';
?>
<div class="rp-wrap">

    <div class="rp-top">
        <div class="rp-spacer"></div>
        <span class="rp-live" data-tip="When this page last loaded its data from the server. The “x ago” label ticks up live — hit Refresh for the newest sync times and health.">
            <span class="rp-dot"></span> <span id="rpUpdated">updated just now</span></span>
        <button class="rp-btn" id="rpRefresh"
                data-tip="Reload right now for the freshest picture — last-sync times, each backup’s health, and what every server is holding.">
            <i class="bi bi-arrow-clockwise"></i> Refresh</button>
        <label class="rp-btn" style="cursor:pointer"
               data-tip="Keep this dashboard live — it quietly re-checks every 60 seconds on its own, so you can leave it open during a failover and watch things stay green. Your choice is remembered on this device.">
            <input type="checkbox" id="rpAuto" style="margin:0 .35rem 0 0"> Auto</label>
        <a class="rp-btn" href="#" data-guide-open="replGuide"
           data-tip="Open the owner guide — how the two sync channels work, why there is no cron to set up, and exactly how to promote a backup if the main ever goes down.">
            <i class="bi bi-question-circle"></i> Guide</a>
    </div>

    <!-- HERO -->
    <div class="rp-hero">
        <span class="rp-role"><i class="bi bi-<?= $isMain ? 'diagram-3' : 'diagram-2' ?>"></i> <?= $isMain ? 'Main server' : 'Backup server' ?></span>
        <h1><i class="bi bi-hdd-network"></i> Replication &amp; failover</h1>
        <?php if ($isMain): ?>
            <div class="sub">This is the <b>writer</b>. It serves the signing key, licences and all data to
                <b><?= (int) ($r['main']['peerCount'] ?? 0) ?></b> backup server<?= ((int) ($r['main']['peerCount'] ?? 0) === 1) ? '' : 's' ?>.
                <?php if (!empty($r['selfUrl'])): ?>Serving at <code><?= $e($r['selfUrl']) ?></code>.<?php endif; ?></div>
        <?php else: ?>
            <div class="sub">This is a <b>read replica</b>. It pulls everything from
                <code><?= $e($r['backup']['mainUrl'] ?? '(not paired)') ?></code>
                <?= !empty($r['backup']['mainLabel']) ? '(' . $e($r['backup']['mainLabel']) . ')' : '' ?>
                and can take over if the main goes down.</div>
        <?php endif; ?>
    </div>

    <?php if ($isMain):
        $m = $r['main'] ?? [];
        $peers = $m['peers'] ?? [];
    ?>
        <!-- MAIN: mesh stat tiles -->
        <div class="rp-grid rp-stats">
            <div class="rp-stat" data-tip="Secondary servers paired to this main. Each one independently keeps its own full, promotable copy of everything — accounts, licences, databases, virtual DBs and projects."><i class="bi bi-diagram-2 ic"></i><b><?= (int) ($m['peerCount'] ?? 0) ?></b><span>Backup servers</span></div>
            <div class="rp-stat" data-tip="Backups that checked in within the last 30 minutes — safely up to date and ready to take over instantly."><i class="bi bi-check-circle ic"></i><b class="<?= ((int)($m['healthyCount']??0) === (int)($m['peerCount']??0) && (int)($m['peerCount']??0)>0) ? 'rp-ok' : '' ?>"><?= (int) ($m['healthyCount'] ?? 0) ?></b><span>Healthy &amp; current</span></div>
            <div class="rp-stat" data-tip="Backups that are live and syncing. Ones you have suspended or revoked are not counted here."><i class="bi bi-activity ic"></i><b><?= (int) ($m['activeCount'] ?? 0) ?></b><span>Active (not suspended)</span></div>
            <div class="rp-stat" data-tip="How long since the least-recently-seen backup last pulled from this server. A big gap usually just means that backup has had little traffic to self-drive it — give it a visit."><i class="bi bi-clock-history ic"></i><b style="font-size:1.15rem"><?= ($m['oldestLag'] ?? null) === null ? '—' : $e($dur((int) $m['oldestLag'])) ?></b><span>Oldest backup last seen</span></div>
        </div>

        <div class="rp-sect-title"><i class="bi bi-diagram-3"></i> Backup servers (the mesh)</div>
        <div class="rp-card" style="padding:.4rem .5rem">
            <?php if (!$peers): ?>
                <div class="rp-empty">
                    <i class="bi bi-hdd-network"></i>
                    <strong>No backup servers paired yet.</strong>
                    <div class="mt-1">Open <a href="<?= $b ?>/licensing/server#tab-servers">Servers &amp; keys → Backup servers</a> to generate a pairing code, then run it on your secondary server.</div>
                </div>
            <?php else: ?>
                <div class="table-responsive">
                <table class="rp-t">
                    <thead><tr>
                        <th>Backup</th><th>Flow</th><th>Status</th><th>Last contact</th><th>From IP</th><th>Paired</th>
                    </tr></thead>
                    <tbody>
                    <?php foreach ($peers as $p): ?>
                        <tr>
                            <td>
                                <strong><?= $e($p['label'] ?: ($p['host'] ?: 'unnamed backup')) ?></strong>
                                <?php if (!empty($p['url'])): ?><div class="rp-muted" style="font-size:.78rem"><?= $e($p['url']) ?></div><?php endif; ?>
                            </td>
                            <td class="rp-flow">main&nbsp;<i class="bi bi-arrow-right"></i>&nbsp;<?= $e($p['host'] ?: 'backup') ?></td>
                            <td><span class="rp-pill <?= $e($p['health']) ?>">
                                <i class="bi bi-<?= $p['health']==='ok'?'check-circle-fill':($p['health']==='suspended'?'pause-circle-fill':($p['health']==='revoked'?'x-circle-fill':'exclamation-circle-fill')) ?>"></i>
                                <?= $e(ucfirst($p['status'] ?: $p['health'])) ?><?= $p['health']==='lagging'?' · lagging':($p['health']==='stale'?' · stale':'') ?>
                            </span></td>
                            <td><?= $when((int) $p['lastSync']) ?></td>
                            <td class="rp-muted" style="font-family:ui-monospace,monospace;font-size:.8rem"><?= $e($p['lastIp'] ?: '—') ?></td>
                            <td><?= $when((int) $p['pairedAt']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
                </div>
            <?php endif; ?>
        </div>

        <div class="rp-note mt-3"><i class="bi bi-info-circle"></i>
            <div>“Last contact” is the most recent time a backup pulled anything from this server — control-plane sync (every 15&nbsp;min) or a data clone. A backup shows <b>lagging</b> after 30&nbsp;min of silence and <b>stale</b> after a day; that usually just means it has had little traffic to self-drive it (see the guide).</div>
        </div>

    <?php else:
        $bk = $r['backup'] ?? [];
        $f = $bk['files'] ?? [];
    ?>
        <!-- BACKUP: sync health tiles -->
        <div class="rp-grid rp-stats">
            <div class="rp-stat"><i class="bi bi-arrow-repeat ic"></i><b style="font-size:1.15rem"><?= (int)($bk['controlLastSync']??0) ? $e(RR::ago((int)$bk['controlLastSync'],$now)) : '—' ?></b><span>Licences &amp; accounts synced</span></div>
            <div class="rp-stat"><i class="bi bi-hdd-network ic"></i><b style="font-size:1.15rem"><?= (int)($bk['cloneLastRun']??0) ? $e(RR::ago((int)$bk['cloneLastRun'],$now)) : 'pending' ?></b><span>Data &amp; files cloned</span></div>
            <div class="rp-stat"><i class="bi bi-hourglass-split ic"></i><b style="font-size:1.15rem"><?= (int)($bk['cloneLastRun']??0) ? $e($dur((int)($bk['cloneNextDue']??0))) : 'soon' ?></b><span>Next data clone</span></div>
            <div class="rp-stat"><i class="bi bi-shield-check ic"></i><b class="<?= empty($bk['controlError']) && !empty($bk['paired']) ? 'rp-ok' : 'rp-warn' ?>" style="font-size:1.15rem"><?= !empty($bk['paired']) ? (empty($bk['controlError']) ? 'Healthy' : 'Warning') : 'Not paired' ?></b><span>Overall status</span></div>
        </div>

        <div class="rp-sect-title"><i class="bi bi-arrow-left-right"></i> The two sync channels</div>
        <div class="rp-grid rp-chans">
            <!-- Control plane -->
            <div class="rp-chan" data-tip="The small, must-be-live records: the signing key, licences, user accounts, plans, and the catalogues of your virtual databases and projects. Pulls every 15 minutes so a failover console is always accurate.">
                <div class="ch-h"><i class="bi bi-arrow-repeat rp-ok"></i> Control plane</div>
                <div class="ch-cad">Licences, user accounts, plans &amp; catalogues · every <?= (int) round(((int)($bk['controlInterval']??900))/60) ?> min · automatic</div>
                <div class="mt-2">
                    <div class="rp-row"><span class="k">Paired to</span><span class="v"><?= $e($bk['mainUrl'] ?: '—') ?></span></div>
                    <div class="rp-row"><span class="k">Last sync</span><span class="v"><?= $when((int) ($bk['controlLastSync'] ?? 0)) ?></span></div>
                    <div class="rp-row"><span class="k">Paired since</span><span class="v"><?= $when((int) ($bk['pairedAt'] ?? 0)) ?></span></div>
                    <div class="rp-row"><span class="k">Signing key</span><span class="v" style="font-family:ui-monospace,monospace;font-size:.8rem"><?= $e($bk['keyId'] ?: '—') ?></span></div>
                    <?php if (!empty($bk['controlError'])): ?>
                        <div class="rp-row"><span class="k rp-bad">Last error</span><span class="v rp-bad" style="max-width:60%"><?= $e($bk['controlError']) ?></span></div>
                    <?php endif; ?>
                </div>
            </div>
            <!-- Data / file clone -->
            <div class="rp-chan" data-tip="The heavy bytes: server databases (restored into safe replica_ copies), the actual virtual-database .sqlite files, and every project’s files. Only what changed is transferred, on a slower schedule the server triggers itself — no cron needed.">
                <div class="ch-h"><i class="bi bi-hdd-network" style="color:var(--pp-accent,#4f46e5)"></i> Data &amp; files</div>
                <div class="ch-cad">Databases, virtual-DB files &amp; project trees · every <?= $e($dur((int)($bk['cloneInterval']??21600))) ?> · self-driving (no cron)</div>
                <div class="mt-2">
                    <div class="rp-row"><span class="k">Automatic</span><span class="v"><?= !empty($bk['cloneEnabled']) ? '<span class="rp-ok">On — self-driven</span>' : '<span class="rp-warn">Off (owner opted out)</span>' ?></span></div>
                    <div class="rp-row"><span class="k">Last clone</span><span class="v"><?= $when((int) ($bk['cloneLastRun'] ?? 0)) ?></span></div>
                    <div class="rp-row"><span class="k">Next clone</span><span class="v"><?= (int)($bk['cloneLastRun']??0) ? ($e($dur((int)($bk['cloneNextDue']??0))) . ($bk['cloneDue']?' · due now':'')) : 'on next visit' ?></span></div>
                    <div class="rp-row"><span class="k">Last result</span><span class="v <?= !empty($bk['cloneLastOk']) ? 'rp-ok' : (($bk['cloneLastResult']??'')!==''?'rp-bad':'') ?>" style="max-width:62%"><?= $e($bk['cloneLastResult'] ?: 'not run yet') ?></span></div>
                </div>
            </div>
        </div>

        <div class="rp-sect-title"><i class="bi bi-hdd-stack"></i> What this backup holds</div>
        <div class="rp-grid rp-stats">
            <div class="rp-stat"><i class="bi bi-database ic"></i><b><?= (int) ($f['vdbFiles'] ?? 0) ?></b><span>Virtual DB files · <?= $e(RR::human((int)($f['vdbBytes']??0))) ?></span></div>
            <div class="rp-stat"><i class="bi bi-folder ic"></i><b><?= (int) ($f['projectCount'] ?? 0) ?></b><span>Projects · <?= $e(RR::human((int)($f['projectBytes']??0))) ?></span></div>
            <div class="rp-stat"><i class="bi bi-people ic"></i><b><?= (int) ($est['users'] ?? 0) ?></b><span>User accounts</span></div>
            <div class="rp-stat"><i class="bi bi-key ic"></i><b><?= (int) ($est['licences'] ?? 0) ?></b><span>Licences</span></div>
        </div>
    <?php endif; ?>

    <!-- WHAT'S PROTECTED (shared) -->
    <div class="rp-sect-title" data-tip="A live count of everything that is copied to every backup. If the main is lost, a promoted backup has all of this — nothing is left behind."><i class="bi bi-shield-lock"></i> Everything under replication</div>
    <div class="rp-grid rp-stats">
        <div class="rp-stat" data-tip="Every owner, admin and user account — with their roles and login details — kept identical on each backup."><b><?= (int) ($est['users'] ?? 0) ?></b><span>User accounts</span></div>
        <div class="rp-stat" data-tip="Licences you have issued, so a backup can answer renewals during an outage exactly as the main would."><b><?= (int) ($est['licences'] ?? 0) ?></b><span>Licences</span></div>
        <div class="rp-stat" data-tip="Individual installations activated against your licences — the seats in use across your estate."><b><?= (int) ($est['instances'] ?? 0) ?></b><span>Installations</span></div>
        <div class="rp-stat" data-tip="Every user’s project: the full design, custom files and in-tree version history — copied file-for-file to each backup."><b><?= (int) ($est['projects'] ?? 0) ?></b><span>Projects</span></div>
        <div class="rp-stat" data-tip="The built-in zero-server SQLite databases. Both the catalogue and the actual .sqlite files are replicated (as consistent copies)."><b><?= (int) ($est['virtualDbs'] ?? 0) ?></b><span>Virtual databases</span></div>
        <div class="rp-stat" data-tip="External server databases you manage. Their content is cloned into safe replica_ copies on each backup."><b><?= (int) ($est['managedDbs'] ?? 0) ?></b><span>Managed databases</span></div>
    </div>
    <div class="rp-note mt-3"><i class="bi bi-magic"></i>
        <div><b>Fully automatic.</b> Both channels run themselves whenever a server receives a request — there is no cron to configure on the hosting side. If the main is ever lost, promote a healthy backup and nothing above is missing.</div>
    </div>

</div>

<script>
(function () {
    // Live relative-time ticking (no server round-trip).
    function rel(ts) {
        var d = Math.floor(Date.now() / 1000) - ts, fut = d < 0; d = Math.abs(d);
        var s = d < 45 ? 'just now' : d < 90 ? 'a minute' : d < 3600 ? Math.round(d/60) + ' minutes'
            : d < 5400 ? 'an hour' : d < 86400 ? Math.round(d/3600) + ' hours'
            : d < 172800 ? 'a day' : d < 2592000 ? Math.round(d/86400) + ' days' : Math.round(d/2592000) + ' months';
        if (s === 'just now') return s;
        return fut ? ('in ' + s) : (s + ' ago');
    }
    function tick() {
        document.querySelectorAll('.rp-rel[data-ts]').forEach(function (el) {
            var ts = parseInt(el.getAttribute('data-ts'), 10);
            if (ts > 0) el.textContent = rel(ts);
        });
    }
    setInterval(tick, 10000); tick();

    // "updated N ago" for the page load itself.
    var loaded = Math.floor(Date.now() / 1000);
    setInterval(function () {
        document.getElementById('rpUpdated').textContent = 'updated ' + rel(loaded);
    }, 5000);

    document.getElementById('rpRefresh').addEventListener('click', function () { location.reload(); });

    // Auto-refresh (full reload) every 60s when toggled; remembered across reloads.
    var auto = document.getElementById('rpAuto');
    auto.checked = localStorage.getItem('rpAuto') === '1';
    var timer = null;
    function apply() {
        localStorage.setItem('rpAuto', auto.checked ? '1' : '0');
        if (timer) { clearInterval(timer); timer = null; }
        if (auto.checked) timer = setInterval(function () { location.reload(); }, 60000);
    }
    auto.addEventListener('change', apply); apply();
})();
</script>

<script src="<?= $e($assetsUrl) ?>/sweetalert2/sweetalert2.min.js"></script>
<?php
$ownerGuide = [
    'id' => 'replGuide',
    'eyebrow' => 'Owner guide',
    'title' => 'Replication & failover',
    'subtitle' => 'How your main server keeps every backup a complete, promotable copy — automatically, with no cron.',
    'icon' => 'hdd-network-fill',
    'topics' => [
        ['id' => 'what', 'label' => 'What replication does', 'icon' => 'stars',
         'keywords' => 'overview backup secondary failover mesh copy replica',
         'body' => '<p class="og-lead">A backup server holds a full, live copy of your main so nothing is lost and there is zero downtime if the main goes down.</p>'
            . '<p>Everything travels one way — the backup <b>pulls</b> from the main (only the backup holds the sync secret, so the main can never be tricked into pushing). The main is the single <b>writer</b>; a backup answers renewals during an outage but never issues on its own.</p>'
            . '<div class="og-tip">You can pair as many backups as you like. Each one independently keeps itself current.</div>'],
        ['id' => 'channels', 'label' => 'The two channels', 'icon' => 'arrow-left-right',
         'keywords' => 'control plane data clone 15 minutes 6 hours cadence',
         'body' => '<h3>Control plane — every 15 minutes</h3><p>The small, must-be-live records: the signing key, licences &amp; installations, user accounts, plans, advisories, and the <b>catalogues</b> of your virtual databases and projects.</p>'
            . '<h3>Data &amp; files — slower (default 6 hours)</h3><p>The heavy bytes: server databases (restored into <code>replica_</code> copies), the actual virtual-database <code>.sqlite</code> files, and every project’s files (designs, custom files, in-tree version history).</p>'
            . '<div class="og-tip">Splitting them keeps the failover console instantly accurate while big data moves on its own, efficient schedule — only what changed is transferred.</div>'],
        ['id' => 'auto', 'label' => 'No cron needed', 'icon' => 'magic',
         'keywords' => 'automatic self driving cron cronless schedule zero config',
         'body' => '<p>Both channels are <b>self-driving</b>. Whenever a backup receives any web request and a sync is due, the server quietly triggers it in the background — after your page has loaded, so nothing is ever slowed down. You configure <b>nothing</b> on the hosting side.</p>'
            . '<div class="og-tip">A busy backup stays perfectly current on its own. The only exception is a backup that gets <em>zero</em> traffic — send it a visit, or click <b>Replicate data &amp; files now</b> on the Licence server page. An optional host cron also exists for that rare case.</div>'],
        ['id' => 'read', 'label' => 'Reading this page', 'icon' => 'table',
         'keywords' => 'timestamps health status lagging stale last contact table',
         'body' => '<p>On the <b>main</b>, the table lists every backup with its <b>last contact</b> (the most recent time it pulled anything), its health, and where it connected from.</p>'
            . '<ul class="og-list"><li><span class="rp-pill ok" style="display:inline-flex">Healthy</span> — checked in within the last 30 minutes.</li>'
            . '<li><span class="rp-pill lagging" style="display:inline-flex">Lagging</span> — quiet for over 30 minutes (usually just low traffic).</li>'
            . '<li><span class="rp-pill stale" style="display:inline-flex">Stale</span> — silent for over a day; give it a visit.</li>'
            . '<li><span class="rp-pill suspended" style="display:inline-flex">Suspended</span> / <span class="rp-pill revoked" style="display:inline-flex">Revoked</span> — you paused or removed it.</li></ul>'
            . '<p>On a <b>backup</b>, the two channel cards show each one’s last sync, the next due time and any error. Every timestamp shows the exact UTC time plus a live “x minutes ago”.</p>'],
        ['id' => 'failover', 'label' => 'If the main goes down', 'icon' => 'life-preserver',
         'keywords' => 'failover promote recover disaster main lost outage',
         'body' => '<p>While the main is down, clients automatically fail over to a healthy backup for renewals, so your customers keep working.</p>'
            . '<p>To make a backup the new permanent main, open its <b>Licence server → Servers &amp; keys</b> and <b>Unpair</b> it — it keeps the synced signing key and all data and becomes the writer. Because this page shows each backup’s freshness, promote the one marked <b>Healthy</b>.</p>'
            . '<div class="og-warn">Only ever run one main at a time. Re-pair the recovered old server as a backup rather than letting two mains issue at once.</div>'],
    ],
];
include constant('BASE_PATH') . '/core/views/owner-ux.php';
?>
</body>
</html>
