beast-agent 2.31.0 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/engine.js +1 -1
- package/src/agent/financetools.js +316 -45
- package/src/agent/finindicators.js +254 -0
- package/src/agent/finrisk.js +132 -0
- package/src/agent/finstats.js +229 -0
- package/src/agent/finwatch.js +110 -0
- package/src/agent/scripts/mt5_bridge.py +65 -0
- package/src/main.js +735 -9
- package/src/preload.js +6 -0
- package/src/renderer/i18n.js +90 -0
- package/src/renderer/index.html +93 -0
- package/src/renderer/renderer.js +490 -12
- package/src/renderer/style.css +36 -0
package/src/renderer/renderer.js
CHANGED
|
@@ -194,6 +194,31 @@ const els = {
|
|
|
194
194
|
finSymSearch: $('#finSymSearch'),
|
|
195
195
|
finSymPick: $('#finSymPick'),
|
|
196
196
|
finSymCount: $('#finSymCount'),
|
|
197
|
+
finWatchdogCb: $('#finWatchdog'),
|
|
198
|
+
finBeOnR: $('#finBeOnR'),
|
|
199
|
+
finTrailStartR: $('#finTrailStartR'),
|
|
200
|
+
finTrailR: $('#finTrailR'),
|
|
201
|
+
finPartialR: $('#finPartialR'),
|
|
202
|
+
finPartialPct: $('#finPartialPct'),
|
|
203
|
+
finMaxDailyLoss: $('#finMaxDailyLoss'),
|
|
204
|
+
finRiskPct: $('#finRiskPct'),
|
|
205
|
+
finMaxPerSymbol: $('#finMaxPerSymbol'),
|
|
206
|
+
finMaxSameSide: $('#finMaxSameSide'),
|
|
207
|
+
finMinMarginLevel: $('#finMinMarginLevel'),
|
|
208
|
+
finDailyLossAction: $('#finDailyLossAction'),
|
|
209
|
+
finNotifyTarget: $('#finNotifyTarget'),
|
|
210
|
+
finWatchDot: $('#finWatchDot'),
|
|
211
|
+
finWatchInfo: $('#finWatchInfo'),
|
|
212
|
+
finAlertList: $('#finAlertList'),
|
|
213
|
+
finReportBtn: $('#finReportBtn'),
|
|
214
|
+
finStatsInfo: $('#finStatsInfo'),
|
|
215
|
+
finStatToday: $('#finStatToday'),
|
|
216
|
+
finStatWeek: $('#finStatWeek'),
|
|
217
|
+
finStatWin: $('#finStatWin'),
|
|
218
|
+
finStatPf: $('#finStatPf'),
|
|
219
|
+
finStatDd: $('#finStatDd'),
|
|
220
|
+
finStatTrades: $('#finStatTrades'),
|
|
221
|
+
finEquityBox: $('#finEquity'),
|
|
197
222
|
stVideoEl: $('#stVideoEl'),
|
|
198
223
|
stVideoName: $('#stVideoName'),
|
|
199
224
|
stVideoOpen: $('#stVideoOpen'),
|
|
@@ -229,6 +254,7 @@ const els = {
|
|
|
229
254
|
cronSchedule: $('#cronSchedule'),
|
|
230
255
|
cronPrompt: $('#cronPrompt'),
|
|
231
256
|
cronAddBtn: $('#cronAddBtn'),
|
|
257
|
+
cronCancel: $('#cronCancel'),
|
|
232
258
|
cronList: $('#cronList'),
|
|
233
259
|
gearBtn: $('#gearBtn'),
|
|
234
260
|
settingsOverlay: $('#settingsOverlay'),
|
|
@@ -1242,6 +1268,7 @@ async function renderActiveSettingsTab() {
|
|
|
1242
1268
|
case 'events': await renderEventsPane(); break;
|
|
1243
1269
|
case 'empati': await renderEmpatiPane(); break;
|
|
1244
1270
|
case 'cron': await openCron(); break;
|
|
1271
|
+
case 'agenda': await renderAgendaPane(); break;
|
|
1245
1272
|
case 'usage': await renderUsagePane(); break;
|
|
1246
1273
|
case 'headroom': await renderHeadroomPane(); break;
|
|
1247
1274
|
case 'agents': await refreshAgentsPane(); break;
|
|
@@ -1327,12 +1354,14 @@ function switchTab(name) {
|
|
|
1327
1354
|
document.querySelectorAll('#setTabs .tab').forEach((b) =>
|
|
1328
1355
|
b.classList.toggle('active', b.dataset.tab === name)
|
|
1329
1356
|
);
|
|
1330
|
-
for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
|
|
1357
|
+
for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'agenda', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
|
|
1331
1358
|
const el = $('#tab-' + p);
|
|
1332
1359
|
if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
|
|
1333
1360
|
}
|
|
1334
1361
|
if (name === 'lang') renderLangPane();
|
|
1335
1362
|
if (name === 'cron') openCron();
|
|
1363
|
+
if (name === 'agenda') renderAgendaPane();
|
|
1364
|
+
if (name === 'email') renderEmailPane();
|
|
1336
1365
|
if (name === 'usage') renderUsagePane();
|
|
1337
1366
|
if (name === 'install') renderInstallPane();
|
|
1338
1367
|
if (name === 'headroom') renderHeadroomPane();
|
|
@@ -2640,7 +2669,25 @@ async function renderEmailPane() {
|
|
|
2640
2669
|
<input id="mailSmtpPort" class="inp" placeholder="465" autocomplete="off" />
|
|
2641
2670
|
<button id="mailSave" class="btn ghost">${_t('mail_save')}</button>
|
|
2642
2671
|
</div>` +
|
|
2643
|
-
'<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>'
|
|
2672
|
+
'<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>' +
|
|
2673
|
+
'<div class="divider"></div>' +
|
|
2674
|
+
'<h2>' + _t('mail_inbox') + '</h2>' +
|
|
2675
|
+
'<div style="display:flex;gap:8px;margin:6px 0;align-items:center">' +
|
|
2676
|
+
'<button id="mailRefresh" class="btn ghost">' + _t('mail_refresh') + '</button>' +
|
|
2677
|
+
'<button id="mailCompose" class="btn ghost">' + _t('mail_compose') + '</button>' +
|
|
2678
|
+
'<span id="mailMsg" class="sub" style="margin:0"></span></div>' +
|
|
2679
|
+
'<div id="mailList"></div>' +
|
|
2680
|
+
'<div id="mailRead" hidden style="margin-top:10px"></div>' +
|
|
2681
|
+
'<div id="mailWrite" hidden style="margin-top:10px">' +
|
|
2682
|
+
'<div class="form-grid" style="grid-template-columns:1fr 1fr">' +
|
|
2683
|
+
'<input id="mwTo" class="inp" placeholder="' + _t('mail_to') + '" autocomplete="off" />' +
|
|
2684
|
+
'<input id="mwSubject" class="inp" placeholder="' + _t('mail_subject') + '" autocomplete="off" />' +
|
|
2685
|
+
'</div>' +
|
|
2686
|
+
'<textarea id="mwBody" class="inp" rows="6" placeholder="' + _t('mail_body') + '" style="margin-top:6px;width:100%"></textarea>' +
|
|
2687
|
+
'<div style="display:flex;gap:8px;margin-top:6px">' +
|
|
2688
|
+
'<button id="mwSend" class="btn">' + _t('mail_send') + '</button>' +
|
|
2689
|
+
'<button id="mwCancel" class="btn ghost">' + _t('btn_close') + '</button>' +
|
|
2690
|
+
'</div></div>';
|
|
2644
2691
|
|
|
2645
2692
|
try {
|
|
2646
2693
|
const em = await beast.getEmail();
|
|
@@ -2672,6 +2719,86 @@ async function renderEmailPane() {
|
|
|
2672
2719
|
if (pass && pass !== PASS_MASK) { savedPass = pass; mailPass.value = PASS_MASK; passChanged = false; }
|
|
2673
2720
|
toast(_t('mail_saved'));
|
|
2674
2721
|
});
|
|
2722
|
+
|
|
2723
|
+
/* ---- gelen kutusu + okuma + gönderme ---- */
|
|
2724
|
+
const mailMsg = $('#mailMsg');
|
|
2725
|
+
const mailList = $('#mailList');
|
|
2726
|
+
const mailRead = $('#mailRead');
|
|
2727
|
+
const mailWrite = $('#mailWrite');
|
|
2728
|
+
const openWrite = (to, subject) => {
|
|
2729
|
+
if (!mailWrite) return;
|
|
2730
|
+
mailWrite.hidden = false;
|
|
2731
|
+
$('#mwTo').value = to || '';
|
|
2732
|
+
$('#mwSubject').value = subject || '';
|
|
2733
|
+
$('#mwBody').value = '';
|
|
2734
|
+
$('#mwBody').focus();
|
|
2735
|
+
};
|
|
2736
|
+
const loadInbox = async () => {
|
|
2737
|
+
if (!mailList) return;
|
|
2738
|
+
if (mailMsg) mailMsg.textContent = '…';
|
|
2739
|
+
if (mailRead) mailRead.hidden = true;
|
|
2740
|
+
const r = await beast.emailList({ limit: 15 }).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
2741
|
+
if (mailMsg) mailMsg.textContent = '';
|
|
2742
|
+
if (!r || r.ok === false) {
|
|
2743
|
+
mailList.innerHTML = '<div class="sub">' + escapeHtml((r && r.error) || '?') + '</div>';
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
const msgs = r.messages || [];
|
|
2747
|
+
mailList.innerHTML = msgs.length
|
|
2748
|
+
? msgs
|
|
2749
|
+
.map(
|
|
2750
|
+
(m) =>
|
|
2751
|
+
'<div class="cj-job" data-uid="' + escapeHtml(String(m.uid)) + '" style="cursor:pointer">' +
|
|
2752
|
+
'<div class="cj-info"><div class="cj-name">' + escapeHtml(m.subject || '(konu yok)') + '</div>' +
|
|
2753
|
+
'<div class="cj-meta">' + escapeHtml(m.from || '') + ' · ' + escapeHtml(fmtWhen(m.date)) + '</div></div></div>'
|
|
2754
|
+
)
|
|
2755
|
+
.join('')
|
|
2756
|
+
: '<div class="sub">' + _t('mail_empty') + '</div>';
|
|
2757
|
+
mailList.querySelectorAll('[data-uid]').forEach((row) =>
|
|
2758
|
+
row.addEventListener('click', async () => {
|
|
2759
|
+
const uid = Number(row.dataset.uid);
|
|
2760
|
+
const subjectEl = row.querySelector('.cj-name');
|
|
2761
|
+
if (mailRead) {
|
|
2762
|
+
mailRead.hidden = false;
|
|
2763
|
+
mailRead.innerHTML = '<div class="sub">…</div>';
|
|
2764
|
+
}
|
|
2765
|
+
const rr = await beast.emailRead(uid).catch(() => null);
|
|
2766
|
+
if (!mailRead) return;
|
|
2767
|
+
if (rr && rr.ok) {
|
|
2768
|
+
mailRead.innerHTML =
|
|
2769
|
+
'<div class="sub" style="white-space:pre-wrap;max-height:220px;overflow:auto">' +
|
|
2770
|
+
escapeHtml(rr.content || '') +
|
|
2771
|
+
'</div><button id="mailReply" class="btn ghost" style="margin-top:6px">' + _t('mail_reply') + '</button>';
|
|
2772
|
+
const rp = $('#mailReply');
|
|
2773
|
+
if (rp)
|
|
2774
|
+
rp.addEventListener('click', () =>
|
|
2775
|
+
openWrite('', 'Re: ' + (subjectEl ? subjectEl.textContent : ''))
|
|
2776
|
+
);
|
|
2777
|
+
} else {
|
|
2778
|
+
mailRead.innerHTML = '<div class="sub">' + escapeHtml((rr && rr.error) || _t('mail_read_fail')) + '</div>';
|
|
2779
|
+
}
|
|
2780
|
+
})
|
|
2781
|
+
);
|
|
2782
|
+
};
|
|
2783
|
+
if ($('#mailRefresh')) $('#mailRefresh').addEventListener('click', loadInbox);
|
|
2784
|
+
if ($('#mailCompose')) $('#mailCompose').addEventListener('click', () => openWrite('', ''));
|
|
2785
|
+
if ($('#mwCancel')) $('#mwCancel').addEventListener('click', () => { if (mailWrite) mailWrite.hidden = true; });
|
|
2786
|
+
if ($('#mwSend')) $('#mwSend').addEventListener('click', async () => {
|
|
2787
|
+
const to = $('#mwTo').value.trim();
|
|
2788
|
+
const subject = $('#mwSubject').value.trim();
|
|
2789
|
+
const body = $('#mwBody').value;
|
|
2790
|
+
if (!to) { toast(_t('mail_need_to')); return; }
|
|
2791
|
+
const r = await beast.emailSend({ to, subject, body }).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
2792
|
+
if (r && r.ok) {
|
|
2793
|
+
toast(_t('mail_sent'));
|
|
2794
|
+
if (mailWrite) mailWrite.hidden = true;
|
|
2795
|
+
loadInbox();
|
|
2796
|
+
} else {
|
|
2797
|
+
toast((r && r.error) || _t('mail_send_fail'));
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
/* ayar kayıtlıysa ve sekme GÖRÜNÜRse gelen kutusunu yükle (boşuna IMAP bağlantısı yok) */
|
|
2801
|
+
if (savedPass && $('#mailUser').value.trim() && !pane.hidden) loadInbox();
|
|
2675
2802
|
}
|
|
2676
2803
|
|
|
2677
2804
|
/* ---------------- integrations (WhatsApp) ---------------- */
|
|
@@ -5572,7 +5699,15 @@ function renderAgentsPane() {
|
|
|
5572
5699
|
/* ---------------- events from engine ---------------- */
|
|
5573
5700
|
|
|
5574
5701
|
function onEvent(ev) {
|
|
5575
|
-
|
|
5702
|
+
if (ev.type === 'notify-click') {
|
|
5703
|
+
/* toast tıklandı: ilgili panele atla */
|
|
5704
|
+
if (ev.target === 'watchers') openWatchModal();
|
|
5705
|
+
else if (ev.target === 'cron') openSettings().then(() => switchTab('cron')).catch(() => {});
|
|
5706
|
+
else if (ev.target === 'agents') { toggleRail(true); refreshAgentsPane(); }
|
|
5707
|
+
else openSettings().catch(() => {});
|
|
5708
|
+
return;
|
|
5709
|
+
}
|
|
5710
|
+
if (ev.type === 'sessions') { refreshSessions(); return; }
|
|
5576
5711
|
if (ev.type === 'approval') {
|
|
5577
5712
|
/* Beast Studio oturumunun onayı: panelde ipucu + kart sohbete düşer */
|
|
5578
5713
|
if (stSessionId && ev.sessionId === stSessionId) {
|
|
@@ -6333,6 +6468,13 @@ async function openWatchLogModal(id) {
|
|
|
6333
6468
|
}
|
|
6334
6469
|
|
|
6335
6470
|
let lastCronKey = '';
|
|
6471
|
+
/* Cron DÜZENLEME: satıra tıkla → form dolar, buton "Güncelle"ye döner */
|
|
6472
|
+
let editingCronId = '';
|
|
6473
|
+
function resetCronEdit() {
|
|
6474
|
+
editingCronId = '';
|
|
6475
|
+
if (els.cronAddBtn) els.cronAddBtn.textContent = _t('cron_add');
|
|
6476
|
+
if (els.cronCancel) els.cronCancel.hidden = true;
|
|
6477
|
+
}
|
|
6336
6478
|
async function renderCronModal(force) {
|
|
6337
6479
|
let jobs = [];
|
|
6338
6480
|
try { jobs = (await beast.cronList()) || []; } catch {}
|
|
@@ -6755,18 +6897,116 @@ function renderCronList(jobs) {
|
|
|
6755
6897
|
btns.appendChild(mkBtn(j.enabled ? '❚❚' : '▶', j.enabled ? _t('cr_pause') : _t('cr_start'), async () => { await beast.cronToggle(j.id); refreshCron(); }));
|
|
6756
6898
|
btns.appendChild(mkBtn('×', _t('cr_del'), async () => { await beast.cronDelete(j.id); refreshCron(); }));
|
|
6757
6899
|
row.appendChild(btns);
|
|
6758
|
-
|
|
6759
|
-
|
|
6900
|
+
const loadEdit = () => {
|
|
6901
|
+
editingCronId = j.id;
|
|
6902
|
+
els.cronName.value = j.name || '';
|
|
6760
6903
|
els.cronPreset.value = '__custom';
|
|
6761
|
-
els.cronSchedule.value = j.schedule;
|
|
6762
|
-
els.cronPrompt.value = j.prompt;
|
|
6763
|
-
|
|
6904
|
+
els.cronSchedule.value = j.schedule || '';
|
|
6905
|
+
els.cronPrompt.value = j.prompt || '';
|
|
6906
|
+
if (els.cronAddBtn) els.cronAddBtn.textContent = _t('cr_update');
|
|
6907
|
+
if (els.cronCancel) els.cronCancel.hidden = false;
|
|
6908
|
+
els.cronName.focus();
|
|
6909
|
+
toast(_t('cr_edit_loaded'));
|
|
6910
|
+
};
|
|
6911
|
+
dot.addEventListener('click', loadEdit);
|
|
6912
|
+
info.addEventListener('click', loadEdit);
|
|
6913
|
+
dot.title = (j.enabled ? _t('cr_dot_on') : _t('cr_dot_off')) + ' — ' + _t('cr_edit_hint');
|
|
6764
6914
|
els.cronList.appendChild(row);
|
|
6765
6915
|
}
|
|
6766
6916
|
}
|
|
6767
6917
|
|
|
6768
6918
|
function openCron() { refreshCron(); }
|
|
6769
6919
|
|
|
6920
|
+
/* ---------------- AJANDA: yaklaşan cron + hatırlatıcı + izleyiciler ---------------- */
|
|
6921
|
+
|
|
6922
|
+
function isReminderLike(j) {
|
|
6923
|
+
if (!j) return false;
|
|
6924
|
+
if (j.kind === 'reminder') return true;
|
|
6925
|
+
const n = String(j.name || '');
|
|
6926
|
+
const p = String(j.prompt || '');
|
|
6927
|
+
return (
|
|
6928
|
+
/^Hatırlatma:/.test(n) ||
|
|
6929
|
+
/^Tekrarlı hatırlatma:/.test(n) ||
|
|
6930
|
+
p.startsWith('[HATIRLATMA ZAMANI]') ||
|
|
6931
|
+
p.startsWith('[TEKRARLI HATIRLATMA]')
|
|
6932
|
+
);
|
|
6933
|
+
}
|
|
6934
|
+
|
|
6935
|
+
function relTime(ms) {
|
|
6936
|
+
const diff = ms - Date.now();
|
|
6937
|
+
const abs = Math.abs(diff);
|
|
6938
|
+
const dk = Math.round(abs / 60000);
|
|
6939
|
+
const saat = Math.floor(dk / 60);
|
|
6940
|
+
const gun = Math.floor(saat / 24);
|
|
6941
|
+
let label;
|
|
6942
|
+
if (dk < 1) return _t('aj_now');
|
|
6943
|
+
else if (dk < 60) label = dk + ' dk';
|
|
6944
|
+
else if (saat < 24) label = saat + ' sa';
|
|
6945
|
+
else label = gun + ' gün';
|
|
6946
|
+
return diff >= 0 ? label + ' ' + _t('aj_later') : label + ' ' + _t('aj_ago');
|
|
6947
|
+
}
|
|
6948
|
+
|
|
6949
|
+
async function renderAgendaPane() {
|
|
6950
|
+
const pane = $('#tab-agenda');
|
|
6951
|
+
if (!pane) return;
|
|
6952
|
+
let jobs = [];
|
|
6953
|
+
let ws = [];
|
|
6954
|
+
try { jobs = (await beast.cronList()) || []; } catch {}
|
|
6955
|
+
try { ws = (await beast.watchersList()) || []; } catch {}
|
|
6956
|
+
const items = [];
|
|
6957
|
+
for (const j of jobs) {
|
|
6958
|
+
if (!j.enabled) continue;
|
|
6959
|
+
const t = j.nextRunAt ? new Date(j.nextRunAt).getTime() : 0;
|
|
6960
|
+
if (!t) continue;
|
|
6961
|
+
items.push({ type: isReminderLike(j) ? 'rem' : 'cron', name: j.name || j.id, at: t, meta: j.schedule, id: j.id });
|
|
6962
|
+
}
|
|
6963
|
+
for (const w of ws) {
|
|
6964
|
+
if (!w.enabled) continue;
|
|
6965
|
+
const every = w.everySec ? w.everySec * 1000 : (w.everyMin || 15) * 60000;
|
|
6966
|
+
const last = w.lastCheckAt ? new Date(w.lastCheckAt).getTime() : 0;
|
|
6967
|
+
items.push({
|
|
6968
|
+
type: 'watch',
|
|
6969
|
+
name: w.name,
|
|
6970
|
+
at: last ? last + every : Date.now(),
|
|
6971
|
+
meta: opText(w) + ' · her ' + fmtEvery(w),
|
|
6972
|
+
id: w.id,
|
|
6973
|
+
});
|
|
6974
|
+
}
|
|
6975
|
+
items.sort((a, b) => a.at - b.at);
|
|
6976
|
+
const badge = { cron: '🕐', rem: '⏰', watch: '👁' };
|
|
6977
|
+
const rows = items.length
|
|
6978
|
+
? items
|
|
6979
|
+
.map(
|
|
6980
|
+
(it) =>
|
|
6981
|
+
'<div class="cron-job ag-item" data-type="' + it.type + '" data-id="' + escapeHtml(it.id) + '" style="cursor:pointer">' +
|
|
6982
|
+
'<div class="cj-info"><div class="cj-name">' + badge[it.type] + ' ' + escapeHtml(it.name) + '</div>' +
|
|
6983
|
+
'<div class="cj-meta">' + escapeHtml(String(it.meta || '')) + ' · <b>' + escapeHtml(relTime(it.at)) + '</b> · ' + escapeHtml(fmtTime(it.at)) + '</div></div></div>'
|
|
6984
|
+
)
|
|
6985
|
+
.join('')
|
|
6986
|
+
: '<div class="cron-empty">' + _t('aj_empty') + '</div>';
|
|
6987
|
+
let notifOn = true;
|
|
6988
|
+
try { notifOn = !!(await beast.notifyGet()).toast; } catch {}
|
|
6989
|
+
pane.innerHTML =
|
|
6990
|
+
'<h2>' + _t('aj_h2') + '</h2><div class="sub">' + _t('aj_sub') + '</div>' +
|
|
6991
|
+
'<label class="lock-row" style="margin-top:8px"><input type="checkbox" id="ajNotify" ' + (notifOn ? 'checked' : '') + '/><span>' + _t('aj_notify') + '</span></label>' +
|
|
6992
|
+
'<div style="margin-top:10px">' + rows + '</div>';
|
|
6993
|
+
const nj = $('#ajNotify');
|
|
6994
|
+
if (nj) nj.addEventListener('change', async (e) => {
|
|
6995
|
+
await beast.notifySet(e.target.checked).catch(() => {});
|
|
6996
|
+
toast(e.target.checked ? _t('aj_notify_on') : _t('aj_notify_off'));
|
|
6997
|
+
});
|
|
6998
|
+
pane.querySelectorAll('.ag-item').forEach((row) => {
|
|
6999
|
+
row.addEventListener('click', () => {
|
|
7000
|
+
if (row.dataset.type === 'watch') {
|
|
7001
|
+
closeSettings();
|
|
7002
|
+
openWatchModal();
|
|
7003
|
+
} else {
|
|
7004
|
+
switchTab('cron');
|
|
7005
|
+
}
|
|
7006
|
+
});
|
|
7007
|
+
});
|
|
7008
|
+
}
|
|
7009
|
+
|
|
6770
7010
|
/* ---------------- boot ---------------- */
|
|
6771
7011
|
|
|
6772
7012
|
async function init() {
|
|
@@ -7161,6 +7401,59 @@ async function init() {
|
|
|
7161
7401
|
if (els.watchLogOverlay) els.watchLogOverlay.addEventListener('click', (e) => {
|
|
7162
7402
|
if (e.target === els.watchLogOverlay) closeWatchLogModal();
|
|
7163
7403
|
});
|
|
7404
|
+
|
|
7405
|
+
/* watcher EKLEME formu: tür seçimine göre alanlar açılır/kapanır */
|
|
7406
|
+
const wKindSel = $('#wAddKind');
|
|
7407
|
+
const wSyncKind = () => {
|
|
7408
|
+
const k = wKindSel ? wKindSel.value : 'web';
|
|
7409
|
+
const web = $('#wAddWeb');
|
|
7410
|
+
const cond = $('#wAddCond');
|
|
7411
|
+
const logs = $('#wAddLogs');
|
|
7412
|
+
if (web) web.hidden = k !== 'web';
|
|
7413
|
+
if (cond) cond.hidden = k === 'logs';
|
|
7414
|
+
if (logs) logs.hidden = k !== 'logs';
|
|
7415
|
+
};
|
|
7416
|
+
if (wKindSel) {
|
|
7417
|
+
wKindSel.addEventListener('change', wSyncKind);
|
|
7418
|
+
wSyncKind();
|
|
7419
|
+
}
|
|
7420
|
+
const wAddBtn = $('#wAddBtn');
|
|
7421
|
+
if (wAddBtn) {
|
|
7422
|
+
wAddBtn.addEventListener('click', async () => {
|
|
7423
|
+
const kind = wKindSel ? wKindSel.value : 'web';
|
|
7424
|
+
const input = {
|
|
7425
|
+
name: $('#wAddName').value.trim(),
|
|
7426
|
+
kind,
|
|
7427
|
+
everyMin: Number($('#wAddEvery').value) || 15,
|
|
7428
|
+
};
|
|
7429
|
+
if (kind === 'web') {
|
|
7430
|
+
input.url = $('#wAddUrl').value.trim();
|
|
7431
|
+
input.path = $('#wAddPath').value.trim();
|
|
7432
|
+
}
|
|
7433
|
+
if (kind !== 'logs') {
|
|
7434
|
+
input.op = $('#wAddOp').value;
|
|
7435
|
+
const v = $('#wAddValue').value.trim();
|
|
7436
|
+
input.value = input.op === 'changed' || v === '' ? null : (Number.isNaN(Number(v)) ? v : Number(v));
|
|
7437
|
+
} else {
|
|
7438
|
+
input.level = $('#wAddLevel').value;
|
|
7439
|
+
input.windowMin = Number($('#wAddWindow').value) || 10;
|
|
7440
|
+
input.re = $('#wAddRe').value.trim();
|
|
7441
|
+
input.op = 'gt';
|
|
7442
|
+
input.value = 0;
|
|
7443
|
+
}
|
|
7444
|
+
const r = await beast.watchersAdd(input).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
7445
|
+
if (!r || r.ok === false) { toast((r && r.error) || 'İzleyici eklenemedi'); return; }
|
|
7446
|
+
for (const id of ['#wAddName', '#wAddUrl', '#wAddPath', '#wAddValue', '#wAddRe']) {
|
|
7447
|
+
const el = $(id);
|
|
7448
|
+
if (el) el.value = '';
|
|
7449
|
+
}
|
|
7450
|
+
lastWatchKey = '';
|
|
7451
|
+
lastWatchPaneKey = '';
|
|
7452
|
+
await renderWatchersModal(true);
|
|
7453
|
+
await renderWatchersPane(true);
|
|
7454
|
+
toast(_t('w_added'));
|
|
7455
|
+
});
|
|
7456
|
+
}
|
|
7164
7457
|
if (els.cronOverlay) els.cronOverlay.addEventListener('click', (e) => {
|
|
7165
7458
|
if (e.target === els.cronOverlay) closeCronModal();
|
|
7166
7459
|
});
|
|
@@ -7173,12 +7466,26 @@ async function init() {
|
|
|
7173
7466
|
const schedule = els.cronSchedule.value.trim();
|
|
7174
7467
|
const prompt = els.cronPrompt.value.trim();
|
|
7175
7468
|
if (!schedule || !prompt) { toast('Cron ifadesi ve görev metni gerekli'); return; }
|
|
7176
|
-
|
|
7177
|
-
|
|
7469
|
+
if (editingCronId) {
|
|
7470
|
+
const r = await beast.cronUpdate(editingCronId, { name, schedule, prompt });
|
|
7471
|
+
if (!r || r.ok === false) { toast((r && r.error) || 'Güncellenemedi'); return; }
|
|
7472
|
+
toast(_t('cr_updated'));
|
|
7473
|
+
} else {
|
|
7474
|
+
const r = await beast.cronAdd({ name, schedule, prompt });
|
|
7475
|
+
if (!r.ok) { toast(r.error || 'Eklenemedi'); return; }
|
|
7476
|
+
toast(_t('cr_added'));
|
|
7477
|
+
}
|
|
7178
7478
|
els.cronName.value = ''; els.cronSchedule.value = ''; els.cronPrompt.value = '';
|
|
7179
|
-
|
|
7479
|
+
resetCronEdit();
|
|
7180
7480
|
refreshCron();
|
|
7181
7481
|
});
|
|
7482
|
+
if (els.cronCancel) {
|
|
7483
|
+
els.cronCancel.addEventListener('click', () => {
|
|
7484
|
+
els.cronName.value = ''; els.cronSchedule.value = ''; els.cronPrompt.value = '';
|
|
7485
|
+
resetCronEdit();
|
|
7486
|
+
toast(_t('cr_edit_cancelled'));
|
|
7487
|
+
});
|
|
7488
|
+
}
|
|
7182
7489
|
document.addEventListener('mousemove', (e) => {
|
|
7183
7490
|
if (!rz) return;
|
|
7184
7491
|
const w = Math.max(300, Math.min(rz.sw - (e.clientX - rz.sx), window.innerWidth - 340));
|
|
@@ -9074,6 +9381,116 @@ function finRenderTrader(trader, cfg) {
|
|
|
9074
9381
|
}
|
|
9075
9382
|
}
|
|
9076
9383
|
|
|
9384
|
+
/* ---- RİSK OTOMASYONU + PERFORMANS panosu ---- */
|
|
9385
|
+
function finDayKey(ms) {
|
|
9386
|
+
const d = new Date(Number(ms) || 0);
|
|
9387
|
+
const p = (x) => String(x).padStart(2, '0');
|
|
9388
|
+
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
|
|
9389
|
+
}
|
|
9390
|
+
|
|
9391
|
+
function finRenderAutomation(cfg, watch) {
|
|
9392
|
+
if (!cfg) return;
|
|
9393
|
+
const ae = document.activeElement;
|
|
9394
|
+
if (els.finWatchdogCb) els.finWatchdogCb.checked = cfg.watchdog !== false;
|
|
9395
|
+
const setNum = (el, v) => {
|
|
9396
|
+
if (el && ae !== el) el.value = v;
|
|
9397
|
+
};
|
|
9398
|
+
setNum(els.finBeOnR, cfg.beOnR != null ? cfg.beOnR : 1);
|
|
9399
|
+
setNum(els.finTrailStartR, cfg.trailStartR != null ? cfg.trailStartR : 1.5);
|
|
9400
|
+
setNum(els.finTrailR, cfg.trailR != null ? cfg.trailR : 0.5);
|
|
9401
|
+
setNum(els.finPartialR, cfg.partialR != null ? cfg.partialR : 0);
|
|
9402
|
+
setNum(els.finPartialPct, cfg.partialPct != null ? cfg.partialPct : 50);
|
|
9403
|
+
setNum(els.finMaxDailyLoss, cfg.maxDailyLossPct != null ? cfg.maxDailyLossPct : 3);
|
|
9404
|
+
setNum(els.finRiskPct, cfg.riskPerTradePct != null ? cfg.riskPerTradePct : 1);
|
|
9405
|
+
setNum(els.finMaxPerSymbol, cfg.maxPerSymbol != null ? cfg.maxPerSymbol : 2);
|
|
9406
|
+
setNum(els.finMaxSameSide, cfg.maxSameSide != null ? cfg.maxSameSide : 3);
|
|
9407
|
+
setNum(els.finMinMarginLevel, cfg.minMarginLevel != null ? cfg.minMarginLevel : 150);
|
|
9408
|
+
if (els.finDailyLossAction && ae !== els.finDailyLossAction) els.finDailyLossAction.value = cfg.dailyLossAction || 'stop';
|
|
9409
|
+
if (els.finNotifyTarget && ae !== els.finNotifyTarget) els.finNotifyTarget.value = cfg.notifyTarget || 'auto';
|
|
9410
|
+
if (els.finWatchDot) {
|
|
9411
|
+
els.finWatchDot.classList.remove('on', 'off', 'busy');
|
|
9412
|
+
els.finWatchDot.classList.add(watch && watch.on ? 'on' : 'off');
|
|
9413
|
+
els.finWatchDot.title = watch && watch.on ? 'Koruma döngüsü çalışıyor (5 sn)' : 'Koruma döngüsü kapalı';
|
|
9414
|
+
}
|
|
9415
|
+
if (els.finWatchInfo) {
|
|
9416
|
+
els.finWatchInfo.textContent = watch && watch.on ? '· izlenen ' + (watch.managed || 0) : '';
|
|
9417
|
+
}
|
|
9418
|
+
}
|
|
9419
|
+
|
|
9420
|
+
function finSparkline(points) {
|
|
9421
|
+
const list = (Array.isArray(points) ? points : []).filter((p) => p && isFinite(Number(p.equity)));
|
|
9422
|
+
if (list.length < 2) return '<div class="fin-empty">Equity verisi toplanıyor…</div>';
|
|
9423
|
+
const w = 100;
|
|
9424
|
+
const h = 28;
|
|
9425
|
+
const vals = list.map((p) => Number(p.equity));
|
|
9426
|
+
let min = Math.min(...vals);
|
|
9427
|
+
let max = Math.max(...vals);
|
|
9428
|
+
if (max - min < 1e-9) max = min + 1;
|
|
9429
|
+
const step = w / (list.length - 1);
|
|
9430
|
+
const pts = vals
|
|
9431
|
+
.map((v, i) => (i * step).toFixed(2) + ',' + (h - 2 - ((v - min) / (max - min)) * (h - 4)).toFixed(2))
|
|
9432
|
+
.join(' ');
|
|
9433
|
+
const up = vals[vals.length - 1] >= vals[0];
|
|
9434
|
+
return '<svg viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="none" class="' + (up ? 'up' : 'down') + '">' +
|
|
9435
|
+
'<polyline points="' + pts + '" fill="none" stroke="currentColor" stroke-width="1.4" vector-effect="non-scaling-stroke" /></svg>';
|
|
9436
|
+
}
|
|
9437
|
+
|
|
9438
|
+
function finRenderStats(stats, equity, cur) {
|
|
9439
|
+
const s = stats || null;
|
|
9440
|
+
if (els.finEquityBox) {
|
|
9441
|
+
els.finEquityBox.innerHTML = finSparkline(equity);
|
|
9442
|
+
}
|
|
9443
|
+
if (els.finStatsInfo) {
|
|
9444
|
+
els.finStatsInfo.textContent = s && s.at
|
|
9445
|
+
? '· son 90 gün · ' + new Date(s.at).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' })
|
|
9446
|
+
: '';
|
|
9447
|
+
}
|
|
9448
|
+
if (!s) {
|
|
9449
|
+
finKvSet(els.finStatToday, '—');
|
|
9450
|
+
finKvSet(els.finStatWeek, '—');
|
|
9451
|
+
finKvSet(els.finStatWin, '—');
|
|
9452
|
+
finKvSet(els.finStatPf, '—');
|
|
9453
|
+
finKvSet(els.finStatDd, '—');
|
|
9454
|
+
finKvSet(els.finStatTrades, '—');
|
|
9455
|
+
return;
|
|
9456
|
+
}
|
|
9457
|
+
const byDay = s.byDay && typeof s.byDay === 'object' ? s.byDay : {};
|
|
9458
|
+
const today = Number(byDay[finDayKey(Date.now())]) || 0;
|
|
9459
|
+
const week = Object.entries(byDay)
|
|
9460
|
+
.filter(([d]) => Date.now() - new Date(d + 'T00:00:00').getTime() < 7 * 86400000)
|
|
9461
|
+
.reduce((a, [, v]) => a + (Number(v) || 0), 0);
|
|
9462
|
+
const fmt = (n) => (n > 0 ? '+' : '') + fmtMoney(n, cur);
|
|
9463
|
+
finKvSet(els.finStatToday, fmt(today), today !== 0 ? (today > 0 ? 'pos' : 'neg') : null);
|
|
9464
|
+
finKvSet(els.finStatWeek, fmt(week), week !== 0 ? (week > 0 ? 'pos' : 'neg') : null);
|
|
9465
|
+
finKvSet(els.finStatWin, '%' + (Number(s.winRate) || 0) + ' (' + (Number(s.wins) || 0) + '/' + (Number(s.losses) || 0) + ')');
|
|
9466
|
+
finKvSet(els.finStatPf, s.profitFactor == null ? '∞' : String(s.profitFactor));
|
|
9467
|
+
const dd = s.drawdown || {};
|
|
9468
|
+
finKvSet(els.finStatDd, '%' + (Number(dd.maxPct) || 0), (Number(dd.maxPct) || 0) > 0 ? 'neg' : null);
|
|
9469
|
+
finKvSet(els.finStatTrades, String(Number(s.trades) || 0));
|
|
9470
|
+
}
|
|
9471
|
+
|
|
9472
|
+
function finRenderAlerts(list) {
|
|
9473
|
+
const box = els.finAlertList;
|
|
9474
|
+
if (!box) return;
|
|
9475
|
+
box.textContent = '';
|
|
9476
|
+
if (!list || !list.length) {
|
|
9477
|
+
const d = document.createElement('div');
|
|
9478
|
+
d.className = 'fin-empty';
|
|
9479
|
+
d.textContent = 'Aktif fiyat alarmı yok';
|
|
9480
|
+
box.appendChild(d);
|
|
9481
|
+
return;
|
|
9482
|
+
}
|
|
9483
|
+
for (const a of list) {
|
|
9484
|
+
const row = document.createElement('div');
|
|
9485
|
+
row.className = 'fin-alert';
|
|
9486
|
+
row.innerHTML =
|
|
9487
|
+
'<span class="fa-sym">' + escapeHtml(String(a.symbol || '?')) + '</span>' +
|
|
9488
|
+
'<span class="fa-cond">' + (a.direction === 'below' ? '≤' : '≥') + ' ' + (Number(a.price) || 0) + '</span>' +
|
|
9489
|
+
(a.note ? '<span class="fa-note">' + escapeHtml(String(a.note)) + '</span>' : '');
|
|
9490
|
+
box.appendChild(row);
|
|
9491
|
+
}
|
|
9492
|
+
}
|
|
9493
|
+
|
|
9077
9494
|
async function finFillModels() {
|
|
9078
9495
|
if (!els.finTraderModel) return;
|
|
9079
9496
|
let models = [];
|
|
@@ -9115,6 +9532,9 @@ async function finSnapshot() {
|
|
|
9115
9532
|
finTraderInputsSet(r.cfg);
|
|
9116
9533
|
finRenderRoles(r.cfg);
|
|
9117
9534
|
finRenderTrader(r.trader, r.cfg);
|
|
9535
|
+
finRenderAutomation(r.cfg, r.watch);
|
|
9536
|
+
finRenderStats(r.stats, r.equity, r.account && r.account.currency ? r.account.currency : '');
|
|
9537
|
+
finRenderAlerts(r.alerts);
|
|
9118
9538
|
}
|
|
9119
9539
|
|
|
9120
9540
|
function finLogLine(line) {
|
|
@@ -9132,7 +9552,11 @@ function finLogLine(line) {
|
|
|
9132
9552
|
function finOnEvent(ev) {
|
|
9133
9553
|
if (!ev || ev.type !== 'finance') return;
|
|
9134
9554
|
if (ev.fn === 'log' && ev.line) finLogLine(ev.line);
|
|
9135
|
-
else if (ev.fn === '
|
|
9555
|
+
else if (ev.fn === 'notify' && ev.line) {
|
|
9556
|
+
finLogLine(ev.line);
|
|
9557
|
+
toast(ev.line);
|
|
9558
|
+
finSnapshot();
|
|
9559
|
+
} else if (ev.fn === 'trade' && ev.line) {
|
|
9136
9560
|
finLogLine(ev.line);
|
|
9137
9561
|
toast(ev.line);
|
|
9138
9562
|
finSnapshot();
|
|
@@ -9219,6 +9643,60 @@ if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => {
|
|
|
9219
9643
|
toast('Max lot: ' + v + ' — sonraki turdan itibaren geçerli');
|
|
9220
9644
|
});
|
|
9221
9645
|
if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
|
|
9646
|
+
|
|
9647
|
+
/* RİSK OTOMASYONU kontrolleri: değişiklik anında kaydedilir (ajan turunu beklemez) */
|
|
9648
|
+
if (els.finWatchdogCb) {
|
|
9649
|
+
els.finWatchdogCb.addEventListener('change', () => {
|
|
9650
|
+
finSaveCfg({ watchdog: els.finWatchdogCb.checked });
|
|
9651
|
+
toast('Risk otomasyonu ' + (els.finWatchdogCb.checked ? 'açık — BE/trailing/kısmi TP 5 sn döngüyle uygulanır' : 'kapalı'));
|
|
9652
|
+
});
|
|
9653
|
+
}
|
|
9654
|
+
const finAutoNum = (el, key, min, max, step) => {
|
|
9655
|
+
if (!el) return;
|
|
9656
|
+
el.addEventListener('change', () => {
|
|
9657
|
+
let v = Number(el.value);
|
|
9658
|
+
if (!isFinite(v)) v = min;
|
|
9659
|
+
v = Math.max(min, Math.min(max, step ? Math.round(v / step) * step : v));
|
|
9660
|
+
el.value = v;
|
|
9661
|
+
finSaveCfg({ [key]: v });
|
|
9662
|
+
});
|
|
9663
|
+
};
|
|
9664
|
+
finAutoNum(els.finBeOnR, 'beOnR', 0, 10);
|
|
9665
|
+
finAutoNum(els.finTrailStartR, 'trailStartR', 0, 10);
|
|
9666
|
+
finAutoNum(els.finTrailR, 'trailR', 0, 5);
|
|
9667
|
+
finAutoNum(els.finPartialR, 'partialR', 0, 10);
|
|
9668
|
+
finAutoNum(els.finPartialPct, 'partialPct', 0, 90, 5);
|
|
9669
|
+
finAutoNum(els.finMaxDailyLoss, 'maxDailyLossPct', 0, 50);
|
|
9670
|
+
finAutoNum(els.finRiskPct, 'riskPerTradePct', 0, 20, 0.1);
|
|
9671
|
+
finAutoNum(els.finMaxPerSymbol, 'maxPerSymbol', 0, 20, 1);
|
|
9672
|
+
finAutoNum(els.finMaxSameSide, 'maxSameSide', 0, 20, 1);
|
|
9673
|
+
finAutoNum(els.finMinMarginLevel, 'minMarginLevel', 0, 1000, 10);
|
|
9674
|
+
if (els.finDailyLossAction) {
|
|
9675
|
+
els.finDailyLossAction.addEventListener('change', () => {
|
|
9676
|
+
finSaveCfg({ dailyLossAction: els.finDailyLossAction.value });
|
|
9677
|
+
const t = els.finDailyLossAction.options[els.finDailyLossAction.selectedIndex].textContent;
|
|
9678
|
+
toast('Günlük limit aksiyonu: ' + t);
|
|
9679
|
+
});
|
|
9680
|
+
}
|
|
9681
|
+
if (els.finNotifyTarget) {
|
|
9682
|
+
els.finNotifyTarget.addEventListener('change', () => {
|
|
9683
|
+
finSaveCfg({ notifyTarget: els.finNotifyTarget.value });
|
|
9684
|
+
toast('Bildirim kanalı: ' + els.finNotifyTarget.options[els.finNotifyTarget.selectedIndex].textContent);
|
|
9685
|
+
});
|
|
9686
|
+
}
|
|
9687
|
+
if (els.finReportBtn) {
|
|
9688
|
+
els.finReportBtn.addEventListener('click', async () => {
|
|
9689
|
+
els.finReportBtn.disabled = true;
|
|
9690
|
+
els.finReportBtn.textContent = '⏳ Rapor…';
|
|
9691
|
+
const r = await beast.financeReport().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
9692
|
+
els.finReportBtn.disabled = false;
|
|
9693
|
+
els.finReportBtn.textContent = '📄 Rapor';
|
|
9694
|
+
if (r && r.ok) toast('Rapor hazır: ' + String(r.path || '').split(/[\\/]/).pop());
|
|
9695
|
+
else toast('Rapor üretilemedi: ' + ((r && r.error) || '?'));
|
|
9696
|
+
finSnapshot();
|
|
9697
|
+
});
|
|
9698
|
+
}
|
|
9699
|
+
|
|
9222
9700
|
/* OTOMATİK İŞLEM onay kutusu KALDIRILDI — trade ajanı daima işlem açabilir */
|
|
9223
9701
|
if (els.finTraderModel) {
|
|
9224
9702
|
els.finTraderModel.addEventListener('change', () => {
|
package/src/renderer/style.css
CHANGED
|
@@ -1484,6 +1484,19 @@ body.browser-open .mini-overlay { right: var(--bw, 480px); }
|
|
|
1484
1484
|
font-size: 12px;
|
|
1485
1485
|
padding: 4px 6px;
|
|
1486
1486
|
}
|
|
1487
|
+
/* ---------- İzleyici EKLEME formu ---------- */
|
|
1488
|
+
#wAddBox {
|
|
1489
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
1490
|
+
background: var(--panel); border: 1px solid var(--border);
|
|
1491
|
+
border-radius: 9px; padding: 8px; margin-bottom: 10px;
|
|
1492
|
+
}
|
|
1493
|
+
.mini-add-row { display: flex; gap: 6px; align-items: center; }
|
|
1494
|
+
.mini-add-row .inp { flex: 1; min-width: 0; margin: 0; padding: 5px 8px; font-size: 12px; }
|
|
1495
|
+
#wAddBtn {
|
|
1496
|
+
flex: none; background: var(--accent); color: #fff; border: none;
|
|
1497
|
+
border-radius: 7px; padding: 5px 11px; font-size: 14px; cursor: pointer;
|
|
1498
|
+
}
|
|
1499
|
+
#wAddBtn:hover { filter: brightness(1.1); }
|
|
1487
1500
|
.wl-row {
|
|
1488
1501
|
display: flex; align-items: baseline; gap: 7px;
|
|
1489
1502
|
background: var(--panel); border: 1px solid var(--border);
|
|
@@ -4832,6 +4845,29 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
|
|
|
4832
4845
|
.fin-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
4833
4846
|
.fin-grid2 .fin-field { margin-bottom: 8px; }
|
|
4834
4847
|
.fin-hint { font-style: normal; opacity: 0.75; }
|
|
4848
|
+
/* RİSK OTOMASYONU + PERFORMANS kartları */
|
|
4849
|
+
.fin-check label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text); cursor: pointer; }
|
|
4850
|
+
.fin-check input { accent-color: var(--accent); margin: 0; }
|
|
4851
|
+
.fin-kv b.pos { color: #22c55e; }
|
|
4852
|
+
.fin-kv b.neg { color: #ef4444; }
|
|
4853
|
+
.fin-equity { height: 34px; margin-bottom: 8px; color: var(--muted); }
|
|
4854
|
+
.fin-equity svg { width: 100%; height: 100%; display: block; }
|
|
4855
|
+
.fin-equity svg.up { color: #22c55e; }
|
|
4856
|
+
.fin-equity svg.down { color: #ef4444; }
|
|
4857
|
+
.fin-alerts { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
|
|
4858
|
+
.fin-alert {
|
|
4859
|
+
display: flex;
|
|
4860
|
+
align-items: center;
|
|
4861
|
+
gap: 6px;
|
|
4862
|
+
font-size: 11px;
|
|
4863
|
+
background: var(--panel2);
|
|
4864
|
+
border-radius: 4px;
|
|
4865
|
+
padding: 3px 6px;
|
|
4866
|
+
min-width: 0;
|
|
4867
|
+
}
|
|
4868
|
+
.fin-alert .fa-sym { font-weight: 700; }
|
|
4869
|
+
.fin-alert .fa-cond { color: #eab308; font-variant-numeric: tabular-nums; }
|
|
4870
|
+
.fin-alert .fa-note { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4835
4871
|
/* ekib sayısı + otomatik atama özeti */
|
|
4836
4872
|
.fin-team { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
4837
4873
|
.fin-team-hint {
|