feat: integrate toast notifications in settings and layout components
- Added ToastService for managing toast notifications. - Replaced alert in settings component with toast notifications for success and error messages. - Included ToastComponent in layout for displaying notifications. - Created loading spinner component for better user experience. - Implemented domain detail component with detailed views for certificates, requirements, and services. - Added functionality to manage and display SSL certificates and their statuses. - Introduced a registry manager class for handling Docker registry operations.
This commit is contained in:
@@ -76,6 +76,11 @@ export class OneboxHttpServer {
|
||||
return this.handleWebSocketUpgrade(req);
|
||||
}
|
||||
|
||||
// Docker Registry v2 API (no auth required - registry handles it)
|
||||
if (path.startsWith('/v2/')) {
|
||||
return await this.oneboxRef.registry.handleRequest(req);
|
||||
}
|
||||
|
||||
// API routes
|
||||
if (path.startsWith('/api/')) {
|
||||
return await this.handleApiRequest(req, path);
|
||||
@@ -199,6 +204,9 @@ export class OneboxHttpServer {
|
||||
} else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'GET') {
|
||||
const name = path.split('/').pop()!;
|
||||
return await this.handleGetServiceRequest(name);
|
||||
} else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'PUT') {
|
||||
const name = path.split('/').pop()!;
|
||||
return await this.handleUpdateServiceRequest(name, req);
|
||||
} else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'DELETE') {
|
||||
const name = path.split('/').pop()!;
|
||||
return await this.handleDeleteServiceRequest(name);
|
||||
@@ -231,6 +239,21 @@ export class OneboxHttpServer {
|
||||
} else if (path.match(/^\/api\/domains\/[^/]+$/) && method === 'GET') {
|
||||
const domainName = path.split('/').pop()!;
|
||||
return await this.handleGetDomainDetailRequest(domainName);
|
||||
} else if (path === '/api/dns' && method === 'GET') {
|
||||
return await this.handleGetDnsRecordsRequest();
|
||||
} else if (path === '/api/dns' && method === 'POST') {
|
||||
return await this.handleCreateDnsRecordRequest(req);
|
||||
} else if (path.match(/^\/api\/dns\/[^/]+$/) && method === 'DELETE') {
|
||||
const domain = path.split('/').pop()!;
|
||||
return await this.handleDeleteDnsRecordRequest(domain);
|
||||
} else if (path === '/api/dns/sync' && method === 'POST') {
|
||||
return await this.handleSyncDnsRecordsRequest();
|
||||
} else if (path.match(/^\/api\/registry\/tags\/[^/]+$/)) {
|
||||
const serviceName = path.split('/').pop()!;
|
||||
return await this.handleGetRegistryTagsRequest(serviceName);
|
||||
} else if (path.match(/^\/api\/registry\/token\/[^/]+$/)) {
|
||||
const serviceName = path.split('/').pop()!;
|
||||
return await this.handleGetRegistryTokenRequest(serviceName);
|
||||
} else {
|
||||
return this.jsonResponse({ success: false, error: 'Not found' }, 404);
|
||||
}
|
||||
@@ -338,6 +361,36 @@ export class OneboxHttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleUpdateServiceRequest(name: string, req: Request): Promise<Response> {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const updates: {
|
||||
image?: string;
|
||||
registry?: string;
|
||||
port?: number;
|
||||
domain?: string;
|
||||
envVars?: Record<string, string>;
|
||||
} = {};
|
||||
|
||||
// Extract valid update fields
|
||||
if (body.image !== undefined) updates.image = body.image;
|
||||
if (body.registry !== undefined) updates.registry = body.registry;
|
||||
if (body.port !== undefined) updates.port = body.port;
|
||||
if (body.domain !== undefined) updates.domain = body.domain;
|
||||
if (body.envVars !== undefined) updates.envVars = body.envVars;
|
||||
|
||||
const service = await this.oneboxRef.services.updateService(name, updates);
|
||||
|
||||
// Broadcast service updated
|
||||
this.broadcastServiceUpdate(name, 'updated', service);
|
||||
|
||||
return this.jsonResponse({ success: true, data: service });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to update service ${name}: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to update service' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDeleteServiceRequest(name: string): Promise<Response> {
|
||||
try {
|
||||
await this.oneboxRef.services.removeService(name);
|
||||
@@ -659,6 +712,87 @@ export class OneboxHttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetDnsRecordsRequest(): Promise<Response> {
|
||||
try {
|
||||
const records = this.oneboxRef.dns.listDNSRecords();
|
||||
return this.jsonResponse({ success: true, data: records });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get DNS records: ${error.message}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: error.message || 'Failed to get DNS records',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleCreateDnsRecordRequest(req: Request): Promise<Response> {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { domain, ip } = body;
|
||||
|
||||
if (!domain) {
|
||||
return this.jsonResponse(
|
||||
{ success: false, error: 'Domain is required' },
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
await this.oneboxRef.dns.addDNSRecord(domain, ip);
|
||||
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
message: `DNS record created for ${domain}`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to create DNS record: ${error.message}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: error.message || 'Failed to create DNS record',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDeleteDnsRecordRequest(domain: string): Promise<Response> {
|
||||
try {
|
||||
await this.oneboxRef.dns.removeDNSRecord(domain);
|
||||
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
message: `DNS record deleted for ${domain}`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete DNS record for ${domain}: ${error.message}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: error.message || 'Failed to delete DNS record',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleSyncDnsRecordsRequest(): Promise<Response> {
|
||||
try {
|
||||
if (!this.oneboxRef.dns.isConfigured()) {
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: 'DNS manager not configured',
|
||||
}, 400);
|
||||
}
|
||||
|
||||
await this.oneboxRef.dns.syncFromCloudflare();
|
||||
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
message: 'DNS records synced from Cloudflare',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to sync DNS records: ${error.message}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: error.message || 'Failed to sync DNS records',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WebSocket upgrade
|
||||
*/
|
||||
@@ -755,6 +889,70 @@ export class OneboxHttpServer {
|
||||
});
|
||||
}
|
||||
|
||||
// ============ Registry Endpoints ============
|
||||
|
||||
private async handleGetRegistryTagsRequest(serviceName: string): Promise<Response> {
|
||||
try {
|
||||
const tags = await this.oneboxRef.registry.getImageTags(serviceName);
|
||||
return this.jsonResponse({ success: true, data: tags });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get registry tags for ${serviceName}: ${error.message}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: error.message || 'Failed to get registry tags',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetRegistryTokenRequest(serviceName: string): Promise<Response> {
|
||||
try {
|
||||
// Get the service to verify it exists
|
||||
const service = this.oneboxRef.database.getServiceByName(serviceName);
|
||||
if (!service) {
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: 'Service not found',
|
||||
}, 404);
|
||||
}
|
||||
|
||||
// If service already has a token, return it
|
||||
if (service.registryToken) {
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
data: {
|
||||
token: service.registryToken,
|
||||
repository: serviceName,
|
||||
baseUrl: this.oneboxRef.registry.getBaseUrl(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Generate new token
|
||||
const token = await this.oneboxRef.registry.createServiceToken(serviceName);
|
||||
|
||||
// Save token to database
|
||||
this.oneboxRef.database.updateService(service.id!, {
|
||||
registryToken: token,
|
||||
registryRepository: serviceName,
|
||||
});
|
||||
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
data: {
|
||||
token: token,
|
||||
repository: serviceName,
|
||||
baseUrl: this.oneboxRef.registry.getBaseUrl(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get registry token for ${serviceName}: ${error.message}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: error.message || 'Failed to get registry token',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create JSON response
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user