2026-01-23 10:01:50 -05:00
< ? php
2026-01-28 20:27:15 -05:00
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php' ;
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php' ;
2026-03-27 19:05:42 -04:00
$nonce = SecurityHeadersMiddleware :: getNonce ();
$pageTitle = 'API Keys' ;
$activeNav = 'admin-api-keys' ;
2026-04-04 12:02:30 -04:00
$_v = $GLOBALS [ 'config' ][ 'ASSET_VERSION' ] ? ? '1' ;
$pageStyles = [ " /assets/css/dashboard.css?v= { $_v } " ];
2026-04-04 15:48:30 -04:00
$pageScripts = [ " /assets/js/keyboard-shortcuts.js?v= { $_v } " ];
2026-03-27 19:05:42 -04:00
include __DIR__ . '/../../views/layout_header.php' ;
2026-01-23 10:01:50 -05:00
?>
2026-03-27 19:05:42 -04:00
<div class="lt-page-header">
<div class="lt-flex lt-flex-gap-sm lt-flex-align-center">
<a href="/" class="lt-btn lt-btn-ghost lt-btn-sm">← Dashboard</a>
<span class="lt-text-muted lt-text-xs">/</span>
<span class="lt-text-muted lt-text-xs">Admin: API Keys</span>
</div>
</div>
<!-- Generate new key -->
<div class="lt-frame lt-mb-md">
<span class="lt-frame-bl">╚</span><span class="lt-frame-br">╝</span>
<div class="lt-section-header">Generate New API Key</div>
<div class="lt-section-body">
<form id="generateKeyForm" class="lt-flex lt-flex-wrap lt-flex-gap-sm lt-flex-align-end">
<div class="lt-form-group" style="flex:2;margin:0">
<label class="lt-label" for="keyName">Key Name *</label>
<input type="text" id="keyName" required class="lt-input" placeholder="e.g., CI/CD Pipeline">
</div>
<div class="lt-form-group" style="flex:1;margin:0">
<label class="lt-label" for="expiresIn">Expires In</label>
<select id="expiresIn" class="lt-select">
<option value="">Never</option>
<option value="30">30 days</option>
<option value="90">90 days</option>
<option value="180">180 days</option>
<option value="365">1 year</option>
</select>
</div>
<button type="submit" class="lt-btn lt-btn-primary" style="margin-bottom:0">GENERATE KEY</button>
</form>
<!-- New key display (hidden by default) -->
<div id="newKeyDisplay" class="lt-frame-inner lt-mt-sm is-hidden">
<div class="lt-subsection-header lt-text-amber">⚠ Copy this key now — you won't see it again!</div>
<div class="lt-flex lt-flex-gap-sm lt-mt-sm">
2026-03-31 19:43:18 -04:00
<input type="text" id="newKeyValue" readonly class="lt-input" style="flex:1;font-family:monospace;opacity:1;cursor:text">
2026-03-27 19:05:42 -04:00
<button type="button" class="lt-btn lt-btn-sm" data-action="copy-api-key">COPY</button>
</div>
2026-01-23 10:01:50 -05:00
</div>
2026-03-27 19:05:42 -04:00
</div>
</div>
<!-- Existing keys -->
<div class="lt-frame lt-mb-md">
<span class="lt-frame-bl">╚</span><span class="lt-frame-br">╝</span>
<div class="lt-section-header">Existing API Keys</div>
<div class="lt-section-body">
<div class="lt-table-wrap">
<table class="lt-table lt-table-responsive" aria-label="API keys">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Key Prefix</th>
<th scope="col">Created By</th>
<th scope="col">Created</th>
<th scope="col">Expires</th>
<th scope="col">Last Used</th>
<th scope="col">Status</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($apiKeys)): ?>
<tr><td colspan="8" class="lt-empty">No API keys found. Generate one above.</td></tr>
<?php else: foreach ($apiKeys as $key): ?>
<?php $expired = $key['expires_at'] && strtotime($key['expires_at']) < time(); ?>
2026-03-28 13:22:12 -04:00
<tr id="key-row-<?= (int)$key['api_key_id'] ?>">
2026-03-27 19:05:42 -04:00
<td data-label="Name"><strong><?= htmlspecialchars($key['key_name']) ?></strong></td>
<td data-label="Prefix" class="lt-text-xs"><code><?= htmlspecialchars($key['key_prefix']) ?>…</code></td>
<td data-label="Created By" class="lt-text-xs"><?= htmlspecialchars($key['display_name'] ?? $key['username'] ?? 'Unknown') ?></td>
<td data-label="Created" class="lt-text-xs lt-text-muted"><?= date('Y-m-d H:i', strtotime($key['created_at'])) ?></td>
<td data-label="Expires" class="lt-text-xs <?= $expired ? 'lt-text-danger' : 'lt-text-cyan' ?>">
<?= $key['expires_at'] ? date('Y-m-d', strtotime($key['expires_at'])) . ($expired ? ' (Expired)' : '') : 'Never' ?>
</td>
<td data-label="Last Used" class="lt-text-xs lt-text-muted">
<?= $key['last_used'] ? date('Y-m-d H:i', strtotime($key['last_used'])) : 'Never' ?>
</td>
<td data-label="Status">
<?php if ($key['is_active']): ?>
<span class="lt-status lt-status-open">Active</span>
<?php else: ?>
<span class="lt-status lt-status-closed">Revoked</span>
<?php endif ?>
</td>
<td data-label="Actions">
<?php if ($key['is_active']): ?>
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger"
2026-03-28 13:22:12 -04:00
data-action="revoke-key" data-id="<?= (int)$key['api_key_id'] ?>">REVOKE</button>
2026-03-27 19:05:42 -04:00
<?php else: ?>
<span class="lt-text-muted lt-text-xs">—</span>
<?php endif ?>
</td>
</tr>
<?php endforeach; endif ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- API usage -->
<div class="lt-frame">
<span class="lt-frame-bl">╚</span><span class="lt-frame-br">╝</span>
<div class="lt-section-header">API Usage</div>
<div class="lt-section-body">
<p class="lt-text-sm lt-text-muted">Include the API key in your requests using the Authorization header:</p>
2026-03-27 19:58:14 -04:00
<div class="lt-code-block">
<div class="lt-code-header">
<span class="lt-code-lang">HTTP HEADER</span>
<button type="button" class="lt-code-copy lt-btn-sm"
data-copy="Authorization: Bearer YOUR_API_KEY"
data-copy-toast>COPY</button>
</div>
<pre><code>Authorization: Bearer YOUR_API_KEY</code></pre>
</div>
<p class="lt-text-xs lt-text-muted" style="margin-top:0.5rem">
Example — create a ticket via cURL:<br>
</p>
<div class="lt-code-block">
<div class="lt-code-header"><span class="lt-code-lang">CURL</span></div>
<pre><code>curl -X POST https://your-instance/api/create_ticket.php \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"My ticket","category":"General","type":"Issue","priority":3}'</code></pre>
</div>
<p class="lt-text-xs lt-text-muted" style="margin-top:0.5rem">API keys provide programmatic access to create and manage tickets. Keep keys secure and rotate them regularly.</p>
2026-03-27 19:05:42 -04:00
</div>
</div>
<script nonce="<?= $nonce ?>">
document.addEventListener('click', function (e) {
var target = e.target.closest('[data-action]');
if (!target) return;
switch (target.getAttribute('data-action')) {
2026-03-27 19:58:14 -04:00
case 'copy-api-key': copyApiKey(); break;
case 'revoke-key': revokeKey(target.getAttribute('data-id')); break;
case 'copy-header-example':
navigator.clipboard.writeText('Authorization: Bearer YOUR_API_KEY')
.then(function() { lt.toast.success('Copied!'); })
.catch(function() { lt.toast.error('Copy failed'); });
break;
2026-03-27 19:05:42 -04:00
}
});
document.getElementById('generateKeyForm').addEventListener('submit', function (e) {
e.preventDefault();
var keyName = document.getElementById('keyName').value.trim();
var expiresIn = document.getElementById('expiresIn').value;
if (!keyName) { lt.toast.error('Please enter a key name'); return; }
lt.api.post('/api/generate_api_key.php', { key_name: keyName, expires_in_days: expiresIn || null })
.then(function (data) {
2026-01-23 10:01:50 -05:00
if (data.success) {
document.getElementById('newKeyValue').value = data.api_key;
2026-03-20 21:20:55 -04:00
document.getElementById('newKeyDisplay').classList.remove('is-hidden');
2026-01-23 10:01:50 -05:00
document.getElementById('keyName').value = '';
2026-03-27 19:05:42 -04:00
lt.toast.success('API key generated!');
setTimeout(function () { location.reload(); }, 5000);
2026-01-23 10:01:50 -05:00
} else {
2026-03-20 11:16:18 -04:00
lt.toast.error(data.error || 'Failed to generate API key');
2026-01-23 10:01:50 -05:00
}
2026-03-27 19:05:42 -04:00
}).catch(function (err) { lt.toast.error('Error: ' + err.message); });
});
function copyApiKey() {
2026-03-28 13:22:12 -04:00
var val = document.getElementById('newKeyValue').value;
2026-04-12 00:03:37 -04:00
lt.clipboard.copy(val).then(function (ok) {
if (ok) lt.toast.success('Copied to clipboard!');
else lt.toast.error('Copy failed — select the key manually');
2026-03-28 13:22:12 -04:00
}).catch(function () {
lt.toast.error('Copy failed — select the key manually');
});
2026-03-27 19:05:42 -04:00
}
function revokeKey(keyId) {
showConfirmModal('Revoke API Key', 'Revoke this API key? This cannot be undone.', 'error', function () {
lt.api.post('/api/revoke_api_key.php', { key_id: keyId })
.then(function (data) {
if (data.success) { lt.toast.success('API key revoked'); location.reload(); }
else lt.toast.error(data.error || 'Failed to revoke');
}).catch(function (err) { lt.toast.error('Error: ' + err.message); });
2026-01-23 10:01:50 -05:00
});
2026-03-27 19:05:42 -04:00
}
2026-01-23 10:01:50 -05:00
2026-03-27 19:05:42 -04:00
if (window.lt) lt.keys.initDefaults();
</script>
2026-01-23 10:01:50 -05:00
2026-03-27 19:05:42 -04:00
<?php include __DIR__ . '/../../views/layout_footer.php'; ?>