@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.
- package/dist/index.js +29837 -0
- package/package.json +15 -15
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -521
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/base-api.ts +0 -216
- package/src/api/bazarr-api.ts +0 -297
- package/src/api/cloudflare-api.ts +0 -493
- package/src/api/custom-format-api.ts +0 -224
- package/src/api/grafana-api.ts +0 -230
- package/src/api/heimdall-api.ts +0 -141
- package/src/api/homarr-api.ts +0 -209
- package/src/api/huntarr-api.ts +0 -457
- package/src/api/index.ts +0 -9
- package/src/api/jellyfin-api.ts +0 -389
- package/src/api/jellyseerr-api.ts +0 -559
- package/src/api/maintainerr-api.ts +0 -461
- package/src/api/naming-config.ts +0 -101
- package/src/api/overseerr-api.ts +0 -392
- package/src/api/plex-api.ts +0 -308
- package/src/api/portainer-api.ts +0 -374
- package/src/api/profilarr-api.ts +0 -391
- package/src/api/prowlarr-api.ts +0 -488
- package/src/api/qbittorrent-api.ts +0 -300
- package/src/api/quality-profile-api.ts +0 -272
- package/src/api/tautulli-api.ts +0 -244
- package/src/api/uptime-kuma-api.ts +0 -343
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1275
- package/src/compose/caddy-config.ts +0 -130
- package/src/compose/generator.ts +0 -287
- package/src/compose/index.ts +0 -7
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -187
- package/src/config/bookmarks-generator.ts +0 -160
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -381
- package/src/config/index.ts +0 -6
- package/src/config/manager.ts +0 -150
- package/src/config/recyclarr-config.ts +0 -181
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -86
- package/src/config/soularr-config.ts +0 -105
- package/src/config/trash-quality-definitions.ts +0 -213
- package/src/data/lidarr-custom-formats.ts +0 -127
- package/src/data/trash-profiles.ts +0 -333
- package/src/docker/client.ts +0 -312
- package/src/docker/index.ts +0 -18
- package/src/index.ts +0 -77
- package/src/setup/actions/arr-common.ts +0 -307
- package/src/setup/actions/dashboards.ts +0 -79
- package/src/setup/actions/download-clients.ts +0 -54
- package/src/setup/actions/jellyseerr.ts +0 -331
- package/src/setup/actions/media-servers.ts +0 -68
- package/src/setup/actions/monitoring.ts +0 -120
- package/src/setup/actions/prowlarr.ts +0 -137
- package/src/setup/actions/utilities.ts +0 -490
- package/src/setup/index.ts +0 -78
- package/src/setup/types.ts +0 -144
- package/src/structure/manager.ts +0 -129
- package/src/ui/App.ts +0 -111
- package/src/ui/components/ApplicationSelector.ts +0 -275
- package/src/ui/components/CredentialsForm.ts +0 -180
- package/src/ui/components/DownloadClientForm.ts +0 -114
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -160
- package/src/ui/components/UpdateNotification.ts +0 -102
- package/src/ui/index.ts +0 -7
- package/src/ui/screens/AdvancedSettings.ts +0 -188
- package/src/ui/screens/ApiKeyViewer.ts +0 -511
- package/src/ui/screens/AppConfigurator.ts +0 -543
- package/src/ui/screens/AppManager.ts +0 -284
- package/src/ui/screens/ContainerControl.ts +0 -581
- package/src/ui/screens/LogsViewer.ts +0 -478
- package/src/ui/screens/MainMenu.ts +0 -299
- package/src/ui/screens/MonitorDashboard.ts +0 -881
- package/src/ui/screens/SecretsEditor.ts +0 -216
- package/src/ui/screens/SettingsScreen.ts +0 -633
- package/src/ui/screens/index.ts +0 -22
- package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
- package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
- package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
- package/src/ui/screens/setup/HomepageSetup.ts +0 -165
- package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
- package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
- package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
- package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
- package/src/ui/screens/setup/QuickSetup.ts +0 -1169
- package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
- package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
- package/src/utils/arch.ts +0 -78
- package/src/utils/browser.ts +0 -27
- package/src/utils/categories.ts +0 -61
- package/src/utils/debug.ts +0 -84
- package/src/utils/env.ts +0 -138
- package/src/utils/index.ts +0 -8
- package/src/utils/logs.ts +0 -119
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
- package/src/utils/migrations.ts +0 -165
- package/src/utils/password.ts +0 -42
- package/src/utils/paths.ts +0 -33
- package/src/utils/unraid.ts +0 -103
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* App Configurator Screen
|
|
3
|
-
* Configures *arr apps via API - sets root folders and download clients
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { BoxRenderable, CliRenderer, KeyEvent, TextRenderable } from "@opentui/core"
|
|
7
|
-
|
|
8
|
-
import { ArrApiClient, createQBittorrentConfig, createSABnzbdConfig } from "~/api/arr-api"
|
|
9
|
-
import { QBittorrentClient } from "~/api/qbittorrent-api"
|
|
10
|
-
import { getApp } from "~/apps/registry"
|
|
11
|
-
import { AppId, EasiarrConfig } from "~/config/schema"
|
|
12
|
-
import { CredentialsForm, CredentialsFormResult } from "~/ui/components/CredentialsForm"
|
|
13
|
-
import { DownloadClientForm, DownloadClientFormResult } from "~/ui/components/DownloadClientForm"
|
|
14
|
-
import { createPageLayout } from "~/ui/components/PageLayout"
|
|
15
|
-
import { getCategoriesForApps } from "~/utils/categories"
|
|
16
|
-
import { readEnvSync, updateEnv } from "~/utils/env"
|
|
17
|
-
|
|
18
|
-
interface ConfigResult {
|
|
19
|
-
appId: AppId
|
|
20
|
-
appName: string
|
|
21
|
-
status: "pending" | "configuring" | "success" | "error" | "skipped"
|
|
22
|
-
message?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type Step = "credentials" | "configure" | "qbittorrent" | "sabnzbd" | "done"
|
|
26
|
-
|
|
27
|
-
export class AppConfigurator extends BoxRenderable {
|
|
28
|
-
private config: EasiarrConfig
|
|
29
|
-
private cliRenderer: CliRenderer
|
|
30
|
-
private keyHandler!: (key: KeyEvent) => void
|
|
31
|
-
private results: ConfigResult[] = []
|
|
32
|
-
private currentStep: Step = "credentials"
|
|
33
|
-
private contentBox!: BoxRenderable
|
|
34
|
-
private pageContainer!: BoxRenderable
|
|
35
|
-
|
|
36
|
-
// Global *arr credentials
|
|
37
|
-
private globalUsername = "admin"
|
|
38
|
-
private globalPassword = ""
|
|
39
|
-
private globalEmail = ""
|
|
40
|
-
private overrideExisting = false
|
|
41
|
-
|
|
42
|
-
// Download client credentials
|
|
43
|
-
private qbHost = "qbittorrent"
|
|
44
|
-
private qbPort = 8080
|
|
45
|
-
private qbUser = "admin"
|
|
46
|
-
private qbPass = ""
|
|
47
|
-
private sabHost = "sabnzbd"
|
|
48
|
-
private sabPort = 8080
|
|
49
|
-
private sabApiKey = ""
|
|
50
|
-
|
|
51
|
-
// Check which download clients are enabled
|
|
52
|
-
private hasQBittorrent = false
|
|
53
|
-
private hasSABnzbd = false
|
|
54
|
-
|
|
55
|
-
constructor(
|
|
56
|
-
renderer: CliRenderer,
|
|
57
|
-
config: EasiarrConfig,
|
|
58
|
-
private onBack: () => void
|
|
59
|
-
) {
|
|
60
|
-
super(renderer, {
|
|
61
|
-
id: "app-configurator",
|
|
62
|
-
width: "100%",
|
|
63
|
-
height: "100%",
|
|
64
|
-
backgroundColor: "#111111",
|
|
65
|
-
zIndex: 200,
|
|
66
|
-
})
|
|
67
|
-
this.cliRenderer = renderer
|
|
68
|
-
this.config = config
|
|
69
|
-
|
|
70
|
-
// Check enabled download clients
|
|
71
|
-
this.hasQBittorrent = config.apps.some((a) => a.id === "qbittorrent" && a.enabled)
|
|
72
|
-
this.hasSABnzbd = config.apps.some((a) => a.id === "sabnzbd" && a.enabled)
|
|
73
|
-
|
|
74
|
-
// Load saved credentials from .env
|
|
75
|
-
this.loadSavedCredentials()
|
|
76
|
-
|
|
77
|
-
// Start with credentials prompt
|
|
78
|
-
this.renderCredentialsPrompt()
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private loadSavedCredentials() {
|
|
82
|
-
const env = readEnvSync()
|
|
83
|
-
if (env.USERNAME_GLOBAL) this.globalUsername = env.USERNAME_GLOBAL
|
|
84
|
-
this.globalPassword = env.PASSWORD_GLOBAL || "Ch4ng3m3!1234securityReasons"
|
|
85
|
-
if (env.EMAIL_GLOBAL) this.globalEmail = env.EMAIL_GLOBAL
|
|
86
|
-
if (env.PASSWORD_QBITTORRENT) this.qbPass = env.PASSWORD_QBITTORRENT
|
|
87
|
-
if (env.API_KEY_SABNZBD) this.sabApiKey = env.API_KEY_SABNZBD
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private renderCredentialsPrompt() {
|
|
91
|
-
this.clear()
|
|
92
|
-
|
|
93
|
-
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
94
|
-
title: "Configure Apps",
|
|
95
|
-
stepInfo: "Global Credentials",
|
|
96
|
-
footerHint: [
|
|
97
|
-
{ type: "key", key: "Tab", value: "Cycle Fields/Shortcuts" },
|
|
98
|
-
{ type: "key", key: "O", value: "Override" },
|
|
99
|
-
{ type: "key", key: "Enter", value: "Continue" },
|
|
100
|
-
{ type: "key", key: "Esc", value: "Skip" },
|
|
101
|
-
],
|
|
102
|
-
})
|
|
103
|
-
this.pageContainer = container
|
|
104
|
-
this.add(container)
|
|
105
|
-
|
|
106
|
-
const form = new CredentialsForm(
|
|
107
|
-
this.cliRenderer,
|
|
108
|
-
{
|
|
109
|
-
title: "Set a global username/password for all *arr applications:",
|
|
110
|
-
initialUsername: this.globalUsername,
|
|
111
|
-
initialPassword: this.globalPassword,
|
|
112
|
-
initialEmail: this.globalEmail,
|
|
113
|
-
showEmail: true,
|
|
114
|
-
showOverride: true,
|
|
115
|
-
initialOverride: this.overrideExisting,
|
|
116
|
-
},
|
|
117
|
-
(result: CredentialsFormResult) => {
|
|
118
|
-
this.globalUsername = result.username
|
|
119
|
-
this.globalPassword = result.password
|
|
120
|
-
this.globalEmail = result.email
|
|
121
|
-
this.overrideExisting = result.override
|
|
122
|
-
this.saveGlobalCredentialsToEnv()
|
|
123
|
-
this.currentStep = "configure"
|
|
124
|
-
this.runConfiguration()
|
|
125
|
-
},
|
|
126
|
-
() => {
|
|
127
|
-
// Cancelled - skip credentials
|
|
128
|
-
this.currentStep = "configure"
|
|
129
|
-
this.runConfiguration()
|
|
130
|
-
}
|
|
131
|
-
)
|
|
132
|
-
content.add(form)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private async saveGlobalCredentialsToEnv() {
|
|
136
|
-
try {
|
|
137
|
-
const updates: Record<string, string> = {}
|
|
138
|
-
if (this.globalUsername) updates.USERNAME_GLOBAL = this.globalUsername
|
|
139
|
-
if (this.globalPassword) updates.PASSWORD_GLOBAL = this.globalPassword
|
|
140
|
-
if (this.globalEmail) updates.EMAIL_GLOBAL = this.globalEmail
|
|
141
|
-
await updateEnv(updates)
|
|
142
|
-
} catch {
|
|
143
|
-
// Ignore errors - not critical
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private async runConfiguration() {
|
|
148
|
-
// Initialize results for apps that have rootFolder
|
|
149
|
-
for (const appConfig of this.config.apps) {
|
|
150
|
-
if (!appConfig.enabled) continue
|
|
151
|
-
const appDef = getApp(appConfig.id)
|
|
152
|
-
if (!appDef?.rootFolder) continue
|
|
153
|
-
|
|
154
|
-
this.results.push({
|
|
155
|
-
appId: appConfig.id,
|
|
156
|
-
appName: appDef.name,
|
|
157
|
-
status: "pending",
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
this.renderConfigProgress()
|
|
162
|
-
|
|
163
|
-
// Configure each app
|
|
164
|
-
for (let i = 0; i < this.results.length; i++) {
|
|
165
|
-
const result = this.results[i]
|
|
166
|
-
result.status = "configuring"
|
|
167
|
-
this.updateDisplay()
|
|
168
|
-
await this.configureApp(result.appId)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// After all apps configured, handle download clients
|
|
172
|
-
if (this.hasQBittorrent) {
|
|
173
|
-
this.currentStep = "qbittorrent"
|
|
174
|
-
this.renderQBittorrentPrompt()
|
|
175
|
-
} else if (this.hasSABnzbd) {
|
|
176
|
-
this.currentStep = "sabnzbd"
|
|
177
|
-
this.renderSABnzbdPrompt()
|
|
178
|
-
} else {
|
|
179
|
-
this.currentStep = "done"
|
|
180
|
-
this.renderDone()
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private async configureApp(appId: AppId) {
|
|
185
|
-
const result = this.results.find((r) => r.appId === appId)
|
|
186
|
-
if (!result) return
|
|
187
|
-
|
|
188
|
-
const appDef = getApp(appId)
|
|
189
|
-
if (!appDef) {
|
|
190
|
-
result.status = "error"
|
|
191
|
-
result.message = "Unknown app"
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Get API key from env
|
|
196
|
-
const apiKey = this.extractApiKey(appId)
|
|
197
|
-
if (!apiKey) {
|
|
198
|
-
result.status = "skipped"
|
|
199
|
-
result.message = "No API key"
|
|
200
|
-
return
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const appConfig = this.config.apps.find((a) => a.id === appId)
|
|
205
|
-
const port = appConfig?.port ?? appDef.defaultPort
|
|
206
|
-
const client = new ArrApiClient(appId, port, apiKey)
|
|
207
|
-
|
|
208
|
-
// Check current root folder
|
|
209
|
-
const rootFolders = await client.getRootFolders()
|
|
210
|
-
const targetPath = appDef.rootFolder?.path
|
|
211
|
-
|
|
212
|
-
// Check if already configured properly
|
|
213
|
-
const hasCorrectRoot = rootFolders.some((rf) => rf.path === targetPath)
|
|
214
|
-
if (hasCorrectRoot && !this.overrideExisting) {
|
|
215
|
-
result.status = "success"
|
|
216
|
-
result.message = "Already configured"
|
|
217
|
-
return
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Add root folder if missing
|
|
221
|
-
if (!hasCorrectRoot && targetPath) {
|
|
222
|
-
await client.addRootFolder(targetPath)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
result.status = "success"
|
|
226
|
-
result.message = "Root folder set"
|
|
227
|
-
} catch (error) {
|
|
228
|
-
result.status = "error"
|
|
229
|
-
result.message = String(error).substring(0, 50)
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
private extractApiKey(appId: AppId): string | null {
|
|
234
|
-
const envKey = `API_KEY_${appId.toUpperCase()}`
|
|
235
|
-
return readEnvSync()[envKey] ?? null
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private getEnabledCategories(): { name: string; savePath: string }[] {
|
|
239
|
-
const enabledAppIds = this.config.apps.filter((a) => a.enabled).map((a) => a.id)
|
|
240
|
-
return getCategoriesForApps(enabledAppIds)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private renderConfigProgress() {
|
|
244
|
-
this.clear()
|
|
245
|
-
|
|
246
|
-
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
247
|
-
title: "Configure Apps",
|
|
248
|
-
stepInfo: "Setting up root folders",
|
|
249
|
-
footerHint: [{ type: "text", value: "Please wait..." }],
|
|
250
|
-
})
|
|
251
|
-
this.pageContainer = container
|
|
252
|
-
this.contentBox = content
|
|
253
|
-
this.add(container)
|
|
254
|
-
|
|
255
|
-
this.updateDisplay()
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private updateDisplay() {
|
|
259
|
-
// Clear content and rebuild
|
|
260
|
-
const contentChildren = [...this.contentBox.getChildren()]
|
|
261
|
-
for (const child of contentChildren) {
|
|
262
|
-
if (child.id) {
|
|
263
|
-
try {
|
|
264
|
-
this.contentBox.remove(child.id)
|
|
265
|
-
} catch {
|
|
266
|
-
/* ignore */
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Header
|
|
272
|
-
this.contentBox.add(
|
|
273
|
-
new TextRenderable(this.cliRenderer, {
|
|
274
|
-
content: "Configuring *arr applications...\n",
|
|
275
|
-
fg: "#4a9eff",
|
|
276
|
-
})
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
// Results
|
|
280
|
-
for (const result of this.results) {
|
|
281
|
-
let icon = "○"
|
|
282
|
-
let color = "#6272a4"
|
|
283
|
-
|
|
284
|
-
switch (result.status) {
|
|
285
|
-
case "configuring":
|
|
286
|
-
icon = "◉"
|
|
287
|
-
color = "#f1fa8c"
|
|
288
|
-
break
|
|
289
|
-
case "success":
|
|
290
|
-
icon = "✓"
|
|
291
|
-
color = "#50fa7b"
|
|
292
|
-
break
|
|
293
|
-
case "error":
|
|
294
|
-
icon = "✗"
|
|
295
|
-
color = "#ff5555"
|
|
296
|
-
break
|
|
297
|
-
case "skipped":
|
|
298
|
-
icon = "⏭"
|
|
299
|
-
color = "#6272a4"
|
|
300
|
-
break
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const message = result.message ? ` - ${result.message}` : ""
|
|
304
|
-
this.contentBox.add(
|
|
305
|
-
new TextRenderable(this.cliRenderer, {
|
|
306
|
-
content: `${icon} ${result.appName}${message}`,
|
|
307
|
-
fg: color,
|
|
308
|
-
})
|
|
309
|
-
)
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private renderQBittorrentPrompt() {
|
|
314
|
-
this.clear()
|
|
315
|
-
|
|
316
|
-
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
317
|
-
title: "Configure Apps",
|
|
318
|
-
stepInfo: "qBittorrent Credentials",
|
|
319
|
-
footerHint: [
|
|
320
|
-
{ type: "text", value: "Enter credentials from qBittorrent WebUI" },
|
|
321
|
-
{ type: "key", key: "Esc", value: "Skip" },
|
|
322
|
-
],
|
|
323
|
-
})
|
|
324
|
-
this.pageContainer = container
|
|
325
|
-
this.add(container)
|
|
326
|
-
|
|
327
|
-
const form = new DownloadClientForm(
|
|
328
|
-
this.cliRenderer,
|
|
329
|
-
{ type: "qbittorrent", initialValue: this.qbPass },
|
|
330
|
-
(result: DownloadClientFormResult) => {
|
|
331
|
-
this.qbPass = result.value
|
|
332
|
-
this.addDownloadClients("qbittorrent")
|
|
333
|
-
},
|
|
334
|
-
() => {
|
|
335
|
-
// Cancelled
|
|
336
|
-
if (this.hasSABnzbd) {
|
|
337
|
-
this.currentStep = "sabnzbd"
|
|
338
|
-
this.renderSABnzbdPrompt()
|
|
339
|
-
} else {
|
|
340
|
-
this.currentStep = "done"
|
|
341
|
-
this.renderDone()
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
)
|
|
345
|
-
content.add(form)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
private renderSABnzbdPrompt() {
|
|
349
|
-
this.clear()
|
|
350
|
-
|
|
351
|
-
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
352
|
-
title: "Configure Apps",
|
|
353
|
-
stepInfo: "SABnzbd Credentials",
|
|
354
|
-
footerHint: [
|
|
355
|
-
{ type: "text", value: "Enter API key from SABnzbd Config → General" },
|
|
356
|
-
{ type: "key", key: "Esc", value: "Skip" },
|
|
357
|
-
],
|
|
358
|
-
})
|
|
359
|
-
this.pageContainer = container
|
|
360
|
-
this.add(container)
|
|
361
|
-
|
|
362
|
-
const form = new DownloadClientForm(
|
|
363
|
-
this.cliRenderer,
|
|
364
|
-
{ type: "sabnzbd", initialValue: this.sabApiKey },
|
|
365
|
-
(result: DownloadClientFormResult) => {
|
|
366
|
-
this.sabApiKey = result.value
|
|
367
|
-
this.addDownloadClients("sabnzbd")
|
|
368
|
-
},
|
|
369
|
-
() => {
|
|
370
|
-
// Cancelled
|
|
371
|
-
this.currentStep = "done"
|
|
372
|
-
this.renderDone()
|
|
373
|
-
}
|
|
374
|
-
)
|
|
375
|
-
content.add(form)
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private async addDownloadClients(type: "qbittorrent" | "sabnzbd") {
|
|
379
|
-
// Configure qBittorrent settings via its API first
|
|
380
|
-
if (type === "qbittorrent") {
|
|
381
|
-
try {
|
|
382
|
-
const qbClient = new QBittorrentClient(this.qbHost, this.qbPort, this.qbUser, this.qbPass)
|
|
383
|
-
await qbClient.login()
|
|
384
|
-
|
|
385
|
-
// Create categories for enabled *arr apps
|
|
386
|
-
const categories = this.getEnabledCategories()
|
|
387
|
-
for (const cat of categories) {
|
|
388
|
-
await qbClient.createCategory(cat.name, cat.savePath)
|
|
389
|
-
}
|
|
390
|
-
} catch {
|
|
391
|
-
// QB may not be ready or credentials wrong - continue anyway
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Save credentials to .env
|
|
396
|
-
await this.saveCredentialsToEnv(type)
|
|
397
|
-
|
|
398
|
-
// Add download clients to *arr apps
|
|
399
|
-
for (const result of this.results) {
|
|
400
|
-
if (result.status !== "success") continue
|
|
401
|
-
|
|
402
|
-
const apiKey = this.extractApiKey(result.appId)
|
|
403
|
-
if (!apiKey) continue
|
|
404
|
-
|
|
405
|
-
const appDef = getApp(result.appId)
|
|
406
|
-
const appConfig = this.config.apps.find((a) => a.id === result.appId)
|
|
407
|
-
const port = appConfig?.port ?? appDef?.defaultPort ?? 8989
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
const client = new ArrApiClient(result.appId, port, apiKey)
|
|
411
|
-
|
|
412
|
-
if (type === "qbittorrent") {
|
|
413
|
-
const qbConfig = createQBittorrentConfig(
|
|
414
|
-
this.qbHost,
|
|
415
|
-
this.qbPort,
|
|
416
|
-
this.qbUser,
|
|
417
|
-
this.qbPass,
|
|
418
|
-
result.appId
|
|
419
|
-
)
|
|
420
|
-
await client.addDownloadClient(qbConfig)
|
|
421
|
-
} else {
|
|
422
|
-
const sabConfig = createSABnzbdConfig(this.sabHost, this.sabPort, this.sabApiKey)
|
|
423
|
-
await client.addDownloadClient(sabConfig)
|
|
424
|
-
}
|
|
425
|
-
} catch {
|
|
426
|
-
// Continue - download client may fail but app is still configured
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Move to next step
|
|
431
|
-
if (type === "qbittorrent" && this.hasSABnzbd) {
|
|
432
|
-
this.currentStep = "sabnzbd"
|
|
433
|
-
this.renderSABnzbdPrompt()
|
|
434
|
-
} else {
|
|
435
|
-
this.currentStep = "done"
|
|
436
|
-
this.renderDone()
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
private async saveCredentialsToEnv(type: "qbittorrent" | "sabnzbd") {
|
|
441
|
-
try {
|
|
442
|
-
const updates: Record<string, string> = {}
|
|
443
|
-
if (type === "qbittorrent" && this.qbPass) {
|
|
444
|
-
updates.PASSWORD_QBITTORRENT = this.qbPass
|
|
445
|
-
} else if (type === "sabnzbd" && this.sabApiKey) {
|
|
446
|
-
updates.API_KEY_SABNZBD = this.sabApiKey
|
|
447
|
-
}
|
|
448
|
-
await updateEnv(updates)
|
|
449
|
-
} catch {
|
|
450
|
-
// Ignore errors - not critical
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
private renderDone() {
|
|
455
|
-
this.clear()
|
|
456
|
-
|
|
457
|
-
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
458
|
-
title: "Configure Apps",
|
|
459
|
-
stepInfo: "Complete",
|
|
460
|
-
footerHint: [{ type: "text", value: "Press any key to return" }],
|
|
461
|
-
})
|
|
462
|
-
this.add(container)
|
|
463
|
-
|
|
464
|
-
const successCount = this.results.filter((r) => r.status === "success").length
|
|
465
|
-
const errorCount = this.results.filter((r) => r.status === "error").length
|
|
466
|
-
|
|
467
|
-
content.add(
|
|
468
|
-
new TextRenderable(this.cliRenderer, {
|
|
469
|
-
content: "Configuration complete!\n",
|
|
470
|
-
fg: "#50fa7b",
|
|
471
|
-
})
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
content.add(
|
|
475
|
-
new TextRenderable(this.cliRenderer, {
|
|
476
|
-
content: `✓ ${successCount} app(s) configured`,
|
|
477
|
-
fg: "#50fa7b",
|
|
478
|
-
})
|
|
479
|
-
)
|
|
480
|
-
|
|
481
|
-
if (errorCount > 0) {
|
|
482
|
-
content.add(
|
|
483
|
-
new TextRenderable(this.cliRenderer, {
|
|
484
|
-
content: `✗ ${errorCount} app(s) had errors (see above)`,
|
|
485
|
-
fg: "#ff5555",
|
|
486
|
-
})
|
|
487
|
-
)
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
content.add(new BoxRenderable(this.cliRenderer, { width: 1, height: 1 }))
|
|
491
|
-
|
|
492
|
-
// Show results summary
|
|
493
|
-
for (const result of this.results) {
|
|
494
|
-
const icon = result.status === "success" ? "✓" : result.status === "skipped" ? "⏭" : "✗"
|
|
495
|
-
const color =
|
|
496
|
-
result.status === "success"
|
|
497
|
-
? "#50fa7b"
|
|
498
|
-
: result.status === "skipped"
|
|
499
|
-
? "#6272a4"
|
|
500
|
-
: "#ff5555"
|
|
501
|
-
const message = result.message ? ` - ${result.message}` : ""
|
|
502
|
-
|
|
503
|
-
content.add(
|
|
504
|
-
new TextRenderable(this.cliRenderer, {
|
|
505
|
-
content: `${icon} ${result.appName}${message}`,
|
|
506
|
-
fg: color,
|
|
507
|
-
})
|
|
508
|
-
)
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
this.keyHandler = () => {
|
|
512
|
-
this.destroy()
|
|
513
|
-
this.onBack()
|
|
514
|
-
}
|
|
515
|
-
this.cliRenderer.keyInput.on("keypress", this.keyHandler)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
private clear() {
|
|
519
|
-
const children = [...this.getChildren()]
|
|
520
|
-
for (const child of children) {
|
|
521
|
-
if (child.id) {
|
|
522
|
-
try {
|
|
523
|
-
this.remove(child.id)
|
|
524
|
-
} catch {
|
|
525
|
-
/* ignore */
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
public destroy() {
|
|
532
|
-
if (this.keyHandler) {
|
|
533
|
-
this.cliRenderer.keyInput.off("keypress", this.keyHandler)
|
|
534
|
-
}
|
|
535
|
-
if (this.parent && this.id) {
|
|
536
|
-
try {
|
|
537
|
-
this.parent.remove(this.id)
|
|
538
|
-
} catch {
|
|
539
|
-
/* ignore */
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|