import { Component, OnInit, inject, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Router, RouterLink } from '@angular/router'; import { ApiService } from '../../core/services/api.service'; interface EnvVar { key: string; value: string; } interface Domain { domain: string; dnsProvider: 'cloudflare' | 'manual' | null; isObsolete: boolean; } @Component({ selector: 'app-service-create', standalone: true, imports: [CommonModule, FormsModule, RouterLink], template: `

Deploy New Service

Lowercase letters, numbers, and hyphens only

Format: image:tag or registry/image:tag

Store your container image in the local Onebox registry instead of using an external image.

@if (useOneboxRegistry) {

Tag to use (e.g., latest, v1.0, develop)

Automatically pull and restart the service when a new image is pushed to the registry

}

Port that your application listens on

@for (domain of availableDomains(); track domain.domain) { } @if (domainWarning()) {

{{ domainWarningTitle() }}

{{ domainWarningMessage() }}

} @else {

Leave empty to skip automatic DNS & SSL. @if (availableDomains().length > 0) { Or select from {{ availableDomains().length }} available domain(s). }

}
@for (env of envVars(); track $index) {
}
@if (error()) {

{{ error() }}

}
`, }) export class ServiceCreateComponent implements OnInit { private apiService = inject(ApiService); private router = inject(Router); name = ''; image = ''; port = 80; domain = ''; autoDNS = true; autoSSL = true; envVars = signal([]); loading = signal(false); error = signal(''); // Onebox Registry useOneboxRegistry = false; registryImageTag = 'latest'; autoUpdateOnPush = false; // Domain validation availableDomains = signal([]); domainWarning = signal(false); domainWarningTitle = signal(''); domainWarningMessage = signal(''); ngOnInit(): void { this.loadDomains(); } loadDomains(): void { this.apiService.getDomains().subscribe({ next: (response) => { if (response.success && response.data) { const domains: Domain[] = response.data.map((d: any) => ({ domain: d.domain.domain, dnsProvider: d.domain.dnsProvider, isObsolete: d.domain.isObsolete, })); this.availableDomains.set(domains); } }, error: () => { // Silently fail - domains list not critical }, }); } onDomainChange(): void { if (!this.domain) { this.domainWarning.set(false); return; } // Extract base domain from entered domain const parts = this.domain.split('.'); if (parts.length < 2) { // Not a valid domain format this.domainWarning.set(false); return; } const baseDomain = parts.slice(-2).join('.'); // Check if base domain exists in available domains const matchingDomain = this.availableDomains().find( (d) => d.domain === baseDomain ); if (!matchingDomain) { this.domainWarning.set(true); this.domainWarningTitle.set('Domain not found'); this.domainWarningMessage.set( `The base domain "${baseDomain}" is not in the Domain table. The service will deploy, but certificate management may not work. Sync your Cloudflare domains or manually add the domain first.` ); } else if (matchingDomain.isObsolete) { this.domainWarning.set(true); this.domainWarningTitle.set('Domain is obsolete'); this.domainWarningMessage.set( `The domain "${baseDomain}" is marked as obsolete (likely removed from Cloudflare). Certificate management may not work properly.` ); } else { this.domainWarning.set(false); } } addEnvVar(): void { this.envVars.update((vars) => [...vars, { key: '', value: '' }]); } removeEnvVar(index: number): void { this.envVars.update((vars) => vars.filter((_, i) => i !== index)); } onSubmit(): void { this.error.set(''); this.loading.set(true); // Convert env vars to object const envVarsObj: Record = {}; for (const env of this.envVars()) { if (env.key && env.value) { envVarsObj[env.key] = env.value; } } const data = { name: this.name, image: this.image, port: this.port, domain: this.domain || undefined, envVars: envVarsObj, autoDNS: this.autoDNS, autoSSL: this.autoSSL, useOneboxRegistry: this.useOneboxRegistry, registryImageTag: this.useOneboxRegistry ? this.registryImageTag : undefined, autoUpdateOnPush: this.useOneboxRegistry ? this.autoUpdateOnPush : undefined, }; this.apiService.createService(data).subscribe({ next: (response) => { this.loading.set(false); if (response.success) { this.router.navigate(['/services']); } else { this.error.set(response.error || 'Failed to deploy service'); } }, error: (err) => { this.loading.set(false); this.error.set(err.error?.error || 'An error occurred'); }, }); } cancel(): void { this.router.navigate(['/services']); } }