@muhammedaksam/easiarr 0.4.2 → 0.5.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.
- package/package.json +1 -1
- package/src/api/arr-api.ts +156 -0
- package/src/api/prowlarr-api.ts +447 -14
- package/src/apps/registry.ts +5 -0
- package/src/config/schema.ts +34 -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 +6 -1
- 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 +4 -1
- package/src/ui/screens/MainMenu.ts +19 -3
- package/src/ui/screens/MonitorDashboard.ts +866 -0
- package/src/ui/screens/ProwlarrSetup.ts +260 -5
- package/src/ui/screens/QBittorrentSetup.ts +4 -1
- package/src/ui/screens/QuickSetup.ts +36 -8
- package/src/ui/screens/TRaSHProfileSetup.ts +6 -1
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Configures Prowlarr integration with *arr apps, FlareSolverr, and proxies
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { BoxRenderable, CliRenderer, TextRenderable, KeyEvent } from "@opentui/core"
|
|
6
|
+
import { BoxRenderable, CliRenderer, TextRenderable, TextNodeRenderable, KeyEvent } from "@opentui/core"
|
|
7
7
|
import { createPageLayout } from "../components/PageLayout"
|
|
8
8
|
import { EasiarrConfig } from "../../config/schema"
|
|
9
9
|
import { getApp } from "../../apps/registry"
|
|
10
|
-
import { ProwlarrClient, ArrAppType } from "../../api/prowlarr-api"
|
|
10
|
+
import { ProwlarrClient, ArrAppType, ProwlarrIndexerSchema, PROWLARR_CATEGORIES } from "../../api/prowlarr-api"
|
|
11
11
|
import { readEnvSync } from "../../utils/env"
|
|
12
12
|
import { debugLog } from "../../utils/debug"
|
|
13
13
|
|
|
@@ -17,7 +17,7 @@ interface SetupResult {
|
|
|
17
17
|
message?: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
type Step = "menu" | "sync-apps" | "flaresolverr" | "sync-profiles" | "done"
|
|
20
|
+
type Step = "menu" | "sync-apps" | "flaresolverr" | "sync-profiles" | "select-indexers" | "done"
|
|
21
21
|
|
|
22
22
|
const ARR_APP_TYPES: Record<string, ArrAppType> = {
|
|
23
23
|
radarr: "Radarr",
|
|
@@ -37,12 +37,19 @@ export class ProwlarrSetup extends BoxRenderable {
|
|
|
37
37
|
private pageContainer!: BoxRenderable
|
|
38
38
|
private menuIndex = 0
|
|
39
39
|
private prowlarrClient: ProwlarrClient | null = null
|
|
40
|
+
private availableIndexers: ProwlarrIndexerSchema[] = []
|
|
41
|
+
private selectedIndexers: Set<number> = new Set() // Using index in availableIndexers array
|
|
42
|
+
private listScrollOffset = 0
|
|
40
43
|
|
|
41
44
|
constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
|
|
42
45
|
const { container: pageContainer, content: contentBox } = createPageLayout(cliRenderer, {
|
|
43
46
|
title: "Prowlarr Setup",
|
|
44
47
|
stepInfo: "Configure indexer sync and proxies",
|
|
45
|
-
footerHint:
|
|
48
|
+
footerHint: [
|
|
49
|
+
{ type: "key", key: "↑↓", value: "Navigate" },
|
|
50
|
+
{ type: "key", key: "Enter", value: "Select" },
|
|
51
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
52
|
+
],
|
|
46
53
|
})
|
|
47
54
|
super(cliRenderer, { width: "100%", height: "100%" })
|
|
48
55
|
this.add(pageContainer)
|
|
@@ -84,6 +91,8 @@ export class ProwlarrSetup extends BoxRenderable {
|
|
|
84
91
|
|
|
85
92
|
if (this.currentStep === "menu") {
|
|
86
93
|
this.handleMenuKeys(key)
|
|
94
|
+
} else if (this.currentStep === "select-indexers") {
|
|
95
|
+
this.handleIndexerSelectionKeys(key)
|
|
87
96
|
} else if (this.currentStep === "done") {
|
|
88
97
|
if (key.name === "return" || key.name === "escape") {
|
|
89
98
|
this.currentStep = "menu"
|
|
@@ -121,6 +130,11 @@ export class ProwlarrSetup extends BoxRenderable {
|
|
|
121
130
|
description: "Add Cloudflare bypass proxy",
|
|
122
131
|
action: () => this.setupFlareSolverr(),
|
|
123
132
|
},
|
|
133
|
+
{
|
|
134
|
+
name: "🔍 Add Public Indexers",
|
|
135
|
+
description: "Search and add public trackers",
|
|
136
|
+
action: () => this.searchIndexers(),
|
|
137
|
+
},
|
|
124
138
|
{
|
|
125
139
|
name: "📊 Create Sync Profiles",
|
|
126
140
|
description: "Limited API indexer profiles",
|
|
@@ -179,7 +193,15 @@ export class ProwlarrSetup extends BoxRenderable {
|
|
|
179
193
|
const port = app.port || appDef?.defaultPort || 7878
|
|
180
194
|
|
|
181
195
|
// In Docker, use container names for inter-container communication
|
|
182
|
-
await this.prowlarrClient.addArrApp(
|
|
196
|
+
await this.prowlarrClient.addArrApp(
|
|
197
|
+
appType,
|
|
198
|
+
app.id,
|
|
199
|
+
port,
|
|
200
|
+
apiKey,
|
|
201
|
+
"prowlarr",
|
|
202
|
+
prowlarrPort,
|
|
203
|
+
appDef?.prowlarrCategoryIds
|
|
204
|
+
)
|
|
183
205
|
|
|
184
206
|
const result = this.results.find((r) => r.name === app.id)
|
|
185
207
|
if (result) {
|
|
@@ -272,11 +294,118 @@ export class ProwlarrSetup extends BoxRenderable {
|
|
|
272
294
|
this.refreshContent()
|
|
273
295
|
}
|
|
274
296
|
|
|
297
|
+
private async searchIndexers(): Promise<void> {
|
|
298
|
+
if (!this.prowlarrClient) return
|
|
299
|
+
|
|
300
|
+
this.currentStep = "select-indexers"
|
|
301
|
+
this.results = [{ name: "Fetching indexers...", status: "configuring" }]
|
|
302
|
+
this.refreshContent()
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const schemas = await this.prowlarrClient.getIndexerSchemas()
|
|
306
|
+
this.availableIndexers = schemas
|
|
307
|
+
.filter((i) => i.privacy === "public" && i.enable)
|
|
308
|
+
// Sort by category count descending (most capable first)
|
|
309
|
+
.sort((a, b) => {
|
|
310
|
+
const infoA = (a.capabilities?.categories || []).length
|
|
311
|
+
const infoB = (b.capabilities?.categories || []).length
|
|
312
|
+
return infoB - infoA
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
this.selectedIndexers.clear()
|
|
316
|
+
this.menuIndex = 0
|
|
317
|
+
this.listScrollOffset = 0
|
|
318
|
+
this.results = []
|
|
319
|
+
} catch (error) {
|
|
320
|
+
this.results = [{ name: "Error", status: "error", message: String(error) }]
|
|
321
|
+
this.currentStep = "done"
|
|
322
|
+
}
|
|
323
|
+
this.refreshContent()
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private handleIndexerSelectionKeys(key: KeyEvent): void {
|
|
327
|
+
if (this.results.length > 0) return // If actively adding
|
|
328
|
+
|
|
329
|
+
if (key.name === "up") {
|
|
330
|
+
this.menuIndex = Math.max(0, this.menuIndex - 1)
|
|
331
|
+
if (this.menuIndex < this.listScrollOffset) {
|
|
332
|
+
this.listScrollOffset = this.menuIndex
|
|
333
|
+
}
|
|
334
|
+
this.refreshContent()
|
|
335
|
+
} else if (key.name === "down") {
|
|
336
|
+
this.menuIndex = Math.min(this.availableIndexers.length - 1, this.menuIndex + 1)
|
|
337
|
+
// Visible items = height - header (approx 15)
|
|
338
|
+
const visibleItems = 15
|
|
339
|
+
if (this.menuIndex >= this.listScrollOffset + visibleItems) {
|
|
340
|
+
this.listScrollOffset = this.menuIndex - visibleItems + 1
|
|
341
|
+
}
|
|
342
|
+
this.refreshContent()
|
|
343
|
+
} else if (key.name === "space") {
|
|
344
|
+
if (this.selectedIndexers.has(this.menuIndex)) {
|
|
345
|
+
this.selectedIndexers.delete(this.menuIndex)
|
|
346
|
+
} else {
|
|
347
|
+
this.selectedIndexers.add(this.menuIndex)
|
|
348
|
+
}
|
|
349
|
+
this.refreshContent()
|
|
350
|
+
} else if (key.name === "return") {
|
|
351
|
+
this.addSelectedIndexers()
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private async addSelectedIndexers(): Promise<void> {
|
|
356
|
+
const toAdd = Array.from(this.selectedIndexers).map((idx) => this.availableIndexers[idx])
|
|
357
|
+
if (toAdd.length === 0) return
|
|
358
|
+
|
|
359
|
+
this.results = toAdd.map((i) => ({ name: i.name, status: "pending" }))
|
|
360
|
+
this.refreshContent()
|
|
361
|
+
|
|
362
|
+
for (const indexer of toAdd) {
|
|
363
|
+
// Update UI
|
|
364
|
+
const res = this.results.find((r) => r.name === indexer.name)
|
|
365
|
+
if (res) res.status = "configuring"
|
|
366
|
+
this.refreshContent()
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
if (!this.prowlarrClient) throw new Error("No client")
|
|
370
|
+
|
|
371
|
+
// Auto-add FlareSolverr tag if it exists
|
|
372
|
+
const tags = await this.prowlarrClient.getTags()
|
|
373
|
+
const fsTag = tags.find((t) => t.label.toLowerCase() === "flaresolverr")
|
|
374
|
+
|
|
375
|
+
if (fsTag) {
|
|
376
|
+
indexer.tags = indexer.tags || []
|
|
377
|
+
if (!indexer.tags.includes(fsTag.id)) {
|
|
378
|
+
indexer.tags.push(fsTag.id)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
await this.prowlarrClient.createIndexer(indexer)
|
|
383
|
+
if (res) {
|
|
384
|
+
res.status = "success"
|
|
385
|
+
const extra = fsTag ? " + FlareSolverr" : ""
|
|
386
|
+
res.message = `Added with ${indexer.capabilities?.categories?.length || 0} categories${extra}`
|
|
387
|
+
}
|
|
388
|
+
} catch (e) {
|
|
389
|
+
if (res) {
|
|
390
|
+
res.status = "error"
|
|
391
|
+
res.message = String(e)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
this.refreshContent()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// After done, stay on done screen
|
|
398
|
+
this.currentStep = "done"
|
|
399
|
+
this.refreshContent()
|
|
400
|
+
}
|
|
401
|
+
|
|
275
402
|
private refreshContent(): void {
|
|
276
403
|
this.contentBox.getChildren().forEach((child) => child.destroy())
|
|
277
404
|
|
|
278
405
|
if (this.currentStep === "menu") {
|
|
279
406
|
this.renderMenu()
|
|
407
|
+
} else if (this.currentStep === "select-indexers" && this.results.length === 0) {
|
|
408
|
+
this.renderIndexerSelection()
|
|
280
409
|
} else {
|
|
281
410
|
this.renderResults()
|
|
282
411
|
}
|
|
@@ -370,6 +499,132 @@ export class ProwlarrSetup extends BoxRenderable {
|
|
|
370
499
|
}
|
|
371
500
|
}
|
|
372
501
|
|
|
502
|
+
private renderIndexerSelection(): void {
|
|
503
|
+
const visibleHeight = 15
|
|
504
|
+
const endIndex = Math.min(this.availableIndexers.length, this.listScrollOffset + visibleHeight)
|
|
505
|
+
const items = this.availableIndexers.slice(this.listScrollOffset, endIndex)
|
|
506
|
+
|
|
507
|
+
// Calculate active category IDs from selected apps
|
|
508
|
+
const activeCategoryIds = new Set<number>()
|
|
509
|
+
this.config.apps.forEach((app) => {
|
|
510
|
+
const def = getApp(app.id)
|
|
511
|
+
def?.prowlarrCategoryIds?.forEach((id) => activeCategoryIds.add(id))
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
this.contentBox.add(
|
|
515
|
+
new TextRenderable(this.cliRenderer, {
|
|
516
|
+
content: `Select Indexers (Space to toggle, Enter to add):\n\n`,
|
|
517
|
+
fg: "#f1fa8c",
|
|
518
|
+
})
|
|
519
|
+
)
|
|
520
|
+
// Removed extra `})` and `)` here to fix syntax.
|
|
521
|
+
// The original instruction had an extra `})` and `)` after the first `this.contentBox.add` call.
|
|
522
|
+
|
|
523
|
+
items.forEach((idx, i) => {
|
|
524
|
+
const realIndex = this.listScrollOffset + i
|
|
525
|
+
const isSelected = this.selectedIndexers.has(realIndex)
|
|
526
|
+
const isCurrent = realIndex === this.menuIndex
|
|
527
|
+
|
|
528
|
+
const check = isSelected ? "[x]" : "[ ]"
|
|
529
|
+
const pointer = isCurrent ? "→" : " "
|
|
530
|
+
|
|
531
|
+
const cats = idx.capabilities?.categories || []
|
|
532
|
+
|
|
533
|
+
// Group capabilities
|
|
534
|
+
const groups = new Map<string, boolean>() // Name -> IsRelevant
|
|
535
|
+
|
|
536
|
+
// Helper to check relevance
|
|
537
|
+
const checkRel = (min: number, max: number) => [...activeCategoryIds].some((id) => id >= min && id < max)
|
|
538
|
+
|
|
539
|
+
// Map to track which badge colors to use
|
|
540
|
+
// We can pre-define colors or just cycle them, but for now let's keep the user's preferred colors if possible,
|
|
541
|
+
// or define a mapping.
|
|
542
|
+
const categoryColors: Record<string, { active: string; inactive: string }> = {
|
|
543
|
+
Movies: { active: "#00ffff", inactive: "#008b8b" },
|
|
544
|
+
TV: { active: "#ff00ff", inactive: "#8b008b" },
|
|
545
|
+
Audio: { active: "#00ff00", inactive: "#006400" },
|
|
546
|
+
Books: { active: "#50fa7b", inactive: "#00008b" },
|
|
547
|
+
XXX: { active: "#ff5555", inactive: "#8b0000" },
|
|
548
|
+
PC: { active: "#f8f8f2", inactive: "#6272a4" },
|
|
549
|
+
Console: { active: "#f1fa8c", inactive: "#8b8000" },
|
|
550
|
+
Other: { active: "#aaaaaa", inactive: "#555555" },
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
cats.forEach((c) => {
|
|
554
|
+
const id = c.id
|
|
555
|
+
let name = ""
|
|
556
|
+
let isRel = false
|
|
557
|
+
|
|
558
|
+
// Find parent category from static data
|
|
559
|
+
const parentCat = PROWLARR_CATEGORIES.find((pc) => {
|
|
560
|
+
// Check if id matches parent
|
|
561
|
+
if (pc.id === id) return true
|
|
562
|
+
// Check if id matches any subcategory
|
|
563
|
+
if (pc.subCategories?.some((sub) => sub.id === id)) return true
|
|
564
|
+
// Check range heuristic if needed, but the static data should cover known IDs
|
|
565
|
+
// Fallback to range check if no exact match found?
|
|
566
|
+
// Actually, the static data structure implies ranges (e.g. Movies 2000-2999)
|
|
567
|
+
// Let's use the ID ranges implied by the static data if possible, or just strict matching.
|
|
568
|
+
// The previous code used ranges. Let's try to match ranges based on the starting ID of the parent category.
|
|
569
|
+
// Assuming categories are 1000s blocks.
|
|
570
|
+
const rangeStart = Math.floor(pc.id / 1000) * 1000
|
|
571
|
+
if (id >= rangeStart && id < rangeStart + 1000) return true
|
|
572
|
+
return false
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
if (parentCat) {
|
|
576
|
+
name = parentCat.name
|
|
577
|
+
const rangeStart = Math.floor(parentCat.id / 1000) * 1000
|
|
578
|
+
isRel = checkRel(rangeStart, rangeStart + 1000)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (name) {
|
|
582
|
+
groups.set(name, groups.get(name) || isRel)
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
const line = new TextRenderable(this.cliRenderer, { content: "" })
|
|
587
|
+
line.add(`${pointer} ${check} ${idx.name} `)
|
|
588
|
+
|
|
589
|
+
// Render Badge Helper
|
|
590
|
+
const addBadge = (name: string) => {
|
|
591
|
+
if (groups.has(name)) {
|
|
592
|
+
const isRel = groups.get(name)
|
|
593
|
+
const colors = categoryColors[name] || categoryColors["Other"]
|
|
594
|
+
const color = isRel ? colors.active : colors.inactive
|
|
595
|
+
|
|
596
|
+
const badge = new TextNodeRenderable({ fg: color })
|
|
597
|
+
badge.add(`[${name}] `)
|
|
598
|
+
if (isRel) {
|
|
599
|
+
badge.attributes = 1
|
|
600
|
+
} // Bold if supported/relevant
|
|
601
|
+
|
|
602
|
+
line.add(badge)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Iterate through our static categories to render badges in order
|
|
607
|
+
PROWLARR_CATEGORIES.forEach((cat) => {
|
|
608
|
+
addBadge(cat.name)
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
line.add("\n")
|
|
612
|
+
line.fg = isCurrent ? "#ffffff" : isSelected ? "#50fa7b" : "#aaaaaa"
|
|
613
|
+
|
|
614
|
+
this.contentBox.add(line)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
const remaining = this.availableIndexers.length - endIndex
|
|
618
|
+
if (remaining > 0) {
|
|
619
|
+
this.contentBox.add(
|
|
620
|
+
new TextRenderable(this.cliRenderer, {
|
|
621
|
+
content: `... and ${remaining} more`,
|
|
622
|
+
fg: "#6272a4",
|
|
623
|
+
})
|
|
624
|
+
)
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
373
628
|
private cleanup(): void {
|
|
374
629
|
this.cliRenderer.keyInput.off("keypress", this.keyHandler)
|
|
375
630
|
this.destroy()
|
|
@@ -34,7 +34,10 @@ export class QBittorrentSetup extends BoxRenderable {
|
|
|
34
34
|
const { container: pageContainer, content: contentBox } = createPageLayout(cliRenderer, {
|
|
35
35
|
title: "qBittorrent Setup",
|
|
36
36
|
stepInfo: "Configure TRaSH-compliant paths and categories",
|
|
37
|
-
footerHint:
|
|
37
|
+
footerHint: [
|
|
38
|
+
{ type: "key", key: "Enter", value: "Submit" },
|
|
39
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
40
|
+
],
|
|
38
41
|
})
|
|
39
42
|
super(cliRenderer, { width: "100%", height: "100%" })
|
|
40
43
|
this.add(pageContainer)
|
|
@@ -106,7 +106,10 @@ export class QuickSetup {
|
|
|
106
106
|
private renderWelcome(): void {
|
|
107
107
|
const { container: page, content } = createPageLayout(this.renderer, {
|
|
108
108
|
title: "Welcome to easiarr",
|
|
109
|
-
footerHint:
|
|
109
|
+
footerHint: [
|
|
110
|
+
{ type: "key", key: "Enter", value: "Select" },
|
|
111
|
+
{ type: "key", key: "q", value: "Quit" },
|
|
112
|
+
],
|
|
110
113
|
})
|
|
111
114
|
|
|
112
115
|
// Spacer
|
|
@@ -248,7 +251,10 @@ export class QuickSetup {
|
|
|
248
251
|
|
|
249
252
|
const { container: page, content } = createPageLayout(this.renderer, {
|
|
250
253
|
title: "About easiarr",
|
|
251
|
-
footerHint:
|
|
254
|
+
footerHint: [
|
|
255
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
256
|
+
{ type: "key", key: "q", value: "Quit" },
|
|
257
|
+
],
|
|
252
258
|
})
|
|
253
259
|
|
|
254
260
|
content.add(new TextRenderable(this.renderer, { id: "about-spacer1", content: "" }))
|
|
@@ -347,12 +353,16 @@ export class QuickSetup {
|
|
|
347
353
|
private renderAppSelection(): void {
|
|
348
354
|
const title = "Select Apps"
|
|
349
355
|
const stepInfo = `${title} (${this.selectedApps.size} selected)`
|
|
350
|
-
const footerHint =
|
|
356
|
+
const footerHint = [
|
|
357
|
+
{ type: "key", key: "←→", value: "Tab" },
|
|
358
|
+
{ type: "key", key: "Enter", value: "Toggle" },
|
|
359
|
+
{ type: "key", key: "q", value: "Quit" },
|
|
360
|
+
] as const
|
|
351
361
|
|
|
352
362
|
const { container: page, content } = createPageLayout(this.renderer, {
|
|
353
363
|
title: title,
|
|
354
364
|
stepInfo: stepInfo,
|
|
355
|
-
footerHint: footerHint,
|
|
365
|
+
footerHint: [...footerHint],
|
|
356
366
|
})
|
|
357
367
|
|
|
358
368
|
// Application Selector
|
|
@@ -442,7 +452,12 @@ export class QuickSetup {
|
|
|
442
452
|
const { container: page, content } = createPageLayout(this.renderer, {
|
|
443
453
|
title: "System Configuration",
|
|
444
454
|
stepInfo: `Step 2/${totalSteps}`,
|
|
445
|
-
footerHint:
|
|
455
|
+
footerHint: [
|
|
456
|
+
{ type: "key", key: "Tab", value: "Next" },
|
|
457
|
+
{ type: "key", key: "Enter", value: "Next/Continue" },
|
|
458
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
459
|
+
{ type: "key", key: "q", value: "Quit" },
|
|
460
|
+
],
|
|
446
461
|
})
|
|
447
462
|
|
|
448
463
|
// Instructions
|
|
@@ -580,7 +595,11 @@ export class QuickSetup {
|
|
|
580
595
|
const { container: page, content } = createPageLayout(this.renderer, {
|
|
581
596
|
title: "VPN Configuration",
|
|
582
597
|
stepInfo: `Step ${stepNum}/${totalSteps}`,
|
|
583
|
-
footerHint:
|
|
598
|
+
footerHint: [
|
|
599
|
+
{ type: "key", key: "Enter", value: "Select" },
|
|
600
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
601
|
+
{ type: "key", key: "q", value: "Quit" },
|
|
602
|
+
],
|
|
584
603
|
})
|
|
585
604
|
|
|
586
605
|
content.add(
|
|
@@ -667,7 +686,12 @@ export class QuickSetup {
|
|
|
667
686
|
const { container: page, content } = createPageLayout(this.renderer, {
|
|
668
687
|
title: "Traefik Configuration",
|
|
669
688
|
stepInfo: `Step ${stepNum}/${totalSteps}`,
|
|
670
|
-
footerHint:
|
|
689
|
+
footerHint: [
|
|
690
|
+
{ type: "key", key: "Tab", value: "Next Field" },
|
|
691
|
+
{ type: "key", key: "Enter", value: "Continue" },
|
|
692
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
693
|
+
{ type: "key", key: "q", value: "Quit" },
|
|
694
|
+
],
|
|
671
695
|
})
|
|
672
696
|
|
|
673
697
|
content.add(
|
|
@@ -908,7 +932,11 @@ export class QuickSetup {
|
|
|
908
932
|
const { container: page, content } = createPageLayout(this.renderer, {
|
|
909
933
|
title: "Confirm Setup",
|
|
910
934
|
stepInfo: `Step ${totalSteps}/${totalSteps}`,
|
|
911
|
-
footerHint:
|
|
935
|
+
footerHint: [
|
|
936
|
+
{ type: "key", key: "Enter", value: "Select" },
|
|
937
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
938
|
+
{ type: "key", key: "q", value: "Quit" },
|
|
939
|
+
],
|
|
912
940
|
})
|
|
913
941
|
|
|
914
942
|
content.add(new TextRenderable(this.renderer, { id: "confirm-spacer", content: "" }))
|
|
@@ -44,7 +44,12 @@ export class TRaSHProfileSetup extends BoxRenderable {
|
|
|
44
44
|
const { container: pageContainer, content: contentBox } = createPageLayout(cliRenderer, {
|
|
45
45
|
title: "TRaSH Guide Setup",
|
|
46
46
|
stepInfo: "Configure quality profiles and custom formats",
|
|
47
|
-
footerHint:
|
|
47
|
+
footerHint: [
|
|
48
|
+
{ type: "key", key: "↑↓", value: "Navigate" },
|
|
49
|
+
{ type: "key", key: "Space", value: "Select" },
|
|
50
|
+
{ type: "key", key: "Enter", value: "Confirm" },
|
|
51
|
+
{ type: "key", key: "Esc", value: "Back" },
|
|
52
|
+
],
|
|
48
53
|
})
|
|
49
54
|
super(cliRenderer, { width: "100%", height: "100%" })
|
|
50
55
|
this.add(pageContainer)
|