berget 0.0.3 → 0.1.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/.prettierrc +6 -0
- package/TODO.md +17 -0
- package/dist/index.js +447 -8
- package/dist/src/client.js +148 -0
- package/dist/src/services/api-key-service.js +114 -0
- package/dist/src/services/auth-service.js +168 -0
- package/dist/src/services/cluster-service.js +55 -0
- package/dist/src/services/collaborator-service.js +37 -0
- package/dist/src/services/flux-service.js +37 -0
- package/dist/src/services/helm-service.js +37 -0
- package/dist/src/services/kubectl-service.js +43 -0
- package/dist/src/utils/error-handler.js +45 -0
- package/index.ts +545 -12
- package/package.json +13 -7
- package/src/client.ts +133 -0
- package/src/services/api-key-service.ts +114 -0
- package/src/services/auth-service.ts +206 -0
- package/src/services/cluster-service.ts +50 -0
- package/src/services/collaborator-service.ts +34 -0
- package/src/services/flux-service.ts +37 -0
- package/src/services/helm-service.ts +37 -0
- package/src/services/kubectl-service.ts +33 -0
- package/src/types/api.d.ts +2037 -0
- package/src/utils/error-handler.ts +40 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import createClient from 'openapi-fetch'
|
|
2
|
+
import type { paths } from './types/api'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
import * as path from 'path'
|
|
5
|
+
import * as os from 'os'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
|
|
8
|
+
// Configuration directory
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.berget')
|
|
10
|
+
const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json')
|
|
11
|
+
|
|
12
|
+
// API Base URL
|
|
13
|
+
// Use --local flag to test against local API
|
|
14
|
+
const isLocalMode = process.argv.includes('--local')
|
|
15
|
+
const API_BASE_URL =
|
|
16
|
+
process.env.BERGET_API_URL ||
|
|
17
|
+
(isLocalMode ? 'http://localhost:3000' : 'https://api.berget.ai')
|
|
18
|
+
|
|
19
|
+
if (isLocalMode && !process.env.BERGET_API_URL) {
|
|
20
|
+
console.log(chalk.yellow('Using local API endpoint: http://localhost:3000'))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Create a typed client for the Berget API
|
|
24
|
+
export const apiClient = createClient<paths>({
|
|
25
|
+
baseUrl: API_BASE_URL,
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
Accept: 'application/json',
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Authentication functions
|
|
33
|
+
export const getAuthToken = (): string | null => {
|
|
34
|
+
try {
|
|
35
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
36
|
+
const tokenData = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'))
|
|
37
|
+
return tokenData.accessToken
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error reading auth token:', error)
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if token is expired (JWT tokens have an exp claim)
|
|
46
|
+
export const isTokenExpired = (token: string): boolean => {
|
|
47
|
+
try {
|
|
48
|
+
const base64Url = token.split('.')[1]
|
|
49
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
|
50
|
+
const jsonPayload = decodeURIComponent(
|
|
51
|
+
atob(base64)
|
|
52
|
+
.split('')
|
|
53
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
54
|
+
.join('')
|
|
55
|
+
)
|
|
56
|
+
const payload = JSON.parse(jsonPayload)
|
|
57
|
+
|
|
58
|
+
// Check if token has expired
|
|
59
|
+
if (payload.exp) {
|
|
60
|
+
return payload.exp * 1000 < Date.now()
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// If we can't decode the token, assume it's expired
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// If there's no exp claim, assume it's valid
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const saveAuthToken = (token: string): void => {
|
|
72
|
+
try {
|
|
73
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
74
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
75
|
+
}
|
|
76
|
+
fs.writeFileSync(TOKEN_FILE, JSON.stringify({ accessToken: token }), 'utf8')
|
|
77
|
+
// Set file permissions to be readable only by the owner
|
|
78
|
+
fs.chmodSync(TOKEN_FILE, 0o600)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Error saving auth token:', error)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const clearAuthToken = (): void => {
|
|
85
|
+
try {
|
|
86
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
87
|
+
fs.unlinkSync(TOKEN_FILE)
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Error clearing auth token:', error)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create an authenticated client
|
|
95
|
+
export const createAuthenticatedClient = () => {
|
|
96
|
+
const token = getAuthToken()
|
|
97
|
+
if (!token) {
|
|
98
|
+
console.warn(
|
|
99
|
+
chalk.yellow(
|
|
100
|
+
'No authentication token found. Please run `berget login` first.'
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
} else if (isTokenExpired(token)) {
|
|
104
|
+
console.warn(
|
|
105
|
+
chalk.yellow(
|
|
106
|
+
'Your authentication token has expired. Please run `berget login` to get a new token.'
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
// Optionally clear the expired token
|
|
110
|
+
clearAuthToken()
|
|
111
|
+
return createClient<paths>({
|
|
112
|
+
baseUrl: API_BASE_URL,
|
|
113
|
+
headers: {
|
|
114
|
+
'Content-Type': 'application/json',
|
|
115
|
+
Accept: 'application/json',
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return createClient<paths>({
|
|
121
|
+
baseUrl: API_BASE_URL,
|
|
122
|
+
headers: token
|
|
123
|
+
? {
|
|
124
|
+
Authorization: `Bearer ${token}`,
|
|
125
|
+
'Content-Type': 'application/json',
|
|
126
|
+
Accept: 'application/json',
|
|
127
|
+
}
|
|
128
|
+
: {
|
|
129
|
+
'Content-Type': 'application/json',
|
|
130
|
+
Accept: 'application/json',
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
import { handleError } from '../utils/error-handler'
|
|
3
|
+
|
|
4
|
+
export interface ApiKey {
|
|
5
|
+
id: number
|
|
6
|
+
name: string
|
|
7
|
+
description: string | null
|
|
8
|
+
created: string
|
|
9
|
+
lastUsed: string | null
|
|
10
|
+
prefix: string
|
|
11
|
+
active: boolean
|
|
12
|
+
modified: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CreateApiKeyOptions {
|
|
16
|
+
name: string
|
|
17
|
+
description?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ApiKeyResponse {
|
|
21
|
+
id: number
|
|
22
|
+
name: string
|
|
23
|
+
description: string | null
|
|
24
|
+
key: string
|
|
25
|
+
created: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ApiKeyService {
|
|
29
|
+
private static instance: ApiKeyService
|
|
30
|
+
private client = createAuthenticatedClient()
|
|
31
|
+
|
|
32
|
+
private constructor() {}
|
|
33
|
+
|
|
34
|
+
public static getInstance(): ApiKeyService {
|
|
35
|
+
if (!ApiKeyService.instance) {
|
|
36
|
+
ApiKeyService.instance = new ApiKeyService()
|
|
37
|
+
}
|
|
38
|
+
return ApiKeyService.instance
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async listApiKeys(): Promise<ApiKey[]> {
|
|
42
|
+
try {
|
|
43
|
+
const { data, error } = await this.client.GET('/v1/api-keys')
|
|
44
|
+
if (error) {
|
|
45
|
+
// Check if this is an authentication error
|
|
46
|
+
const errorObj = typeof error === 'string' ? JSON.parse(error) : error;
|
|
47
|
+
if (errorObj.status === 401) {
|
|
48
|
+
throw new Error(JSON.stringify({
|
|
49
|
+
error: "Authentication failed. Your session may have expired.",
|
|
50
|
+
code: "AUTH_FAILED",
|
|
51
|
+
details: "Please run 'berget login' to authenticate again."
|
|
52
|
+
}))
|
|
53
|
+
}
|
|
54
|
+
throw new Error(JSON.stringify(error))
|
|
55
|
+
}
|
|
56
|
+
return data || []
|
|
57
|
+
} catch (error) {
|
|
58
|
+
handleError('Failed to list API keys', error)
|
|
59
|
+
throw error
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public async createApiKey(options: CreateApiKeyOptions): Promise<ApiKeyResponse> {
|
|
64
|
+
try {
|
|
65
|
+
const { data, error } = await this.client.POST('/v1/api-keys', {
|
|
66
|
+
body: options
|
|
67
|
+
})
|
|
68
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
69
|
+
return data!
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Failed to create API key:', error)
|
|
72
|
+
throw error
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public async deleteApiKey(id: string): Promise<boolean> {
|
|
77
|
+
try {
|
|
78
|
+
const { error } = await this.client.DELETE('/v1/api-keys/{id}', {
|
|
79
|
+
params: { path: { id } }
|
|
80
|
+
})
|
|
81
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
82
|
+
return true
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Failed to delete API key:', error)
|
|
85
|
+
throw error
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public async rotateApiKey(id: string): Promise<ApiKeyResponse> {
|
|
90
|
+
try {
|
|
91
|
+
const { data, error } = await this.client.PUT('/v1/api-keys/{id}/rotate', {
|
|
92
|
+
params: { path: { id } }
|
|
93
|
+
})
|
|
94
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
95
|
+
return data!
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Failed to rotate API key:', error)
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public async getApiKeyUsage(id: string): Promise<any> {
|
|
103
|
+
try {
|
|
104
|
+
const { data, error } = await this.client.GET('/v1/api-keys/{id}/usage', {
|
|
105
|
+
params: { path: { id } }
|
|
106
|
+
})
|
|
107
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
108
|
+
return data
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Failed to get API key usage:', error)
|
|
111
|
+
throw error
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAuthenticatedClient,
|
|
3
|
+
saveAuthToken,
|
|
4
|
+
clearAuthToken,
|
|
5
|
+
apiClient,
|
|
6
|
+
} from '../client'
|
|
7
|
+
import open from 'open'
|
|
8
|
+
import chalk from 'chalk'
|
|
9
|
+
import { handleError } from '../utils/error-handler'
|
|
10
|
+
|
|
11
|
+
export class AuthService {
|
|
12
|
+
private static instance: AuthService
|
|
13
|
+
private client = createAuthenticatedClient()
|
|
14
|
+
|
|
15
|
+
private constructor() {}
|
|
16
|
+
|
|
17
|
+
public static getInstance(): AuthService {
|
|
18
|
+
if (!AuthService.instance) {
|
|
19
|
+
AuthService.instance = new AuthService()
|
|
20
|
+
}
|
|
21
|
+
return AuthService.instance
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async login(): Promise<boolean> {
|
|
25
|
+
try {
|
|
26
|
+
// Clear any existing token to ensure a fresh login
|
|
27
|
+
clearAuthToken()
|
|
28
|
+
|
|
29
|
+
console.log(chalk.blue('Initiating login process...'))
|
|
30
|
+
|
|
31
|
+
// Step 1: Initiate device authorization
|
|
32
|
+
const { data: deviceData, error: deviceError } = await apiClient.POST(
|
|
33
|
+
'/v1/auth/device',
|
|
34
|
+
{}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if (deviceError || !deviceData) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
deviceError
|
|
40
|
+
? JSON.stringify(deviceError)
|
|
41
|
+
: 'Failed to get device authorization data'
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Display information to user
|
|
46
|
+
console.log(chalk.cyan('\nTo complete login:'))
|
|
47
|
+
console.log(
|
|
48
|
+
chalk.cyan(
|
|
49
|
+
`1. Open this URL: ${chalk.bold(
|
|
50
|
+
deviceData.verification_url || 'https://auth.berget.ai/device'
|
|
51
|
+
)}`
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
console.log(
|
|
55
|
+
chalk.cyan(
|
|
56
|
+
`2. Enter this code: ${chalk.bold(deviceData.user_code || '')}\n`
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Try to open browser automatically
|
|
61
|
+
try {
|
|
62
|
+
if (deviceData.verification_url) {
|
|
63
|
+
await open(deviceData.verification_url)
|
|
64
|
+
console.log(
|
|
65
|
+
chalk.dim(
|
|
66
|
+
"Browser opened automatically. If it didn't open, please use the URL above."
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.log(
|
|
72
|
+
chalk.yellow(
|
|
73
|
+
'Could not open browser automatically. Please open the URL manually.'
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(chalk.dim('\nWaiting for authentication to complete...'))
|
|
79
|
+
|
|
80
|
+
// Step 2: Poll for completion
|
|
81
|
+
const startTime = Date.now()
|
|
82
|
+
const expiresIn =
|
|
83
|
+
deviceData.expires_in !== undefined ? deviceData.expires_in : 900
|
|
84
|
+
const expiresAt = startTime + expiresIn * 1000
|
|
85
|
+
let pollInterval = (deviceData.interval || 5) * 1000
|
|
86
|
+
|
|
87
|
+
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
88
|
+
let spinnerIdx = 0
|
|
89
|
+
|
|
90
|
+
while (Date.now() < expiresAt) {
|
|
91
|
+
// Wait for the polling interval
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
|
93
|
+
|
|
94
|
+
// Update spinner
|
|
95
|
+
process.stdout.write(
|
|
96
|
+
`\r${chalk.blue(spinner[spinnerIdx])} Waiting for authentication...`
|
|
97
|
+
)
|
|
98
|
+
spinnerIdx = (spinnerIdx + 1) % spinner.length
|
|
99
|
+
|
|
100
|
+
// Check if authentication is complete
|
|
101
|
+
const deviceCode = deviceData.device_code || ''
|
|
102
|
+
const { data: tokenData, error: tokenError } = await apiClient.POST(
|
|
103
|
+
'/v1/auth/device/token',
|
|
104
|
+
{
|
|
105
|
+
body: {
|
|
106
|
+
device_code: deviceCode,
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if (tokenError) {
|
|
112
|
+
// Parse the error to get status and other details
|
|
113
|
+
const errorObj =
|
|
114
|
+
typeof tokenError === 'string' ? JSON.parse(tokenError) : tokenError
|
|
115
|
+
|
|
116
|
+
const status = errorObj.status || 0
|
|
117
|
+
const errorCode = errorObj.code || ''
|
|
118
|
+
|
|
119
|
+
if (status === 401 || errorCode === 'AUTHORIZATION_PENDING') {
|
|
120
|
+
// Still waiting for user to complete authorization
|
|
121
|
+
continue
|
|
122
|
+
} else if (status === 429) {
|
|
123
|
+
// Slow down
|
|
124
|
+
pollInterval *= 2
|
|
125
|
+
continue
|
|
126
|
+
} else if (status === 400) {
|
|
127
|
+
// Error or expired
|
|
128
|
+
if (errorCode === 'EXPIRED_TOKEN') {
|
|
129
|
+
console.log(
|
|
130
|
+
chalk.red('\n\nAuthentication timed out. Please try again.')
|
|
131
|
+
)
|
|
132
|
+
} else if (errorCode !== 'AUTHORIZATION_PENDING') {
|
|
133
|
+
// Only show error if it's not the expected "still waiting" error
|
|
134
|
+
const errorMessage = errorObj.message || JSON.stringify(errorObj)
|
|
135
|
+
console.log(chalk.red(`\n\nError: ${errorMessage}`))
|
|
136
|
+
return false
|
|
137
|
+
} else {
|
|
138
|
+
// If it's AUTHORIZATION_PENDING, continue polling
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
return false
|
|
142
|
+
} else {
|
|
143
|
+
// For any other error, log it but continue polling
|
|
144
|
+
// This makes the flow more resilient to temporary issues
|
|
145
|
+
if (process.env.DEBUG) {
|
|
146
|
+
console.log(
|
|
147
|
+
chalk.yellow(`\n\nReceived error: ${JSON.stringify(errorObj)}`)
|
|
148
|
+
)
|
|
149
|
+
console.log(
|
|
150
|
+
chalk.yellow('Continuing to wait for authentication...')
|
|
151
|
+
)
|
|
152
|
+
process.stdout.write(
|
|
153
|
+
`\r${chalk.blue(
|
|
154
|
+
spinner[spinnerIdx]
|
|
155
|
+
)} Waiting for authentication...`
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
} else if (tokenData && tokenData.token) {
|
|
161
|
+
// Success!
|
|
162
|
+
saveAuthToken(tokenData.token)
|
|
163
|
+
|
|
164
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r') // Clear the spinner line
|
|
165
|
+
console.log(chalk.green('✓ Successfully logged in to Berget'))
|
|
166
|
+
|
|
167
|
+
if (tokenData.user) {
|
|
168
|
+
const user = tokenData.user as any
|
|
169
|
+
console.log(
|
|
170
|
+
chalk.green(`Logged in as ${user.name || user.email || 'User'}`)
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.red('\n\nAuthentication timed out. Please try again.'))
|
|
179
|
+
return false
|
|
180
|
+
} catch (error) {
|
|
181
|
+
handleError('Login failed', error)
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public async isAuthenticated(): Promise<boolean> {
|
|
187
|
+
try {
|
|
188
|
+
// Call an API endpoint that requires authentication
|
|
189
|
+
const { data, error } = await this.client.GET('/v1/users/me')
|
|
190
|
+
return !!data && !error
|
|
191
|
+
} catch {
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public async getUserProfile() {
|
|
197
|
+
try {
|
|
198
|
+
const { data, error } = await this.client.GET('/v1/users/me')
|
|
199
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
200
|
+
return data
|
|
201
|
+
} catch (error) {
|
|
202
|
+
handleError('Failed to get user profile', error)
|
|
203
|
+
throw error
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
|
|
3
|
+
export interface Cluster {
|
|
4
|
+
id: string
|
|
5
|
+
name: string
|
|
6
|
+
status: string
|
|
7
|
+
nodes: number
|
|
8
|
+
created: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ClusterService {
|
|
12
|
+
private static instance: ClusterService
|
|
13
|
+
private client = createAuthenticatedClient()
|
|
14
|
+
|
|
15
|
+
private constructor() {}
|
|
16
|
+
|
|
17
|
+
public static getInstance(): ClusterService {
|
|
18
|
+
if (!ClusterService.instance) {
|
|
19
|
+
ClusterService.instance = new ClusterService()
|
|
20
|
+
}
|
|
21
|
+
return ClusterService.instance
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async getClusterUsage(clusterId: string): Promise<any> {
|
|
25
|
+
try {
|
|
26
|
+
const { data, error } = await this.client.GET(
|
|
27
|
+
'/v1/clusters/{clusterId}/usage',
|
|
28
|
+
{
|
|
29
|
+
params: { path: { clusterId } },
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
33
|
+
return data
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Failed to get cluster usage:', error)
|
|
36
|
+
throw error
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async listClusters(): Promise<Cluster[]> {
|
|
41
|
+
try {
|
|
42
|
+
const { data, error } = await this.client.GET('/v1/clusters')
|
|
43
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
44
|
+
return data?.data || []
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Failed to list clusters:', error)
|
|
47
|
+
throw error
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
|
|
3
|
+
export interface Collaborator {
|
|
4
|
+
username: string
|
|
5
|
+
role: string
|
|
6
|
+
status: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class CollaboratorService {
|
|
10
|
+
private static instance: CollaboratorService
|
|
11
|
+
private client = createAuthenticatedClient()
|
|
12
|
+
|
|
13
|
+
private constructor() {}
|
|
14
|
+
|
|
15
|
+
public static getInstance(): CollaboratorService {
|
|
16
|
+
if (!CollaboratorService.instance) {
|
|
17
|
+
CollaboratorService.instance = new CollaboratorService()
|
|
18
|
+
}
|
|
19
|
+
return CollaboratorService.instance
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// This endpoint is not available in the API
|
|
23
|
+
public async addCollaborator(
|
|
24
|
+
clusterId: string,
|
|
25
|
+
githubUsername: string
|
|
26
|
+
): Promise<Collaborator[]> {
|
|
27
|
+
throw new Error('This functionality is not available in the API')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// This endpoint is not available in the API
|
|
31
|
+
public async listCollaborators(clusterId: string): Promise<Collaborator[]> {
|
|
32
|
+
throw new Error('This functionality is not available in the API')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
|
|
3
|
+
export interface FluxInstallOptions {
|
|
4
|
+
cluster: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface FluxBootstrapOptions {
|
|
8
|
+
provider: string
|
|
9
|
+
owner?: string
|
|
10
|
+
repository?: string
|
|
11
|
+
path?: string
|
|
12
|
+
personal?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class FluxService {
|
|
16
|
+
private static instance: FluxService
|
|
17
|
+
private client = createAuthenticatedClient()
|
|
18
|
+
|
|
19
|
+
private constructor() {}
|
|
20
|
+
|
|
21
|
+
public static getInstance(): FluxService {
|
|
22
|
+
if (!FluxService.instance) {
|
|
23
|
+
FluxService.instance = new FluxService()
|
|
24
|
+
}
|
|
25
|
+
return FluxService.instance
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// This endpoint is not available in the API
|
|
29
|
+
public async installFlux(options: FluxInstallOptions): Promise<boolean> {
|
|
30
|
+
throw new Error('This functionality is not available in the API')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// This endpoint is not available in the API
|
|
34
|
+
public async bootstrapFlux(options: FluxBootstrapOptions): Promise<boolean> {
|
|
35
|
+
throw new Error('This functionality is not available in the API')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
|
|
3
|
+
export interface HelmRepoAddOptions {
|
|
4
|
+
name: string
|
|
5
|
+
url: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface HelmInstallOptions {
|
|
9
|
+
name: string
|
|
10
|
+
chart: string
|
|
11
|
+
namespace?: string
|
|
12
|
+
values?: Record<string, string>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class HelmService {
|
|
16
|
+
private static instance: HelmService
|
|
17
|
+
private client = createAuthenticatedClient()
|
|
18
|
+
|
|
19
|
+
private constructor() {}
|
|
20
|
+
|
|
21
|
+
public static getInstance(): HelmService {
|
|
22
|
+
if (!HelmService.instance) {
|
|
23
|
+
HelmService.instance = new HelmService()
|
|
24
|
+
}
|
|
25
|
+
return HelmService.instance
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// This endpoint is not available in the API
|
|
29
|
+
public async addRepo(options: HelmRepoAddOptions): Promise<boolean> {
|
|
30
|
+
throw new Error('This functionality is not available in the API')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// This endpoint is not available in the API
|
|
34
|
+
public async installChart(options: HelmInstallOptions): Promise<any> {
|
|
35
|
+
throw new Error('This functionality is not available in the API')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
|
|
3
|
+
export class KubectlService {
|
|
4
|
+
private static instance: KubectlService
|
|
5
|
+
private client = createAuthenticatedClient()
|
|
6
|
+
|
|
7
|
+
private constructor() {}
|
|
8
|
+
|
|
9
|
+
public static getInstance(): KubectlService {
|
|
10
|
+
if (!KubectlService.instance) {
|
|
11
|
+
KubectlService.instance = new KubectlService()
|
|
12
|
+
}
|
|
13
|
+
return KubectlService.instance
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// This endpoint is not available in the API
|
|
17
|
+
public async createNamespace(name: string): Promise<boolean> {
|
|
18
|
+
throw new Error('This functionality is not available in the API')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// This endpoint is not available in the API
|
|
22
|
+
public async applyConfiguration(filename: string): Promise<boolean> {
|
|
23
|
+
throw new Error('This functionality is not available in the API')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// This endpoint is not available in the API
|
|
27
|
+
public async getResources(
|
|
28
|
+
resource: string,
|
|
29
|
+
namespace?: string
|
|
30
|
+
): Promise<any[]> {
|
|
31
|
+
throw new Error('This functionality is not available in the API')
|
|
32
|
+
}
|
|
33
|
+
}
|