Files
quantumbotx/static/js/trading_bots.js
T
Reynov Christian 998f85267d Add start and stop all bots functionality to improve bot management
- Implement `start_all_bots` and `stop_all_bots` functions in `controller.py` to handle starting and stopping all bots.
- Add corresponding API routes in `api_bots.py` for starting and stopping all bots.
- Update `trading_bots.js` to include UI elements and event listeners for starting and stopping all bots.
- Improve `stop_bot` function to prevent race conditions by using `pop()` on `active_bots`.
2025-08-02 00:10:41 +08:00

322 lines
14 KiB
JavaScript

// static/js/trading_bots.js - VERSI PERBAIKAN
document.addEventListener('DOMContentLoaded', function() {
// --- Elemen DOM ---
const tableBody = document.getElementById('bots-table-body');
const modal = document.getElementById('create-bot-modal');
const form = document.getElementById('create-bot-form');
const modalTitle = document.getElementById('modal-title');
const submitBtn = document.getElementById('submit-bot-btn'); // <-- 1. Ambil elemen tombol
const createBotBtn = document.getElementById('create-bot-btn');
const startAllBtn = document.getElementById('start-all-btn');
const stopAllBtn = document.getElementById('stop-all-btn');
const cancelBtn = document.getElementById('cancel-create');
const paramsContainer = document.getElementById('strategy-params-container');
const strategySelect = document.getElementById('strategy');
let currentBotId = null; // Variabel untuk melacak bot yang sedang diedit
// --- Fungsi ---
// Fungsi untuk mengisi nilai parameter strategi saat mengedit bot
function fillStrategyParams(params) {
if (!params) return;
Object.entries(params).forEach(([key, value]) => {
const inputElement = document.getElementById(key);
if (inputElement) {
inputElement.value = value;
}
});
}
// Fungsi untuk memuat daftar strategi ke dalam form
async function loadStrategies() {
try {
const response = await fetch('/api/strategies');
if (!response.ok) throw new Error('Gagal memuat strategi.');
const strategies = await response.json();
if (strategySelect) {
strategySelect.innerHTML = '<option value="" disabled selected>Pilih sebuah strategi</option>';
strategies.forEach(strategy => {
const option = document.createElement('option');
option.value = strategy.id;
option.textContent = strategy.name;
strategySelect.appendChild(option);
});
}
} catch (error) {
console.error('Error loading strategies:', error);
if (strategySelect) {
strategySelect.innerHTML = '<option value="">Gagal memuat strategi</option>';
}
}
}
// Fungsi untuk mengambil dan menampilkan semua bot
async function fetchBots() {
try {
const response = await fetch('/api/bots');
const bots = await response.json();
tableBody.innerHTML = ''; // Kosongkan tabel sebelum mengisi
if (bots.length === 0) {
tableBody.innerHTML = '<tr><td colspan="5" class="text-center p-6 text-gray-500">Belum ada bot yang dibuat.</td></tr>';
return;
}
bots.forEach(bot => {
const statusClass = bot.status === 'Aktif' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800';
const startStopButton = bot.status === 'Aktif'
? `<button data-action="stop" data-id="${bot.id}" class="text-yellow-600 hover:text-yellow-900" title="Hentikan Bot"><i class="fas fa-pause-circle fa-lg"></i></button>`
: `<button data-action="start" data-id="${bot.id}" class="text-green-600 hover:text-green-900" title="Jalankan Bot"><i class="fas fa-play-circle fa-lg"></i></button>`;
const row = `
<tr class="hover:bg-gray-50">
<td class="px-4 py-4">
<div class="font-medium text-gray-900">${bot.name}</div>
<div class="text-sm text-gray-500">${bot.market}</div>
</td>
<td class="px-4 py-4 text-sm text-gray-500">
<div>Lot: ${bot.lot_size}</div>
<div>SL: ${bot.sl_pips} pips | TP: ${bot.tp_pips} pips</div>
</td>
<td class="px-4 py-4 text-sm text-gray-500">
<div>Strategi: ${bot.strategy_name}</div>
<div>TF: ${bot.timeframe} | Interval: ${bot.check_interval_seconds}s</div>
</td>
<td class="px-4 py-4">
<span class="px-2 py-1 rounded-full text-xs font-semibold ${statusClass}">${bot.status}</span>
</td>
<td class="px-4 py-4 text-center">
<div class="flex justify-center items-center gap-4">
${startStopButton}
<a href="/bots/${bot.id}" class="text-blue-600 hover:text-blue-900" title="Lihat Detail & Analisis"><i class="fas fa-chart-line"></i></a>
<button data-action="edit" data-id="${bot.id}" class="text-gray-600 hover:text-gray-900" title="Edit Bot"><i class="fas fa-pencil-alt"></i></button>
<button data-action="delete" data-id="${bot.id}" class="text-red-600 hover:text-red-900" title="Hapus Bot"><i class="fas fa-trash"></i></button>
</div>
</td>
</tr>
`;
tableBody.innerHTML += row;
});
} catch (err) {
console.error("Gagal memuat bot:", err);
tableBody.innerHTML = '<tr><td colspan="5" class="text-center p-6 text-red-500">Gagal memuat data bot.</td></tr>';
}
}
// --- Event Listeners ---
// Buka modal untuk membuat bot baru
createBotBtn.addEventListener("click", () => {
currentBotId = null;
paramsContainer.innerHTML = ''; // Kosongkan parameter
form.reset();
submitBtn.textContent = 'Buat Bot'; // <-- 2. Set teks untuk mode 'Create'
modalTitle.textContent = '🚀 Buat Bot Baru';
// Set nilai default
form.elements.lot_size.value = 0.01;
form.elements.timeframe.value = 'H1';
form.elements.sl_pips.value = 100;
form.elements.tp_pips.value = 200;
form.elements.check_interval_seconds.value = 60;
modal.classList.remove('hidden');
});
// Event listener untuk tombol Start All
startAllBtn.addEventListener('click', async () => {
if (!confirm('Apakah Anda yakin ingin menjalankan semua bot yang sedang dijeda?')) return;
try {
const res = await fetch('/api/bots/start_all', { method: 'POST' });
const result = await res.json();
if (res.ok) {
alert(`✅ ${result.message}`);
fetchBots(); // Refresh tabel
} else {
alert(`❌ ${result.error}`);
}
} catch (err) {
console.error('Error starting all bots:', err);
alert('❌ Gagal terhubung ke server.');
}
});
// Event listener untuk tombol Stop All
stopAllBtn.addEventListener('click', async () => {
if (!confirm('Apakah Anda yakin ingin menghentikan semua bot yang sedang berjalan?')) return;
try {
const res = await fetch('/api/bots/stop_all', { method: 'POST' });
const result = await res.json();
if (res.ok) {
alert(`✅ ${result.message}`);
fetchBots(); // Refresh tabel
} else {
alert(`❌ ${result.error}`);
}
} catch (err) {
console.error('Error stopping all bots:', err);
alert('❌ Gagal terhubung ke server.');
}
});
// Tutup modal
cancelBtn.addEventListener("click", () => {
modal.classList.add('hidden');
});
// Submit form (untuk membuat atau mengedit bot)
form.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => {
if (['lot_size', 'sl_pips', 'tp_pips', 'check_interval_seconds'].includes(key)) {
data[key] = parseFloat(value);
} else {
data[key] = value;
}
});
// Kumpulkan parameter strategi dinamis
const params = {};
const paramInputs = paramsContainer.querySelectorAll('input');
paramInputs.forEach(input => {
params[input.name] = parseFloat(input.value) || input.value;
});
data.params = params;
const url = currentBotId ? `/api/bots/${currentBotId}` : '/api/bots';
const method = currentBotId ? 'PUT' : 'POST';
try {
const res = await fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await res.json();
if (res.ok) {
alert(`✅ ${result.message || 'Operasi berhasil'}`);
modal.classList.add('hidden');
fetchBots(); // Refresh tabel setelah berhasil
} else {
alert(`❌ Gagal: ${result.error || 'Terjadi kesalahan tidak diketahui'}`);
}
} catch (err) {
console.error('Error submitting bot form:', err);
alert("❌ Gagal terhubung ke server.");
}
});
// Event listener untuk tombol aksi di tabel (start, stop, edit, delete)
tableBody.addEventListener('click', async (e) => {
const button = e.target.closest('button[data-action]');
if (!button) return;
const action = button.dataset.action;
const botId = button.dataset.id;
if (action === 'edit') {
try {
const res = await fetch(`/api/bots/${botId}`);
const bot = await res.json();
if (res.ok) {
currentBotId = botId;
submitBtn.textContent = 'Ubah Bot'; // <-- 3. Set teks untuk mode 'Edit'
modalTitle.textContent = '✏️ Edit Bot';
// Isi form dengan data bot yang ada
for (const key in bot) {
if (form.elements[key]) {
form.elements[key].value = bot[key];
}
}
// Trigger perubahan strategi untuk memuat dan mengisi parameter
strategySelect.dispatchEvent(new Event('change', { 'bubbles': true }));
// Isi nilai parameter yang sudah ada
if (bot.strategy_params) {
setTimeout(() => fillStrategyParams(bot.strategy_params), 200); // Beri waktu untuk form dibuat
}
modal.classList.remove('hidden');
} else {
alert(`❌ Gagal memuat data bot: ${bot.error}`);
}
} catch (err) {
console.error(`Error fetching bot ${botId} for edit:`, err);
alert('❌ Gagal terhubung ke server untuk mengedit.');
}
return;
}
if (action === 'delete') {
if (!confirm('Apakah Anda yakin ingin menghapus bot ini?')) {
return;
}
}
let endpoint = `/api/bots/${botId}`;
let method = 'POST';
if (action === 'delete') {
method = 'DELETE';
} else if (action === 'start' || action === 'stop') {
endpoint = `/api/bots/${botId}/${action}`;
}
try {
const res = await fetch(endpoint, { method });
const result = await res.json();
if (res.ok && !result.error) {
alert(`✅ ${result.message || 'Operasi berhasil'}`);
fetchBots(); // Refresh tabel
} else {
alert(`❌ ${result.error || 'Operasi gagal'}`);
}
} catch (err) {
console.error(`Error performing action '${action}' on bot ${botId}:`, err);
alert("❌ Gagal terhubung ke server untuk melakukan aksi.");
}
});
// Event listener untuk dropdown strategi
strategySelect.addEventListener('change', async (e) => {
const strategyId = e.target.value;
paramsContainer.innerHTML = '<p class="text-sm text-gray-500">Memuat parameter...</p>';
if (!strategyId) {
paramsContainer.innerHTML = '';
return;
}
try {
const res = await fetch(`/api/strategies/${strategyId}/params`);
const params = await res.json();
paramsContainer.innerHTML = ''; // Kosongkan lagi
if (params.length > 0) {
params.forEach(param => {
const paramField = `
<div class="col-span-1">
<label for="${param.name}" class="block text-sm font-medium text-gray-700">${param.label}</label>
<input type="${param.type || 'number'}" name="${param.name}" id="${param.name}" value="${param.default}" step="${param.step || 'any'}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
`;
paramsContainer.innerHTML += paramField;
});
} else {
paramsContainer.innerHTML = '<p class="text-sm text-gray-500 col-span-2">Strategi ini tidak memiliki parameter kustom.</p>';
}
} catch (err) {
console.error('Gagal memuat parameter strategi:', err);
paramsContainer.innerHTML = '<p class="text-sm text-red-500 col-span-2">Gagal memuat parameter.</p>';
}
});
// --- Panggilan Awal ---
loadStrategies(); // Muat strategi saat halaman pertama kali dibuka
fetchBots(); // Ambil data bot saat halaman pertama kali dibuka
setInterval(fetchBots, 10000); // Refresh data bot setiap 10 detik
});