izteamslots 1.1.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/.env.example +1 -0
- package/CONTRIBUTING.md +128 -0
- package/README.md +249 -0
- package/app.py +25 -0
- package/backend/__init__.py +3 -0
- package/backend/__main__.py +3 -0
- package/backend/account_store.py +448 -0
- package/backend/chatgpt_workspace_api.py +104 -0
- package/backend/dto.py +106 -0
- package/backend/file_logger.py +82 -0
- package/backend/jobs.py +77 -0
- package/backend/mail/__init__.py +98 -0
- package/backend/mail/base.py +86 -0
- package/backend/mail/boomlify.py +178 -0
- package/backend/mail/imap.py +221 -0
- package/backend/mail/trickads.py +121 -0
- package/backend/openai_web_auth.py +1402 -0
- package/backend/rpc_protocol.py +78 -0
- package/backend/rpc_server.py +233 -0
- package/backend/slot_orchestrator.py +400 -0
- package/backend/ui_facade.py +368 -0
- package/bin/izteamslots.sh +16 -0
- package/package.json +30 -0
- package/requirements.txt +2 -0
- package/scripts/setup.sh +82 -0
- package/ui/package.json +19 -0
- package/ui/src/main.ts +4 -0
- package/ui/src/menus/format.ts +163 -0
- package/ui/src/menus/mainMenus.ts +221 -0
- package/ui/src/menus/types.ts +75 -0
- package/ui/src/screens/MainScreen.ts +1175 -0
- package/ui/src/transport/stdioClient.ts +162 -0
- package/ui/tsconfig.json +13 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { AppState, DashboardData, MenuContext, MenuName, MenuOption, WorkerRow } from "./types"
|
|
2
|
+
|
|
3
|
+
export function getMenuOptions(menuName: MenuName, state: AppState): MenuOption[] {
|
|
4
|
+
if (menuName === "main") {
|
|
5
|
+
return [
|
|
6
|
+
{ id: "menu_admins", label: "Админы", hint: "Доступы, токены, браузерные профили" },
|
|
7
|
+
{ id: "menu_slots", label: "Слоты", hint: "Создание, перелогин и обслуживание" },
|
|
8
|
+
{ id: "menu_mail", label: "Почта", hint: "Ящики и входящие письма" },
|
|
9
|
+
{ id: "menu_exit", label: "Выход", hint: "Закрыть приложение" },
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (menuName === "admins") {
|
|
14
|
+
return [
|
|
15
|
+
{ id: "adm_add", label: "Добавить админа", hint: "Создать профиль и выбрать режим входа" },
|
|
16
|
+
{ id: "adm_relogin", label: "Перелогинить", hint: "Выбрать админа и режим: авто или вручную" },
|
|
17
|
+
{ id: "adm_open", label: "Открыть браузер", hint: "Запустить профиль для ручной проверки" },
|
|
18
|
+
{ id: "adm_delete", label: "Удалить", hint: "Удалить админа и связанные данные", destructive: true },
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (menuName === "slots") {
|
|
23
|
+
return [
|
|
24
|
+
{ id: "slots_create", label: "Создать слоты", hint: "Запустить пайплайн регистрации через админа" },
|
|
25
|
+
{ id: "slots_relogin", label: "Перелогинить", hint: "Выбрать: один слот или все сразу" },
|
|
26
|
+
{ id: "slots_open", label: "Открыть браузер", hint: "Проверить конкретный слот вручную" },
|
|
27
|
+
{ id: "slots_delete", label: "Удалить слот", hint: "Очистить аккаунт и локальные данные", destructive: true },
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (menuName === "mail") {
|
|
32
|
+
return [
|
|
33
|
+
...state.accounts.map((a, i) => ({
|
|
34
|
+
id: `mail_pick:${i}`,
|
|
35
|
+
label: `${a.email}`,
|
|
36
|
+
hint: a.kind === "admin" ? "Почта администратора" : "Почта слота",
|
|
37
|
+
})),
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (menuName === "pick_admin") {
|
|
42
|
+
return state.admins.map((a) => ({
|
|
43
|
+
id: `pick_admin:${a.email}`,
|
|
44
|
+
label: a.email,
|
|
45
|
+
hint: a.has_access_token ? "Токен готов" : "Нужен повторный вход",
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (menuName === "pick_worker") {
|
|
50
|
+
return state.workers.map((w) => ({
|
|
51
|
+
id: `pick_worker:${w.email}`,
|
|
52
|
+
label: w.email,
|
|
53
|
+
hint: `Статус: ${humanizeWorkerStatus(w.status)}`,
|
|
54
|
+
}))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (menuName === "pick_account") {
|
|
58
|
+
return state.accounts.map((a, i) => ({
|
|
59
|
+
id: `pick_account:${i}`,
|
|
60
|
+
label: a.email,
|
|
61
|
+
hint: a.kind === "admin" ? "Админский ящик" : "Почта слота",
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (menuName === "confirm") {
|
|
66
|
+
return [
|
|
67
|
+
{ id: "confirm_yes", label: "Да, удалить", hint: "Подтвердить необратимое действие", destructive: true },
|
|
68
|
+
{ id: "confirm_no", label: "Отмена", hint: "Вернуться без изменений" },
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getDashboard(state: AppState): DashboardData {
|
|
76
|
+
const admins_total = state.admins.length
|
|
77
|
+
const admins_ready = state.admins.filter((a) => a.has_access_token && a.has_browser_profile).length
|
|
78
|
+
const admins_with_token = state.admins.filter((a) => a.has_access_token).length
|
|
79
|
+
const admins_with_profile = state.admins.filter((a) => a.has_browser_profile).length
|
|
80
|
+
const workers_total = state.workers.length
|
|
81
|
+
const workers_ready = state.workers.filter((w) => w.has_access_token && w.has_browser_profile).length
|
|
82
|
+
const workers_registered = state.workers.filter((w) => w.status === "registered").length
|
|
83
|
+
const workers_invited = state.workers.filter((w) => w.status === "invited").length
|
|
84
|
+
const workers_created = state.workers.filter((w) => w.status === "created").length
|
|
85
|
+
const workers_with_password = state.workers.filter((w) => w.has_openai_password).length
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
admins_total,
|
|
89
|
+
admins_ready,
|
|
90
|
+
admins_with_token,
|
|
91
|
+
admins_with_profile,
|
|
92
|
+
workers_total,
|
|
93
|
+
workers_ready,
|
|
94
|
+
workers_registered,
|
|
95
|
+
workers_invited,
|
|
96
|
+
workers_created,
|
|
97
|
+
workers_with_password,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function humanizeWorkerStatus(status: string): string {
|
|
102
|
+
if (status === "registered") return "Готов"
|
|
103
|
+
if (status === "invited") return "Приглашён"
|
|
104
|
+
if (status === "created") return "Создан"
|
|
105
|
+
return status
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getScreenTitle(menuName: MenuName, ctx: MenuContext): string {
|
|
109
|
+
if (menuName === "main") return "Обзор системы"
|
|
110
|
+
if (menuName === "admins") return "Администраторы"
|
|
111
|
+
if (menuName === "slots") return "Слоты"
|
|
112
|
+
if (menuName === "mail") return "Почтовые ящики"
|
|
113
|
+
if (menuName === "pick_admin") return ctx.title ?? "Выбор администратора"
|
|
114
|
+
if (menuName === "pick_worker") return ctx.title ?? "Выбор слота"
|
|
115
|
+
if (menuName === "pick_account") return ctx.title ?? "Выбор аккаунта"
|
|
116
|
+
if (menuName === "confirm") return ctx.title ?? "Подтверждение"
|
|
117
|
+
return "izTeamSlots"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getScreenDescription(menuName: MenuName, state: AppState, ctx: MenuContext): string {
|
|
121
|
+
if (menuName === "main") {
|
|
122
|
+
return "Операционный центр для админов, слотов и временной почты."
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (menuName === "admins") {
|
|
126
|
+
return state.admins.length === 0
|
|
127
|
+
? "Добавьте первого администратора, чтобы открыть доступ к созданию слотов."
|
|
128
|
+
: "Управляйте доступами, токенами и ручным открытием браузерных профилей."
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (menuName === "slots") {
|
|
132
|
+
return state.workers.length === 0
|
|
133
|
+
? "Пока нет слотов. Запустите создание через выбранного администратора."
|
|
134
|
+
: "Следите за состоянием слотов и быстро восстанавливайте проблемные аккаунты."
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (menuName === "mail") {
|
|
138
|
+
return state.accounts.length === 0
|
|
139
|
+
? "Почтовые ящики появятся после создания админов и слотов."
|
|
140
|
+
: "Выберите аккаунт слева, чтобы забрать входящие письма."
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (menuName === "pick_admin") return ctx.title ?? "Выберите администратора для следующего действия."
|
|
144
|
+
if (menuName === "pick_worker") return ctx.title ?? "Выберите слот для следующего действия."
|
|
145
|
+
if (menuName === "pick_account") return ctx.title ?? "Выберите аккаунт."
|
|
146
|
+
if (menuName === "confirm") return "Проверьте объект удаления и подтвердите действие."
|
|
147
|
+
|
|
148
|
+
return "Операционный экран."
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getTable(menuName: MenuName, state: AppState, ctx: MenuContext): { headers: string[]; rows: string[][] } {
|
|
152
|
+
if (menuName === "admins" || menuName === "pick_admin") {
|
|
153
|
+
return {
|
|
154
|
+
headers: ["Email", "Статус", "Профиль", "WS", "Последний вход"],
|
|
155
|
+
rows: state.admins.map((a) => [
|
|
156
|
+
a.email,
|
|
157
|
+
a.status_label,
|
|
158
|
+
a.has_browser_profile ? "Есть" : "Нет",
|
|
159
|
+
String(a.workspace_count),
|
|
160
|
+
a.last_login ?? "-",
|
|
161
|
+
]),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (menuName === "slots" || menuName === "pick_worker") {
|
|
166
|
+
let workers: WorkerRow[] = state.workers
|
|
167
|
+
if (ctx.admin_email) {
|
|
168
|
+
workers = workers.filter((w) => w.admin_email === ctx.admin_email)
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
headers: ["Email", "Состояние", "Доступ", "Админ", "Пароль"],
|
|
172
|
+
rows: workers.map((w) => [
|
|
173
|
+
w.email,
|
|
174
|
+
w.status_label,
|
|
175
|
+
w.has_browser_profile ? "Профиль" : w.has_access_token ? "Токен" : "Нужно войти",
|
|
176
|
+
w.admin_email ?? "-",
|
|
177
|
+
w.has_openai_password ? "Есть" : "Нет",
|
|
178
|
+
]),
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (menuName === "mail" || menuName === "pick_account") {
|
|
183
|
+
return {
|
|
184
|
+
headers: ["Тип", "Email"],
|
|
185
|
+
rows: state.accounts.map((a) => [a.kind, a.email]),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (menuName === "confirm") {
|
|
190
|
+
return {
|
|
191
|
+
headers: ["Действие", "Объект"],
|
|
192
|
+
rows: [[ctx.confirm_action ?? "", ctx.target ?? ""]],
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
headers: ["Info"],
|
|
198
|
+
rows: [["izTeamSlots"]],
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function getHint(menuName: MenuName, _ctx: MenuContext): string {
|
|
203
|
+
if (menuName === "main") return "↑↓ или 1-4: выбор Enter: открыть r: обновить q: выход"
|
|
204
|
+
if (menuName === "confirm") return "Enter: подтвердить Esc: отмена"
|
|
205
|
+
return "↑↓ или 1-9: выбор Enter: действие Esc: назад r: обновить y: копировать лог"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function parentMenu(menuName: MenuName, ctx: MenuContext): MenuName {
|
|
209
|
+
if (ctx.parent) return ctx.parent
|
|
210
|
+
const map: Record<MenuName, MenuName> = {
|
|
211
|
+
main: "main",
|
|
212
|
+
admins: "main",
|
|
213
|
+
slots: "main",
|
|
214
|
+
mail: "main",
|
|
215
|
+
pick_admin: "admins",
|
|
216
|
+
pick_worker: "slots",
|
|
217
|
+
pick_account: "mail",
|
|
218
|
+
confirm: "main",
|
|
219
|
+
}
|
|
220
|
+
return map[menuName]
|
|
221
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export type MenuName =
|
|
2
|
+
| "main"
|
|
3
|
+
| "admins"
|
|
4
|
+
| "slots"
|
|
5
|
+
| "mail"
|
|
6
|
+
| "pick_admin"
|
|
7
|
+
| "pick_worker"
|
|
8
|
+
| "pick_account"
|
|
9
|
+
| "confirm"
|
|
10
|
+
|
|
11
|
+
export interface MenuOption {
|
|
12
|
+
id: string
|
|
13
|
+
label: string
|
|
14
|
+
hint?: string
|
|
15
|
+
description?: string
|
|
16
|
+
badge?: string
|
|
17
|
+
destructive?: boolean
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AdminRow {
|
|
22
|
+
email: string
|
|
23
|
+
has_access_token: boolean
|
|
24
|
+
has_browser_profile: boolean
|
|
25
|
+
workspace_id: string | null
|
|
26
|
+
workspace_count: number
|
|
27
|
+
created_at: string | null
|
|
28
|
+
last_login: string | null
|
|
29
|
+
status_label: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface WorkerRow {
|
|
33
|
+
email: string
|
|
34
|
+
status: string
|
|
35
|
+
has_access_token: boolean
|
|
36
|
+
has_browser_profile: boolean
|
|
37
|
+
workspace_id: string | null
|
|
38
|
+
admin_email: string | null
|
|
39
|
+
has_openai_password: boolean
|
|
40
|
+
created_at: string | null
|
|
41
|
+
status_label: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MailAccountRow {
|
|
45
|
+
kind: string
|
|
46
|
+
email: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AppState {
|
|
50
|
+
admins: AdminRow[]
|
|
51
|
+
workers: WorkerRow[]
|
|
52
|
+
accounts: MailAccountRow[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MenuContext {
|
|
56
|
+
parent?: MenuName
|
|
57
|
+
action?: string
|
|
58
|
+
title?: string
|
|
59
|
+
admin_email?: string
|
|
60
|
+
target?: string
|
|
61
|
+
confirm_action?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface DashboardData {
|
|
65
|
+
admins_total: number
|
|
66
|
+
admins_ready: number
|
|
67
|
+
admins_with_token: number
|
|
68
|
+
admins_with_profile: number
|
|
69
|
+
workers_total: number
|
|
70
|
+
workers_ready: number
|
|
71
|
+
workers_registered: number
|
|
72
|
+
workers_invited: number
|
|
73
|
+
workers_created: number
|
|
74
|
+
workers_with_password: number
|
|
75
|
+
}
|