feat: migrate WAHA configuration from system-wide to per-user settings with API key support
This commit is contained in:
48
db.php
48
db.php
@@ -56,41 +56,47 @@ function notify_group($group_id, $message) {
|
||||
function notify_waha($action, $module, $item_name) {
|
||||
global $pdo;
|
||||
try {
|
||||
$u_id = $_SESSION['user_id'] ?? 0;
|
||||
// Fetch all users with WhatsApp channel enabled and WAHA configured
|
||||
$stmt = $pdo->query("SELECT id, username, waha_endpoint, waha_chat_id, waha_template, waha_session, waha_api_key FROM users WHERE notify_channels LIKE '%whatsapp%' AND waha_endpoint IS NOT NULL AND waha_endpoint != ''");
|
||||
$wa_users = $stmt->fetchAll();
|
||||
if (empty($wa_users)) return;
|
||||
|
||||
$current_user_name = "System";
|
||||
if($u_id) {
|
||||
$u = get_user($u_id);
|
||||
if($u) $current_user_name = $u['username'];
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
$u = get_user($_SESSION['user_id']);
|
||||
if ($u) $current_user_name = $u['username'];
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SELECT waha_endpoint, waha_chat_id, waha_template FROM users WHERE notify_channels LIKE '%whatsapp%' AND waha_endpoint IS NOT NULL AND waha_endpoint != ''");
|
||||
while ($user = $stmt->fetch()) {
|
||||
$endpoint = $user['waha_endpoint'];
|
||||
$chat_id = $user['waha_chat_id'];
|
||||
$template = $user['waha_template'] ?: "*Update:* {action} on {module} '{item}' by {user}";
|
||||
|
||||
if(!$endpoint || !$chat_id) continue;
|
||||
|
||||
|
||||
foreach ($wa_users as $wu) {
|
||||
$endpoint = $wu['waha_endpoint'];
|
||||
$chat_id = $wu['waha_chat_id'];
|
||||
if (!$endpoint || !$chat_id) continue;
|
||||
|
||||
$template = $wu['waha_template'] ?: "*Update:* {action} on {module} '{item}' by {user}";
|
||||
$text = str_replace(
|
||||
['{action}', '{module}', '{item}', '{user}'],
|
||||
[$action, $module, $item_name, $current_user_name],
|
||||
$template
|
||||
);
|
||||
|
||||
|
||||
$payload = json_encode([
|
||||
"chatId" => $chat_id,
|
||||
"text" => $text,
|
||||
"session" => "default"
|
||||
"chatId" => $chat_id,
|
||||
"text" => $text,
|
||||
"session" => $wu['waha_session'] ?: 'default'
|
||||
]);
|
||||
|
||||
|
||||
$ch = curl_init($endpoint);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
|
||||
$headers = ['Content-Type: application/json'];
|
||||
if (!empty($wu['waha_api_key'])) {
|
||||
$headers[] = 'X-Api-Key: ' . $wu['waha_api_key'];
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
} catch(Exception $e) {}
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
|
||||
206
views/admin.php
206
views/admin.php
@@ -3,13 +3,15 @@ if(empty($_SESSION['is_admin'])) die("Access Denied");
|
||||
|
||||
// ── DB Migrations ─────────────────────────────────────────────────────────────
|
||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_endpoint TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_chat_id TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_template TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_session TEXT DEFAULT 'default'"); } catch(Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_api_key TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||
try {
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS system_config (k TEXT PRIMARY KEY, v TEXT)");
|
||||
$pdo->exec("INSERT OR IGNORE INTO system_config (k, v) VALUES ('completed_days', '7')");
|
||||
$pdo->exec("INSERT OR IGNORE INTO system_config (k, v) VALUES ('archived_days', '90')");
|
||||
$pdo->exec("INSERT OR IGNORE INTO system_config (k, v) VALUES ('waha_endpoint', '')");
|
||||
$pdo->exec("INSERT OR IGNORE INTO system_config (k, v) VALUES ('waha_chat_id', '')");
|
||||
$pdo->exec("INSERT OR IGNORE INTO system_config (k, v) VALUES ('waha_template', '')");
|
||||
} catch(Exception $e) {}
|
||||
|
||||
// ── Handle POST ───────────────────────────────────────────────────────────────
|
||||
@@ -79,8 +81,13 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if(isset($_POST['update_user_channels'])) {
|
||||
$ch = $_POST['channels'] ?? [];
|
||||
$ch_str = is_array($ch) ? implode(',', $ch) : '';
|
||||
$stmt = $pdo->prepare("UPDATE users SET notify_channels=?, telegram_id=?, email=?, whatsapp_number=? WHERE id=?");
|
||||
$stmt->execute([$ch_str, $_POST['telegram_id'], $_POST['email'], $_POST['whatsapp_number'], $_POST['user_id']]);
|
||||
$waha_ep = trim($_POST['waha_endpoint'] ?? '');
|
||||
$waha_cid = trim($_POST['waha_chat_id'] ?? '');
|
||||
$waha_tpl = trim($_POST['waha_template'] ?? '');
|
||||
$waha_ses = trim($_POST['waha_session'] ?? '') ?: 'default';
|
||||
$waha_key = trim($_POST['waha_api_key'] ?? '');
|
||||
$stmt = $pdo->prepare("UPDATE users SET notify_channels=?, telegram_id=?, email=?, whatsapp_number=?, waha_endpoint=?, waha_chat_id=?, waha_template=?, waha_session=?, waha_api_key=? WHERE id=?");
|
||||
$stmt->execute([$ch_str, $_POST['telegram_id'], $_POST['email'], $_POST['whatsapp_number'], $waha_ep ?: null, $waha_cid ?: null, $waha_tpl ?: null, $waha_ses, $waha_key ?: null, $_POST['user_id']]);
|
||||
$success_msg = "User notification preferences updated!";
|
||||
}
|
||||
|
||||
@@ -88,15 +95,12 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$stmt = $pdo->prepare("UPDATE system_config SET v=? WHERE k=?");
|
||||
$stmt->execute([$_POST['completed_days'] ?? 7, 'completed_days']);
|
||||
$stmt->execute([$_POST['archived_days'] ?? 90, 'archived_days']);
|
||||
$stmt->execute([$_POST['waha_endpoint'] ?? '', 'waha_endpoint']);
|
||||
$stmt->execute([$_POST['waha_chat_id'] ?? '', 'waha_chat_id']);
|
||||
$stmt->execute([$_POST['waha_template'] ?? '', 'waha_template']);
|
||||
$success_msg = "System configuration saved!";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Fetch Data ────────────────────────────────────────────────────────────────
|
||||
$users = query("SELECT id, username, is_admin, notify_channels, telegram_id, email, whatsapp_number, profile_pic FROM users ORDER BY is_admin DESC, username ASC")->fetchAll();
|
||||
$users = query("SELECT id, username, is_admin, notify_channels, telegram_id, email, whatsapp_number, profile_pic, waha_endpoint, waha_chat_id, waha_template, waha_session, waha_api_key FROM users ORDER BY is_admin DESC, username ASC")->fetchAll();
|
||||
$all_groups = query("SELECT * FROM groups ORDER BY name")->fetchAll();
|
||||
$conf_comp = query("SELECT v FROM system_config WHERE k='completed_days'")->fetchColumn() ?: 7;
|
||||
$conf_arch = query("SELECT v FROM system_config WHERE k='archived_days'")->fetchColumn() ?: 90;
|
||||
@@ -368,60 +372,100 @@ $active_tab = $_GET['tab'] ?? 'overview';
|
||||
<div class="admin-tab-pane <?= $active_tab==='channels'?'active':'' ?>" id="tab-channels">
|
||||
<div class="card">
|
||||
<h3 style="font-size:1rem; font-weight:700; margin-bottom:0.5rem;"><i class="fa fa-bell"></i> User Notification Channels</h3>
|
||||
<p style="font-size:0.85rem; color:var(--text-light); margin-bottom:1.25rem;">Configure notification channels (Native, Telegram, Email, WhatsApp) per user.</p>
|
||||
<div style="overflow-x:auto;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Role</th>
|
||||
<th>Channels</th>
|
||||
<th>Telegram ID</th>
|
||||
<th>Email</th>
|
||||
<th>WhatsApp</th>
|
||||
<th>Save</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($users as $u):
|
||||
$chs = explode(',', $u['notify_channels'] ?? '');
|
||||
?>
|
||||
<tr>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="user_id" value="<?= $u['id'] ?>">
|
||||
<td>
|
||||
<div style="display:flex; align-items:center; gap:0.5rem;">
|
||||
<div style="width:28px;height:28px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;flex-shrink:0;overflow:hidden;">
|
||||
<?php if(($u['profile_pic']??null) && file_exists($u['profile_pic'])): ?>
|
||||
<img src="<?= htmlspecialchars($u['profile_pic']) ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<?= strtoupper(mb_substr($u['username'],0,1)) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<strong><?= htmlspecialchars($u['username']) ?></strong>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="<?= $u['is_admin']?'admin-badge-admin':'admin-badge-user' ?>"><?= $u['is_admin']?'Admin':'User' ?></span></td>
|
||||
<td>
|
||||
<div style="display:flex; gap:0.5rem; flex-wrap:wrap;">
|
||||
<?php foreach(['native','email','telegram','whatsapp'] as $ch): ?>
|
||||
<label style="display:flex; align-items:center; gap:3px; font-size:0.8rem; cursor:pointer; white-space:nowrap;">
|
||||
<input type="checkbox" name="channels[]" value="<?= $ch ?>" <?= in_array($ch,$chs)?'checked':'' ?>>
|
||||
<?= ucfirst($ch) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td><input type="text" name="telegram_id" value="<?= htmlspecialchars($u['telegram_id']??'') ?>" class="form-control" style="width:100px; padding:0.35rem;"></td>
|
||||
<td><input type="email" name="email" value="<?= htmlspecialchars($u['email']??'') ?>" class="form-control" style="width:160px; padding:0.35rem;"></td>
|
||||
<td><input type="text" name="whatsapp_number" value="<?= htmlspecialchars($u['whatsapp_number']??'') ?>" class="form-control" style="width:120px; padding:0.35rem;"></td>
|
||||
<td><button type="submit" name="update_user_channels" class="btn btn-sm"><i class="fa fa-save"></i></button></td>
|
||||
</form>
|
||||
</tr>
|
||||
<p style="font-size:0.85rem; color:var(--text-light); margin-bottom:1.25rem;">Configure notification channels per user. WAHA settings appear when <strong>WhatsApp</strong> is enabled.</p>
|
||||
|
||||
<?php foreach($users as $u):
|
||||
$chs = explode(',', $u['notify_channels'] ?? '');
|
||||
$uid = $u['id'];
|
||||
$hasWA = in_array('whatsapp', $chs);
|
||||
?>
|
||||
<div class="card" style="margin-bottom:1rem; padding:1rem; border:1px solid var(--border);">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="user_id" value="<?= $uid ?>">
|
||||
|
||||
<!-- User header row -->
|
||||
<div style="display:flex; align-items:center; gap:0.75rem; flex-wrap:wrap;">
|
||||
<!-- Avatar -->
|
||||
<div style="width:36px;height:36px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:0.85rem;font-weight:700;flex-shrink:0;overflow:hidden;">
|
||||
<?php if(($u['profile_pic']??null) && file_exists($u['profile_pic'])): ?>
|
||||
<img src="<?= htmlspecialchars($u['profile_pic']) ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<?= strtoupper(mb_substr($u['username'],0,1)) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Name & role -->
|
||||
<div style="flex:1;">
|
||||
<div style="font-weight:700; font-size:0.95rem;"><?= htmlspecialchars($u['username']) ?></div>
|
||||
<div><span class="<?= $u['is_admin']?'admin-badge-admin':'admin-badge-user' ?>" style="font-size:0.7rem;"><?= $u['is_admin']?'Admin':'User' ?></span></div>
|
||||
</div>
|
||||
|
||||
<!-- Save button -->
|
||||
<button type="submit" name="update_user_channels" class="btn btn-sm"><i class="fa fa-save"></i> Save</button>
|
||||
</div>
|
||||
|
||||
<!-- Channel checkboxes -->
|
||||
<div style="display:flex; gap:1rem; flex-wrap:wrap; margin-top:0.85rem; padding-top:0.75rem; border-top:1px dashed var(--border);">
|
||||
<?php foreach(['native','email','telegram','whatsapp'] as $ch): ?>
|
||||
<label style="display:flex; align-items:center; gap:5px; font-size:0.85rem; cursor:pointer; font-weight:500;">
|
||||
<input type="checkbox" name="channels[]" value="<?= $ch ?>"
|
||||
<?= in_array($ch,$chs)?'checked':'' ?>
|
||||
<?= $ch==='whatsapp' ? "onchange=\"toggleWAHA({$uid}, this.checked)\"" : '' ?>>
|
||||
<?php
|
||||
$icons = ['native'=>'🔔','email'=>'📧','telegram'=>'✈️','whatsapp'=>'📱'];
|
||||
echo $icons[$ch] . ' ' . ucfirst($ch);
|
||||
?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Extra fields row -->
|
||||
<div style="display:flex; gap:1rem; flex-wrap:wrap; margin-top:0.75rem; align-items:flex-end;">
|
||||
<div class="form-group" style="margin:0; min-width:130px;">
|
||||
<label style="font-size:0.75rem;">Telegram ID</label>
|
||||
<input type="text" name="telegram_id" value="<?= htmlspecialchars($u['telegram_id']??'') ?>" class="form-control" style="padding:0.35rem;" placeholder="@username or ID">
|
||||
</div>
|
||||
<div class="form-group" style="margin:0; min-width:180px;">
|
||||
<label style="font-size:0.75rem;">Email</label>
|
||||
<input type="email" name="email" value="<?= htmlspecialchars($u['email']??'') ?>" class="form-control" style="padding:0.35rem;" placeholder="user@example.com">
|
||||
</div>
|
||||
<div class="form-group" style="margin:0; min-width:140px;">
|
||||
<label style="font-size:0.75rem;">WhatsApp Number</label>
|
||||
<input type="text" name="whatsapp_number" value="<?= htmlspecialchars($u['whatsapp_number']??'') ?>" class="form-control" style="padding:0.35rem;" placeholder="6281234567890">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WAHA expandable section -->
|
||||
<div id="waha-section-<?= $uid ?>" style="<?= $hasWA ? '' : 'display:none;' ?> margin-top:0.75rem; padding:0.85rem; background:rgba(37,211,102,0.06); border:1px solid rgba(37,211,102,0.25); border-radius:8px;">
|
||||
<div style="font-size:0.78rem; font-weight:700; color:#25D366; margin-bottom:0.65rem;"><i class="fab fa-whatsapp"></i> WAHA WhatsApp API Settings</div>
|
||||
<div style="display:flex; gap:1rem; flex-wrap:wrap; align-items:flex-end;">
|
||||
<div class="form-group" style="margin:0; flex:2; min-width:200px;">
|
||||
<label style="font-size:0.75rem;">WAHA Endpoint URL</label>
|
||||
<input type="text" name="waha_endpoint" value="<?= htmlspecialchars($u['waha_endpoint']??'') ?>" class="form-control" style="padding:0.35rem;" placeholder="http://localhost:3000/api/sendText">
|
||||
</div>
|
||||
<div class="form-group" style="margin:0; flex:1; min-width:120px;">
|
||||
<label style="font-size:0.75rem;">Session Name</label>
|
||||
<input type="text" name="waha_session" value="<?= htmlspecialchars($u['waha_session'] ?? 'default') ?>" class="form-control" style="padding:0.35rem;" placeholder="default">
|
||||
</div>
|
||||
<div class="form-group" style="margin:0; flex:1; min-width:160px;">
|
||||
<label style="font-size:0.75rem;">Chat ID</label>
|
||||
<input type="text" name="waha_chat_id" value="<?= htmlspecialchars($u['waha_chat_id']??'') ?>" class="form-control" style="padding:0.35rem;" placeholder="6281234567890@c.us">
|
||||
</div>
|
||||
<div class="form-group" style="margin:0; flex:1; min-width:160px;">
|
||||
<label style="font-size:0.75rem;">API Key <span style="font-weight:400; color:var(--text-light);">(X-Api-Key)</span></label>
|
||||
<input type="password" name="waha_api_key" value="<?= htmlspecialchars($u['waha_api_key']??'') ?>" class="form-control" style="padding:0.35rem; font-family:monospace;" placeholder="Leave blank if not required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:0.6rem;">
|
||||
<label style="font-size:0.75rem;">Message Template</label>
|
||||
<textarea name="waha_template" class="form-control" rows="2" style="padding:0.35rem; font-size:0.8rem;" placeholder="*Alert:* {action} on {module} '{item}' by {user}"><?= htmlspecialchars($u['waha_template']??'') ?></textarea>
|
||||
<div style="font-size:0.72rem; color:var(--text-light); margin-top:3px;">Variables: <code>{action}</code> <code>{module}</code> <code>{item}</code> <code>{user}</code></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -445,31 +489,6 @@ $active_tab = $_GET['tab'] ?? 'overview';
|
||||
</div>
|
||||
<button type="submit" name="update_sys_config" class="btn" style="margin-top:1.25rem;"><i class="fa fa-save"></i> Save Configuration</button>
|
||||
</form>
|
||||
<h3 style="font-size:1rem; font-weight:700; margin-bottom:0.5rem; margin-top:2rem;"><i class="fab fa-whatsapp"></i> WAHA WhatsApp Notifications</h3>
|
||||
<p style="font-size:0.85rem; color:var(--text-light); margin-bottom:1.5rem;">Configure centralized WhatsApp notifications globally via WAHA (WhatsApp HTTP API).</p>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="completed_days" value="<?= htmlspecialchars($conf_comp) ?>">
|
||||
<input type="hidden" name="archived_days" value="<?= htmlspecialchars($conf_arch) ?>">
|
||||
|
||||
<div style="display:grid; grid-template-columns:1fr; gap:1.5rem; max-width:600px;">
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>WAHA Endpoint URL</label>
|
||||
<input type="text" name="waha_endpoint" value="<?= htmlspecialchars($waha_endpoint) ?>" class="form-control" placeholder="http://localhost:3000/api/sendText">
|
||||
<div style="font-size:0.75rem; color:var(--text-light); margin-top:4px;">The full /api/sendText HTTP API endpoint where WAHA is hosted. Leave blank to disable WhatsApp Notification feature.</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>Default Chat ID</label>
|
||||
<input type="text" name="waha_chat_id" value="<?= htmlspecialchars($waha_chat_id) ?>" class="form-control" placeholder="1234567890@c.us or group-id@g.us">
|
||||
<div style="font-size:0.75rem; color:var(--text-light); margin-top:4px;">The centralized group or destination ID.</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>Message Template</label>
|
||||
<textarea name="waha_template" class="form-control" rows="3" placeholder="*System Alert:* {action} on {module} '{item}' by {user}"><?= htmlspecialchars($waha_template) ?></textarea>
|
||||
<div style="font-size:0.75rem; color:var(--text-light); margin-top:4px;">Available Variables: <code>{action}</code>, <code>{module}</code>, <code>{item}</code>, <code>{user}</code></div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" name="update_sys_config" class="btn" style="margin-top:1.25rem;"><i class="fa fa-save"></i> Save WAHA Configuration</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Danger Zone -->
|
||||
@@ -493,9 +512,22 @@ function switchTab(name) {
|
||||
document.querySelectorAll('.admin-tab-pane').forEach(p => p.classList.remove('active'));
|
||||
document.querySelector(`[onclick="switchTab('${name}')"]`).classList.add('active');
|
||||
document.getElementById('tab-'+name).classList.add('active');
|
||||
// Update URL without reload
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('tab', name);
|
||||
history.replaceState({}, '', url);
|
||||
}
|
||||
|
||||
function toggleWAHA(uid, enabled) {
|
||||
const section = document.getElementById('waha-section-' + uid);
|
||||
if (!section) return;
|
||||
if (enabled) {
|
||||
section.style.display = 'block';
|
||||
section.style.animation = 'fadeIn 0.25s ease';
|
||||
} else {
|
||||
section.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@keyframes fadeIn { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:none; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user