@muhammedaksam/easiarr 0.4.3 → 0.5.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.
- package/package.json +1 -1
- package/src/api/arr-api.ts +156 -0
- package/src/api/portainer-api.ts +231 -0
- package/src/apps/registry.ts +1 -0
- package/src/config/schema.ts +35 -0
- package/src/index.ts +4 -0
- package/src/ui/components/FooterHint.ts +119 -0
- package/src/ui/components/PageLayout.ts +58 -10
- package/src/ui/screens/AdvancedSettings.ts +4 -1
- package/src/ui/screens/ApiKeyViewer.ts +146 -5
- package/src/ui/screens/AppConfigurator.ts +16 -5
- package/src/ui/screens/AppManager.ts +6 -1
- package/src/ui/screens/ContainerControl.ts +4 -1
- package/src/ui/screens/FullAutoSetup.ts +78 -2
- package/src/ui/screens/MainMenu.ts +19 -3
- package/src/ui/screens/MonitorDashboard.ts +866 -0
- package/src/ui/screens/ProwlarrSetup.ts +5 -1
- package/src/ui/screens/QBittorrentSetup.ts +4 -1
- package/src/ui/screens/QuickSetup.ts +36 -8
- package/src/ui/screens/TRaSHProfileSetup.ts +6 -1
- package/src/utils/password.ts +42 -0
|
@@ -6,7 +6,8 @@ import { parse as parseYaml } from "yaml"
|
|
|
6
6
|
import { createPageLayout } from "../components/PageLayout"
|
|
7
7
|
import { EasiarrConfig, AppDefinition } from "../../config/schema"
|
|
8
8
|
import { getApp } from "../../apps/registry"
|
|
9
|
-
import { updateEnv } from "../../utils/env"
|
|
9
|
+
import { updateEnv, readEnvSync } from "../../utils/env"
|
|
10
|
+
import { PortainerApiClient } from "../../api/portainer-api"
|
|
10
11
|
|
|
11
12
|
/** Generate a random 32-character hex API key */
|
|
12
13
|
function generateApiKey(): string {
|
|
@@ -103,12 +104,18 @@ function updateIniValue(content: string, section: string, updates: Record<string
|
|
|
103
104
|
|
|
104
105
|
type KeyStatus = "found" | "missing" | "error" | "generated"
|
|
105
106
|
|
|
107
|
+
interface PortainerCredentials {
|
|
108
|
+
apiKey: string
|
|
109
|
+
password?: string // Only set if padded (different from global)
|
|
110
|
+
}
|
|
111
|
+
|
|
106
112
|
export class ApiKeyViewer extends BoxRenderable {
|
|
107
113
|
private config: EasiarrConfig
|
|
108
114
|
private keys: Array<{ appId: string; app: string; key: string; status: KeyStatus }> = []
|
|
109
115
|
private keyHandler!: (key: KeyEvent) => void
|
|
110
116
|
private cliRenderer: CliRenderer
|
|
111
117
|
private statusText: TextRenderable | null = null
|
|
118
|
+
private portainerCredentials: PortainerCredentials | null = null
|
|
112
119
|
|
|
113
120
|
constructor(renderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
|
|
114
121
|
super(renderer, {
|
|
@@ -131,6 +138,12 @@ export class ApiKeyViewer extends BoxRenderable {
|
|
|
131
138
|
for (const appConfig of this.config.apps) {
|
|
132
139
|
if (!appConfig.enabled) continue
|
|
133
140
|
|
|
141
|
+
// Handle Portainer separately (uses API, not config file)
|
|
142
|
+
if (appConfig.id === "portainer") {
|
|
143
|
+
this.scanPortainer(appConfig.port || 9000)
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
const appDef = getApp(appConfig.id)
|
|
135
148
|
if (!appDef || !appDef.apiKeyMeta) continue
|
|
136
149
|
|
|
@@ -160,6 +173,41 @@ export class ApiKeyViewer extends BoxRenderable {
|
|
|
160
173
|
}
|
|
161
174
|
}
|
|
162
175
|
|
|
176
|
+
private scanPortainer(_port: number) {
|
|
177
|
+
const env = readEnvSync()
|
|
178
|
+
const existingApiKey = env["API_KEY_PORTAINER"]
|
|
179
|
+
|
|
180
|
+
if (existingApiKey) {
|
|
181
|
+
this.keys.push({
|
|
182
|
+
appId: "portainer",
|
|
183
|
+
app: "Portainer",
|
|
184
|
+
key: existingApiKey,
|
|
185
|
+
status: "found",
|
|
186
|
+
})
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Will attempt to initialize/login when saving
|
|
191
|
+
const globalPassword = env["GLOBAL_PASSWORD"]
|
|
192
|
+
if (!globalPassword) {
|
|
193
|
+
this.keys.push({
|
|
194
|
+
appId: "portainer",
|
|
195
|
+
app: "Portainer",
|
|
196
|
+
key: "No GLOBAL_PASSWORD set in .env",
|
|
197
|
+
status: "missing",
|
|
198
|
+
})
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Add pending entry - actual API call happens on save
|
|
203
|
+
this.keys.push({
|
|
204
|
+
appId: "portainer",
|
|
205
|
+
app: "Portainer",
|
|
206
|
+
key: "Press S to generate API key",
|
|
207
|
+
status: "missing",
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
163
211
|
private extractApiKey(
|
|
164
212
|
appDef: AppDefinition,
|
|
165
213
|
content: string,
|
|
@@ -235,7 +283,12 @@ export class ApiKeyViewer extends BoxRenderable {
|
|
|
235
283
|
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
236
284
|
title: "API Key Extractor",
|
|
237
285
|
stepInfo: "Found Keys",
|
|
238
|
-
footerHint: hasFoundKeys
|
|
286
|
+
footerHint: hasFoundKeys
|
|
287
|
+
? [
|
|
288
|
+
{ type: "key", key: "S", value: "Save to .env" },
|
|
289
|
+
{ type: "key", key: "Esc/Enter", value: "Return" },
|
|
290
|
+
]
|
|
291
|
+
: [{ type: "key", key: "Esc/Enter", value: "Return" }],
|
|
239
292
|
})
|
|
240
293
|
this.add(container)
|
|
241
294
|
|
|
@@ -333,20 +386,47 @@ export class ApiKeyViewer extends BoxRenderable {
|
|
|
333
386
|
|
|
334
387
|
private async saveToEnv() {
|
|
335
388
|
const foundKeys = this.keys.filter((k) => k.status === "found" || k.status === "generated")
|
|
336
|
-
if (foundKeys.length === 0) return
|
|
337
389
|
|
|
338
390
|
try {
|
|
339
391
|
// Build updates object with API keys
|
|
340
392
|
const updates: Record<string, string> = {}
|
|
393
|
+
|
|
341
394
|
for (const k of foundKeys) {
|
|
342
|
-
|
|
395
|
+
if (k.appId !== "portainer") {
|
|
396
|
+
updates[`API_KEY_${k.appId.toUpperCase()}`] = k.key
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Handle Portainer separately - need to call API
|
|
401
|
+
const portainerEntry = this.keys.find((k) => k.appId === "portainer")
|
|
402
|
+
if (portainerEntry && portainerEntry.status === "missing") {
|
|
403
|
+
await this.initializePortainer(updates)
|
|
404
|
+
} else if (portainerEntry && portainerEntry.status === "found") {
|
|
405
|
+
updates["API_KEY_PORTAINER"] = portainerEntry.key
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Save Portainer credentials if we have them
|
|
409
|
+
if (this.portainerCredentials) {
|
|
410
|
+
updates["API_KEY_PORTAINER"] = this.portainerCredentials.apiKey
|
|
411
|
+
if (this.portainerCredentials.password) {
|
|
412
|
+
updates["PORTAINER_PASSWORD"] = this.portainerCredentials.password
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (Object.keys(updates).length === 0) {
|
|
417
|
+
if (this.statusText) {
|
|
418
|
+
this.statusText.content = "No keys to save"
|
|
419
|
+
this.statusText.fg = "#f1fa8c"
|
|
420
|
+
}
|
|
421
|
+
return
|
|
343
422
|
}
|
|
344
423
|
|
|
345
424
|
await updateEnv(updates)
|
|
346
425
|
|
|
347
426
|
// Update status
|
|
348
427
|
if (this.statusText) {
|
|
349
|
-
|
|
428
|
+
const count = Object.keys(updates).length
|
|
429
|
+
this.statusText.content = `✓ Saved ${count} key(s) to .env`
|
|
350
430
|
}
|
|
351
431
|
} catch (e) {
|
|
352
432
|
if (this.statusText) {
|
|
@@ -355,4 +435,65 @@ export class ApiKeyViewer extends BoxRenderable {
|
|
|
355
435
|
}
|
|
356
436
|
}
|
|
357
437
|
}
|
|
438
|
+
|
|
439
|
+
private async initializePortainer(_updates: Record<string, string>) {
|
|
440
|
+
const env = readEnvSync()
|
|
441
|
+
const globalUsername = env["GLOBAL_USERNAME"] || "admin"
|
|
442
|
+
const globalPassword = env["GLOBAL_PASSWORD"]
|
|
443
|
+
|
|
444
|
+
if (!globalPassword) return
|
|
445
|
+
|
|
446
|
+
const portainerConfig = this.config.apps.find((a) => a.id === "portainer" && a.enabled)
|
|
447
|
+
if (!portainerConfig) return
|
|
448
|
+
|
|
449
|
+
const port = portainerConfig.port || 9000
|
|
450
|
+
const client = new PortainerApiClient("localhost", port)
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
// Check if reachable
|
|
454
|
+
const healthy = await client.isHealthy()
|
|
455
|
+
if (!healthy) {
|
|
456
|
+
if (this.statusText) {
|
|
457
|
+
this.statusText.content = "Portainer not reachable"
|
|
458
|
+
this.statusText.fg = "#f1fa8c"
|
|
459
|
+
}
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Try to initialize or login
|
|
464
|
+
const result = await client.initializeAdmin(globalUsername, globalPassword)
|
|
465
|
+
|
|
466
|
+
if (result) {
|
|
467
|
+
// New initialization
|
|
468
|
+
const apiKey = await client.generateApiKey(result.actualPassword, "easiarr-api-key")
|
|
469
|
+
this.portainerCredentials = {
|
|
470
|
+
apiKey,
|
|
471
|
+
password: result.passwordWasPadded ? result.actualPassword : undefined,
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Update the display
|
|
475
|
+
const portainerEntry = this.keys.find((k) => k.appId === "portainer")
|
|
476
|
+
if (portainerEntry) {
|
|
477
|
+
portainerEntry.key = apiKey
|
|
478
|
+
portainerEntry.status = "generated"
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
// Already initialized, try login
|
|
482
|
+
await client.login(globalUsername, globalPassword)
|
|
483
|
+
const apiKey = await client.generateApiKey(globalPassword, "easiarr-api-key")
|
|
484
|
+
this.portainerCredentials = { apiKey }
|
|
485
|
+
|
|
486
|
+
const portainerEntry = this.keys.find((k) => k.appId === "portainer")
|
|
487
|
+
if (portainerEntry) {
|
|
488
|
+
portainerEntry.key = apiKey
|
|
489
|
+
portainerEntry.status = "generated"
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
} catch (e) {
|
|
493
|
+
if (this.statusText) {
|
|
494
|
+
this.statusText.content = `Portainer error: ${e}`
|
|
495
|
+
this.statusText.fg = "#ff5555"
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
358
499
|
}
|
|
@@ -95,7 +95,12 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
95
95
|
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
96
96
|
title: "Configure Apps",
|
|
97
97
|
stepInfo: "Global Credentials",
|
|
98
|
-
footerHint:
|
|
98
|
+
footerHint: [
|
|
99
|
+
{ type: "key", key: "Tab", value: "Cycle Fields/Shortcuts" },
|
|
100
|
+
{ type: "key", key: "O", value: "Override" },
|
|
101
|
+
{ type: "key", key: "Enter", value: "Continue" },
|
|
102
|
+
{ type: "key", key: "Esc", value: "Skip" },
|
|
103
|
+
],
|
|
99
104
|
})
|
|
100
105
|
this.pageContainer = container
|
|
101
106
|
this.add(container)
|
|
@@ -358,7 +363,7 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
358
363
|
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
359
364
|
title: "Configure Apps",
|
|
360
365
|
stepInfo: "Setting up root folders",
|
|
361
|
-
footerHint: "Please wait...",
|
|
366
|
+
footerHint: [{ type: "text", value: "Please wait..." }],
|
|
362
367
|
})
|
|
363
368
|
this.pageContainer = container
|
|
364
369
|
this.contentBox = content
|
|
@@ -427,7 +432,10 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
427
432
|
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
428
433
|
title: "Configure Apps",
|
|
429
434
|
stepInfo: "qBittorrent Credentials",
|
|
430
|
-
footerHint:
|
|
435
|
+
footerHint: [
|
|
436
|
+
{ type: "text", value: "Enter credentials from qBittorrent WebUI" },
|
|
437
|
+
{ type: "key", key: "Esc", value: "Skip" },
|
|
438
|
+
],
|
|
431
439
|
})
|
|
432
440
|
this.pageContainer = container
|
|
433
441
|
this.add(container)
|
|
@@ -483,7 +491,10 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
483
491
|
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
484
492
|
title: "Configure Apps",
|
|
485
493
|
stepInfo: "SABnzbd Credentials",
|
|
486
|
-
footerHint:
|
|
494
|
+
footerHint: [
|
|
495
|
+
{ type: "text", value: "Enter API key from SABnzbd Config → General" },
|
|
496
|
+
{ type: "key", key: "Esc", value: "Skip" },
|
|
497
|
+
],
|
|
487
498
|
})
|
|
488
499
|
this.pageContainer = container
|
|
489
500
|
this.add(container)
|
|
@@ -615,7 +626,7 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
615
626
|
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
616
627
|
title: "Configure Apps",
|
|
617
628
|
stepInfo: "Complete",
|
|
618
|
-
footerHint: "Press any key to return",
|
|
629
|
+
footerHint: [{ type: "text", value: "Press any key to return" }],
|
|
619
630
|
})
|
|
620
631
|
this.add(container)
|
|
621
632
|
|
|
@@ -54,7 +54,12 @@ export class AppManager {
|
|
|
54
54
|
const { container: page, content } = createPageLayout(this.renderer as CliRenderer, {
|
|
55
55
|
title: "Manage Apps",
|
|
56
56
|
stepInfo: "Toggle apps linked to your configuration",
|
|
57
|
-
footerHint:
|
|
57
|
+
footerHint: [
|
|
58
|
+
{ type: "key", key: "←→", value: "Tab" },
|
|
59
|
+
{ type: "key", key: "Enter", value: "Toggle" },
|
|
60
|
+
{ type: "key", key: "s", value: "Save" },
|
|
61
|
+
{ type: "key", key: "q", value: "Back" },
|
|
62
|
+
],
|
|
58
63
|
})
|
|
59
64
|
this.page = page
|
|
60
65
|
|
|
@@ -47,7 +47,10 @@ export class ContainerControl {
|
|
|
47
47
|
const { container: page, content } = createPageLayout(this.renderer as CliRenderer, {
|
|
48
48
|
title: "Container Control",
|
|
49
49
|
stepInfo: finalStepInfo,
|
|
50
|
-
footerHint:
|
|
50
|
+
footerHint: [
|
|
51
|
+
{ type: "key", key: "Enter", value: "Select/Action" },
|
|
52
|
+
{ type: "key", key: "q", value: "Back" },
|
|
53
|
+
],
|
|
51
54
|
})
|
|
52
55
|
|
|
53
56
|
if (!dockerOk) {
|
|
@@ -9,10 +9,11 @@ import type { EasiarrConfig } from "../../config/schema"
|
|
|
9
9
|
import { ArrApiClient, type AddRootFolderOptions } from "../../api/arr-api"
|
|
10
10
|
import { ProwlarrClient, type ArrAppType } from "../../api/prowlarr-api"
|
|
11
11
|
import { QBittorrentClient, type QBittorrentCategory } from "../../api/qbittorrent-api"
|
|
12
|
+
import { PortainerApiClient } from "../../api/portainer-api"
|
|
12
13
|
import { getApp } from "../../apps/registry"
|
|
13
14
|
// import type { AppId } from "../../config/schema"
|
|
14
15
|
import { getCategoriesForApps } from "../../utils/categories"
|
|
15
|
-
import { readEnvSync } from "../../utils/env"
|
|
16
|
+
import { readEnvSync, updateEnv } from "../../utils/env"
|
|
16
17
|
import { debugLog } from "../../utils/debug"
|
|
17
18
|
|
|
18
19
|
interface SetupStep {
|
|
@@ -47,7 +48,10 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
47
48
|
const { container: pageContainer, content: contentBox } = createPageLayout(cliRenderer, {
|
|
48
49
|
title: "Full Auto Setup",
|
|
49
50
|
stepInfo: "Configure all services automatically",
|
|
50
|
-
footerHint:
|
|
51
|
+
footerHint: [
|
|
52
|
+
{ type: "key", key: "Enter", value: "Start/Continue" },
|
|
53
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
54
|
+
],
|
|
51
55
|
})
|
|
52
56
|
super(cliRenderer, { width: "100%", height: "100%" })
|
|
53
57
|
this.add(pageContainer)
|
|
@@ -74,6 +78,7 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
74
78
|
{ name: "Prowlarr Apps", status: "pending" },
|
|
75
79
|
{ name: "FlareSolverr", status: "pending" },
|
|
76
80
|
{ name: "qBittorrent", status: "pending" },
|
|
81
|
+
{ name: "Portainer", status: "pending" },
|
|
77
82
|
]
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -119,6 +124,9 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
119
124
|
// Step 5: qBittorrent
|
|
120
125
|
await this.setupQBittorrent()
|
|
121
126
|
|
|
127
|
+
// Step 6: Portainer
|
|
128
|
+
await this.setupPortainer()
|
|
129
|
+
|
|
122
130
|
this.isRunning = false
|
|
123
131
|
this.isDone = true
|
|
124
132
|
this.refreshContent()
|
|
@@ -327,6 +335,74 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
327
335
|
this.refreshContent()
|
|
328
336
|
}
|
|
329
337
|
|
|
338
|
+
private async setupPortainer(): Promise<void> {
|
|
339
|
+
this.updateStep("Portainer", "running")
|
|
340
|
+
this.refreshContent()
|
|
341
|
+
|
|
342
|
+
const portainerConfig = this.config.apps.find((a) => a.id === "portainer" && a.enabled)
|
|
343
|
+
if (!portainerConfig) {
|
|
344
|
+
this.updateStep("Portainer", "skipped", "Not enabled")
|
|
345
|
+
this.refreshContent()
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!this.globalPassword) {
|
|
350
|
+
this.updateStep("Portainer", "skipped", "No GLOBAL_PASSWORD set")
|
|
351
|
+
this.refreshContent()
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
const port = portainerConfig.port || 9000
|
|
357
|
+
const client = new PortainerApiClient("localhost", port)
|
|
358
|
+
|
|
359
|
+
// Check if we can reach Portainer
|
|
360
|
+
const healthy = await client.isHealthy()
|
|
361
|
+
if (!healthy) {
|
|
362
|
+
this.updateStep("Portainer", "skipped", "Not reachable yet")
|
|
363
|
+
this.refreshContent()
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Initialize admin user (auto-pads password if needed)
|
|
368
|
+
const result = await client.initializeAdmin(this.globalUsername, this.globalPassword)
|
|
369
|
+
|
|
370
|
+
if (result) {
|
|
371
|
+
// Generate API key and save to .env
|
|
372
|
+
const apiKey = await client.generateApiKey(result.actualPassword, "easiarr-api-key")
|
|
373
|
+
|
|
374
|
+
const envUpdates: Record<string, string> = {
|
|
375
|
+
API_KEY_PORTAINER: apiKey,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Save password if it was padded (different from global)
|
|
379
|
+
if (result.passwordWasPadded) {
|
|
380
|
+
envUpdates.PORTAINER_PASSWORD = result.actualPassword
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await updateEnv(envUpdates)
|
|
384
|
+
this.updateStep("Portainer", "success", "Admin + API key created")
|
|
385
|
+
} else {
|
|
386
|
+
// Already initialized, try to login and get API key if we don't have one
|
|
387
|
+
if (!this.env["API_KEY_PORTAINER"]) {
|
|
388
|
+
try {
|
|
389
|
+
await client.login(this.globalUsername, this.globalPassword)
|
|
390
|
+
const apiKey = await client.generateApiKey(this.globalPassword, "easiarr-api-key")
|
|
391
|
+
await updateEnv({ API_KEY_PORTAINER: apiKey })
|
|
392
|
+
this.updateStep("Portainer", "success", "API key generated")
|
|
393
|
+
} catch {
|
|
394
|
+
this.updateStep("Portainer", "skipped", "Already initialized")
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
this.updateStep("Portainer", "skipped", "Already configured")
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
} catch (e) {
|
|
401
|
+
this.updateStep("Portainer", "error", `${e}`)
|
|
402
|
+
}
|
|
403
|
+
this.refreshContent()
|
|
404
|
+
}
|
|
405
|
+
|
|
330
406
|
private updateStep(name: string, status: SetupStep["status"], message?: string): void {
|
|
331
407
|
const step = this.steps.find((s) => s.name === name)
|
|
332
408
|
if (step) {
|
|
@@ -15,6 +15,7 @@ import { TRaSHProfileSetup } from "./TRaSHProfileSetup"
|
|
|
15
15
|
import { ProwlarrSetup } from "./ProwlarrSetup"
|
|
16
16
|
import { QBittorrentSetup } from "./QBittorrentSetup"
|
|
17
17
|
import { FullAutoSetup } from "./FullAutoSetup"
|
|
18
|
+
import { MonitorDashboard } from "./MonitorDashboard"
|
|
18
19
|
|
|
19
20
|
export class MainMenu {
|
|
20
21
|
private renderer: RenderContext
|
|
@@ -37,7 +38,7 @@ export class MainMenu {
|
|
|
37
38
|
const { container: page, content } = createPageLayout(this.renderer as CliRenderer, {
|
|
38
39
|
title: "Main Menu",
|
|
39
40
|
stepInfo: "Docker Compose Generator for *arr Ecosystem",
|
|
40
|
-
footerHint: "Enter Select
|
|
41
|
+
footerHint: [{ type: "key", key: "Enter", value: "Select" }],
|
|
41
42
|
})
|
|
42
43
|
this.page = page
|
|
43
44
|
|
|
@@ -82,7 +83,7 @@ export class MainMenu {
|
|
|
82
83
|
this.menu = new SelectRenderable(this.renderer, {
|
|
83
84
|
id: "main-menu-select",
|
|
84
85
|
width: "100%",
|
|
85
|
-
|
|
86
|
+
flexGrow: 1,
|
|
86
87
|
options: [
|
|
87
88
|
{
|
|
88
89
|
name: "📦 Manage Apps",
|
|
@@ -124,6 +125,10 @@ export class MainMenu {
|
|
|
124
125
|
name: "🚀 Full Auto Setup",
|
|
125
126
|
description: "Run all configurations (Auth, Root Folders, Prowlarr, etc.)",
|
|
126
127
|
},
|
|
128
|
+
{
|
|
129
|
+
name: "📊 Monitor Dashboard",
|
|
130
|
+
description: "Configure app health monitoring",
|
|
131
|
+
},
|
|
127
132
|
{ name: "❌ Exit", description: "Close easiarr" },
|
|
128
133
|
],
|
|
129
134
|
})
|
|
@@ -211,7 +216,18 @@ export class MainMenu {
|
|
|
211
216
|
this.container.add(autoSetup)
|
|
212
217
|
break
|
|
213
218
|
}
|
|
214
|
-
case 10:
|
|
219
|
+
case 10: {
|
|
220
|
+
// Monitor Dashboard
|
|
221
|
+
this.menu.blur()
|
|
222
|
+
this.page.visible = false
|
|
223
|
+
const monitor = new MonitorDashboard(this.renderer as CliRenderer, this.config, () => {
|
|
224
|
+
this.page.visible = true
|
|
225
|
+
this.menu.focus()
|
|
226
|
+
})
|
|
227
|
+
this.container.add(monitor)
|
|
228
|
+
break
|
|
229
|
+
}
|
|
230
|
+
case 11:
|
|
215
231
|
process.exit(0)
|
|
216
232
|
break
|
|
217
233
|
}
|