@muhammedaksam/easiarr 0.1.8 → 0.1.10
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 +6 -3
- package/src/ui/screens/AppConfigurator.ts +29 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhammedaksam/easiarr",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
package/src/api/arr-api.ts
CHANGED
|
@@ -86,6 +86,7 @@ export function createQBittorrentConfig(
|
|
|
86
86
|
{ name: "username", value: username },
|
|
87
87
|
{ name: "password", value: password },
|
|
88
88
|
{ name: categoryField, value: category },
|
|
89
|
+
{ name: "savePath", value: "/data/torrents" },
|
|
89
90
|
{ name: "recentMoviePriority", value: 0 },
|
|
90
91
|
{ name: "olderMoviePriority", value: 0 },
|
|
91
92
|
{ name: "initialState", value: 0 },
|
|
@@ -111,6 +112,7 @@ export function createSABnzbdConfig(host: string, port: number, apiKey: string,
|
|
|
111
112
|
{ name: "port", value: port },
|
|
112
113
|
{ name: "apiKey", value: apiKey },
|
|
113
114
|
{ name: categoryField, value: category },
|
|
115
|
+
{ name: "savePath", value: "/data/usenet" },
|
|
114
116
|
{ name: "recentMoviePriority", value: -100 },
|
|
115
117
|
{ name: "olderMoviePriority", value: -100 },
|
|
116
118
|
],
|
|
@@ -210,8 +212,8 @@ export class ArrApiClient {
|
|
|
210
212
|
return null // Skip - password already configured
|
|
211
213
|
}
|
|
212
214
|
|
|
213
|
-
// Update with authentication settings
|
|
214
|
-
const updatedConfig = {
|
|
215
|
+
// Update with authentication settings (id must be in body, not URL)
|
|
216
|
+
const updatedConfig: HostConfig = {
|
|
215
217
|
...currentConfig,
|
|
216
218
|
authenticationMethod: "forms",
|
|
217
219
|
authenticationRequired: "enabled",
|
|
@@ -220,7 +222,8 @@ export class ArrApiClient {
|
|
|
220
222
|
passwordConfirmation: password,
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
|
|
225
|
+
// PUT to /config/host with id in body (not /config/host/{id})
|
|
226
|
+
return this.request<HostConfig>("/config/host", {
|
|
224
227
|
method: "PUT",
|
|
225
228
|
body: JSON.stringify(updatedConfig),
|
|
226
229
|
})
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
} from "@opentui/core"
|
|
14
14
|
import { existsSync, readFileSync } from "node:fs"
|
|
15
15
|
import { writeFile, readFile } from "node:fs/promises"
|
|
16
|
-
import { join } from "node:path"
|
|
17
16
|
import { createPageLayout } from "../components/PageLayout"
|
|
18
17
|
import { EasiarrConfig, AppId } from "../../config/schema"
|
|
19
18
|
import { getApp } from "../../apps/registry"
|
|
@@ -114,7 +113,7 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
114
113
|
const { container, content } = createPageLayout(this.cliRenderer, {
|
|
115
114
|
title: "Configure Apps",
|
|
116
115
|
stepInfo: "Global Credentials",
|
|
117
|
-
footerHint: "
|
|
116
|
+
footerHint: "Tab Cycle Fields/Shortcuts O Override Enter Continue Esc Skip",
|
|
118
117
|
})
|
|
119
118
|
this.pageContainer = container
|
|
120
119
|
this.add(container)
|
|
@@ -161,23 +160,29 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
161
160
|
content.add(overrideText)
|
|
162
161
|
|
|
163
162
|
userInput.focus()
|
|
164
|
-
let focusedInput = userInput
|
|
163
|
+
let focusedInput: InputRenderable | null = userInput
|
|
165
164
|
|
|
166
165
|
// Handle key events
|
|
167
166
|
this.keyHandler = (key: KeyEvent) => {
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
// Skip shortcut keys when an input is focused (allow typing 'o')
|
|
168
|
+
const inputIsFocused = focusedInput !== null
|
|
169
|
+
|
|
170
|
+
if (key.name === "o" && !inputIsFocused) {
|
|
171
|
+
// Toggle override only when no input is focused
|
|
170
172
|
this.overrideExisting = !this.overrideExisting
|
|
171
173
|
overrideText.content = `[O] Override existing: ${this.overrideExisting ? "Yes" : "No"}`
|
|
172
174
|
overrideText.fg = this.overrideExisting ? "#50fa7b" : "#6272a4"
|
|
173
175
|
} else if (key.name === "tab") {
|
|
174
|
-
//
|
|
176
|
+
// Cycle focus: username -> password -> no focus (shortcuts work) -> username
|
|
175
177
|
if (focusedInput === userInput) {
|
|
176
178
|
userInput.blur()
|
|
177
179
|
passInput.focus()
|
|
178
180
|
focusedInput = passInput
|
|
179
|
-
} else {
|
|
181
|
+
} else if (focusedInput === passInput) {
|
|
180
182
|
passInput.blur()
|
|
183
|
+
focusedInput = null // No focus state - shortcuts available
|
|
184
|
+
} else {
|
|
185
|
+
// No input focused, go back to username
|
|
181
186
|
userInput.focus()
|
|
182
187
|
focusedInput = userInput
|
|
183
188
|
}
|
|
@@ -186,6 +191,7 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
186
191
|
this.cliRenderer.keyInput.off("keypress", this.keyHandler)
|
|
187
192
|
userInput.blur()
|
|
188
193
|
passInput.blur()
|
|
194
|
+
focusedInput = null
|
|
189
195
|
this.currentStep = "configure"
|
|
190
196
|
this.runConfiguration()
|
|
191
197
|
} else if (key.name === "return") {
|
|
@@ -196,6 +202,7 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
196
202
|
this.cliRenderer.keyInput.off("keypress", this.keyHandler)
|
|
197
203
|
userInput.blur()
|
|
198
204
|
passInput.blur()
|
|
205
|
+
focusedInput = null
|
|
199
206
|
|
|
200
207
|
// Save credentials to .env
|
|
201
208
|
this.saveGlobalCredentialsToEnv()
|
|
@@ -338,27 +345,25 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
338
345
|
}
|
|
339
346
|
|
|
340
347
|
private extractApiKey(appId: AppId): string | null {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (volumes.length === 0) return null
|
|
346
|
-
|
|
347
|
-
const parts = volumes[0].split(":")
|
|
348
|
-
const hostPath = parts[0]
|
|
349
|
-
const configFilePath = join(hostPath, appDef.apiKeyMeta.configFile)
|
|
348
|
+
// Use API keys from .env file (format: API_KEY_APPNAME)
|
|
349
|
+
try {
|
|
350
|
+
const envPath = getComposePath().replace("docker-compose.yml", ".env")
|
|
351
|
+
if (!existsSync(envPath)) return null
|
|
350
352
|
|
|
351
|
-
|
|
353
|
+
const content = readFileSync(envPath, "utf-8")
|
|
354
|
+
const envKey = `API_KEY_${appId.toUpperCase()}`
|
|
352
355
|
|
|
353
|
-
|
|
356
|
+
for (const line of content.split("\n")) {
|
|
357
|
+
const [key, ...val] = line.split("=")
|
|
358
|
+
if (key?.trim() === envKey && val.length > 0) {
|
|
359
|
+
return val.join("=").trim()
|
|
360
|
+
}
|
|
361
|
+
}
|
|
354
362
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return match?.[1] || null
|
|
363
|
+
return null
|
|
364
|
+
} catch {
|
|
365
|
+
return null
|
|
359
366
|
}
|
|
360
|
-
|
|
361
|
-
return null
|
|
362
367
|
}
|
|
363
368
|
|
|
364
369
|
private renderConfigProgress() {
|