From e7df1dc63aaa78ba222bed679a314a64657fd27b Mon Sep 17 00:00:00 2001 From: Ivan Neplokhov Date: Tue, 3 Mar 2026 23:24:33 +0400 Subject: [PATCH] some --- src/main/index.js | 10 +- src/renderer/components/TasksSidebar.jsx | 20 +- src/renderer/hooks/useTabProps.js | 6 + src/renderer/hooks/useTaskStatusView.js | 15 +- src/renderer/tabs/AccountsTab.jsx | 458 ++--------------------- src/renderer/tabs/EventsTab.jsx | 53 ++- 6 files changed, 121 insertions(+), 441 deletions(-) diff --git a/src/main/index.js b/src/main/index.js index 7c9f8f1..82773c0 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1338,6 +1338,8 @@ ipcMain.handle("tasks:status", (_event, id) => { const readiness = { ok: true, reasons: [] }; let restrictedAccounts = []; let totalInvites = 0; + let totalInvitesSuccess = 0; + let totalInvitesAttempts = 0; let taskInviteLimitTotal = 0; let accountDailyLimitTotal = 0; let inviteAccountsCount = 0; @@ -1385,11 +1387,13 @@ ipcMain.handle("tasks:status", (_event, id) => { const account = accountsById.get(row.account_id); return sum + Math.max(0, Number(account && account.daily_limit ? account.daily_limit : 0)); }, 0); - totalInvites = - Number(store.countInvitesByStatus(id, "success") || 0) + totalInvitesSuccess = Number(store.countInvitesByStatus(id, "success") || 0); + totalInvitesAttempts = + totalInvitesSuccess + Number(store.countInvitesByStatus(id, "failed") || 0) + Number(store.countInvitesByStatus(id, "skipped") || 0) + Number(store.countInvitesByStatus(id, "unconfirmed") || 0); + totalInvites = totalInvitesAttempts; const monitorRows = accountRows.filter((row) => row.role_monitor); if (!inviteRows.length) { const fallbackAvailable = accountRows @@ -1510,6 +1514,8 @@ ipcMain.handle("tasks:status", (_event, id) => { queueCount, dailyUsed, totalInvites, + totalInvitesSuccess, + totalInvitesAttempts, unconfirmedCount, dailyLimit: effectiveLimit, taskDailyLimitBase: task ? Number(task.daily_limit || 0) : 0, diff --git a/src/renderer/components/TasksSidebar.jsx b/src/renderer/components/TasksSidebar.jsx index 17670b6..a15c431 100644 --- a/src/renderer/components/TasksSidebar.jsx +++ b/src/renderer/components/TasksSidebar.jsx @@ -87,9 +87,11 @@ export default function TasksSidebar({ const dailyLabel = status ? `Лимит сегодня: ${status.dailyUsed}/${status.dailyLimit}${warmupDelta > 0 ? ` (старт ${warmupStart} +${warmupDelta} прогрев)` : ""}` : "Лимит сегодня: —"; - const totalLabel = status ? `Инвайтов всего: ${status.totalInvites || 0}` : "Инвайтов всего: —"; + const totalSuccessLabel = status ? `Инвайтов всего: ${status.totalInvitesSuccess || 0}` : "Инвайтов всего: —"; + const totalAttemptsLabel = status ? `Попыток всего: ${status.totalInvitesAttempts || 0}` : "Попыток всего: —"; const taskLimitLabel = status ? `Лимит задачи: ${status.dailyLimit || 0}` : "Лимит задачи: —"; - const accountLimitLabel = status ? `Лимит аккаунтов: ${status.accountDailyLimitTotal || 0}` : "Лимит аккаунтов: —"; + const accountCycleLimitLabel = status ? `Лимит аккаунтов (цикл): ${status.taskInviteLimitTotal || 0}` : "Лимит аккаунтов (цикл): —"; + const accountDailyCapLabel = status ? `Потолок аккаунтов (день): ${status.accountDailyLimitTotal || 0}` : "Потолок аккаунтов (день): —"; const cycleLabel = status && status.running ? `Цикл: ${formatCountdown(status.nextRunAt)}` : "Цикл: —"; const lastMessageRaw = status && status.monitorInfo && status.monitorInfo.lastMessageAt ? status.monitorInfo.lastMessageAt @@ -115,9 +117,11 @@ export default function TasksSidebar({ `Статус: ${statusLabel}`, `Очередь: ${status ? status.queueCount : "—"}`, `Лимит сегодня: ${status ? `${status.dailyUsed}/${status.dailyLimit}${warmupDelta > 0 ? ` (старт ${warmupStart} +${warmupDelta} прогрев)` : ""}` : "—"}`, - `Инвайтов всего: ${status ? status.totalInvites || 0 : "—"}`, + `Инвайтов всего (успешных): ${status ? status.totalInvitesSuccess || 0 : "—"}`, + `Попыток всего: ${status ? status.totalInvitesAttempts || 0 : "—"}`, `Лимит задачи: ${status ? status.dailyLimit || 0 : "—"}`, - `Лимит аккаунтов: ${status ? status.accountDailyLimitTotal || 0 : "—"}`, + `Лимит аккаунтов (цикл): ${status ? status.taskInviteLimitTotal || 0 : "—"}`, + `Потолок аккаунтов (день): ${status ? status.accountDailyLimitTotal || 0 : "—"}`, `Мониторинг: ${monitoring ? "активен" : "нет"}`, `Мониторит: ${monitorLabel}`, `Последнее: ${lastMessage}`, @@ -146,11 +150,15 @@ export default function TasksSidebar({
{queueLabel} {dailyLabel} - {totalLabel} + {totalSuccessLabel}
{taskLimitLabel} - {accountLimitLabel} + {accountCycleLimitLabel} + {accountDailyCapLabel} +
+
+ {totalAttemptsLabel} {cycleLabel}
diff --git a/src/renderer/hooks/useTabProps.js b/src/renderer/hooks/useTabProps.js index 3f3f43b..366f755 100644 --- a/src/renderer/hooks/useTabProps.js +++ b/src/renderer/hooks/useTabProps.js @@ -340,6 +340,12 @@ export default function useTabProps( const eventsTabProps = { accountEvents, + selectedTaskId, + selectedTask, + competitorLinks: String(competitorText || "") + .split("\n") + .map((item) => item.trim()) + .filter(Boolean), formatTimestamp, onClearEvents, accountById, diff --git a/src/renderer/hooks/useTaskStatusView.js b/src/renderer/hooks/useTaskStatusView.js index 7edc06a..9034cd3 100644 --- a/src/renderer/hooks/useTaskStatusView.js +++ b/src/renderer/hooks/useTaskStatusView.js @@ -72,6 +72,17 @@ export default function useTaskStatusView({ const inviteAccessChecked = inviteAccessStatus && inviteAccessStatus.length > 0; const inviteAccessOk = inviteAccessChecked && inviteAccessStatus.every((item) => item.canInvite); const inviteAccessWarn = inviteAccessChecked && !inviteAccessOk; + const inviteAccessById = new Map((inviteAccessStatus || []).map((item) => [Number(item.accountId), item])); + const resolveInviteMembership = (accountId) => { + const direct = membershipStatus && membershipStatus[accountId] ? membershipStatus[accountId] : null; + if (direct) return direct; + const access = inviteAccessById.get(Number(accountId)); + if (!access) return null; + return { + ourGroupMember: access.member === true, + ourGroupPending: false + }; + }; const masterAdminId = Number(selectedTask?.invite_admin_master_id || 0); const masterAccessRow = inviteAccessChecked ? (inviteAccessStatus || []).find((item) => Number(item.accountId) === masterAdminId) @@ -121,7 +132,7 @@ export default function useTaskStatusView({ const account = accountById.get(accountId); const label = account ? formatAccountLabel(account) : String(accountId); const row = inviteById.get(Number(accountId)); - const member = membershipStatus && membershipStatus[accountId] ? membershipStatus[accountId] : null; + const member = resolveInviteMembership(accountId); const membershipText = !member ? "статус участия не проверен" : member.ourGroupMember @@ -210,7 +221,7 @@ export default function useTaskStatusView({ ); const inviteMembershipList = inviteAccountIds.map((id) => ({ id, - status: membershipStatus ? membershipStatus[id] : null + status: resolveInviteMembership(id) })); const inviteMembershipUnknown = inviteMembershipList.filter((item) => !item.status).length; const inviteMembershipPending = inviteMembershipList.filter((item) => item.status && item.status.ourGroupPending).length; diff --git a/src/renderer/tabs/AccountsTab.jsx b/src/renderer/tabs/AccountsTab.jsx index de0664b..2d11206 100644 --- a/src/renderer/tabs/AccountsTab.jsx +++ b/src/renderer/tabs/AccountsTab.jsx @@ -47,18 +47,6 @@ function AccountsTab({ const [healthFilter, setHealthFilter] = useState("all"); const [proxyBusy, setProxyBusy] = useState(false); const [proxyNotice, setProxyNotice] = useState(null); - const [proxyImportText, setProxyImportText] = useState(""); - const [bulkProxyId, setBulkProxyId] = useState(0); - const [proxyForm, setProxyForm] = useState({ - id: 0, - name: "", - protocol: "socks5", - host: "", - port: "1080", - username: "", - password: "", - enabled: true - }); const inviteAccessById = React.useMemo(() => { const map = new Map(); (inviteAccessStatus || []).forEach((item) => { @@ -67,6 +55,24 @@ function AccountsTab({ }); return map; }, [inviteAccessStatus]); + const resolveMembership = React.useCallback((accountId) => { + const direct = membershipStatus && membershipStatus[accountId] ? membershipStatus[accountId] : null; + if (direct) return { value: direct, fromInviteAccess: false }; + const access = inviteAccessById.get(Number(accountId)); + if (!access) return { value: null, fromInviteAccess: false }; + return { + value: { + competitorCount: 0, + competitorTotal: 0, + competitorGroups: [], + ourGroupMember: access.member === true, + ourGroupPending: false, + ourGroupPendingAt: "", + ourGroup: null + }, + fromInviteAccess: true + }; + }, [membershipStatus, inviteAccessById]); const adminConfirmConfigRisk = React.useMemo( () => (typeof computeAdminConfirmConfigRisk === "function" ? computeAdminConfirmConfigRisk(taskAccountRoles) : null), [computeAdminConfirmConfigRisk, taskAccountRoles] @@ -107,18 +113,6 @@ function AccountsTab({ const visibleFreeOrSelected = (accountBuckets.freeOrSelected || []).filter(healthFilterMatch); const visibleBusy = (accountBuckets.busy || []).filter(healthFilterMatch); const authDupCount = (accounts || []).filter(isAuthKeyDuplicatedAccount).length; - const resetProxyForm = () => { - setProxyForm({ - id: 0, - name: "", - protocol: "socks5", - host: "", - port: "1080", - username: "", - password: "", - enabled: true - }); - }; const setProxyMessage = (text, tone = "info") => { setProxyNotice({ text, tone, at: Date.now() }); }; @@ -130,61 +124,6 @@ function AccountsTab({ setProxyBusy(false); } }; - const onSaveProxy = async () => { - const host = String(proxyForm.host || "").trim(); - const port = Number(proxyForm.port || 0); - if (!host || !port) { - setProxyMessage("Укажи host и port прокси.", "error"); - return; - } - await runProxyAction(async () => { - const result = await saveProxy({ - id: Number(proxyForm.id || 0), - name: proxyForm.name, - protocol: proxyForm.protocol, - host, - port, - username: proxyForm.username, - password: proxyForm.password, - enabled: Boolean(proxyForm.enabled) - }); - if (result && result.ok) { - const testInfo = result.test - ? (result.test.ok - ? ` Проверка: OK${result.test.latencyMs ? ` (${result.test.latencyMs} ms)` : ""}.` - : ` Проверка: ${result.test.error || "ошибка"}.`) - : ""; - setProxyMessage(`Прокси сохранен.${testInfo}`, "success"); - resetProxyForm(); - } else { - setProxyMessage((result && result.error) || "Не удалось сохранить прокси.", "error"); - } - }); - }; - const onTestProxy = async (proxy) => { - await runProxyAction(async () => { - const result = await testProxy(proxy && proxy.id ? { id: proxy.id } : proxyForm); - if (result && result.ok) { - setProxyMessage(`Прокси доступен${result.latencyMs ? ` (${result.latencyMs} ms)` : ""}.`, "success"); - } else { - setProxyMessage(`Проверка не прошла: ${(result && result.error) || "ошибка"}.`, "error"); - } - }); - }; - const onDeleteProxy = async (proxy) => { - if (!proxy || !proxy.id) return; - await runProxyAction(async () => { - const result = await removeProxy(proxy.id); - if (result && result.ok) { - setProxyMessage("Прокси удален.", "success"); - if (Number(proxyForm.id || 0) === Number(proxy.id)) { - resetProxyForm(); - } - } else { - setProxyMessage((result && result.error) || "Не удалось удалить прокси.", "error"); - } - }); - }; const onAccountProxyChange = async (accountId, nextProxyId) => { await runProxyAction(async () => { try { @@ -195,358 +134,17 @@ function AccountsTab({ } }); }; - const parseProxyLine = (line, defaultProtocol = "socks5") => { - const raw = String(line || "").trim(); - if (!raw) return null; - try { - if (raw.includes("://")) { - const url = new URL(raw); - const protocol = String(url.protocol || "").replace(":", "").toLowerCase(); - const host = (url.hostname || "").trim(); - const port = Number(url.port || 0); - if (host && port) { - return { - protocol: protocol === "socks4" ? "socks4" : "socks5", - host, - port, - username: decodeURIComponent(url.username || ""), - password: decodeURIComponent(url.password || "") - }; - } - } - } catch (_error) { - // fallback below - } - const parts = raw.split(":").map((part) => part.trim()); - if (parts.length < 2) return null; - const host = parts[0] || ""; - const port = Number(parts[1] || 0); - if (!host || !port) return null; - if (parts.length >= 4) { - return { - protocol: defaultProtocol === "socks4" ? "socks4" : "socks5", - host, - port, - username: parts[2] || "", - password: parts.slice(3).join(":") || "" - }; - } - return { - protocol: defaultProtocol === "socks4" ? "socks4" : "socks5", - host, - port, - username: "", - password: "" - }; - }; - const onImportProxies = async () => { - const lines = String(proxyImportText || "") - .split(/\r?\n/) - .map((line) => line.trim()) - .filter(Boolean); - if (!lines.length) { - setProxyMessage("Вставь список прокси для импорта.", "error"); - return; - } - let imported = 0; - let failed = 0; - await runProxyAction(async () => { - for (let i = 0; i < lines.length; i += 1) { - const parsed = parseProxyLine(lines[i], proxyForm.protocol || "socks5"); - if (!parsed) { - failed += 1; - continue; - } - const payload = { - name: `import_${Date.now()}_${i + 1}`, - protocol: parsed.protocol, - host: parsed.host, - port: parsed.port, - username: parsed.username, - password: parsed.password, - enabled: true - }; - const result = await saveProxy(payload); - if (result && result.ok) imported += 1; - else failed += 1; - } - await reloadProxies(); - }); - setProxyMessage(`Импорт завершен: добавлено ${imported}, ошибок ${failed}.`, failed ? "warn" : "success"); - if (imported > 0) { - setProxyImportText(""); - } - }; - const onImportProxiesFromFile = async () => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".txt,.csv,text/plain,text/csv"; - input.onchange = async () => { - const file = input.files && input.files[0] ? input.files[0] : null; - if (!file) return; - try { - const text = await file.text(); - setProxyImportText(text || ""); - setProxyMessage(`Файл загружен: ${file.name}. Нажми «Импортировать».`, "info"); - } catch (error) { - setProxyMessage(`Не удалось прочитать файл: ${error.message || String(error)}`, "error"); - } - }; - input.click(); - }; - const onTestAllProxies = async () => { - const rows = Array.isArray(proxies) ? proxies : []; - if (!rows.length) { - setProxyMessage("Нет прокси для проверки.", "error"); - return; - } - let okCount = 0; - let failCount = 0; - await runProxyAction(async () => { - for (const proxy of rows) { - const result = await testProxy({ id: proxy.id }); - if (result && result.ok) okCount += 1; - else failCount += 1; - } - await reloadProxies(); - }); - setProxyMessage(`Проверка завершена: OK ${okCount}, ошибки ${failCount}.`, failCount ? "warn" : "success"); - }; - const onAutoDistributeProxies = async () => { - const enabledProxies = (proxies || []).filter((proxy) => proxy && proxy.enabled); - if (!enabledProxies.length) { - setProxyMessage("Нет активных прокси для распределения.", "error"); - return; - } - const accountIds = (selectedAccountIds || []).map((id) => Number(id || 0)).filter((id) => id > 0); - if (!accountIds.length) { - setProxyMessage("Для распределения выбери аккаунты в задаче.", "error"); - return; - } - const assignments = accountIds.map((accountId, index) => ({ - accountId, - proxyId: Number(enabledProxies[index % enabledProxies.length].id || 0) - })); - await runProxyAction(async () => { - try { - const result = await setAccountsProxyMap(assignments); - setProxyMessage( - `Распределение выполнено: аккаунтов ${accountIds.length}, обновлено ${result.changed}, reconnect ok ${result.reconnected}, ошибок ${result.failed}.`, - result.failed ? "warn" : "success" - ); - } catch (error) { - setProxyMessage(error.message || String(error), "error"); - } - }); - }; - const onAssignBulkProxy = async () => { - const ids = (selectedAccountIds || []).map((id) => Number(id || 0)).filter((id) => id > 0); - if (!ids.length) { - setProxyMessage("Для массовой привязки выбери аккаунты в задаче.", "error"); - return; - } - await runProxyAction(async () => { - try { - const result = await setAccountsProxyBulk(ids, Number(bulkProxyId || 0)); - setProxyMessage( - `Массовая привязка выполнена: ${result.changed}/${ids.length}, reconnect ok ${result.reconnected}, ошибок ${result.failed}.`, - result.failed ? "warn" : "success" - ); - } catch (error) { - setProxyMessage(error.message || String(error), "error"); - } - }); - }; return (

Аккаунты

-
-
-

Мои прокси

- + {proxyNotice && ( +
+ {proxyNotice.text}
- {proxyNotice && ( -
- {proxyNotice.text} -
- )} -
- - - - - - - - - - {proxyForm.id > 0 && ( - - )} -
-
-