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:
2025-11-24 01:31:15 +00:00
parent b6ac4f209a
commit c9beae93c8
23 changed files with 2475 additions and 130 deletions

237
ts/classes/registry.ts Normal file
View File

@@ -0,0 +1,237 @@
/**
* Onebox Registry Manager
*
* Manages the local Docker registry using:
* - @push.rocks/smarts3 (S3-compatible server with filesystem storage)
* - @push.rocks/smartregistry (OCI-compliant Docker registry)
*/
import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
export class RegistryManager {
private s3Server: any = null;
private registry: any = null;
private jwtSecret: string;
private baseUrl: string;
private isInitialized = false;
constructor(private options: {
dataDir?: string;
port?: number;
baseUrl?: string;
} = {}) {
this.jwtSecret = this.getJwtSecret();
this.baseUrl = options.baseUrl || 'localhost:5000';
}
/**
* Initialize the registry (start smarts3 and smartregistry)
*/
async init(): Promise<void> {
if (this.isInitialized) {
logger.warn('Registry already initialized');
return;
}
try {
const dataDir = this.options.dataDir || './.nogit/registry-data';
const port = this.options.port || 4000;
logger.info(`Starting smarts3 server on port ${port}...`);
// 1. Start smarts3 server (S3-compatible storage with filesystem backend)
this.s3Server = await plugins.smarts3.Smarts3.createAndStart({
server: {
port: port,
host: '0.0.0.0',
},
storage: {
bucketsDir: dataDir,
cleanSlate: false, // Preserve data across restarts
},
});
logger.success(`smarts3 server started on port ${port}`);
// 2. Configure smartregistry to use smarts3
logger.info('Initializing smartregistry...');
this.registry = new plugins.smartregistry.SmartRegistry({
storage: {
endpoint: 'localhost',
port: port,
accessKey: 'onebox', // smarts3 doesn't validate credentials
accessSecret: 'onebox',
useSsl: false,
region: 'us-east-1',
bucketName: 'onebox-registry',
},
auth: {
jwtSecret: this.jwtSecret,
ociTokens: {
enabled: true,
issuer: 'onebox-registry',
service: 'onebox-registry',
},
},
oci: {
enabled: true,
basePath: '/v2',
},
});
await this.registry.init();
this.isInitialized = true;
logger.success('Onebox Registry initialized successfully');
} catch (error) {
logger.error(`Failed to initialize registry: ${error.message}`);
throw error;
}
}
/**
* Handle incoming HTTP requests to the registry
*/
async handleRequest(req: Request): Promise<Response> {
if (!this.isInitialized) {
return new Response('Registry not initialized', { status: 503 });
}
try {
return await this.registry.handleRequest(req);
} catch (error) {
logger.error(`Registry request error: ${error.message}`);
return new Response('Internal registry error', { status: 500 });
}
}
/**
* Create a push/pull token for a service
*/
async createServiceToken(serviceName: string): Promise<string> {
if (!this.isInitialized) {
throw new Error('Registry not initialized');
}
const repository = serviceName;
const scopes = [
`oci:repository:${repository}:push`,
`oci:repository:${repository}:pull`,
];
// Create OCI JWT token (expires in 1 year = 365 * 24 * 60 * 60 seconds)
const token = await this.registry.authManager.createOciToken(
'onebox',
scopes,
31536000 // 365 days in seconds
);
return token;
}
/**
* Get all tags for a repository
*/
async getImageTags(repository: string): Promise<string[]> {
if (!this.isInitialized) {
throw new Error('Registry not initialized');
}
try {
const tags = await this.registry.getTags(repository);
return tags || [];
} catch (error) {
logger.warn(`Failed to get tags for ${repository}: ${error.message}`);
return [];
}
}
/**
* Get the manifest digest for a specific image tag
*/
async getImageDigest(repository: string, tag: string): Promise<string | null> {
if (!this.isInitialized) {
throw new Error('Registry not initialized');
}
try {
const manifest = await this.registry.getManifest(repository, tag);
if (manifest && manifest.digest) {
return manifest.digest;
}
return null;
} catch (error) {
logger.warn(`Failed to get digest for ${repository}:${tag}: ${error.message}`);
return null;
}
}
/**
* Delete an image by tag
*/
async deleteImage(repository: string, tag: string): Promise<void> {
if (!this.isInitialized) {
throw new Error('Registry not initialized');
}
try {
await this.registry.deleteManifest(repository, tag);
logger.info(`Deleted image ${repository}:${tag}`);
} catch (error) {
logger.error(`Failed to delete image ${repository}:${tag}: ${error.message}`);
throw error;
}
}
/**
* Get or generate the JWT secret for token signing
*/
private getJwtSecret(): string {
// In production, this should be stored securely
// For now, use a consistent secret stored in environment or generate one
const secret = Deno.env.get('REGISTRY_JWT_SECRET');
if (secret) {
return secret;
}
// Generate a random secret (this will be different on each restart)
// In production, you'd want to persist this
const randomSecret = crypto.randomUUID() + crypto.randomUUID();
logger.warn('Using generated JWT secret (will be different on restart)');
logger.warn('Set REGISTRY_JWT_SECRET environment variable for persistence');
return randomSecret;
}
/**
* Get the registry base URL
*/
getBaseUrl(): string {
return this.baseUrl;
}
/**
* Get the full image name for a service
*/
getImageName(serviceName: string, tag: string = 'latest'): string {
return `${this.baseUrl}/${serviceName}:${tag}`;
}
/**
* Stop the registry and smarts3 server
*/
async stop(): Promise<void> {
if (this.s3Server) {
try {
await this.s3Server.stop();
logger.info('smarts3 server stopped');
} catch (error) {
logger.error(`Error stopping smarts3: ${error.message}`);
}
}
this.isInitialized = false;
logger.info('Registry stopped');
}
}