Files
big-brother/web/src/app/pages/caddy/caddy.ts
T
2026-10-12 14:25:22 +02:00

128 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}