<?php
/**
* License Admin Panel
* URL: https://tmitstore.com/license/admin.php
* Protect this URL with .htaccess or basic auth for extra security.
*/
session_start();
require_once __DIR__ . '/config.php';
$page_error = '';
$page_success = '';
// ---- Login / logout ----
if (isset($_POST['admin_login'])) {
$pwd = $_POST['admin_password'] ?? '';
if (password_verify($pwd, LIC_ADMIN_HASH)) {
$_SESSION['lic_admin'] = true;
} else {
$page_error = 'Incorrect password.';
}
}
if (isset($_GET['logout'])) {
unset($_SESSION['lic_admin']);
header('Location: admin.php');
exit;
}
$logged_in = !empty($_SESSION['lic_admin']);
// ---- Actions (require login) ----
if ($logged_in) {
// Generate random license key helper
function genKey(): string {
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
$key = [];
for ($g = 0; $g < 4; $g++) {
$part = '';
for ($c = 0; $c < 5; $c++) {
$part .= $chars[random_int(0, strlen($chars) - 1)];
}
$key[] = $part;
}
return implode('-', $key);
}
// Add license
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add') {
$domain = strtolower(trim($_POST['domain'] ?? ''));
$domain = preg_replace('/^www\./', '', $domain);
$customer = trim($_POST['customer'] ?? '');
$notes = trim($_POST['notes'] ?? '');
$expires_at = $_POST['expires_at'] !== '' ? $_POST['expires_at'] : null;
$key = trim($_POST['license_key'] ?? '');
if ($key === '') $key = genKey();
// Validate
if (!$domain) {
$page_error = 'Domain is required.';
} else {
try {
$lic_pdo->prepare(
"INSERT INTO licenses (license_key, domain, product, status, expires_at, customer, notes)
VALUES (?, ?, ?, 'active', ?, ?, ?)"
)->execute([$key, $domain, LIC_PRODUCT, $expires_at, $customer, $notes]);
$page_success = "License added! Key: <strong>{$key}</strong> for domain <strong>{$domain}</strong>";
} catch (PDOException $e) {
$page_error = 'Error: ' . htmlspecialchars($e->getMessage());
}
}
}
// Toggle status
if (isset($_GET['toggle'])) {
$id = (int) $_GET['toggle'];
$lic_pdo->prepare(
"UPDATE licenses SET status = IF(status='active','suspended','active') WHERE id = ?"
)->execute([$id]);
header('Location: admin.php');
exit;
}
// Delete
if (isset($_GET['delete'])) {
$id = (int) $_GET['delete'];
$lic_pdo->prepare("DELETE FROM licenses WHERE id = ?")->execute([$id]);
header('Location: admin.php');
exit;
}
// Update expiry
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_expiry') {
$id = (int) ($_POST['lic_id'] ?? 0);
$exp = $_POST['new_expiry'] !== '' ? $_POST['new_expiry'] : null;
$lic_pdo->prepare("UPDATE licenses SET expires_at = ? WHERE id = ?")->execute([$exp, $id]);
$page_success = 'Expiry date updated.';
}
// Fetch search
$search = trim($_GET['search'] ?? '');
$w_sql = $search ? "WHERE domain LIKE ? OR license_key LIKE ? OR customer LIKE ?" : '';
$w_params = $search ? ["%$search%", "%$search%", "%$search%"] : [];
$licenses_stmt = $lic_pdo->prepare(
"SELECT * FROM licenses {$w_sql} ORDER BY created_at DESC"
);
$licenses_stmt->execute($w_params);
$licenses = $licenses_stmt->fetchAll();
// Stats
$stats = $lic_pdo->query(
"SELECT
COUNT(*) AS total,
SUM(status='active') AS active,
SUM(status='suspended') AS suspended,
SUM(status='expired') AS expired
FROM licenses WHERE product = '" . LIC_PRODUCT . "'"
)->fetch();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>License Admin — tmitstore.com</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
<style>
body { background: #f0f2f5; }
.key-badge { font-family: monospace; font-size: 0.85em; letter-spacing: 1px; }
.stat-num { font-size: 1.8rem; font-weight: 700; }
</style>
</head>
<body>
<nav class="navbar navbar-dark bg-dark px-4">
<span class="navbar-brand fw-bold"><i class="bi bi-shield-lock me-2"></i>License Admin — ISP Stock Manager</span>
<?php if ($logged_in): ?>
<a href="admin.php?logout=1" class="btn btn-outline-light btn-sm">
<i class="bi bi-box-arrow-right"></i> Logout
</a>
<?php endif; ?>
</nav>
<div class="container-fluid px-4 mt-4">
<?php if (!$logged_in): ?>
<!-- ===== Login ===== -->
<div class="row justify-content-center mt-5">
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card-body p-4">
<h5 class="fw-bold mb-3"><i class="bi bi-lock me-2"></i>Admin Login</h5>
<?php if ($page_error): ?>
<div class="alert alert-danger py-2"><?= htmlspecialchars($page_error) ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label class="form-label fw-semibold">Password</label>
<input type="password" name="admin_password" class="form-control" autofocus required>
</div>
<button type="submit" name="admin_login" class="btn btn-dark w-100 fw-semibold">Login</button>
</form>
</div>
</div>
</div>
</div>
<?php else: ?>
<!-- ===== Dashboard ===== -->
<?php if ($page_success): ?>
<div class="alert alert-success alert-dismissible fade show">
<i class="bi bi-check-circle me-2"></i><?= $page_success ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if ($page_error): ?>
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i><?= htmlspecialchars($page_error) ?>
</div>
<?php endif; ?>
<!-- Stats -->
<div class="row g-3 mb-4">
<?php foreach ([
['Total', $stats['total'], 'primary', 'bi-key'],
['Active', $stats['active'], 'success', 'bi-check-circle'],
['Suspended', $stats['suspended'], 'warning', 'bi-pause-circle'],
['Expired', $stats['expired'], 'danger', 'bi-x-circle'],
] as [$label, $val, $cls, $icon]): ?>
<div class="col-6 col-md-3">
<div class="card border-<?= $cls ?> h-100">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<div class="text-muted small"><?= $label ?></div>
<div class="stat-num text-<?= $cls ?>"><?= $val ?></div>
</div>
<i class="bi <?= $icon ?> text-<?= $cls ?> opacity-25" style="font-size:2.5rem;"></i>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="row g-4">
<!-- ===== Add License ===== -->
<div class="col-lg-4">
<div class="card">
<div class="card-header bg-primary text-white fw-semibold">
<i class="bi bi-plus-circle me-1"></i> Issue New License
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="add">
<div class="mb-2">
<label class="form-label fw-semibold small">Customer Domain <span class="text-danger">*</span></label>
<input type="text" name="domain" class="form-control form-control-sm"
placeholder="e.g., client-isp.com" required>
<div class="form-text">www. is stripped automatically.</div>
</div>
<div class="mb-2">
<label class="form-label fw-semibold small">License Key</label>
<input type="text" name="license_key" class="form-control form-control-sm font-monospace"
placeholder="Leave blank to auto-generate">
</div>
<div class="mb-2">
<label class="form-label fw-semibold small">Customer Name</label>
<input type="text" name="customer" class="form-control form-control-sm"
placeholder="Optional">
</div>
<div class="mb-2">
<label class="form-label fw-semibold small">Expires On</label>
<input type="date" name="expires_at" class="form-control form-control-sm">
<div class="form-text">Leave blank = lifetime license.</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold small">Notes</label>
<textarea name="notes" class="form-control form-control-sm" rows="2"
placeholder="Order ID, payment method, etc."></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-sm fw-semibold">
<i class="bi bi-key me-1"></i> Issue License
</button>
</div>
</form>
</div>
</div>
</div>
<!-- ===== License List ===== -->
<div class="col-lg-8">
<div class="card">
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center">
<span><i class="bi bi-list-ul me-1"></i>All Licenses
<span class="badge bg-light text-dark ms-1"><?= count($licenses) ?></span>
</span>
<form class="d-flex gap-2" method="GET">
<input type="text" class="form-control form-control-sm"
name="search" value="<?= htmlspecialchars($search) ?>"
placeholder="Search domain / key…" style="width:200px;">
<button type="submit" class="btn btn-light btn-sm">Go</button>
<?php if ($search): ?>
<a href="admin.php" class="btn btn-outline-light btn-sm">Clear</a>
<?php endif; ?>
</form>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover table-sm mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Domain / Customer</th>
<th>License Key</th>
<th class="text-center">Status</th>
<th>Expires</th>
<th>Last Check</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($licenses)): ?>
<tr><td colspan="6" class="text-center text-muted py-4">No licenses found.</td></tr>
<?php else: ?>
<?php foreach ($licenses as $lic): ?>
<tr>
<td>
<div class="fw-semibold small"><?= htmlspecialchars($lic['domain']) ?></div>
<?php if ($lic['customer']): ?>
<div class="text-muted" style="font-size:0.78em;"><?= htmlspecialchars($lic['customer']) ?></div>
<?php endif; ?>
</td>
<td>
<span class="key-badge badge bg-light text-dark border" title="Click to copy" style="cursor:pointer;"
onclick="navigator.clipboard.writeText('<?= htmlspecialchars($lic['license_key']) ?>');this.textContent='Copied!'
;setTimeout(()=>this.textContent='<?= $lic['license_key'] ?>',1500)">
<?= htmlspecialchars($lic['license_key']) ?>
</span>
</td>
<td class="text-center">
<?php
$cls_map = ['active'=>'success','suspended'=>'warning','expired'=>'danger'];
$cls = $cls_map[$lic['status']] ?? 'secondary';
?>
<span class="badge bg-<?= $cls ?>">
<?= ucfirst($lic['status']) ?>
</span>
</td>
<td class="small text-muted">
<?= $lic['expires_at'] ?? '<span class="badge bg-success">Lifetime</span>' ?>
</td>
<td class="small text-muted">
<?= $lic['last_check'] ? date('d M Y H:i', strtotime($lic['last_check'])) : '—' ?>
</td>
<td class="text-center">
<!-- Toggle status -->
<a href="admin.php?toggle=<?= $lic['id'] ?>"
class="btn btn-sm <?= $lic['status'] === 'active' ? 'btn-warning' : 'btn-success' ?>"
title="<?= $lic['status'] === 'active' ? 'Suspend' : 'Activate' ?>"
onclick="return confirm('<?= $lic['status'] === 'active' ? 'Suspend' : 'Activate' ?> this license?')">
<i class="bi bi-<?= $lic['status'] === 'active' ? 'pause' : 'play' ?>-fill"></i>
</a>
<!-- Delete -->
<a href="admin.php?delete=<?= $lic['id'] ?>"
class="btn btn-sm btn-danger"
title="Delete"
onclick="return confirm('Permanently delete this license?')">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div><!-- /container -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>