@muhammedaksam/easiarr 1.3.5 → 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/README.md +8 -5
- package/dist/index.js +29837 -0
- package/package.json +18 -16
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -508
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/bazarr-api.ts +0 -356
- package/src/api/cloudflare-api.ts +0 -577
- package/src/api/custom-format-api.ts +0 -212
- package/src/api/grafana-api.ts +0 -314
- package/src/api/heimdall-api.ts +0 -209
- package/src/api/homarr-api.ts +0 -296
- package/src/api/huntarr-api.ts +0 -622
- package/src/api/index.ts +0 -1
- package/src/api/jellyfin-api.ts +0 -473
- package/src/api/jellyseerr-api.ts +0 -660
- package/src/api/maintainerr-api.ts +0 -731
- package/src/api/naming-config.ts +0 -93
- package/src/api/overseerr-api.ts +0 -513
- package/src/api/plex-api.ts +0 -358
- package/src/api/portainer-api.ts +0 -467
- package/src/api/profilarr-api.ts +0 -380
- package/src/api/prowlarr-api.ts +0 -859
- package/src/api/qbittorrent-api.ts +0 -339
- package/src/api/quality-profile-api.ts +0 -269
- package/src/api/tautulli-api.ts +0 -277
- package/src/api/uptime-kuma-api.ts +0 -342
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1255
- package/src/compose/caddy-config.ts +0 -129
- package/src/compose/generator.ts +0 -262
- package/src/compose/index.ts +0 -3
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -185
- package/src/config/bookmarks-generator.ts +0 -149
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -376
- package/src/config/index.ts +0 -3
- package/src/config/manager.ts +0 -149
- package/src/config/recyclarr-config.ts +0 -179
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -85
- 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 -326
- package/src/docker/client.ts +0 -291
- package/src/docker/index.ts +0 -1
- package/src/index.ts +0 -77
- package/src/structure/manager.ts +0 -128
- package/src/ui/App.ts +0 -109
- package/src/ui/components/ApplicationSelector.ts +0 -272
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -152
- package/src/ui/components/UpdateNotification.ts +0 -101
- package/src/ui/index.ts +0 -1
- package/src/ui/screens/AdvancedSettings.ts +0 -181
- package/src/ui/screens/ApiKeyViewer.ts +0 -500
- package/src/ui/screens/AppConfigurator.ts +0 -731
- package/src/ui/screens/AppManager.ts +0 -276
- package/src/ui/screens/CloudflaredSetup.ts +0 -974
- package/src/ui/screens/ContainerControl.ts +0 -579
- package/src/ui/screens/FullAutoSetup.ts +0 -1719
- package/src/ui/screens/HomepageSetup.ts +0 -306
- package/src/ui/screens/JellyfinSetup.ts +0 -471
- package/src/ui/screens/JellyseerrSetup.ts +0 -612
- package/src/ui/screens/LogsViewer.ts +0 -468
- package/src/ui/screens/MainMenu.ts +0 -288
- package/src/ui/screens/MonitorDashboard.ts +0 -866
- package/src/ui/screens/ProwlarrSetup.ts +0 -623
- package/src/ui/screens/QBittorrentSetup.ts +0 -268
- package/src/ui/screens/QuickSetup.ts +0 -1161
- package/src/ui/screens/RecyclarrSetup.ts +0 -418
- package/src/ui/screens/SecretsEditor.ts +0 -214
- package/src/ui/screens/SettingsScreen.ts +0 -605
- package/src/ui/screens/TRaSHProfileSetup.ts +0 -395
- package/src/ui/screens/index.ts +0 -4
- package/src/util/arch.ts +0 -78
- package/src/utils/browser.ts +0 -26
- package/src/utils/categories.ts +0 -59
- package/src/utils/debug.ts +0 -91
- package/src/utils/env.ts +0 -137
- package/src/utils/logs.ts +0 -118
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -101
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -90
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -44
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -107
- package/src/utils/migrations.ts +0 -164
- package/src/utils/password.ts +0 -42
- package/src/utils/unraid.ts +0 -101
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom Format API Client
|
|
3
|
-
* Manages Custom Formats for Radarr/Sonarr with TRaSH Guides import capability
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface CFSpecification {
|
|
7
|
-
name: string
|
|
8
|
-
implementation: string
|
|
9
|
-
negate: boolean
|
|
10
|
-
required: boolean
|
|
11
|
-
fields: { name: string; value: unknown }[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface CustomFormat {
|
|
15
|
-
id?: number
|
|
16
|
-
name: string
|
|
17
|
-
includeCustomFormatWhenRenaming: boolean
|
|
18
|
-
specifications: CFSpecification[]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// TRaSH GitHub raw URL for Custom Formats
|
|
22
|
-
const TRASH_CF_BASE_URL = "https://raw.githubusercontent.com/TRaSH-Guides/Guides/master/docs/json"
|
|
23
|
-
|
|
24
|
-
export class CustomFormatClient {
|
|
25
|
-
private baseUrl: string
|
|
26
|
-
private apiKey: string
|
|
27
|
-
private apiVersion: string
|
|
28
|
-
|
|
29
|
-
constructor(host: string, port: number, apiKey: string, apiVersion = "v3") {
|
|
30
|
-
this.baseUrl = `http://${host}:${port}/api/${apiVersion}`
|
|
31
|
-
this.apiKey = apiKey
|
|
32
|
-
this.apiVersion = apiVersion
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
36
|
-
const url = `${this.baseUrl}${endpoint}`
|
|
37
|
-
const headers: Record<string, string> = {
|
|
38
|
-
"X-Api-Key": this.apiKey,
|
|
39
|
-
"Content-Type": "application/json",
|
|
40
|
-
...((options.headers as Record<string, string>) || {}),
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const response = await fetch(url, { ...options, headers })
|
|
44
|
-
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const text = await response.text()
|
|
50
|
-
if (!text) return {} as T
|
|
51
|
-
return JSON.parse(text) as T
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Custom Format CRUD
|
|
55
|
-
async getCustomFormats(): Promise<CustomFormat[]> {
|
|
56
|
-
return this.request<CustomFormat[]>("/customformat")
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async getCustomFormat(id: number): Promise<CustomFormat> {
|
|
60
|
-
return this.request<CustomFormat>(`/customformat/${id}`)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async createCustomFormat(cf: Omit<CustomFormat, "id">): Promise<CustomFormat> {
|
|
64
|
-
return this.request<CustomFormat>("/customformat", {
|
|
65
|
-
method: "POST",
|
|
66
|
-
body: JSON.stringify(cf),
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async updateCustomFormat(id: number, cf: CustomFormat): Promise<CustomFormat> {
|
|
71
|
-
return this.request<CustomFormat>(`/customformat/${id}`, {
|
|
72
|
-
method: "PUT",
|
|
73
|
-
body: JSON.stringify({ ...cf, id }),
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async deleteCustomFormat(id: number): Promise<void> {
|
|
78
|
-
await this.request(`/customformat/${id}`, { method: "DELETE" })
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Import from JSON (TRaSH format)
|
|
82
|
-
async importCustomFormat(cfJson: Omit<CustomFormat, "id">): Promise<CustomFormat> {
|
|
83
|
-
// Check if CF already exists by name
|
|
84
|
-
const existing = await this.getCustomFormats()
|
|
85
|
-
const duplicate = existing.find((cf) => cf.name === cfJson.name)
|
|
86
|
-
|
|
87
|
-
if (duplicate) {
|
|
88
|
-
// Update existing CF
|
|
89
|
-
return this.updateCustomFormat(duplicate.id!, { ...cfJson, id: duplicate.id })
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return this.createCustomFormat(cfJson)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Import multiple CFs
|
|
96
|
-
async importCustomFormats(cfs: Omit<CustomFormat, "id">[]): Promise<{ success: number; failed: number }> {
|
|
97
|
-
let success = 0
|
|
98
|
-
let failed = 0
|
|
99
|
-
|
|
100
|
-
for (const cf of cfs) {
|
|
101
|
-
try {
|
|
102
|
-
await this.importCustomFormat(cf)
|
|
103
|
-
success++
|
|
104
|
-
} catch {
|
|
105
|
-
failed++
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return { success, failed }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Fetch CF from TRaSH GitHub
|
|
113
|
-
static async fetchTRaSHCustomFormat(app: "radarr" | "sonarr", cfName: string): Promise<CustomFormat | null> {
|
|
114
|
-
try {
|
|
115
|
-
const url = `${TRASH_CF_BASE_URL}/${app}/cf/${cfName}.json`
|
|
116
|
-
const response = await fetch(url)
|
|
117
|
-
if (!response.ok) return null
|
|
118
|
-
return (await response.json()) as CustomFormat
|
|
119
|
-
} catch {
|
|
120
|
-
return null
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Fetch multiple CFs from TRaSH
|
|
125
|
-
static async fetchTRaSHCustomFormats(
|
|
126
|
-
app: "radarr" | "sonarr",
|
|
127
|
-
cfNames: string[]
|
|
128
|
-
): Promise<{ cfs: CustomFormat[]; failed: string[] }> {
|
|
129
|
-
const cfs: CustomFormat[] = []
|
|
130
|
-
const failed: string[] = []
|
|
131
|
-
|
|
132
|
-
for (const name of cfNames) {
|
|
133
|
-
const cf = await this.fetchTRaSHCustomFormat(app, name)
|
|
134
|
-
if (cf) {
|
|
135
|
-
cfs.push(cf)
|
|
136
|
-
} else {
|
|
137
|
-
failed.push(name)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return { cfs, failed }
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Common TRaSH Custom Format names
|
|
146
|
-
export const TRASH_CF_NAMES = {
|
|
147
|
-
radarr: {
|
|
148
|
-
unwanted: ["br-disk", "lq", "lq-release-title", "3d", "x265-hd", "extras"],
|
|
149
|
-
hdr: [
|
|
150
|
-
"dv-hdr10plus",
|
|
151
|
-
"dv-hdr10",
|
|
152
|
-
"dv",
|
|
153
|
-
"dv-hlg",
|
|
154
|
-
"dv-sdr",
|
|
155
|
-
"hdr10plus",
|
|
156
|
-
"hdr10",
|
|
157
|
-
"hdr",
|
|
158
|
-
"hdr-undefined",
|
|
159
|
-
"pq",
|
|
160
|
-
"hlg",
|
|
161
|
-
],
|
|
162
|
-
audio: [
|
|
163
|
-
"truehd-atmos",
|
|
164
|
-
"dts-x",
|
|
165
|
-
"truehd",
|
|
166
|
-
"dts-hd-ma",
|
|
167
|
-
"flac",
|
|
168
|
-
"pcm",
|
|
169
|
-
"dts-hd-hra",
|
|
170
|
-
"ddplus-atmos",
|
|
171
|
-
"ddplus",
|
|
172
|
-
"dts-es",
|
|
173
|
-
"dts",
|
|
174
|
-
"aac",
|
|
175
|
-
"dd",
|
|
176
|
-
],
|
|
177
|
-
streaming: ["amzn", "atvp", "dsnp", "hbo", "hmax", "hulu", "ma", "nf", "pcok", "pmtp"],
|
|
178
|
-
movieVersions: ["imax-enhanced", "imax", "hybrid", "criterion-collection", "special-edition", "theatrical-cut"],
|
|
179
|
-
misc: ["repack-proper", "repack2", "multi", "hq-remux", "hq-webdl", "hq"],
|
|
180
|
-
},
|
|
181
|
-
sonarr: {
|
|
182
|
-
unwanted: ["br-disk", "lq", "lq-release-title", "x265-hd", "extras"],
|
|
183
|
-
hdr: [
|
|
184
|
-
"dv-hdr10plus",
|
|
185
|
-
"dv-hdr10",
|
|
186
|
-
"dv",
|
|
187
|
-
"dv-hlg",
|
|
188
|
-
"dv-sdr",
|
|
189
|
-
"hdr10plus",
|
|
190
|
-
"hdr10",
|
|
191
|
-
"hdr",
|
|
192
|
-
"hdr-undefined",
|
|
193
|
-
"pq",
|
|
194
|
-
"hlg",
|
|
195
|
-
],
|
|
196
|
-
streaming: ["amzn", "atvp", "dsnp", "hbo", "hmax", "hulu", "nf", "pcok", "pmtp"],
|
|
197
|
-
hqGroups: ["web-tier-01", "web-tier-02", "web-tier-03"],
|
|
198
|
-
misc: ["repack-proper", "repack2", "multi"],
|
|
199
|
-
},
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Get all CF names for a category
|
|
203
|
-
export function getAllCFNames(app: "radarr" | "sonarr"): string[] {
|
|
204
|
-
const cfNames = TRASH_CF_NAMES[app]
|
|
205
|
-
return Object.values(cfNames).flat()
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Get CF names for specific categories
|
|
209
|
-
export function getCFNamesForCategories(app: "radarr" | "sonarr", categories: string[]): string[] {
|
|
210
|
-
const cfNames = TRASH_CF_NAMES[app] as Record<string, string[]>
|
|
211
|
-
return categories.flatMap((cat) => cfNames[cat] || [])
|
|
212
|
-
}
|
package/src/api/grafana-api.ts
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Grafana API Client
|
|
3
|
-
* Handles Grafana auto-setup including admin password change and Prometheus datasource
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { debugLog } from "../utils/debug"
|
|
7
|
-
import type { IAutoSetupClient, AutoSetupOptions, AutoSetupResult } from "./auto-setup-types"
|
|
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 implements IAutoSetupClient {
|
|
29
|
-
private host: string
|
|
30
|
-
private port: number
|
|
31
|
-
private username: string
|
|
32
|
-
private password: string
|
|
33
|
-
|
|
34
|
-
constructor(host: string, port: number = 3000, username: string = "admin", password: string = "admin") {
|
|
35
|
-
this.host = host
|
|
36
|
-
this.port = port
|
|
37
|
-
this.username = username
|
|
38
|
-
this.password = password
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get base URL for Grafana
|
|
43
|
-
*/
|
|
44
|
-
private get baseUrl(): string {
|
|
45
|
-
return `http://${this.host}:${this.port}`
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Get Basic Auth header
|
|
50
|
-
*/
|
|
51
|
-
private getAuthHeader(): string {
|
|
52
|
-
const credentials = Buffer.from(`${this.username}:${this.password}`).toString("base64")
|
|
53
|
-
return `Basic ${credentials}`
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Common headers for Grafana API requests
|
|
58
|
-
*/
|
|
59
|
-
private getHeaders(): Record<string, string> {
|
|
60
|
-
return {
|
|
61
|
-
"Content-Type": "application/json",
|
|
62
|
-
Accept: "application/json",
|
|
63
|
-
Authorization: this.getAuthHeader(),
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Update credentials (after password change)
|
|
69
|
-
*/
|
|
70
|
-
setCredentials(username: string, password: string): void {
|
|
71
|
-
this.username = username
|
|
72
|
-
this.password = password
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Check if Grafana is reachable
|
|
77
|
-
*/
|
|
78
|
-
async isHealthy(): Promise<boolean> {
|
|
79
|
-
try {
|
|
80
|
-
const response = await fetch(`${this.baseUrl}/api/health`, {
|
|
81
|
-
method: "GET",
|
|
82
|
-
})
|
|
83
|
-
debugLog("GrafanaApi", `Health check: ${response.status}`)
|
|
84
|
-
return response.ok
|
|
85
|
-
} catch (error) {
|
|
86
|
-
debugLog("GrafanaApi", `Health check failed: ${error}`)
|
|
87
|
-
return false
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Check if Grafana is already configured (has non-default password)
|
|
93
|
-
*/
|
|
94
|
-
async isInitialized(): Promise<boolean> {
|
|
95
|
-
try {
|
|
96
|
-
// Try to login with default credentials
|
|
97
|
-
const response = await fetch(`${this.baseUrl}/api/user`, {
|
|
98
|
-
method: "GET",
|
|
99
|
-
headers: {
|
|
100
|
-
Authorization: `Basic ${Buffer.from("admin:admin").toString("base64")}`,
|
|
101
|
-
},
|
|
102
|
-
})
|
|
103
|
-
// If login with admin:admin fails, it's already configured
|
|
104
|
-
return !response.ok
|
|
105
|
-
} catch {
|
|
106
|
-
return true
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Change admin password
|
|
112
|
-
*/
|
|
113
|
-
async changeAdminPassword(newPassword: string): Promise<boolean> {
|
|
114
|
-
debugLog("GrafanaApi", "Changing admin password...")
|
|
115
|
-
|
|
116
|
-
const response = await fetch(`${this.baseUrl}/api/user/password`, {
|
|
117
|
-
method: "PUT",
|
|
118
|
-
headers: this.getHeaders(),
|
|
119
|
-
body: JSON.stringify({
|
|
120
|
-
oldPassword: this.password,
|
|
121
|
-
newPassword: newPassword,
|
|
122
|
-
}),
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
if (response.ok) {
|
|
126
|
-
debugLog("GrafanaApi", "Admin password changed successfully")
|
|
127
|
-
this.password = newPassword
|
|
128
|
-
return true
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const text = await response.text()
|
|
132
|
-
debugLog("GrafanaApi", `Failed to change password: ${response.status} - ${text}`)
|
|
133
|
-
return false
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Get list of datasources
|
|
138
|
-
*/
|
|
139
|
-
async getDataSources(): Promise<GrafanaDataSource[]> {
|
|
140
|
-
const response = await fetch(`${this.baseUrl}/api/datasources`, {
|
|
141
|
-
method: "GET",
|
|
142
|
-
headers: this.getHeaders(),
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
if (!response.ok) {
|
|
146
|
-
throw new Error(`Failed to get datasources: ${response.status}`)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return response.json()
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Check if a datasource with the given name exists
|
|
154
|
-
*/
|
|
155
|
-
async dataSourceExists(name: string): Promise<boolean> {
|
|
156
|
-
const dataSources = await this.getDataSources()
|
|
157
|
-
return dataSources.some((ds) => ds.name === name)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Create a Prometheus datasource
|
|
162
|
-
*/
|
|
163
|
-
async createPrometheusDataSource(
|
|
164
|
-
name: string = "Prometheus",
|
|
165
|
-
url: string = "http://prometheus:9090",
|
|
166
|
-
isDefault: boolean = true
|
|
167
|
-
): Promise<boolean> {
|
|
168
|
-
debugLog("GrafanaApi", `Creating Prometheus datasource: ${name} -> ${url}`)
|
|
169
|
-
|
|
170
|
-
const payload: GrafanaDataSource = {
|
|
171
|
-
name,
|
|
172
|
-
type: "prometheus",
|
|
173
|
-
access: "proxy",
|
|
174
|
-
url,
|
|
175
|
-
isDefault,
|
|
176
|
-
jsonData: {
|
|
177
|
-
httpMethod: "POST",
|
|
178
|
-
timeInterval: "15s",
|
|
179
|
-
},
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const response = await fetch(`${this.baseUrl}/api/datasources`, {
|
|
183
|
-
method: "POST",
|
|
184
|
-
headers: this.getHeaders(),
|
|
185
|
-
body: JSON.stringify(payload),
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
if (response.ok) {
|
|
189
|
-
debugLog("GrafanaApi", `Prometheus datasource "${name}" created successfully`)
|
|
190
|
-
return true
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Check if already exists (409 Conflict)
|
|
194
|
-
if (response.status === 409) {
|
|
195
|
-
debugLog("GrafanaApi", `Datasource "${name}" already exists`)
|
|
196
|
-
return true
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const text = await response.text()
|
|
200
|
-
debugLog("GrafanaApi", `Failed to create datasource: ${response.status} - ${text}`)
|
|
201
|
-
return false
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Generate an API key for external integrations
|
|
206
|
-
*/
|
|
207
|
-
async createApiKey(name: string = "easiarr", role: string = "Admin"): Promise<string | null> {
|
|
208
|
-
debugLog("GrafanaApi", `Creating API key: ${name}`)
|
|
209
|
-
|
|
210
|
-
const response = await fetch(`${this.baseUrl}/api/auth/keys`, {
|
|
211
|
-
method: "POST",
|
|
212
|
-
headers: this.getHeaders(),
|
|
213
|
-
body: JSON.stringify({
|
|
214
|
-
name,
|
|
215
|
-
role,
|
|
216
|
-
secondsToLive: 0, // Never expires
|
|
217
|
-
}),
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
if (response.ok) {
|
|
221
|
-
const data = await response.json()
|
|
222
|
-
debugLog("GrafanaApi", "API key created successfully")
|
|
223
|
-
return data.key
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// May already exist
|
|
227
|
-
if (response.status === 409) {
|
|
228
|
-
debugLog("GrafanaApi", "API key already exists")
|
|
229
|
-
return null
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const text = await response.text()
|
|
233
|
-
debugLog("GrafanaApi", `Failed to create API key: ${response.status} - ${text}`)
|
|
234
|
-
return null
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Get Grafana server info
|
|
239
|
-
*/
|
|
240
|
-
async getServerInfo(): Promise<GrafanaHealthResponse | null> {
|
|
241
|
-
try {
|
|
242
|
-
const response = await fetch(`${this.baseUrl}/api/health`, {
|
|
243
|
-
method: "GET",
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
if (response.ok) {
|
|
247
|
-
return response.json()
|
|
248
|
-
}
|
|
249
|
-
} catch {
|
|
250
|
-
// Ignore
|
|
251
|
-
}
|
|
252
|
-
return null
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Run the auto-setup process for Grafana
|
|
257
|
-
*/
|
|
258
|
-
async setup(options: AutoSetupOptions): Promise<AutoSetupResult> {
|
|
259
|
-
const { username, password } = options
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
// Check if reachable
|
|
263
|
-
const healthy = await this.isHealthy()
|
|
264
|
-
if (!healthy) {
|
|
265
|
-
return { success: false, message: "Grafana not reachable" }
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Check if already configured
|
|
269
|
-
const initialized = await this.isInitialized()
|
|
270
|
-
|
|
271
|
-
if (!initialized) {
|
|
272
|
-
// First login - change default password
|
|
273
|
-
this.setCredentials("admin", "admin")
|
|
274
|
-
|
|
275
|
-
const changed = await this.changeAdminPassword(password)
|
|
276
|
-
if (!changed) {
|
|
277
|
-
return { success: false, message: "Failed to change admin password" }
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
// Try to login with provided credentials
|
|
281
|
-
this.setCredentials(username, password)
|
|
282
|
-
|
|
283
|
-
// Verify login by fetching user
|
|
284
|
-
const response = await fetch(`${this.baseUrl}/api/user`, {
|
|
285
|
-
method: "GET",
|
|
286
|
-
headers: this.getHeaders(),
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
if (!response.ok) {
|
|
290
|
-
return { success: false, message: "Login failed - check credentials" }
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Now configure Prometheus datasource if prometheus is enabled
|
|
295
|
-
// Use container name for internal communication
|
|
296
|
-
const prometheusExists = await this.dataSourceExists("Prometheus")
|
|
297
|
-
if (!prometheusExists) {
|
|
298
|
-
await this.createPrometheusDataSource("Prometheus", "http://prometheus:9090", true)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Generate API key for Homepage widget etc.
|
|
302
|
-
const apiKey = await this.createApiKey("easiarr-api-key")
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
success: true,
|
|
306
|
-
message: initialized ? "Configured" : "Password changed, Prometheus added",
|
|
307
|
-
data: apiKey ? { apiKey } : undefined,
|
|
308
|
-
envUpdates: apiKey ? { API_KEY_GRAFANA: apiKey } : undefined,
|
|
309
|
-
}
|
|
310
|
-
} catch (error) {
|
|
311
|
-
return { success: false, message: `${error}` }
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|