@muhammedaksam/easiarr 1.4.0 → 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 (109) hide show
  1. package/dist/index.js +29837 -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,633 +0,0 @@
1
- /**
2
- * Settings Screen
3
- * Edit Traefik, VPN, and system configuration
4
- */
5
-
6
- import type { CliRenderer, KeyEvent } from "@opentui/core"
7
-
8
- import {
9
- BoxRenderable,
10
- InputRenderable,
11
- InputRenderableEvents,
12
- SelectRenderable,
13
- SelectRenderableEvents,
14
- TextRenderable,
15
- } from "@opentui/core"
16
-
17
- import type { EasiarrConfig, VpnMode } from "~/config/schema"
18
- import { saveCompose } from "~/compose"
19
- import { saveConfig } from "~/config"
20
- import { createPageLayout } from "~/ui/components/PageLayout"
21
-
22
- type SettingsSection = "traefik" | "vpn" | "system"
23
-
24
- export class SettingsScreen extends BoxRenderable {
25
- private config: EasiarrConfig
26
- private onBack: () => void
27
- private keyHandler: ((key: KeyEvent) => void) | null = null
28
- private activeSection: SettingsSection = "traefik"
29
- private page: BoxRenderable | null = null
30
- private cliRenderer: CliRenderer
31
-
32
- // Traefik settings (local copies to edit)
33
- private traefikDomain: string
34
- private traefikEntrypoint: string
35
- private traefikMiddlewares: string
36
-
37
- // VPN settings
38
- private vpnMode: VpnMode
39
-
40
- // System settings
41
- private rootDir: string
42
- private puid: string
43
- private pgid: string
44
- private timezone: string
45
- private umask: string
46
- private logMount: boolean
47
-
48
- constructor(renderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
49
- super(renderer, {
50
- id: "settings-screen",
51
- width: "100%",
52
- height: "100%",
53
- flexDirection: "column",
54
- })
55
-
56
- this.cliRenderer = renderer
57
- this.config = config
58
- this.onBack = onBack
59
-
60
- // Initialize local copies from config
61
- this.traefikDomain = config.traefik?.domain || "${CLOUDFLARE_DNS_ZONE}"
62
- this.traefikEntrypoint = config.traefik?.entrypoint || "web"
63
- this.traefikMiddlewares = config.traefik?.middlewares?.join(",") || ""
64
-
65
- this.vpnMode = config.vpn?.mode || "none"
66
-
67
- this.rootDir = config.rootDir
68
- this.puid = config.uid.toString()
69
- this.pgid = config.gid.toString()
70
- this.timezone = config.timezone
71
- this.umask = config.umask
72
- this.logMount = config.logMount ?? false
73
-
74
- this.renderContent()
75
- }
76
-
77
- private renderContent(): void {
78
- // Clear previous
79
- if (this.keyHandler) {
80
- this.cliRenderer.keyInput.off("keypress", this.keyHandler)
81
- this.keyHandler = null
82
- }
83
-
84
- const children = this.getChildren()
85
- for (const child of children) {
86
- this.remove(child.id)
87
- }
88
-
89
- const { container: page, content } = createPageLayout(this.cliRenderer as CliRenderer, {
90
- title: "Settings",
91
- stepInfo: "Edit Traefik, VPN, and System configuration",
92
- footerHint: [
93
- { type: "key", key: "Tab", value: "Next" },
94
- { type: "key", key: "Enter", value: "Select/Continue" },
95
- { type: "key", key: "s", value: "Save" },
96
- { type: "key", key: "Esc", value: "Back" },
97
- ],
98
- })
99
- this.page = page
100
-
101
- // Section tabs
102
- const tabsBox = new BoxRenderable(this.cliRenderer, {
103
- width: "100%",
104
- height: 1,
105
- flexDirection: "row",
106
- marginBottom: 1,
107
- })
108
-
109
- const sections: { id: SettingsSection; label: string }[] = [
110
- { id: "traefik", label: "Traefik" },
111
- { id: "vpn", label: "VPN" },
112
- { id: "system", label: "System" },
113
- ]
114
-
115
- for (const section of sections) {
116
- const isActive = this.activeSection === section.id
117
- tabsBox.add(
118
- new TextRenderable(this.cliRenderer, {
119
- content: ` ${section.label} `,
120
- fg: isActive ? "#4a9eff" : "#666666",
121
- marginRight: 2,
122
- })
123
- )
124
- }
125
-
126
- content.add(tabsBox)
127
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
128
-
129
- // Render active section
130
- switch (this.activeSection) {
131
- case "traefik":
132
- this.renderTraefikSection(content)
133
- break
134
- case "vpn":
135
- this.renderVpnSection(content)
136
- break
137
- case "system":
138
- this.renderSystemSection(content)
139
- break
140
- }
141
-
142
- this.add(page)
143
- }
144
-
145
- private renderTraefikSection(content: BoxRenderable): void {
146
- if (!this.config.traefik?.enabled) {
147
- content.add(
148
- new TextRenderable(this.cliRenderer, {
149
- content: "Traefik is not enabled. Enable it in App Manager first.",
150
- fg: "#ff6666",
151
- })
152
- )
153
- this.setupBackHandler()
154
- return
155
- }
156
-
157
- content.add(
158
- new TextRenderable(this.cliRenderer, {
159
- content: "Configure Traefik reverse proxy settings:",
160
- fg: "#888888",
161
- })
162
- )
163
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
164
-
165
- // Domain
166
- const domainRow = new BoxRenderable(this.cliRenderer, {
167
- width: "100%",
168
- height: 1,
169
- flexDirection: "row",
170
- marginBottom: 1,
171
- })
172
- domainRow.add(
173
- new TextRenderable(this.cliRenderer, {
174
- content: "Domain:".padEnd(16),
175
- fg: "#aaaaaa",
176
- })
177
- )
178
- const domainInput = new InputRenderable(this.cliRenderer, {
179
- id: "settings-traefik-domain",
180
- width: 40,
181
- placeholder: "${CLOUDFLARE_DNS_ZONE}",
182
- backgroundColor: "#2a2a3e",
183
- textColor: "#ffffff",
184
- focusedBackgroundColor: "#3a3a4e",
185
- })
186
- domainInput.value = this.traefikDomain
187
- domainInput.on(InputRenderableEvents.CHANGE, (v) => (this.traefikDomain = v))
188
- domainRow.add(domainInput)
189
- content.add(domainRow)
190
-
191
- // Entrypoint
192
- const epRow = new BoxRenderable(this.cliRenderer, {
193
- width: "100%",
194
- height: 1,
195
- flexDirection: "row",
196
- marginBottom: 1,
197
- })
198
- epRow.add(
199
- new TextRenderable(this.cliRenderer, {
200
- content: "Entrypoint:".padEnd(16),
201
- fg: "#aaaaaa",
202
- })
203
- )
204
- const epInput = new InputRenderable(this.cliRenderer, {
205
- id: "settings-traefik-entrypoint",
206
- width: 20,
207
- placeholder: "web",
208
- backgroundColor: "#2a2a3e",
209
- textColor: "#ffffff",
210
- focusedBackgroundColor: "#3a3a4e",
211
- })
212
- epInput.value = this.traefikEntrypoint
213
- epInput.on(InputRenderableEvents.CHANGE, (v) => (this.traefikEntrypoint = v))
214
- epRow.add(epInput)
215
-
216
- // Hint for cloudflared
217
- const cloudflaredEnabled = this.config.apps.some((a) => a.id === "cloudflared" && a.enabled)
218
- if (cloudflaredEnabled && this.traefikEntrypoint === "websecure") {
219
- epRow.add(
220
- new TextRenderable(this.cliRenderer, {
221
- content: " ⚠️ Use 'web' for Cloudflare Tunnel",
222
- fg: "#ffcc00",
223
- })
224
- )
225
- }
226
- content.add(epRow)
227
-
228
- // Middlewares
229
- const mwRow = new BoxRenderable(this.cliRenderer, {
230
- width: "100%",
231
- height: 1,
232
- flexDirection: "row",
233
- marginBottom: 1,
234
- })
235
- mwRow.add(
236
- new TextRenderable(this.cliRenderer, {
237
- content: "Middlewares:".padEnd(16),
238
- fg: "#aaaaaa",
239
- })
240
- )
241
- const mwInput = new InputRenderable(this.cliRenderer, {
242
- id: "settings-traefik-middlewares",
243
- width: 50,
244
- placeholder: "security-headers@file",
245
- backgroundColor: "#2a2a3e",
246
- textColor: "#ffffff",
247
- focusedBackgroundColor: "#3a3a4e",
248
- })
249
- mwInput.value = this.traefikMiddlewares
250
- mwInput.on(InputRenderableEvents.CHANGE, (v) => (this.traefikMiddlewares = v))
251
- mwRow.add(mwInput)
252
- content.add(mwRow)
253
-
254
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
255
-
256
- // Navigation
257
- const navMenu = new SelectRenderable(this.cliRenderer, {
258
- id: "settings-traefik-nav",
259
- width: "100%",
260
- height: 4,
261
- options: [
262
- { name: "💾 Save Changes", description: "Save and regenerate docker-compose.yml" },
263
- { name: "➡️ Next: VPN", description: "Go to VPN settings" },
264
- { name: "◀ Back", description: "Return to main menu" },
265
- ],
266
- })
267
-
268
- navMenu.on(SelectRenderableEvents.ITEM_SELECTED, async (index) => {
269
- if (index === 0) {
270
- await this.save()
271
- } else if (index === 1) {
272
- this.activeSection = "vpn"
273
- this.renderContent()
274
- } else {
275
- this.cleanup()
276
- this.onBack()
277
- }
278
- })
279
-
280
- content.add(navMenu)
281
-
282
- // Focus management
283
- const inputs = [domainInput, epInput, mwInput, navMenu]
284
- let focusIndex = 0
285
- inputs[0].focus()
286
-
287
- this.keyHandler = (key: KeyEvent) => {
288
- if (key.name === "escape") {
289
- this.cleanup()
290
- this.onBack()
291
- } else if (key.name === "s" && !key.ctrl) {
292
- this.save()
293
- } else if (key.name === "tab" || key.name === "enter") {
294
- if (key.name === "enter" && focusIndex === inputs.length - 1) return
295
- inputs[focusIndex].blur()
296
- focusIndex = key.shift
297
- ? (focusIndex - 1 + inputs.length) % inputs.length
298
- : (focusIndex + 1) % inputs.length
299
- inputs[focusIndex].focus()
300
- }
301
- }
302
- ;(this.cliRenderer as CliRenderer).keyInput.on("keypress", this.keyHandler)
303
- }
304
-
305
- private renderVpnSection(content: BoxRenderable): void {
306
- content.add(
307
- new TextRenderable(this.cliRenderer, {
308
- content: "Configure VPN routing through Gluetun:",
309
- fg: "#888888",
310
- })
311
- )
312
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
313
-
314
- const gluetunEnabled = this.config.apps.some((a) => a.id === "gluetun" && a.enabled)
315
- if (!gluetunEnabled) {
316
- content.add(
317
- new TextRenderable(this.cliRenderer, {
318
- content: "Gluetun VPN is not enabled. Enable it in App Manager first.",
319
- fg: "#ff6666",
320
- })
321
- )
322
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
323
- }
324
-
325
- // Current mode display
326
- const modeLabels: Record<VpnMode, string> = {
327
- full: "🛡️ Full VPN",
328
- mini: "⚡ Mini VPN",
329
- none: "❌ No VPN Routing",
330
- }
331
- content.add(
332
- new TextRenderable(this.cliRenderer, {
333
- content: `Current mode: ${modeLabels[this.vpnMode]}`,
334
- fg: "#50fa7b",
335
- })
336
- )
337
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
338
-
339
- // Combined menu with VPN options + navigation
340
- const menu = new SelectRenderable(this.cliRenderer, {
341
- id: "settings-vpn-menu",
342
- width: "100%",
343
- height: 8,
344
- options: [
345
- {
346
- name: "🛡️ Full VPN",
347
- description: "Route Downloaders, Indexers, and Media Servers through VPN",
348
- },
349
- { name: "⚡ Mini VPN", description: "Route ONLY Downloaders through VPN (Recommended)" },
350
- { name: "❌ No VPN Routing", description: "Run container but don't route traffic" },
351
- { name: "◀ Previous: Traefik", description: "Go to Traefik settings" },
352
- { name: "➡️ Next: System", description: "Go to System settings" },
353
- { name: "💾 Save Changes", description: "Save and regenerate docker-compose.yml" },
354
- { name: "✕ Back", description: "Return to main menu" },
355
- ],
356
- })
357
-
358
- const modes: VpnMode[] = ["full", "mini", "none"]
359
-
360
- menu.on(SelectRenderableEvents.ITEM_SELECTED, async (index) => {
361
- if (index < 3) {
362
- // VPN mode selection
363
- this.vpnMode = modes[index]
364
- this.renderContent() // Re-render to show selection
365
- } else if (index === 3) {
366
- this.activeSection = "traefik"
367
- this.renderContent()
368
- } else if (index === 4) {
369
- this.activeSection = "system"
370
- this.renderContent()
371
- } else if (index === 5) {
372
- await this.save()
373
- } else {
374
- this.cleanup()
375
- this.onBack()
376
- }
377
- })
378
-
379
- content.add(menu)
380
- menu.focus()
381
-
382
- this.keyHandler = (key: KeyEvent) => {
383
- if (key.name === "escape") {
384
- this.cleanup()
385
- this.onBack()
386
- }
387
- }
388
- this.cliRenderer.keyInput.on("keypress", this.keyHandler)
389
- }
390
-
391
- private renderSystemSection(content: BoxRenderable): void {
392
- content.add(
393
- new TextRenderable(this.cliRenderer, {
394
- content: "Configure system-wide settings:",
395
- fg: "#888888",
396
- })
397
- )
398
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
399
-
400
- const createField = (
401
- id: string,
402
- label: string,
403
- value: string,
404
- placeholder: string,
405
- width: number = 40
406
- ) => {
407
- const row = new BoxRenderable(this.cliRenderer, {
408
- width: "100%",
409
- height: 1,
410
- flexDirection: "row",
411
- marginBottom: 1,
412
- })
413
- row.add(
414
- new TextRenderable(this.cliRenderer, {
415
- content: label.padEnd(16),
416
- fg: "#aaaaaa",
417
- })
418
- )
419
- const input = new InputRenderable(this.cliRenderer, {
420
- id,
421
- width,
422
- placeholder,
423
- backgroundColor: "#2a2a3e",
424
- textColor: "#ffffff",
425
- focusedBackgroundColor: "#3a3a4e",
426
- })
427
- input.value = value
428
- row.add(input)
429
- content.add(row)
430
- return input
431
- }
432
-
433
- const rootInput = createField(
434
- "settings-sys-root",
435
- "Root Path:",
436
- this.rootDir,
437
- "/home/user/media",
438
- 50
439
- )
440
- const puidInput = createField("settings-sys-puid", "PUID:", this.puid, "1000", 10)
441
- const pgidInput = createField("settings-sys-pgid", "PGID:", this.pgid, "1000", 10)
442
- const tzInput = createField("settings-sys-tz", "Timezone:", this.timezone, "Europe/London", 30)
443
- const umaskInput = createField("settings-sys-umask", "Umask:", this.umask, "002", 10)
444
-
445
- rootInput.on(InputRenderableEvents.CHANGE, (v) => (this.rootDir = v))
446
- puidInput.on(InputRenderableEvents.CHANGE, (v) => (this.puid = v))
447
- pgidInput.on(InputRenderableEvents.CHANGE, (v) => (this.pgid = v))
448
- tzInput.on(InputRenderableEvents.CHANGE, (v) => (this.timezone = v))
449
- umaskInput.on(InputRenderableEvents.CHANGE, (v) => (this.umask = v))
450
-
451
- // Log mount toggle
452
- const logMountRow = new BoxRenderable(this.cliRenderer, {
453
- width: "100%",
454
- height: 1,
455
- flexDirection: "row",
456
- marginBottom: 1,
457
- })
458
- logMountRow.add(
459
- new TextRenderable(this.cliRenderer, {
460
- content: "Bind Logs:".padEnd(16),
461
- fg: "#aaaaaa",
462
- })
463
- )
464
- const logMountToggle = new SelectRenderable(this.cliRenderer, {
465
- id: "settings-sys-logmount",
466
- width: 30,
467
- height: 1,
468
- options: [
469
- {
470
- name: this.logMount ? "✓ Enabled" : "○ Disabled",
471
- description: "Bind-mount container logs to ${ROOT_DIR}/logs/",
472
- },
473
- ],
474
- })
475
- logMountToggle.on(SelectRenderableEvents.ITEM_SELECTED, () => {
476
- this.logMount = !this.logMount
477
- this.renderContent()
478
- })
479
- logMountRow.add(logMountToggle)
480
- content.add(logMountRow)
481
-
482
- content.add(new TextRenderable(this.cliRenderer, { content: " " }))
483
-
484
- const navMenu = new SelectRenderable(this.cliRenderer, {
485
- id: "settings-sys-nav",
486
- width: "100%",
487
- height: 4,
488
- options: [
489
- {
490
- name: "💾 Save All Changes",
491
- description: "Save config and regenerate docker-compose.yml",
492
- },
493
- { name: "◀ Back", description: "Return to main menu" },
494
- ],
495
- })
496
-
497
- navMenu.on(SelectRenderableEvents.ITEM_SELECTED, async (index) => {
498
- if (index === 0) {
499
- await this.save()
500
- } else {
501
- this.cleanup()
502
- this.onBack()
503
- }
504
- })
505
-
506
- content.add(navMenu)
507
-
508
- const inputs = [rootInput, puidInput, pgidInput, tzInput, umaskInput, logMountToggle, navMenu]
509
- let focusIndex = 0
510
- inputs[0].focus()
511
-
512
- this.keyHandler = (key: KeyEvent) => {
513
- if (key.name === "escape") {
514
- this.cleanup()
515
- this.onBack()
516
- } else if (key.name === "s" && !key.ctrl) {
517
- this.save()
518
- } else if (key.name === "tab" || key.name === "enter") {
519
- if (key.name === "enter" && focusIndex === inputs.length - 1) return
520
- inputs[focusIndex].blur()
521
- focusIndex = key.shift
522
- ? (focusIndex - 1 + inputs.length) % inputs.length
523
- : (focusIndex + 1) % inputs.length
524
- inputs[focusIndex].focus()
525
- }
526
- }
527
- ;(this.cliRenderer as CliRenderer).keyInput.on("keypress", this.keyHandler)
528
- }
529
-
530
- private setupSectionNav(
531
- content: BoxRenderable,
532
- prev: SettingsSection,
533
- next: SettingsSection
534
- ): void {
535
- const navMenu = new SelectRenderable(this.cliRenderer, {
536
- id: "settings-section-nav",
537
- width: "100%",
538
- height: 4,
539
- options: [
540
- { name: "◀ Previous", description: `Go to ${prev} settings` },
541
- { name: "➡️ Next", description: `Go to ${next} settings` },
542
- { name: "💾 Save", description: "Save all changes" },
543
- { name: "✕ Back", description: "Return to main menu" },
544
- ],
545
- })
546
-
547
- navMenu.on(SelectRenderableEvents.ITEM_SELECTED, async (index) => {
548
- if (index === 0) {
549
- this.activeSection = prev
550
- this.renderContent()
551
- } else if (index === 1) {
552
- this.activeSection = next
553
- this.renderContent()
554
- } else if (index === 2) {
555
- await this.save()
556
- } else {
557
- this.cleanup()
558
- this.onBack()
559
- }
560
- })
561
-
562
- content.add(navMenu)
563
- }
564
-
565
- private setupBackHandler(): void {
566
- const navMenu = new SelectRenderable(this.cliRenderer, {
567
- id: "settings-back-nav",
568
- width: "100%",
569
- height: 2,
570
- options: [{ name: "◀ Back", description: "Return to main menu" }],
571
- })
572
-
573
- navMenu.on(SelectRenderableEvents.ITEM_SELECTED, () => {
574
- this.cleanup()
575
- this.onBack()
576
- })
577
-
578
- if (this.page) {
579
- const content = this.page.getChildren()[0] as BoxRenderable
580
- content?.add(navMenu)
581
- }
582
- navMenu.focus()
583
-
584
- this.keyHandler = (key: KeyEvent) => {
585
- if (key.name === "escape") {
586
- this.cleanup()
587
- this.onBack()
588
- }
589
- }
590
- ;(this.cliRenderer as CliRenderer).keyInput.on("keypress", this.keyHandler)
591
- }
592
-
593
- private async save(): Promise<void> {
594
- // Update config with local values
595
- if (this.config.traefik) {
596
- this.config.traefik.domain = this.traefikDomain
597
- this.config.traefik.entrypoint = this.traefikEntrypoint
598
- this.config.traefik.middlewares = this.traefikMiddlewares
599
- .split(",")
600
- .map((m) => m.trim())
601
- .filter((m) => m)
602
- }
603
-
604
- if (this.config.vpn) {
605
- this.config.vpn.mode = this.vpnMode
606
- }
607
-
608
- this.config.rootDir = this.rootDir
609
- this.config.uid = parseInt(this.puid, 10) || 1000
610
- this.config.gid = parseInt(this.pgid, 10) || 1000
611
- this.config.timezone = this.timezone
612
- this.config.umask = this.umask
613
- this.config.logMount = this.logMount
614
- this.config.updatedAt = new Date().toISOString()
615
-
616
- await saveConfig(this.config)
617
- await saveCompose(this.config)
618
-
619
- this.cleanup()
620
- this.onBack()
621
- }
622
-
623
- private cleanup(): void {
624
- if (this.keyHandler) {
625
- ;(this.cliRenderer as CliRenderer).keyInput.off("keypress", this.keyHandler)
626
- this.keyHandler = null
627
- }
628
- const parent = this.parent
629
- if (parent) {
630
- parent.remove(this.id)
631
- }
632
- }
633
- }
@@ -1,22 +0,0 @@
1
- export * from "./MainMenu"
2
- export * from "./AppManager"
3
- export * from "./ContainerControl"
4
- export * from "./MonitorDashboard"
5
- export * from "./SettingsScreen"
6
- export * from "./ApiKeyViewer"
7
- export * from "./AppConfigurator"
8
- export * from "./LogsViewer"
9
- export * from "./AdvancedSettings"
10
- export * from "./SecretsEditor"
11
-
12
- // Setup screens
13
- export * from "./setup/FullAutoSetup"
14
- export * from "./setup/QuickSetup"
15
- export * from "./setup/JellyfinSetup"
16
- export * from "./setup/JellyseerrSetup"
17
- export * from "./setup/ProwlarrSetup"
18
- export * from "./setup/QBittorrentSetup"
19
- export * from "./setup/RecyclarrSetup"
20
- export * from "./setup/TRaSHProfileSetup"
21
- export * from "./setup/HomepageSetup"
22
- export * from "./setup/CloudflaredSetup"