first commit

This commit is contained in:
Rutra committed 2026-10-12 14:25:22 +02:00
commit 3c0479faf0
169 files changed
+27617

No files matched your search

+127
View File
@@ -0,0 +1,127 @@
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { Api } from '../../core/api';
import { Feedback } from '../../core/feedback';
import { errorMessage, formatDateTime } from '../../core/format';
import type { CaddyInfo, CaddyVersion } from '../../core/models';
import { Icon } from '../../shared/icon';
@Component({
selector: 'app-caddy',
imports: [FormsModule, Icon],
templateUrl: './caddy.html',
styleUrl: './caddy.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CaddyPage {
private api = inject(Api);
private feedback = inject(Feedback);
protected readonly info = signal<CaddyInfo | null>(null);
protected readonly content = signal('');
protected readonly reason = signal('');
protected readonly saving = signal(false);
protected readonly error = signal<string | null>(null);
protected readonly output = signal<{ ok: boolean; text: string } | null>(null);
protected readonly versions = signal<CaddyVersion[]>([]);
protected readonly viewed = signal<CaddyVersion | null>(null);
protected readonly dirty = computed(
() => this.info() !== null && this.content() !== this.info()!.content,
);
protected readonly lineCount = computed(() => this.content().split('\n').length);
protected readonly dateTime = formatDateTime;
constructor() {
this.load();
}
protected async load() {
try {
const [info, versions] = await Promise.all([this.api.caddy(), this.api.caddyVersions()]);
this.info.set(info);
this.content.set(info.content);
this.versions.set(versions);
this.error.set(null);
} catch (error) {
this.error.set(errorMessage(error));
}
}
protected async save() {
if (!this.dirty()) return;
const ok = await this.feedback.confirm({
title: 'Enregistrer et recharger Caddy ?',
message:
'Si Caddy refuse la nouvelle configuration, l’ancien fichier est remis en place automatiquement.',
confirmLabel: 'Enregistrer et recharger',
});
if (!ok) return;
await this.run(
() => this.api.saveCaddy(this.content(), this.reason().trim()),
'Caddyfile enregistré et Caddy rechargé.',
);
}
protected async reload() {
await this.run(() => this.api.reloadCaddy(), 'Caddy rechargé.');
}
protected reset() {
this.content.set(this.info()?.content ?? '');
this.output.set(null);
}
protected async view(version: CaddyVersion) {
try {
this.viewed.set(await this.api.caddyVersion(version.id));
} catch (error) {
this.feedback.error(errorMessage(error));
}
}
protected async restore(version: CaddyVersion) {
const ok = await this.feedback.confirm({
title: `Restaurer la version #${version.id} ?`,
message: `Version enregistrée le ${formatDateTime(version.createdAt)}. Le fichier actuel est lui-même gardé dans l’historique.`,
confirmLabel: 'Restaurer',
});
if (!ok) return;
this.viewed.set(null);
await this.run(() => this.api.restoreCaddy(version.id), `Version #${version.id} restaurée.`);
}
/**
* Tab inserts a tab character (Caddyfile indentation) instead of
* leaving the field.
*/
protected onKeydown(event: KeyboardEvent) {
if (event.key !== 'Tab' || event.shiftKey) return;
event.preventDefault();
const textarea = event.target as HTMLTextAreaElement;
const { selectionStart, selectionEnd, value } = textarea;
const next = `${value.slice(0, selectionStart)}\t${value.slice(selectionEnd)}`;
this.content.set(next);
textarea.value = next;
textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
}
private async run(action: () => Promise<{ output: string }>, success: string) {
this.saving.set(true);
this.output.set(null);
try {
const result = await action();
this.output.set({ ok: true, text: result.output || 'OK' });
this.feedback.success(success);
this.reason.set('');
await this.load();
} catch (error) {
const body = error instanceof HttpErrorResponse ? error.error : null;
this.output.set({ ok: false, text: body?.output || errorMessage(error) });
this.feedback.error(errorMessage(error));
} finally {
this.saving.set(false);
}
}
}