@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,230 +0,0 @@
1
- /**
2
- * Grafana API Client
3
- * Handles Grafana auto-setup including admin password change and Prometheus datasource
4
- */
5
-
6
- import type { AutoSetupOptions, AutoSetupResult, IAutoSetupClient } from "./auto-setup-types"
7
- import { BaseApiClient } from "./base-api"
8
-
9
- interface GrafanaDataSource {
10
- id?: number
11
- uid?: string
12
- orgId?: number
13
- name: string
14
- type: string
15
- access: string
16
- url: string
17
- isDefault?: boolean
18
- jsonData?: Record<string, unknown>
19
- secureJsonData?: Record<string, unknown>
20
- }
21
-
22
- interface GrafanaHealthResponse {
23
- commit: string
24
- database: string
25
- version: string
26
- }
27
-
28
- export class GrafanaClient extends BaseApiClient implements IAutoSetupClient {
29
- protected readonly logPrefix = "GrafanaApi"
30
- private username: string
31
- private password: string
32
-
33
- constructor(
34
- host: string,
35
- port: number = 3000,
36
- username: string = "admin",
37
- password: string = "admin"
38
- ) {
39
- super(host, port)
40
- this.username = username
41
- this.password = password
42
- }
43
-
44
- /**
45
- * Get Basic Auth header
46
- */
47
- private getAuthHeader(): string {
48
- const credentials = Buffer.from(`${this.username}:${this.password}`).toString("base64")
49
- return `Basic ${credentials}`
50
- }
51
-
52
- /**
53
- * Common headers for Grafana API requests
54
- */
55
- private getHeaders(): Record<string, string> {
56
- return {
57
- "Content-Type": "application/json",
58
- Accept: "application/json",
59
- Authorization: this.getAuthHeader(),
60
- }
61
- }
62
-
63
- /**
64
- * Update credentials (after password change)
65
- */
66
- setCredentials(username: string, password: string): void {
67
- this.username = username
68
- this.password = password
69
- }
70
-
71
- /**
72
- * Check if Grafana is reachable
73
- */
74
- async isHealthy(): Promise<boolean> {
75
- const response = await this.get<GrafanaHealthResponse>("/api/health")
76
- return response.success
77
- }
78
-
79
- /**
80
- * Check if Grafana is already configured (has non-default password)
81
- */
82
- async isInitialized(): Promise<boolean> {
83
- try {
84
- const response = await fetch(`${this.baseUrl}/api/user`, {
85
- method: "GET",
86
- headers: {
87
- Authorization: `Basic ${Buffer.from("admin:admin").toString("base64")}`,
88
- },
89
- })
90
- return !response.ok
91
- } catch {
92
- return true
93
- }
94
- }
95
-
96
- /**
97
- * Change admin password
98
- */
99
- async changeAdminPassword(newPassword: string): Promise<boolean> {
100
- const response = await this.put<unknown, { oldPassword: string; newPassword: string }>(
101
- "/api/user/password",
102
- { oldPassword: this.password, newPassword },
103
- { headers: this.getHeaders() }
104
- )
105
-
106
- if (response.success) {
107
- this.password = newPassword
108
- return true
109
- }
110
- return false
111
- }
112
-
113
- /**
114
- * Get list of datasources
115
- */
116
- async getDataSources(): Promise<GrafanaDataSource[]> {
117
- const response = await this.get<GrafanaDataSource[]>("/api/datasources", {
118
- headers: this.getHeaders(),
119
- })
120
- return response.data ?? []
121
- }
122
-
123
- /**
124
- * Check if a datasource with the given name exists
125
- */
126
- async dataSourceExists(name: string): Promise<boolean> {
127
- const dataSources = await this.getDataSources()
128
- return dataSources.some((ds) => ds.name === name)
129
- }
130
-
131
- /**
132
- * Create a Prometheus datasource
133
- */
134
- async createPrometheusDataSource(
135
- name: string = "Prometheus",
136
- url: string = "http://prometheus:9090",
137
- isDefault: boolean = true
138
- ): Promise<boolean> {
139
- const payload: GrafanaDataSource = {
140
- name,
141
- type: "prometheus",
142
- access: "proxy",
143
- url,
144
- isDefault,
145
- jsonData: {
146
- httpMethod: "POST",
147
- timeInterval: "15s",
148
- },
149
- }
150
-
151
- const response = await this.post<unknown, GrafanaDataSource>("/api/datasources", payload, {
152
- headers: this.getHeaders(),
153
- })
154
-
155
- // Success or already exists (409)
156
- return response.success || response.status === 409
157
- }
158
-
159
- /**
160
- * Generate an API key for external integrations
161
- */
162
- async createApiKey(name: string = "easiarr", role: string = "Admin"): Promise<string | null> {
163
- const response = await this.post<
164
- { key: string },
165
- { name: string; role: string; secondsToLive: number }
166
- >("/api/auth/keys", { name, role, secondsToLive: 0 }, { headers: this.getHeaders() })
167
-
168
- if (response.success && response.data?.key) {
169
- return response.data.key
170
- }
171
- return null
172
- }
173
-
174
- /**
175
- * Get Grafana server info
176
- */
177
- async getServerInfo(): Promise<GrafanaHealthResponse | null> {
178
- const response = await this.get<GrafanaHealthResponse>("/api/health")
179
- return response.data ?? null
180
- }
181
-
182
- /**
183
- * Run the auto-setup process for Grafana
184
- */
185
- async setup(options: AutoSetupOptions): Promise<AutoSetupResult> {
186
- const { username, password } = options
187
-
188
- try {
189
- const healthy = await this.isHealthy()
190
- if (!healthy) {
191
- return { success: false, message: "Grafana not reachable" }
192
- }
193
-
194
- const initialized = await this.isInitialized()
195
-
196
- if (!initialized) {
197
- this.setCredentials("admin", "admin")
198
- const changed = await this.changeAdminPassword(password)
199
- if (!changed) {
200
- return { success: false, message: "Failed to change admin password" }
201
- }
202
- } else {
203
- this.setCredentials(username, password)
204
- const response = await fetch(`${this.baseUrl}/api/user`, {
205
- method: "GET",
206
- headers: this.getHeaders(),
207
- })
208
- if (!response.ok) {
209
- return { success: false, message: "Login failed - check credentials" }
210
- }
211
- }
212
-
213
- const prometheusExists = await this.dataSourceExists("Prometheus")
214
- if (!prometheusExists) {
215
- await this.createPrometheusDataSource("Prometheus", "http://prometheus:9090", true)
216
- }
217
-
218
- const apiKey = await this.createApiKey("easiarr-api-key")
219
-
220
- return {
221
- success: true,
222
- message: initialized ? "Configured" : "Password changed, Prometheus added",
223
- data: apiKey ? { apiKey } : undefined,
224
- envUpdates: apiKey ? { API_KEY_GRAFANA: apiKey } : undefined,
225
- }
226
- } catch (error) {
227
- return { success: false, message: `${error}` }
228
- }
229
- }
230
- }
@@ -1,141 +0,0 @@
1
- /**
2
- * Heimdall API Client
3
- * Handles Heimdall dashboard auto-setup with application tiles
4
- */
5
-
6
- import type { AutoSetupOptions, AutoSetupResult, IAutoSetupClient } from "./auto-setup-types"
7
- import type { AppConfig } from "~/config/schema"
8
- import { getApp } from "~/apps/registry"
9
- import { BaseApiClient } from "./base-api"
10
-
11
- interface HeimdallApp {
12
- id?: number
13
- title: string
14
- url: string
15
- colour?: string
16
- icon?: string
17
- appdescription?: string
18
- pinned?: boolean
19
- }
20
-
21
- export class HeimdallClient extends BaseApiClient implements IAutoSetupClient {
22
- protected readonly logPrefix = "HeimdallApi"
23
-
24
- constructor(host: string, port: number = 80) {
25
- super(host, port)
26
- }
27
-
28
- /**
29
- * Check if Heimdall is reachable
30
- */
31
- async isHealthy(): Promise<boolean> {
32
- try {
33
- const response = await fetch(this.baseUrl, { method: "GET" })
34
- return response.ok
35
- } catch {
36
- return false
37
- }
38
- }
39
-
40
- /**
41
- * Check if already configured
42
- */
43
- async isInitialized(): Promise<boolean> {
44
- return true
45
- }
46
-
47
- /**
48
- * Get list of apps
49
- */
50
- async getApps(): Promise<HeimdallApp[]> {
51
- const response = await this.get<HeimdallApp[]>("/api/items")
52
- return response.data ?? []
53
- }
54
-
55
- /**
56
- * Add an app/tile to Heimdall
57
- */
58
- async addApp(app: HeimdallApp): Promise<boolean> {
59
- const response = await this.post<unknown, HeimdallApp>("/api/items", app)
60
- return response.success
61
- }
62
-
63
- /**
64
- * Build app config for an easiarr app
65
- */
66
- buildAppConfig(appConfig: AppConfig): HeimdallApp | null {
67
- const appDef = getApp(appConfig.id)
68
- if (!appDef || appDef.defaultPort === 0) return null
69
-
70
- const port = appConfig.port || appDef.defaultPort
71
-
72
- return {
73
- title: appDef.name,
74
- url: `http://${appConfig.id}:${port}`,
75
- appdescription: appDef.description,
76
- pinned: true,
77
- colour: this.getColorForCategory(appDef.category),
78
- }
79
- }
80
-
81
- /**
82
- * Get a color based on app category
83
- */
84
- private getColorForCategory(category: string): string {
85
- const colors: Record<string, string> = {
86
- servarr: "#ffc107",
87
- indexer: "#17a2b8",
88
- downloader: "#28a745",
89
- mediaserver: "#6c5ce7",
90
- request: "#e17055",
91
- monitoring: "#00cec9",
92
- infrastructure: "#636e72",
93
- vpn: "#fd79a8",
94
- utility: "#74b9ff",
95
- }
96
- return colors[category] || "#6c757d"
97
- }
98
-
99
- /**
100
- * Run the auto-setup process for Heimdall
101
- */
102
- async setup(_options: AutoSetupOptions): Promise<AutoSetupResult> {
103
- try {
104
- const healthy = await this.isHealthy()
105
- if (!healthy) {
106
- return { success: false, message: "Heimdall not reachable" }
107
- }
108
-
109
- const existingApps = await this.getApps()
110
-
111
- return {
112
- success: true,
113
- message: "Ready - add tiles via UI",
114
- data: { existingAppsCount: existingApps.length },
115
- }
116
- } catch (error) {
117
- return { success: false, message: `${error}` }
118
- }
119
- }
120
-
121
- /**
122
- * Auto-add apps for enabled easiarr services
123
- */
124
- async setupEasiarrApps(apps: AppConfig[]): Promise<number> {
125
- let addedCount = 0
126
-
127
- for (const appConfig of apps) {
128
- if (!appConfig.enabled) continue
129
-
130
- const heimdallApp = this.buildAppConfig(appConfig)
131
- if (!heimdallApp) continue
132
-
133
- const success = await this.addApp(heimdallApp)
134
- if (success) {
135
- addedCount++
136
- }
137
- }
138
-
139
- return addedCount
140
- }
141
- }
@@ -1,209 +0,0 @@
1
- /**
2
- * Homarr API Client
3
- * Handles Homarr dashboard auto-setup with user and app management
4
- * Based on Homarr OpenAPI v1.0.0
5
- */
6
-
7
- import type { AutoSetupOptions, AutoSetupResult, IAutoSetupClient } from "./auto-setup-types"
8
- import type { AppConfig } from "~/config/schema"
9
- import { getApp } from "~/apps/registry"
10
- import { debugLog } from "~/utils/debug"
11
- import { BaseApiClient } from "./base-api"
12
-
13
- interface HomarrApp {
14
- id?: string
15
- appId?: string
16
- name: string
17
- description: string | null
18
- iconUrl: string
19
- href: string | null
20
- pingUrl: string | null
21
- }
22
-
23
- interface HomarrUser {
24
- id: string
25
- name: string | null
26
- email: string | null
27
- }
28
-
29
- interface HomarrInfo {
30
- version: string
31
- }
32
-
33
- export class HomarrClient extends BaseApiClient implements IAutoSetupClient {
34
- protected readonly logPrefix = "HomarrApi"
35
- private apiKey?: string
36
-
37
- constructor(host: string, port: number = 7575, apiKey?: string) {
38
- super(host, port)
39
- this.apiKey = apiKey
40
- }
41
-
42
- /**
43
- * Set API key for authenticated requests
44
- */
45
- setApiKey(apiKey: string): void {
46
- this.apiKey = apiKey
47
- }
48
-
49
- /**
50
- * Common headers for Homarr API requests
51
- */
52
- private getHeaders(): Record<string, string> {
53
- const headers: Record<string, string> = {
54
- "Content-Type": "application/json",
55
- Accept: "application/json",
56
- }
57
- if (this.apiKey) {
58
- headers["ApiKey"] = this.apiKey
59
- }
60
- return headers
61
- }
62
-
63
- /**
64
- * Check if Homarr is reachable
65
- */
66
- async isHealthy(): Promise<boolean> {
67
- try {
68
- const response = await fetch(this.baseUrl, { method: "GET" })
69
- return response.ok
70
- } catch {
71
- return false
72
- }
73
- }
74
-
75
- /**
76
- * Check if already configured (has users)
77
- */
78
- async isInitialized(): Promise<boolean> {
79
- return true
80
- }
81
-
82
- /**
83
- * Get Homarr version info
84
- */
85
- async getInfo(): Promise<HomarrInfo | null> {
86
- const response = await this.get<HomarrInfo>("/api/info", { headers: this.getHeaders() })
87
- return response.data ?? null
88
- }
89
-
90
- /**
91
- * Get all users
92
- */
93
- async getUsers(): Promise<HomarrUser[]> {
94
- const response = await this.get<HomarrUser[]>("/api/users", { headers: this.getHeaders() })
95
- return response.data ?? []
96
- }
97
-
98
- /**
99
- * Create a user
100
- */
101
- async createUser(username: string, password: string, email?: string): Promise<boolean> {
102
- const response = await this.post<unknown>(
103
- "/api/users",
104
- {
105
- username,
106
- password,
107
- confirmPassword: password,
108
- email: email || "",
109
- groupIds: [],
110
- },
111
- { headers: this.getHeaders() }
112
- )
113
- return response.success
114
- }
115
-
116
- /**
117
- * Get all apps
118
- */
119
- async getApps(): Promise<HomarrApp[]> {
120
- const response = await this.get<HomarrApp[]>("/api/apps", { headers: this.getHeaders() })
121
- return response.data ?? []
122
- }
123
-
124
- /**
125
- * Create an app
126
- */
127
- async createApp(app: Omit<HomarrApp, "id" | "appId">): Promise<string | null> {
128
- const response = await this.post<{ appId: string }>("/api/apps", app, {
129
- headers: this.getHeaders(),
130
- })
131
- return response.data?.appId ?? null
132
- }
133
-
134
- /**
135
- * Build app config for an easiarr app
136
- */
137
- buildAppConfig(appConfig: AppConfig): Omit<HomarrApp, "id" | "appId"> | null {
138
- const appDef = getApp(appConfig.id)
139
- if (!appDef || appDef.defaultPort === 0) return null
140
-
141
- const port = appConfig.port || appDef.defaultPort
142
-
143
- return {
144
- name: appDef.name,
145
- description: appDef.description || null,
146
- iconUrl: `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${appConfig.id}.png`,
147
- href: `http://${appConfig.id}:${port}`,
148
- pingUrl: `http://${appConfig.id}:${port}`,
149
- }
150
- }
151
-
152
- /**
153
- * Run the auto-setup process for Homarr
154
- */
155
- async setup(options: AutoSetupOptions): Promise<AutoSetupResult> {
156
- const { username, password } = options
157
-
158
- try {
159
- const healthy = await this.isHealthy()
160
- if (!healthy) {
161
- return { success: false, message: "Homarr not reachable" }
162
- }
163
-
164
- const users = await this.getUsers()
165
- let userCreated = false
166
-
167
- if (users.length === 0) {
168
- userCreated = await this.createUser(username, password)
169
- }
170
-
171
- return {
172
- success: true,
173
- message: userCreated ? "User created, ready" : "Ready - add apps via UI or API",
174
- data: { userCreated },
175
- }
176
- } catch (error) {
177
- return { success: false, message: `${error}` }
178
- }
179
- }
180
-
181
- /**
182
- * Auto-add apps for enabled easiarr services
183
- */
184
- async setupEasiarrApps(apps: AppConfig[]): Promise<number> {
185
- let addedCount = 0
186
-
187
- const existingApps = await this.getApps()
188
- const existingNames = new Set(existingApps.map((a) => a.name))
189
-
190
- for (const appConfig of apps) {
191
- if (!appConfig.enabled) continue
192
-
193
- const homarrApp = this.buildAppConfig(appConfig)
194
- if (!homarrApp) continue
195
-
196
- if (existingNames.has(homarrApp.name)) {
197
- debugLog("HomarrApi", `App "${homarrApp.name}" already exists, skipping`)
198
- continue
199
- }
200
-
201
- const appId = await this.createApp(homarrApp)
202
- if (appId) {
203
- addedCount++
204
- }
205
- }
206
-
207
- return addedCount
208
- }
209
- }