178 lines
5.7 KiB
TypeScript
178 lines
5.7 KiB
TypeScript
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
|
||
import { FormsModule } from '@angular/forms';
|
||
import { RouterLink } from '@angular/router';
|
||
import { Api } from '../../core/api';
|
||
import { errorMessage } from '../../core/format';
|
||
import type {
|
||
AuditStatus,
|
||
CreateProjectInput,
|
||
ProjectOptions,
|
||
ProjectType,
|
||
} from '../../core/models';
|
||
import { Icon } from '../../shared/icon';
|
||
import { JobConsole } from '../../shared/job-console';
|
||
|
||
const TYPES: { value: ProjectType; label: string; description: string }[] = [
|
||
{
|
||
value: 'node',
|
||
label: 'Node.js',
|
||
description: 'Une app Node lancée par une commande (node start.js…).',
|
||
},
|
||
{
|
||
value: 'adonis',
|
||
label: 'AdonisJS',
|
||
description: 'Compilée (node ace build) puis lancée en production.',
|
||
},
|
||
{ value: 'wordpress', label: 'WordPress', description: 'Image officielle, base MariaDB dédiée.' },
|
||
{
|
||
value: 'static',
|
||
label: 'Site statique',
|
||
description: 'Fichiers servis directement par Caddy.',
|
||
},
|
||
];
|
||
|
||
const NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
||
|
||
@Component({
|
||
selector: 'app-project-new',
|
||
imports: [FormsModule, RouterLink, Icon, JobConsole],
|
||
templateUrl: './project-new.html',
|
||
styleUrl: './project-new.scss',
|
||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||
})
|
||
export class ProjectNewPage {
|
||
private api = inject(Api);
|
||
|
||
protected readonly types = TYPES;
|
||
protected readonly options = signal<ProjectOptions | null>(null);
|
||
|
||
protected readonly type = signal<ProjectType>('node');
|
||
protected readonly name = signal('');
|
||
protected readonly category = signal('api');
|
||
protected readonly gitUrl = signal('');
|
||
protected readonly port = signal(3333);
|
||
protected readonly command = signal('');
|
||
protected readonly env = signal('');
|
||
protected readonly createDatabase = signal(false);
|
||
protected readonly addCaddy = signal(true);
|
||
protected readonly spa = signal(true);
|
||
/**
|
||
* Null while the domain follows the name.
|
||
*/
|
||
protected readonly customDomain = signal<string | null>(null);
|
||
|
||
protected readonly submitting = signal(false);
|
||
protected readonly error = signal<string | null>(null);
|
||
protected readonly jobId = signal<number | null>(null);
|
||
protected readonly jobStatus = signal<AuditStatus | null>(null);
|
||
|
||
protected readonly domain = computed(
|
||
() =>
|
||
this.customDomain() ??
|
||
(this.name() ? `${this.name()}.${this.options()?.baseDomain ?? ''}` : ''),
|
||
);
|
||
|
||
protected readonly isApp = computed(() => this.type() === 'node' || this.type() === 'adonis');
|
||
protected readonly nameValid = computed(() => NAME_PATTERN.test(this.name()));
|
||
|
||
protected readonly targetPath = computed(() => {
|
||
const root = this.options()?.srvRoot ?? '/srv';
|
||
const folder =
|
||
this.type() === 'static'
|
||
? 'html'
|
||
: this.type() === 'wordpress'
|
||
? 'wordpress'
|
||
: this.category();
|
||
return `${root}/${folder}/${this.name() || '<nom>'}`.replace(/\\/g, '/');
|
||
});
|
||
|
||
/**
|
||
* What the creation will do, step by step.
|
||
*/
|
||
protected readonly steps = computed(() => {
|
||
const steps: string[] = [`Créer le dossier ${this.targetPath()}`];
|
||
const type = this.type();
|
||
if (type === 'static') {
|
||
steps.push('Y mettre une page d’attente (à remplacer par ton build)');
|
||
} else {
|
||
if (this.isApp()) steps.push('Cloner le dépôt dans project/');
|
||
if (type === 'wordpress' || this.createDatabase()) {
|
||
steps.push(
|
||
`Créer la base MariaDB « ${(this.name() || '<nom>').replace(/-/g, '_')} » et son utilisateur`,
|
||
);
|
||
}
|
||
steps.push(
|
||
type === 'wordpress' ? 'Écrire le .env (identifiants de la base)' : 'Écrire project/.env',
|
||
);
|
||
steps.push('Écrire le docker-compose.yml (template de la procédure)');
|
||
steps.push('Lancer : docker compose up -d');
|
||
}
|
||
if (this.addCaddy())
|
||
steps.push(`Ajouter ${this.domain() || '<domaine>'} au Caddyfile et recharger Caddy`);
|
||
return steps;
|
||
});
|
||
|
||
constructor() {
|
||
this.api
|
||
.projectOptions()
|
||
.then((options) => {
|
||
this.options.set(options);
|
||
this.command.set(options.defaultNodeCommand);
|
||
})
|
||
.catch((error) => this.error.set(errorMessage(error)));
|
||
}
|
||
|
||
protected selectType(type: ProjectType) {
|
||
this.type.set(type);
|
||
if (type === 'wordpress') this.category.set('wordpress');
|
||
else if (this.category() === 'wordpress') this.category.set('api');
|
||
}
|
||
|
||
protected async submit() {
|
||
this.error.set(null);
|
||
if (!this.nameValid()) {
|
||
this.error.set('Le nom ne peut contenir que des minuscules, des chiffres et des tirets.');
|
||
return;
|
||
}
|
||
if (this.isApp() && !this.gitUrl().trim()) {
|
||
this.error.set('L’URL du dépôt git est obligatoire.');
|
||
return;
|
||
}
|
||
|
||
const type = this.type();
|
||
const input: CreateProjectInput = {
|
||
type,
|
||
name: this.name(),
|
||
category: type === 'wordpress' ? 'wordpress' : this.category(),
|
||
createDatabase: type === 'wordpress' || (this.isApp() && this.createDatabase()),
|
||
domain: this.domain(),
|
||
addCaddy: this.addCaddy(),
|
||
spa: this.spa(),
|
||
};
|
||
if (this.isApp()) {
|
||
input.gitUrl = this.gitUrl().trim();
|
||
input.port = Number(this.port());
|
||
input.env = this.env();
|
||
if (type === 'node') input.command = this.command().trim();
|
||
}
|
||
|
||
this.submitting.set(true);
|
||
try {
|
||
const { jobId } = await this.api.createProject(input);
|
||
this.jobStatus.set(null);
|
||
this.jobId.set(jobId);
|
||
} catch (error) {
|
||
this.error.set(errorMessage(error));
|
||
} finally {
|
||
this.submitting.set(false);
|
||
}
|
||
}
|
||
|
||
protected projectLink() {
|
||
const type = this.type();
|
||
const category =
|
||
type === 'static' ? 'html' : type === 'wordpress' ? 'wordpress' : this.category();
|
||
return ['/projects', category, this.name()];
|
||
}
|
||
}
|