mirror of
https://github.com/johndoe6345789/docker-swarm-termina.git
synced 2026-04-25 06:05:00 +00:00
The previous change to use next/font/google caused build failures in CI because Next.js tries to download fonts from Google Fonts during build time, which fails with TLS/network errors in restricted environments. Changes: - Removed next/font/google dependency from app/layout.tsx - Reverted to simpler approach without external font dependencies - Added missing properties to CommandResponse interface: - workdir: string (used by useSimpleTerminal) - exit_code: number (used to determine output vs error type) - Fixed TypeScript error in useSimpleTerminal.ts by ensuring content is always a string with || '' fallback Verified: - npm run build: ✓ Builds successfully - npm run lint: ✓ 0 errors, 0 warnings - npm test: ✓ 282/282 unit tests passing This fixes the CI build failures in: - Build and Push to GHCR workflow - Run Tests / frontend-tests workflow https://claude.ai/code/session_7d4f1b7d-7a0d-44db-b437-c76b6b61dfb2
281 lines
6.8 KiB
TypeScript
281 lines
6.8 KiB
TypeScript
import { triggerAuthError } from './store/authErrorHandler';
|
|
|
|
// Type definition for window.__ENV__
|
|
declare global {
|
|
interface Window {
|
|
__ENV__?: {
|
|
NEXT_PUBLIC_API_URL?: string;
|
|
};
|
|
}
|
|
}
|
|
|
|
export const API_BASE_URL =
|
|
typeof window !== 'undefined' && window.__ENV__?.NEXT_PUBLIC_API_URL
|
|
? window.__ENV__.NEXT_PUBLIC_API_URL
|
|
: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000';
|
|
|
|
export interface Container {
|
|
id: string;
|
|
name: string;
|
|
image: string;
|
|
status: string;
|
|
uptime: string;
|
|
}
|
|
|
|
export interface AuthResponse {
|
|
success: boolean;
|
|
token?: string;
|
|
username?: string;
|
|
message?: string;
|
|
}
|
|
|
|
export interface ContainersResponse {
|
|
containers: Container[];
|
|
}
|
|
|
|
export interface CommandResponse {
|
|
success: boolean;
|
|
output?: string;
|
|
error?: string;
|
|
workdir?: string;
|
|
exit_code?: number;
|
|
}
|
|
|
|
export interface ContainerActionResponse {
|
|
success: boolean;
|
|
message?: string;
|
|
error?: string;
|
|
}
|
|
|
|
class ApiClient {
|
|
private token: string | null = null;
|
|
|
|
setToken(token: string | null) {
|
|
this.token = token;
|
|
if (token) {
|
|
localStorage.setItem('auth_token', token);
|
|
} else {
|
|
localStorage.removeItem('auth_token');
|
|
localStorage.removeItem('auth_username');
|
|
}
|
|
}
|
|
|
|
getToken(): string | null {
|
|
if (!this.token && typeof window !== 'undefined') {
|
|
this.token = localStorage.getItem('auth_token');
|
|
}
|
|
return this.token;
|
|
}
|
|
|
|
getUsername(): string | null {
|
|
if (typeof window !== 'undefined') {
|
|
return localStorage.getItem('auth_username');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
setUsername(username: string | null) {
|
|
if (typeof window !== 'undefined') {
|
|
if (username) {
|
|
localStorage.setItem('auth_username', username);
|
|
} else {
|
|
localStorage.removeItem('auth_username');
|
|
}
|
|
}
|
|
}
|
|
|
|
async login(username: string, password: string): Promise<AuthResponse> {
|
|
const response = await fetch(`${API_BASE_URL}/api/auth/login`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.success && data.token) {
|
|
this.setToken(data.token);
|
|
this.setUsername(data.username || username);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
async logout(): Promise<void> {
|
|
const token = this.getToken();
|
|
if (token) {
|
|
await fetch(`${API_BASE_URL}/api/auth/logout`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
}
|
|
this.setToken(null);
|
|
}
|
|
|
|
async getContainers(): Promise<Container[]> {
|
|
const token = this.getToken();
|
|
if (!token) {
|
|
triggerAuthError();
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/containers`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
this.setToken(null);
|
|
triggerAuthError();
|
|
throw new Error('Session expired');
|
|
}
|
|
throw new Error('Failed to fetch containers');
|
|
}
|
|
|
|
const data: ContainersResponse = await response.json();
|
|
return data.containers;
|
|
}
|
|
|
|
async executeCommand(containerId: string, command: string): Promise<CommandResponse> {
|
|
const token = this.getToken();
|
|
if (!token) {
|
|
triggerAuthError();
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/containers/${containerId}/exec`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ command }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
this.setToken(null);
|
|
triggerAuthError();
|
|
throw new Error('Session expired');
|
|
}
|
|
throw new Error('Failed to execute command');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async startContainer(containerId: string): Promise<ContainerActionResponse> {
|
|
const token = this.getToken();
|
|
if (!token) {
|
|
triggerAuthError();
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/containers/${containerId}/start`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
this.setToken(null);
|
|
triggerAuthError();
|
|
throw new Error('Session expired');
|
|
}
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Failed to start container');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async stopContainer(containerId: string): Promise<ContainerActionResponse> {
|
|
const token = this.getToken();
|
|
if (!token) {
|
|
triggerAuthError();
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/containers/${containerId}/stop`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
this.setToken(null);
|
|
triggerAuthError();
|
|
throw new Error('Session expired');
|
|
}
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Failed to stop container');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async restartContainer(containerId: string): Promise<ContainerActionResponse> {
|
|
const token = this.getToken();
|
|
if (!token) {
|
|
triggerAuthError();
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/containers/${containerId}/restart`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
this.setToken(null);
|
|
triggerAuthError();
|
|
throw new Error('Session expired');
|
|
}
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Failed to restart container');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async removeContainer(containerId: string): Promise<ContainerActionResponse> {
|
|
const token = this.getToken();
|
|
if (!token) {
|
|
triggerAuthError();
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/containers/${containerId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
this.setToken(null);
|
|
triggerAuthError();
|
|
throw new Error('Session expired');
|
|
}
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Failed to remove container');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
}
|
|
|
|
export const apiClient = new ApiClient();
|