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.
@@ -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
+ }