@muhammedaksam/easiarr 1.4.0 → 1.6.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.
Files changed (109) hide show
  1. package/dist/index.js +29750 -0
  2. package/package.json +15 -15
  3. package/src/VersionInfo.ts +0 -12
  4. package/src/api/arr-api.ts +0 -521
  5. package/src/api/auto-setup-types.ts +0 -64
  6. package/src/api/base-api.ts +0 -216
  7. package/src/api/bazarr-api.ts +0 -297
  8. package/src/api/cloudflare-api.ts +0 -493
  9. package/src/api/custom-format-api.ts +0 -224
  10. package/src/api/grafana-api.ts +0 -230
  11. package/src/api/heimdall-api.ts +0 -141
  12. package/src/api/homarr-api.ts +0 -209
  13. package/src/api/huntarr-api.ts +0 -457
  14. package/src/api/index.ts +0 -9
  15. package/src/api/jellyfin-api.ts +0 -389
  16. package/src/api/jellyseerr-api.ts +0 -559
  17. package/src/api/maintainerr-api.ts +0 -461
  18. package/src/api/naming-config.ts +0 -101
  19. package/src/api/overseerr-api.ts +0 -392
  20. package/src/api/plex-api.ts +0 -308
  21. package/src/api/portainer-api.ts +0 -374
  22. package/src/api/profilarr-api.ts +0 -391
  23. package/src/api/prowlarr-api.ts +0 -488
  24. package/src/api/qbittorrent-api.ts +0 -300
  25. package/src/api/quality-profile-api.ts +0 -272
  26. package/src/api/tautulli-api.ts +0 -244
  27. package/src/api/uptime-kuma-api.ts +0 -343
  28. package/src/apps/categories.ts +0 -14
  29. package/src/apps/index.ts +0 -2
  30. package/src/apps/registry.ts +0 -1275
  31. package/src/compose/caddy-config.ts +0 -130
  32. package/src/compose/generator.ts +0 -287
  33. package/src/compose/index.ts +0 -7
  34. package/src/compose/templates.ts +0 -78
  35. package/src/compose/traefik-config.ts +0 -187
  36. package/src/config/bookmarks-generator.ts +0 -160
  37. package/src/config/defaults.ts +0 -37
  38. package/src/config/homepage-config.ts +0 -381
  39. package/src/config/index.ts +0 -6
  40. package/src/config/manager.ts +0 -150
  41. package/src/config/recyclarr-config.ts +0 -181
  42. package/src/config/schema.ts +0 -310
  43. package/src/config/slskd-config.ts +0 -86
  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 -333
  48. package/src/docker/client.ts +0 -312
  49. package/src/docker/index.ts +0 -18
  50. package/src/index.ts +0 -77
  51. package/src/setup/actions/arr-common.ts +0 -307
  52. package/src/setup/actions/dashboards.ts +0 -79
  53. package/src/setup/actions/download-clients.ts +0 -54
  54. package/src/setup/actions/jellyseerr.ts +0 -331
  55. package/src/setup/actions/media-servers.ts +0 -68
  56. package/src/setup/actions/monitoring.ts +0 -120
  57. package/src/setup/actions/prowlarr.ts +0 -137
  58. package/src/setup/actions/utilities.ts +0 -490
  59. package/src/setup/index.ts +0 -78
  60. package/src/setup/types.ts +0 -144
  61. package/src/structure/manager.ts +0 -129
  62. package/src/ui/App.ts +0 -111
  63. package/src/ui/components/ApplicationSelector.ts +0 -275
  64. package/src/ui/components/CredentialsForm.ts +0 -180
  65. package/src/ui/components/DownloadClientForm.ts +0 -114
  66. package/src/ui/components/FileEditor.ts +0 -91
  67. package/src/ui/components/FooterHint.ts +0 -119
  68. package/src/ui/components/PageLayout.ts +0 -160
  69. package/src/ui/components/UpdateNotification.ts +0 -102
  70. package/src/ui/index.ts +0 -7
  71. package/src/ui/screens/AdvancedSettings.ts +0 -188
  72. package/src/ui/screens/ApiKeyViewer.ts +0 -511
  73. package/src/ui/screens/AppConfigurator.ts +0 -543
  74. package/src/ui/screens/AppManager.ts +0 -284
  75. package/src/ui/screens/ContainerControl.ts +0 -581
  76. package/src/ui/screens/LogsViewer.ts +0 -478
  77. package/src/ui/screens/MainMenu.ts +0 -299
  78. package/src/ui/screens/MonitorDashboard.ts +0 -881
  79. package/src/ui/screens/SecretsEditor.ts +0 -216
  80. package/src/ui/screens/SettingsScreen.ts +0 -633
  81. package/src/ui/screens/index.ts +0 -22
  82. package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
  83. package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
  84. package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
  85. package/src/ui/screens/setup/HomepageSetup.ts +0 -165
  86. package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
  87. package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
  88. package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
  89. package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
  90. package/src/ui/screens/setup/QuickSetup.ts +0 -1169
  91. package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
  92. package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
  93. package/src/utils/arch.ts +0 -78
  94. package/src/utils/browser.ts +0 -27
  95. package/src/utils/categories.ts +0 -61
  96. package/src/utils/debug.ts +0 -84
  97. package/src/utils/env.ts +0 -138
  98. package/src/utils/index.ts +0 -8
  99. package/src/utils/logs.ts +0 -119
  100. package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
  101. package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
  102. package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
  103. package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
  104. package/src/utils/migrations.ts +0 -165
  105. package/src/utils/password.ts +0 -42
  106. package/src/utils/paths.ts +0 -33
  107. package/src/utils/unraid.ts +0 -103
  108. package/src/utils/update-checker.ts +0 -210
  109. package/src/utils/url-utils.ts +0 -38
@@ -1,478 +0,0 @@
1
- /**
2
- * Container Logs Viewer Screen
3
- * Full-screen log viewer with streaming, search, and save functionality
4
- */
5
-
6
- import type { CliRenderer, KeyEvent, RenderContext } from "@opentui/core"
7
-
8
- import {
9
- BoxRenderable,
10
- SelectRenderable,
11
- SelectRenderableEvents,
12
- TextRenderable,
13
- } from "@opentui/core"
14
-
15
- import type { EasiarrConfig } from "~/config/schema"
16
- import type { ContainerStatus } from "~/docker"
17
- import { getContainerLogs, getContainerStatuses } from "~/docker"
18
- import { createPageLayout } from "~/ui/components/PageLayout"
19
- import { formatBytes, formatRelativeTime, listSavedLogs, saveLog } from "~/utils/logs"
20
-
21
- type ViewMode = "select" | "logs" | "saved"
22
-
23
- export class LogsViewer extends BoxRenderable {
24
- private renderer: CliRenderer
25
- private config: EasiarrConfig
26
- private onBack: () => void
27
- private keyHandler: ((k: KeyEvent) => void) | null = null
28
-
29
- // State
30
- private mode: ViewMode = "select"
31
- private containers: ContainerStatus[] = []
32
- private selectedContainer: ContainerStatus | null = null
33
- private logContent: string[] = []
34
- private scrollOffset = 0
35
- private lineCount = 100 // Number of log lines to fetch
36
- private visibleLines = 35 // Number of visible lines in log view
37
- private isLoading = false
38
- private statusMessage = ""
39
- private savedLogs: Array<{ filename: string; path: string; date: Date; size: number }> = []
40
-
41
- // UI Components
42
- private page!: BoxRenderable
43
- private content!: BoxRenderable
44
- private statusBar!: TextRenderable
45
-
46
- constructor(renderer: RenderContext, config: EasiarrConfig, onBack: () => void) {
47
- super(renderer, { width: "100%", height: "100%", flexDirection: "column" })
48
- this.renderer = renderer as CliRenderer
49
- this.config = config
50
- this.onBack = onBack
51
-
52
- this.init()
53
- }
54
-
55
- private async init(): Promise<void> {
56
- await this.loadContainers()
57
- this.buildUI()
58
- this.attachKeyHandler()
59
- }
60
-
61
- private async loadContainers(): Promise<void> {
62
- this.isLoading = true
63
- const all = await getContainerStatuses()
64
- // Only show running containers (can fetch logs from them)
65
- this.containers = all.filter((c) => c.status === "running")
66
- this.isLoading = false
67
- }
68
-
69
- private buildUI(): void {
70
- const { container: page, content } = createPageLayout(this.renderer, {
71
- title: "📋 Container Logs",
72
- stepInfo: `${this.containers.length} running containers`,
73
- footerHint: this.getFooterHints(),
74
- })
75
- this.page = page
76
- this.content = content
77
-
78
- this.statusBar = new TextRenderable(this.renderer, {
79
- id: "status-bar",
80
- content: "",
81
- fg: "#f1fa8c",
82
- })
83
-
84
- this.renderContent()
85
- this.add(page)
86
- }
87
-
88
- private getFooterHints(): Array<
89
- { type: "key"; key: string; value: string } | { type: "separator" }
90
- > {
91
- switch (this.mode) {
92
- case "select":
93
- return [
94
- { type: "key", key: "Enter", value: "View Logs" },
95
- { type: "key", key: "h", value: "Saved Logs" },
96
- { type: "key", key: "q", value: "Back" },
97
- ]
98
- case "logs":
99
- return [
100
- { type: "key", key: "↑↓", value: "Scroll" },
101
- { type: "key", key: "s", value: "Save" },
102
- { type: "key", key: "r", value: "Refresh" },
103
- { type: "key", key: "+/-", value: "Lines" },
104
- { type: "key", key: "q", value: "Back" },
105
- ]
106
- case "saved":
107
- return [
108
- { type: "key", key: "Enter", value: "View" },
109
- { type: "key", key: "q", value: "Back" },
110
- ]
111
- }
112
- }
113
-
114
- private clearContent(): void {
115
- const children = [...this.content.getChildren()]
116
- for (const child of children) {
117
- if (child.id) this.content.remove(child.id)
118
- }
119
- }
120
-
121
- private renderContent(): void {
122
- this.clearContent()
123
-
124
- switch (this.mode) {
125
- case "select":
126
- this.renderContainerSelect()
127
- break
128
- case "logs":
129
- this.renderLogView()
130
- break
131
- case "saved":
132
- this.renderSavedLogs()
133
- break
134
- }
135
-
136
- this.content.add(this.statusBar)
137
- }
138
-
139
- private renderContainerSelect(): void {
140
- if (this.containers.length === 0) {
141
- this.content.add(
142
- new TextRenderable(this.renderer, {
143
- id: "no-containers",
144
- content: "No running containers found.\nStart containers first to view logs.",
145
- fg: "#888888",
146
- })
147
- )
148
- return
149
- }
150
-
151
- this.content.add(
152
- new TextRenderable(this.renderer, {
153
- id: "select-header",
154
- content: "Select a container to view logs:",
155
- fg: "#4a9eff",
156
- })
157
- )
158
- this.content.add(new TextRenderable(this.renderer, { content: "" }))
159
-
160
- const menu = new SelectRenderable(this.renderer, {
161
- id: "container-select",
162
- width: "100%",
163
- flexGrow: 1,
164
- options: this.containers.map((c) => ({
165
- name: `🐳 ${c.name}`,
166
- description: c.ports ? `Ports: ${c.ports.split(",")[0]}` : "",
167
- })),
168
- })
169
-
170
- menu.on(SelectRenderableEvents.ITEM_SELECTED, async (index) => {
171
- this.selectedContainer = this.containers[index]
172
- await this.fetchLogs()
173
- this.mode = "logs"
174
- this.scrollOffset = 0
175
- this.rebuildUI()
176
- })
177
-
178
- this.content.add(menu)
179
- setTimeout(() => menu.focus(), 10)
180
- }
181
-
182
- private renderLogView(): void {
183
- if (!this.selectedContainer) return
184
-
185
- // Header with container info
186
- const header = new BoxRenderable(this.renderer, {
187
- id: "log-header",
188
- width: "100%",
189
- flexDirection: "row",
190
- marginBottom: 1,
191
- })
192
-
193
- header.add(
194
- new TextRenderable(this.renderer, {
195
- id: "container-name",
196
- content: `📦 ${this.selectedContainer.name}`,
197
- fg: "#4a9eff",
198
- })
199
- )
200
- header.add(
201
- new TextRenderable(this.renderer, {
202
- id: "log-info",
203
- content: ` (${this.lineCount} lines, scroll: ${this.scrollOffset}/${Math.max(0, this.logContent.length - this.visibleLines)})`,
204
- fg: "#666666",
205
- })
206
- )
207
-
208
- this.content.add(header)
209
-
210
- // Log content box
211
- const logBox = new BoxRenderable(this.renderer, {
212
- id: "log-box",
213
- width: "100%",
214
- flexGrow: 1,
215
- flexDirection: "column",
216
- border: true,
217
- borderStyle: "single",
218
- borderColor: "#444444",
219
- padding: 1,
220
- overflow: "hidden",
221
- })
222
-
223
- if (this.isLoading) {
224
- logBox.add(
225
- new TextRenderable(this.renderer, {
226
- id: "loading",
227
- content: "Loading logs...",
228
- fg: "#f1fa8c",
229
- })
230
- )
231
- } else if (this.logContent.length === 0) {
232
- logBox.add(
233
- new TextRenderable(this.renderer, {
234
- id: "no-logs",
235
- content: "No logs available",
236
- fg: "#888888",
237
- })
238
- )
239
- } else {
240
- // Show visible lines based on scroll offset
241
- const lines = this.logContent.slice(this.scrollOffset, this.scrollOffset + this.visibleLines)
242
-
243
- lines.forEach((line, idx) => {
244
- // Colorize log levels
245
- let fg = "#888888"
246
- if (line.includes("ERROR") || line.includes("error")) fg = "#ff5555"
247
- else if (line.includes("WARN") || line.includes("warn")) fg = "#f1fa8c"
248
- else if (line.includes("INFO") || line.includes("info")) fg = "#50fa7b"
249
- else if (line.includes("DEBUG") || line.includes("debug")) fg = "#6272a4"
250
-
251
- logBox.add(
252
- new TextRenderable(this.renderer, {
253
- id: `log-line-${idx}`,
254
- content: line.substring(0, 120), // Truncate long lines
255
- fg,
256
- })
257
- )
258
- })
259
- }
260
-
261
- this.content.add(logBox)
262
- }
263
-
264
- private async renderSavedLogs(): Promise<void> {
265
- if (!this.selectedContainer) {
266
- this.content.add(
267
- new TextRenderable(this.renderer, {
268
- id: "no-selection",
269
- content: "Select a container first to view saved logs.",
270
- fg: "#888888",
271
- })
272
- )
273
- return
274
- }
275
-
276
- this.savedLogs = await listSavedLogs(this.selectedContainer.name)
277
-
278
- this.content.add(
279
- new TextRenderable(this.renderer, {
280
- id: "saved-header",
281
- content: `📁 Saved logs for ${this.selectedContainer.name}:`,
282
- fg: "#4a9eff",
283
- })
284
- )
285
- this.content.add(new TextRenderable(this.renderer, { content: "" }))
286
-
287
- if (this.savedLogs.length === 0) {
288
- this.content.add(
289
- new TextRenderable(this.renderer, {
290
- id: "no-saved",
291
- content: "No saved logs found. Press 's' while viewing logs to save.",
292
- fg: "#888888",
293
- })
294
- )
295
- return
296
- }
297
-
298
- const menu = new SelectRenderable(this.renderer, {
299
- id: "saved-select",
300
- width: "100%",
301
- flexGrow: 1,
302
- options: this.savedLogs.map((log) => ({
303
- name: `📄 ${log.filename}`,
304
- description: `${formatBytes(log.size)} - ${formatRelativeTime(log.date)}`,
305
- })),
306
- })
307
-
308
- menu.on(SelectRenderableEvents.ITEM_SELECTED, async (index) => {
309
- const log = this.savedLogs[index]
310
- // Load and display saved log
311
- const content = await Bun.file(log.path).text()
312
- this.logContent = content.split("\n")
313
- this.scrollOffset = 0
314
- this.mode = "logs"
315
- this.rebuildUI()
316
- })
317
-
318
- this.content.add(menu)
319
- setTimeout(() => menu.focus(), 10)
320
- }
321
-
322
- private async fetchLogs(): Promise<void> {
323
- if (!this.selectedContainer) return
324
-
325
- this.isLoading = true
326
- this.setStatus("Fetching logs...", "#f1fa8c")
327
-
328
- const result = await getContainerLogs(this.selectedContainer.name, this.lineCount)
329
-
330
- if (result.success) {
331
- this.logContent = result.output.split("\n").filter((line) => line.trim())
332
- this.setStatus(`Loaded ${this.logContent.length} lines`, "#50fa7b")
333
- } else {
334
- this.logContent = []
335
- this.setStatus("Failed to fetch logs", "#ff5555")
336
- }
337
-
338
- this.isLoading = false
339
- }
340
-
341
- private async saveCurrentLogs(): Promise<void> {
342
- if (!this.selectedContainer || this.logContent.length === 0) {
343
- this.setStatus("No logs to save", "#ff5555")
344
- return
345
- }
346
-
347
- this.setStatus("Saving logs...", "#f1fa8c")
348
- const content = this.logContent.join("\n")
349
- const filepath = await saveLog(this.selectedContainer.name, content)
350
- this.setStatus(`✓ Saved to ${filepath}`, "#50fa7b")
351
- }
352
-
353
- private setStatus(message: string, color: string): void {
354
- this.statusMessage = message
355
- if (this.statusBar) {
356
- this.statusBar.content = message
357
- this.statusBar.fg = color
358
- }
359
- }
360
-
361
- private rebuildUI(): void {
362
- // Remove old page
363
- if (this.page) {
364
- this.remove(this.page.id)
365
- }
366
- this.buildUI()
367
- }
368
-
369
- private attachKeyHandler(): void {
370
- this.keyHandler = async (k: KeyEvent) => {
371
- switch (this.mode) {
372
- case "select":
373
- await this.handleSelectModeKey(k)
374
- break
375
- case "logs":
376
- await this.handleLogsModeKey(k)
377
- break
378
- case "saved":
379
- await this.handleSavedModeKey(k)
380
- break
381
- }
382
- }
383
- this.renderer.keyInput.on("keypress", this.keyHandler)
384
- }
385
-
386
- private async handleSelectModeKey(k: KeyEvent): Promise<void> {
387
- switch (k.name) {
388
- case "h":
389
- if (this.selectedContainer) {
390
- this.mode = "saved"
391
- this.rebuildUI()
392
- }
393
- break
394
- case "q":
395
- case "escape":
396
- this.cleanup()
397
- this.onBack()
398
- break
399
- }
400
- }
401
-
402
- private async handleLogsModeKey(k: KeyEvent): Promise<void> {
403
- const maxScroll = Math.max(0, this.logContent.length - this.visibleLines)
404
-
405
- switch (k.name) {
406
- case "up":
407
- this.scrollOffset = Math.max(0, this.scrollOffset - 1)
408
- this.renderContent()
409
- break
410
- case "down":
411
- this.scrollOffset = Math.min(maxScroll, this.scrollOffset + 1)
412
- this.renderContent()
413
- break
414
- case "pageup":
415
- this.scrollOffset = Math.max(0, this.scrollOffset - 10)
416
- this.renderContent()
417
- break
418
- case "pagedown":
419
- this.scrollOffset = Math.min(maxScroll, this.scrollOffset + 10)
420
- this.renderContent()
421
- break
422
- case "home":
423
- this.scrollOffset = 0
424
- this.renderContent()
425
- break
426
- case "end":
427
- this.scrollOffset = maxScroll
428
- this.renderContent()
429
- break
430
- case "s":
431
- await this.saveCurrentLogs()
432
- break
433
- case "r":
434
- await this.fetchLogs()
435
- this.renderContent()
436
- break
437
- case "+":
438
- case "=":
439
- this.lineCount = Math.min(500, this.lineCount + 50)
440
- await this.fetchLogs()
441
- this.rebuildUI()
442
- break
443
- case "-":
444
- this.lineCount = Math.max(50, this.lineCount - 50)
445
- await this.fetchLogs()
446
- this.rebuildUI()
447
- break
448
- case "h":
449
- this.mode = "saved"
450
- this.rebuildUI()
451
- break
452
- case "q":
453
- case "escape":
454
- this.mode = "select"
455
- this.selectedContainer = null
456
- this.logContent = []
457
- this.rebuildUI()
458
- break
459
- }
460
- }
461
-
462
- private async handleSavedModeKey(k: KeyEvent): Promise<void> {
463
- switch (k.name) {
464
- case "q":
465
- case "escape":
466
- this.mode = this.selectedContainer ? "logs" : "select"
467
- this.rebuildUI()
468
- break
469
- }
470
- }
471
-
472
- private cleanup(): void {
473
- if (this.keyHandler) {
474
- this.renderer.keyInput.off("keypress", this.keyHandler)
475
- this.keyHandler = null
476
- }
477
- }
478
- }