feat: Implement platform service providers for MinIO and MongoDB

- Added base interface and abstract class for platform service providers.
- Created MinIOProvider class for S3-compatible storage with deployment, provisioning, and deprovisioning functionalities.
- Implemented MongoDBProvider class for MongoDB service with similar capabilities.
- Introduced error handling utilities for better error management.
- Developed TokensComponent for managing registry tokens in the UI, including creation, deletion, and display of tokens.
This commit is contained in:
2025-11-25 04:20:19 +00:00
parent 9aa6906ca5
commit 8ebd677478
28 changed files with 3462 additions and 490 deletions

View File

@@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms';
import { ApiService } from '../../core/services/api.service';
import { ToastService } from '../../core/services/toast.service';
import { LogStreamService } from '../../core/services/log-stream.service';
import { IService, IServiceUpdate } from '../../core/types/api.types';
import { IService, IServiceUpdate, IPlatformResource } from '../../core/types/api.types';
import {
CardComponent,
CardHeaderComponent,
@@ -209,6 +209,61 @@ import {
</ui-card>
}
<!-- Platform Resources -->
@if (service()!.platformRequirements || platformResources().length > 0) {
<ui-card>
<ui-card-header class="flex flex-col space-y-1.5">
<ui-card-title>Platform Resources</ui-card-title>
<ui-card-description>Managed infrastructure provisioned for this service</ui-card-description>
</ui-card-header>
<ui-card-content class="space-y-4">
@if (platformResources().length > 0) {
@for (resource of platformResources(); track resource.id) {
<div class="border rounded-lg p-4 space-y-2">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
@if (resource.resourceType === 'database') {
<svg class="h-5 w-5 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
} @else if (resource.resourceType === 'bucket') {
<svg class="h-5 w-5 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
</svg>
}
<span class="font-medium">{{ resource.resourceName }}</span>
</div>
<ui-badge [variant]="resource.platformService.status === 'running' ? 'success' : 'secondary'">
{{ resource.platformService.status }}
</ui-badge>
</div>
<div class="text-sm text-muted-foreground">
{{ resource.platformService.type === 'mongodb' ? 'MongoDB Database' : 'S3 Bucket (MinIO)' }}
</div>
<div class="mt-2 pt-2 border-t">
<p class="text-xs font-medium text-muted-foreground mb-1">Injected Environment Variables</p>
<div class="flex flex-wrap gap-1">
@for (key of getEnvKeys(resource.envVars); track key) {
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-mono bg-muted">{{ key }}</span>
}
</div>
</div>
</div>
}
} @else if (service()!.platformRequirements) {
<div class="text-sm text-muted-foreground">
@if (service()!.platformRequirements!.mongodb) {
<p>MongoDB database pending provisioning...</p>
}
@if (service()!.platformRequirements!.s3) {
<p>S3 bucket pending provisioning...</p>
}
</div>
}
</ui-card-content>
</ui-card>
}
<!-- Onebox Registry Info -->
@if (service()!.useOneboxRegistry) {
<ui-card>
@@ -225,21 +280,11 @@ import {
<dt class="text-sm font-medium text-muted-foreground">Tag</dt>
<dd class="text-sm">{{ service()!.registryImageTag || 'latest' }}</dd>
</div>
@if (service()!.registryToken) {
<div>
<dt class="text-sm font-medium text-muted-foreground">Push Token</dt>
<dd class="flex items-center gap-2">
<input
uiInput
type="password"
[value]="service()!.registryToken"
readonly
class="font-mono text-xs"
/>
<button uiButton variant="outline" size="sm" (click)="copyToken()">Copy</button>
</dd>
</div>
}
<div class="pt-2 border-t">
<a routerLink="/tokens" class="text-sm text-primary hover:underline">
Manage registry tokens for CI/CD pipelines →
</a>
</div>
<div>
<dt class="text-sm font-medium text-muted-foreground">Auto-update on push</dt>
<dd class="text-sm">{{ service()!.autoUpdateOnPush ? 'Enabled' : 'Disabled' }}</dd>
@@ -346,6 +391,7 @@ export class ServiceDetailComponent implements OnInit, OnDestroy {
@ViewChild('logContainer') logContainer!: ElementRef<HTMLDivElement>;
service = signal<IService | null>(null);
platformResources = signal<IPlatformResource[]>([]);
loading = signal(false);
actionLoading = signal(false);
editMode = signal(false);
@@ -389,6 +435,11 @@ export class ServiceDetailComponent implements OnInit, OnDestroy {
port: response.data.port,
domain: response.data.domain,
};
// Load platform resources if service has platform requirements
if (response.data.platformRequirements) {
this.loadPlatformResources(name);
}
} else {
this.toast.error(response.error || 'Service not found');
this.router.navigate(['/services']);
@@ -400,6 +451,17 @@ export class ServiceDetailComponent implements OnInit, OnDestroy {
}
}
async loadPlatformResources(name: string): Promise<void> {
try {
const response = await this.api.getServicePlatformResources(name);
if (response.success && response.data) {
this.platformResources.set(response.data);
}
} catch {
// Silent fail - platform resources are optional
}
}
startLogStream(): void {
const name = this.service()?.name;
if (name) {
@@ -546,12 +608,4 @@ export class ServiceDetailComponent implements OnInit, OnDestroy {
this.deleteDialogOpen.set(false);
}
}
copyToken(): void {
const token = this.service()?.registryToken;
if (token) {
navigator.clipboard.writeText(token);
this.toast.success('Token copied to clipboard');
}
}
}