first commit
This commit is contained in:
commit
3c0479faf0
169 files changed
+27617
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user