@muhammedaksam/easiarr 1.3.5 → 1.4.1

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.
Files changed (94) hide show
  1. package/README.md +8 -5
  2. package/dist/index.js +29837 -0
  3. package/package.json +18 -16
  4. package/src/VersionInfo.ts +0 -12
  5. package/src/api/arr-api.ts +0 -508
  6. package/src/api/auto-setup-types.ts +0 -64
  7. package/src/api/bazarr-api.ts +0 -356
  8. package/src/api/cloudflare-api.ts +0 -577
  9. package/src/api/custom-format-api.ts +0 -212
  10. package/src/api/grafana-api.ts +0 -314
  11. package/src/api/heimdall-api.ts +0 -209
  12. package/src/api/homarr-api.ts +0 -296
  13. package/src/api/huntarr-api.ts +0 -622
  14. package/src/api/index.ts +0 -1
  15. package/src/api/jellyfin-api.ts +0 -473
  16. package/src/api/jellyseerr-api.ts +0 -660
  17. package/src/api/maintainerr-api.ts +0 -731
  18. package/src/api/naming-config.ts +0 -93
  19. package/src/api/overseerr-api.ts +0 -513
  20. package/src/api/plex-api.ts +0 -358
  21. package/src/api/portainer-api.ts +0 -467
  22. package/src/api/profilarr-api.ts +0 -380
  23. package/src/api/prowlarr-api.ts +0 -859
  24. package/src/api/qbittorrent-api.ts +0 -339
  25. package/src/api/quality-profile-api.ts +0 -269
  26. package/src/api/tautulli-api.ts +0 -277
  27. package/src/api/uptime-kuma-api.ts +0 -342
  28. package/src/apps/categories.ts +0 -14
  29. package/src/apps/index.ts +0 -2
  30. package/src/apps/registry.ts +0 -1255
  31. package/src/compose/caddy-config.ts +0 -129
  32. package/src/compose/generator.ts +0 -262
  33. package/src/compose/index.ts +0 -3
  34. package/src/compose/templates.ts +0 -78
  35. package/src/compose/traefik-config.ts +0 -185
  36. package/src/config/bookmarks-generator.ts +0 -149
  37. package/src/config/defaults.ts +0 -37
  38. package/src/config/homepage-config.ts +0 -376
  39. package/src/config/index.ts +0 -3
  40. package/src/config/manager.ts +0 -149
  41. package/src/config/recyclarr-config.ts +0 -179
  42. package/src/config/schema.ts +0 -310
  43. package/src/config/slskd-config.ts +0 -85
  44. package/src/config/soularr-config.ts +0 -105
  45. package/src/config/trash-quality-definitions.ts +0 -213
  46. package/src/data/lidarr-custom-formats.ts +0 -127
  47. package/src/data/trash-profiles.ts +0 -326
  48. package/src/docker/client.ts +0 -291
  49. package/src/docker/index.ts +0 -1
  50. package/src/index.ts +0 -77
  51. package/src/structure/manager.ts +0 -128
  52. package/src/ui/App.ts +0 -109
  53. package/src/ui/components/ApplicationSelector.ts +0 -272
  54. package/src/ui/components/FileEditor.ts +0 -91
  55. package/src/ui/components/FooterHint.ts +0 -119
  56. package/src/ui/components/PageLayout.ts +0 -152
  57. package/src/ui/components/UpdateNotification.ts +0 -101
  58. package/src/ui/index.ts +0 -1
  59. package/src/ui/screens/AdvancedSettings.ts +0 -181
  60. package/src/ui/screens/ApiKeyViewer.ts +0 -500
  61. package/src/ui/screens/AppConfigurator.ts +0 -731
  62. package/src/ui/screens/AppManager.ts +0 -276
  63. package/src/ui/screens/CloudflaredSetup.ts +0 -974
  64. package/src/ui/screens/ContainerControl.ts +0 -579
  65. package/src/ui/screens/FullAutoSetup.ts +0 -1719
  66. package/src/ui/screens/HomepageSetup.ts +0 -306
  67. package/src/ui/screens/JellyfinSetup.ts +0 -471
  68. package/src/ui/screens/JellyseerrSetup.ts +0 -612
  69. package/src/ui/screens/LogsViewer.ts +0 -468
  70. package/src/ui/screens/MainMenu.ts +0 -288
  71. package/src/ui/screens/MonitorDashboard.ts +0 -866
  72. package/src/ui/screens/ProwlarrSetup.ts +0 -623
  73. package/src/ui/screens/QBittorrentSetup.ts +0 -268
  74. package/src/ui/screens/QuickSetup.ts +0 -1161
  75. package/src/ui/screens/RecyclarrSetup.ts +0 -418
  76. package/src/ui/screens/SecretsEditor.ts +0 -214
  77. package/src/ui/screens/SettingsScreen.ts +0 -605
  78. package/src/ui/screens/TRaSHProfileSetup.ts +0 -395
  79. package/src/ui/screens/index.ts +0 -4
  80. package/src/util/arch.ts +0 -78
  81. package/src/utils/browser.ts +0 -26
  82. package/src/utils/categories.ts +0 -59
  83. package/src/utils/debug.ts +0 -91
  84. package/src/utils/env.ts +0 -137
  85. package/src/utils/logs.ts +0 -118
  86. package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -101
  87. package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -90
  88. package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -44
  89. package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -107
  90. package/src/utils/migrations.ts +0 -164
  91. package/src/utils/password.ts +0 -42
  92. package/src/utils/unraid.ts +0 -101
  93. package/src/utils/update-checker.ts +0 -210
  94. package/src/utils/url-utils.ts +0 -38
@@ -1,612 +0,0 @@
1
- /**
2
- * Jellyseerr Setup Screen
3
- * Automates the Jellyseerr setup wizard via API
4
- */
5
-
6
- import { BoxRenderable, CliRenderer, TextRenderable, KeyEvent } from "@opentui/core"
7
- import { createPageLayout } from "../components/PageLayout"
8
- import { EasiarrConfig } from "../../config/schema"
9
- import { JellyseerrClient } from "../../api/jellyseerr-api"
10
- import { getApp } from "../../apps/registry"
11
- import { readEnvSync, writeEnvSync } from "../../utils/env"
12
- import { debugLog } from "../../utils/debug"
13
-
14
- interface SetupResult {
15
- name: string
16
- status: "pending" | "configuring" | "success" | "error" | "skipped"
17
- message?: string
18
- }
19
-
20
- type Step = "menu" | "running" | "done"
21
-
22
- export class JellyseerrSetup extends BoxRenderable {
23
- private config: EasiarrConfig
24
- private cliRenderer: CliRenderer
25
- private onBack: () => void
26
- private keyHandler!: (key: KeyEvent) => void
27
- private results: SetupResult[] = []
28
- private currentStep: Step = "menu"
29
- private contentBox!: BoxRenderable
30
- private menuIndex = 0
31
- private jellyseerrClient: JellyseerrClient | null = null
32
- private mediaServerType: "jellyfin" | "plex" | "emby" | null = null
33
-
34
- constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
35
- const { container: pageContainer, content: contentBox } = createPageLayout(cliRenderer, {
36
- title: "Jellyseerr Setup",
37
- stepInfo: "Configure Jellyseerr via API",
38
- footerHint: [
39
- { type: "key", key: "↑↓", value: "Navigate" },
40
- { type: "key", key: "Enter", value: "Select" },
41
- { type: "key", key: "Esc", value: "Back" },
42
- ],
43
- })
44
- super(cliRenderer, { width: "100%", height: "100%" })
45
- this.add(pageContainer)
46
-
47
- this.config = config
48
- this.cliRenderer = cliRenderer
49
- this.onBack = onBack
50
- this.contentBox = contentBox
51
-
52
- this.initClient()
53
- this.detectMediaServer()
54
- this.initKeyHandler()
55
- this.refreshContent()
56
- }
57
-
58
- private initClient(): void {
59
- const jellyseerrConfig = this.config.apps.find((a) => a.id === "jellyseerr")
60
- if (jellyseerrConfig?.enabled) {
61
- const port = jellyseerrConfig.port || 5055
62
- this.jellyseerrClient = new JellyseerrClient("localhost", port)
63
- }
64
- }
65
-
66
- private detectMediaServer(): void {
67
- // Check which media server is enabled
68
- const jellyfin = this.config.apps.find((a) => a.id === "jellyfin" && a.enabled)
69
- const plex = this.config.apps.find((a) => a.id === "plex" && a.enabled)
70
-
71
- if (jellyfin) this.mediaServerType = "jellyfin"
72
- else if (plex) this.mediaServerType = "plex"
73
- }
74
-
75
- private initKeyHandler(): void {
76
- this.keyHandler = (key: KeyEvent) => {
77
- debugLog("Jellyseerr", `Key: ${key.name}, step=${this.currentStep}`)
78
-
79
- if (key.name === "escape" || (key.name === "c" && key.ctrl)) {
80
- if (this.currentStep === "menu") {
81
- this.cleanup()
82
- } else if (this.currentStep === "done") {
83
- this.currentStep = "menu"
84
- this.refreshContent()
85
- }
86
- return
87
- }
88
-
89
- if (this.currentStep === "menu") {
90
- this.handleMenuKeys(key)
91
- } else if (this.currentStep === "done") {
92
- if (key.name === "return" || key.name === "escape") {
93
- this.currentStep = "menu"
94
- this.refreshContent()
95
- }
96
- }
97
- }
98
- this.cliRenderer.keyInput.on("keypress", this.keyHandler)
99
- debugLog("Jellyseerr", "Key handler registered")
100
- }
101
-
102
- private handleMenuKeys(key: KeyEvent): void {
103
- const menuItems = this.getMenuItems()
104
-
105
- if (key.name === "up" && this.menuIndex > 0) {
106
- this.menuIndex--
107
- this.refreshContent()
108
- } else if (key.name === "down" && this.menuIndex < menuItems.length - 1) {
109
- this.menuIndex++
110
- this.refreshContent()
111
- } else if (key.name === "return") {
112
- this.executeMenuItem(this.menuIndex)
113
- }
114
- }
115
-
116
- private getMenuItems(): { name: string; description: string; action: () => void }[] {
117
- return [
118
- {
119
- name: "🚀 Run Setup Wizard",
120
- description: "Configure media server and create admin user",
121
- action: () => this.runSetupWizard(),
122
- },
123
- {
124
- name: "📚 Sync Libraries",
125
- description: "Sync and enable libraries from media server",
126
- action: () => this.syncLibraries(),
127
- },
128
- {
129
- name: "🔗 Configure Radarr/Sonarr",
130
- description: "Connect *arr apps for request automation",
131
- action: () => this.configureArrApps(),
132
- },
133
- {
134
- name: "↩️ Back",
135
- description: "Return to main menu",
136
- action: () => this.cleanup(),
137
- },
138
- ]
139
- }
140
-
141
- private executeMenuItem(index: number): void {
142
- const items = this.getMenuItems()
143
- if (index >= 0 && index < items.length) {
144
- items[index].action()
145
- }
146
- }
147
-
148
- private async runSetupWizard(): Promise<void> {
149
- if (!this.jellyseerrClient) {
150
- this.results = [{ name: "Jellyseerr", status: "error", message: "Not enabled in config" }]
151
- this.currentStep = "done"
152
- this.refreshContent()
153
- return
154
- }
155
-
156
- if (!this.mediaServerType) {
157
- this.results = [{ name: "Media Server", status: "error", message: "No media server enabled" }]
158
- this.currentStep = "done"
159
- this.refreshContent()
160
- return
161
- }
162
-
163
- this.currentStep = "running"
164
- this.results = [
165
- { name: "Check status", status: "configuring" },
166
- { name: "Authenticate", status: "pending" },
167
- { name: "Configure media server", status: "pending" },
168
- { name: "Sync libraries", status: "pending" },
169
- { name: "Save API key", status: "pending" },
170
- { name: "Finalize setup", status: "pending" },
171
- ]
172
- this.refreshContent()
173
-
174
- try {
175
- // Step 1: Check if already initialized
176
- const isInit = await this.jellyseerrClient.isInitialized()
177
- if (isInit) {
178
- this.results[0].status = "skipped"
179
- this.results[0].message = "Already initialized"
180
- this.results.slice(1).forEach((r) => {
181
- r.status = "skipped"
182
- r.message = "Setup already complete"
183
- })
184
- this.currentStep = "done"
185
- this.refreshContent()
186
- return
187
- }
188
- this.results[0].status = "success"
189
- this.results[0].message = "Setup needed"
190
- this.refreshContent()
191
-
192
- // Get credentials
193
- const env = readEnvSync()
194
- const username = env["USERNAME_GLOBAL"] || "admin"
195
- const password = env["PASSWORD_GLOBAL"] || "Ch4ng3m3!1234securityReasons"
196
-
197
- if (this.mediaServerType === "jellyfin") {
198
- const jellyfinDef = getApp("jellyfin")
199
- // Use internal port for container-to-container communication (always 8096)
200
- const internalPort = jellyfinDef?.internalPort || jellyfinDef?.defaultPort || 8096
201
- const jellyfinHost = "jellyfin" // Hostname only for auth
202
- const jellyfinFullUrl = `http://${jellyfinHost}:${internalPort}` // Full URL for settings
203
- const userEmail = `${username}@easiarr.local`
204
-
205
- debugLog("Jellyseerr", `Connecting to Jellyfin at ${jellyfinFullUrl}`)
206
-
207
- try {
208
- // Step 2: Authenticate with Jellyseerr (creates admin user AND gets session cookie)
209
- this.results[1].status = "configuring"
210
- this.refreshContent()
211
- await this.jellyseerrClient.authenticateJellyfin(username, password, jellyfinHost, internalPort, userEmail)
212
- this.results[1].status = "success"
213
- this.results[1].message = `User: ${username}`
214
- this.refreshContent()
215
-
216
- // Step 3: Configure media server (now we have the session cookie)
217
- this.results[2].status = "configuring"
218
- this.refreshContent()
219
- await this.jellyseerrClient.updateJellyfinSettings({
220
- hostname: jellyfinFullUrl,
221
- adminUser: username,
222
- adminPass: password,
223
- })
224
- this.results[2].status = "success"
225
- this.results[2].message = `Jellyfin @ ${jellyfinHost}`
226
- this.refreshContent()
227
- } catch (error: unknown) {
228
- const err = error instanceof Error ? error : new Error(String(error))
229
- debugLog("Jellyseerr", `Auth failed: ${err.message}`)
230
- this.results[1].status = "error"
231
- this.results[1].message = "Auth Failed"
232
-
233
- if (err.message.includes("NO_ADMIN_USER")) {
234
- this.results[2].message = "Jellyfin user not Admin"
235
- this.results[2].status = "error"
236
- // Wait for user to read the message
237
- await new Promise((resolve) => setTimeout(resolve, 8000))
238
- } else if (err.message.includes("401")) {
239
- this.results[2].message = "Invalid Credentials"
240
- this.results[2].status = "error"
241
- await new Promise((resolve) => setTimeout(resolve, 8000))
242
- } else {
243
- this.results[2].message = "Connection Error"
244
- this.results[2].status = "error"
245
- await new Promise((resolve) => setTimeout(resolve, 5000))
246
- }
247
- throw err // Re-throw to stop the wizard
248
- }
249
- } else {
250
- // Plex/Emby - skip for now, needs token-based auth
251
- this.results[1].status = "skipped"
252
- this.results[1].message = "Token auth needed"
253
- this.results[2].status = "skipped"
254
- this.results[2].message = `${this.mediaServerType} requires manual setup`
255
- this.refreshContent()
256
- }
257
-
258
- // Step 4: Sync libraries
259
- this.results[3].status = "configuring"
260
- this.refreshContent()
261
- try {
262
- const libraries = await this.jellyseerrClient.syncJellyfinLibraries()
263
- const libraryIds = libraries.map((lib) => lib.id)
264
- if (libraryIds.length > 0) {
265
- await this.jellyseerrClient.enableLibraries(libraryIds)
266
- }
267
- this.results[3].status = "success"
268
- this.results[3].message = `${libraries.length} libraries synced`
269
- } catch {
270
- this.results[3].status = "error"
271
- this.results[3].message = "Library sync failed"
272
- }
273
- this.refreshContent()
274
-
275
- // Step 5: Save API key
276
- this.results[4].status = "configuring"
277
- this.refreshContent()
278
- try {
279
- const mainSettings = await this.jellyseerrClient.getMainSettings()
280
- if (mainSettings.apiKey) {
281
- env["API_KEY_JELLYSEERR"] = mainSettings.apiKey
282
- writeEnvSync(env)
283
- this.results[4].status = "success"
284
- this.results[4].message = `Key: ${mainSettings.apiKey.substring(0, 8)}...`
285
- } else {
286
- this.results[4].status = "error"
287
- this.results[4].message = "No API key returned"
288
- }
289
- } catch {
290
- this.results[4].status = "error"
291
- this.results[4].message = "Failed to get API key"
292
- }
293
- this.refreshContent()
294
-
295
- // Step 6: Initialize (finalize setup wizard)
296
- this.results[5].status = "configuring"
297
- this.refreshContent()
298
- try {
299
- await this.jellyseerrClient.initialize()
300
- this.results[5].status = "success"
301
- this.results[5].message = "Setup complete"
302
- } catch {
303
- this.results[5].status = "error"
304
- this.results[5].message = "Failed to finalize"
305
- }
306
- this.refreshContent()
307
- } catch (error) {
308
- const current = this.results.find((r) => r.status === "configuring")
309
- if (current) {
310
- current.status = "error"
311
- current.message = error instanceof Error ? error.message : String(error)
312
- }
313
- }
314
-
315
- this.currentStep = "done"
316
- this.refreshContent()
317
- }
318
-
319
- private async syncLibraries(): Promise<void> {
320
- if (!this.jellyseerrClient) {
321
- this.results = [{ name: "Jellyseerr", status: "error", message: "Not enabled" }]
322
- this.currentStep = "done"
323
- this.refreshContent()
324
- return
325
- }
326
-
327
- this.currentStep = "running"
328
- this.results = [
329
- { name: "Sync libraries", status: "configuring" },
330
- { name: "Enable all", status: "pending" },
331
- ]
332
- this.refreshContent()
333
-
334
- try {
335
- const libraries = await this.jellyseerrClient.syncJellyfinLibraries()
336
- this.results[0].status = "success"
337
- this.results[0].message = `Found ${libraries.length} libraries`
338
- this.refreshContent()
339
-
340
- this.results[1].status = "configuring"
341
- this.refreshContent()
342
- const libraryIds = libraries.map((lib) => lib.id)
343
- if (libraryIds.length > 0) {
344
- await this.jellyseerrClient.enableLibraries(libraryIds)
345
- }
346
- this.results[1].status = "success"
347
- this.results[1].message = libraries.map((l) => l.name).join(", ")
348
- this.refreshContent()
349
- } catch (error) {
350
- const current = this.results.find((r) => r.status === "configuring")
351
- if (current) {
352
- current.status = "error"
353
- current.message = error instanceof Error ? error.message : String(error)
354
- }
355
- }
356
-
357
- this.currentStep = "done"
358
- this.refreshContent()
359
- }
360
-
361
- private async configureArrApps(): Promise<void> {
362
- if (!this.jellyseerrClient) {
363
- this.results = [{ name: "Jellyseerr", status: "error", message: "Not enabled" }]
364
- this.currentStep = "done"
365
- this.refreshContent()
366
- return
367
- }
368
-
369
- this.currentStep = "running"
370
- this.results = []
371
-
372
- const env = readEnvSync()
373
-
374
- // Check for Radarr
375
- const radarrConfig = this.config.apps.find((a) => a.id === "radarr" && a.enabled)
376
- if (radarrConfig) {
377
- this.results.push({ name: "Radarr", status: "pending" })
378
- }
379
-
380
- // Check for Sonarr
381
- const sonarrConfig = this.config.apps.find((a) => a.id === "sonarr" && a.enabled)
382
- if (sonarrConfig) {
383
- this.results.push({ name: "Sonarr", status: "pending" })
384
- }
385
-
386
- if (this.results.length === 0) {
387
- this.results = [{ name: "No *arr apps", status: "skipped", message: "Enable Radarr/Sonarr first" }]
388
- this.currentStep = "done"
389
- this.refreshContent()
390
- return
391
- }
392
-
393
- this.refreshContent()
394
-
395
- // Configure Radarr
396
- if (radarrConfig) {
397
- const idx = this.results.findIndex((r) => r.name === "Radarr")
398
- this.results[idx].status = "configuring"
399
- this.refreshContent()
400
-
401
- const apiKey = env["API_KEY_RADARR"]
402
- if (!apiKey) {
403
- this.results[idx].status = "error"
404
- this.results[idx].message = "No API key in .env"
405
- } else {
406
- try {
407
- const radarrDef = getApp("radarr")
408
- const port = radarrConfig.port || radarrDef?.defaultPort || 7878
409
- const rootFolder = radarrDef?.rootFolder?.path || "/data/media/movies"
410
-
411
- const result = await this.jellyseerrClient.configureRadarr("radarr", port, apiKey, rootFolder)
412
- if (result) {
413
- this.results[idx].status = "success"
414
- this.results[idx].message = `Profile: ${result.activeProfileName}`
415
- } else {
416
- this.results[idx].status = "error"
417
- this.results[idx].message = "Configuration failed"
418
- }
419
- } catch (e) {
420
- this.results[idx].status = "error"
421
- this.results[idx].message = e instanceof Error ? e.message : String(e)
422
- }
423
- }
424
- this.refreshContent()
425
- }
426
-
427
- // Configure Sonarr
428
- if (sonarrConfig) {
429
- const idx = this.results.findIndex((r) => r.name === "Sonarr")
430
- this.results[idx].status = "configuring"
431
- this.refreshContent()
432
-
433
- const apiKey = env["API_KEY_SONARR"]
434
- if (!apiKey) {
435
- this.results[idx].status = "error"
436
- this.results[idx].message = "No API key in .env"
437
- } else {
438
- try {
439
- const sonarrDef = getApp("sonarr")
440
- const port = sonarrConfig.port || sonarrDef?.defaultPort || 8989
441
- const rootFolder = sonarrDef?.rootFolder?.path || "/data/media/tv"
442
-
443
- const result = await this.jellyseerrClient.configureSonarr("sonarr", port, apiKey, rootFolder)
444
- if (result) {
445
- this.results[idx].status = "success"
446
- this.results[idx].message = `Profile: ${result.activeProfileName}`
447
- } else {
448
- this.results[idx].status = "error"
449
- this.results[idx].message = "Configuration failed"
450
- }
451
- } catch (e) {
452
- this.results[idx].status = "error"
453
- this.results[idx].message = e instanceof Error ? e.message : String(e)
454
- }
455
- }
456
- this.refreshContent()
457
- }
458
-
459
- this.currentStep = "done"
460
- this.refreshContent()
461
- }
462
-
463
- private refreshContent(): void {
464
- this.contentBox.getChildren().forEach((child) => child.destroy())
465
-
466
- if (this.currentStep === "menu") {
467
- this.renderMenu()
468
- } else {
469
- this.renderResults()
470
- }
471
- }
472
-
473
- private renderMenu(): void {
474
- // Show status
475
- this.checkHealth()
476
-
477
- this.contentBox.add(
478
- new TextRenderable(this.cliRenderer, {
479
- content: "Select an action:\n\n",
480
- fg: "#aaaaaa",
481
- })
482
- )
483
-
484
- this.getMenuItems().forEach((item, idx) => {
485
- const pointer = idx === this.menuIndex ? "→ " : " "
486
- const fg = idx === this.menuIndex ? "#50fa7b" : "#8be9fd"
487
-
488
- this.contentBox.add(
489
- new TextRenderable(this.cliRenderer, {
490
- content: `${pointer}${item.name}\n`,
491
- fg,
492
- })
493
- )
494
- this.contentBox.add(
495
- new TextRenderable(this.cliRenderer, {
496
- content: ` ${item.description}\n\n`,
497
- fg: "#6272a4",
498
- })
499
- )
500
- })
501
- }
502
-
503
- private async checkHealth(): Promise<void> {
504
- if (!this.jellyseerrClient) {
505
- this.contentBox.add(
506
- new TextRenderable(this.cliRenderer, {
507
- content: "⚠️ Jellyseerr not enabled in config!\n\n",
508
- fg: "#ff5555",
509
- })
510
- )
511
- return
512
- }
513
-
514
- if (!this.mediaServerType) {
515
- this.contentBox.add(
516
- new TextRenderable(this.cliRenderer, {
517
- content: "⚠️ No media server enabled (Jellyfin/Plex/Emby)\n\n",
518
- fg: "#ff5555",
519
- })
520
- )
521
- return
522
- }
523
-
524
- try {
525
- const isHealthy = await this.jellyseerrClient.isHealthy()
526
- const isInit = isHealthy ? await this.jellyseerrClient.isInitialized() : false
527
-
528
- if (!isHealthy) {
529
- this.contentBox.add(
530
- new TextRenderable(this.cliRenderer, {
531
- content: "⚠️ Jellyseerr is not reachable. Make sure the container is running.\n\n",
532
- fg: "#ffb86c",
533
- })
534
- )
535
- } else if (!isInit) {
536
- this.contentBox.add(
537
- new TextRenderable(this.cliRenderer, {
538
- content: `✨ Jellyseerr needs setup. Will connect to ${this.mediaServerType}.\n\n`,
539
- fg: "#50fa7b",
540
- })
541
- )
542
- } else {
543
- this.contentBox.add(
544
- new TextRenderable(this.cliRenderer, {
545
- content: "✓ Jellyseerr is running and configured.\n\n",
546
- fg: "#50fa7b",
547
- })
548
- )
549
- }
550
- } catch {
551
- // Ignore
552
- }
553
- }
554
-
555
- private renderResults(): void {
556
- const headerText = this.currentStep === "done" ? "Results:\n\n" : "Configuring...\n\n"
557
- this.contentBox.add(
558
- new TextRenderable(this.cliRenderer, {
559
- content: headerText,
560
- fg: this.currentStep === "done" ? "#50fa7b" : "#f1fa8c",
561
- })
562
- )
563
-
564
- for (const result of this.results) {
565
- let status = ""
566
- let fg = "#aaaaaa"
567
- switch (result.status) {
568
- case "pending":
569
- status = "⏳"
570
- break
571
- case "configuring":
572
- status = "🔄"
573
- fg = "#f1fa8c"
574
- break
575
- case "success":
576
- status = "✓"
577
- fg = "#50fa7b"
578
- break
579
- case "error":
580
- status = "✗"
581
- fg = "#ff5555"
582
- break
583
- case "skipped":
584
- status = "⊘"
585
- fg = "#6272a4"
586
- break
587
- }
588
-
589
- let content = `${status} ${result.name}`
590
- if (result.message) {
591
- content += ` - ${result.message}`
592
- }
593
-
594
- this.contentBox.add(new TextRenderable(this.cliRenderer, { content: content + "\n", fg }))
595
- }
596
-
597
- if (this.currentStep === "done") {
598
- this.contentBox.add(
599
- new TextRenderable(this.cliRenderer, {
600
- content: "\nPress Enter or Esc to continue...",
601
- fg: "#6272a4",
602
- })
603
- )
604
- }
605
- }
606
-
607
- private cleanup(): void {
608
- this.cliRenderer.keyInput.off("keypress", this.keyHandler)
609
- this.destroy()
610
- this.onBack()
611
- }
612
- }