Mise en place de la connexion au canal privé pour la bêta avec Patreon et passage en v0.8.0
This commit is contained in:
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "loremind-web",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "loremind-web",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.0.0",
|
||||
"@angular/common": "^17.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "loremind-web",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"description": "LoreMind Frontend - Angular",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
|
||||
94
web/src/app/services/license.service.ts
Normal file
94
web/src/app/services/license.service.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, catchError, of } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Reflet de LicenseStatus (enum cote backend).
|
||||
*/
|
||||
export type LicenseStatus = 'NONE' | 'VALID' | 'GRACE' | 'EXPIRED' | 'UNVERIFIABLE';
|
||||
|
||||
export interface LicenseStatusDTO {
|
||||
enabled: boolean;
|
||||
status: LicenseStatus;
|
||||
patreonUserId: string | null;
|
||||
tierId: string | null;
|
||||
instanceId: string | null;
|
||||
expiresAt: string | null;
|
||||
lastRefreshAttemptAt: string | null;
|
||||
lastRefreshSucceeded: boolean | null;
|
||||
betaChannelEnabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflet de UpdateCheckService.BetaStatus.
|
||||
*/
|
||||
export interface BetaStatusDTO {
|
||||
enabled: boolean;
|
||||
updateAvailable: boolean;
|
||||
anyUnknown: boolean;
|
||||
images: Array<{
|
||||
image: string;
|
||||
localDigest: string | null;
|
||||
remoteDigest: string | null;
|
||||
status: 'UP_TO_DATE' | 'UPDATE_AVAILABLE' | 'UNKNOWN';
|
||||
updateAvailable: boolean;
|
||||
}>;
|
||||
checkedAt: string;
|
||||
disabledReason: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service Angular pour la gestion de la licence Patreon.
|
||||
* Tous les endpoints sont proteges par HTTP Basic (admin).
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LicenseService {
|
||||
private readonly apiUrl = '/api/license';
|
||||
private readonly authOptions = { withCredentials: true };
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getStatus(): Observable<LicenseStatusDTO | null> {
|
||||
return this.http.get<LicenseStatusDTO>(this.apiUrl, this.authOptions).pipe(
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
|
||||
getConnectUrl(): Observable<{ url: string } | null> {
|
||||
return this.http.get<{ url: string }>(`${this.apiUrl}/connect-url`, this.authOptions).pipe(
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
|
||||
install(jwt: string): Observable<LicenseStatusDTO | { error: string }> {
|
||||
return this.http.post<LicenseStatusDTO>(`${this.apiUrl}/install`, { jwt }, this.authOptions).pipe(
|
||||
catchError((err) => of({ error: err?.error?.error ?? 'Echec de l\'installation' }))
|
||||
);
|
||||
}
|
||||
|
||||
disconnect(): Observable<boolean> {
|
||||
return this.http.delete<void>(this.apiUrl, this.authOptions).pipe(
|
||||
// Convertit en boolean : true = succes, false = erreur
|
||||
// (catchError plus bas masque les detail HTTP)
|
||||
catchError(() => of(false as any))
|
||||
) as unknown as Observable<boolean>;
|
||||
}
|
||||
|
||||
refresh(): Observable<LicenseStatusDTO | null> {
|
||||
return this.http.post<LicenseStatusDTO>(`${this.apiUrl}/refresh`, null, this.authOptions).pipe(
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
|
||||
setBetaChannel(enabled: boolean): Observable<LicenseStatusDTO | null> {
|
||||
return this.http.put<LicenseStatusDTO>(`${this.apiUrl}/beta-channel`, { enabled }, this.authOptions).pipe(
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
|
||||
checkBeta(): Observable<BetaStatusDTO | null> {
|
||||
return this.http.get<BetaStatusDTO>('/api/admin/updates/check-beta', this.authOptions).pipe(
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -221,58 +221,205 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Bloc Mises a jour -->
|
||||
<section class="card" *ngIf="config.updateCheckEnabled">
|
||||
<!-- Bloc Mises a jour (canal stable + canal beta Patreon fusionnes) -->
|
||||
<section class="card" *ngIf="config.updateCheckEnabled || licenseStatus?.enabled">
|
||||
<h2>Mises a jour</h2>
|
||||
<p class="hint">Verifie aupres du registry Docker si une nouvelle version
|
||||
des conteneurs (core, brain, web) est disponible. Postgres et MinIO sont
|
||||
exclus — ils sont mis a jour manuellement.</p>
|
||||
|
||||
<div class="form-row">
|
||||
<button type="button" class="btn-secondary" (click)="checkUpdates()" [disabled]="updateChecking">
|
||||
<lucide-icon [img]="RefreshCw" [size]="14"></lucide-icon>
|
||||
<span>{{ updateChecking ? 'Verification...' : 'Verifier maintenant' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- ====================================================== -->
|
||||
<!-- Sous-section : canal stable -->
|
||||
<!-- ====================================================== -->
|
||||
<div class="channel-block" *ngIf="config.updateCheckEnabled">
|
||||
<h3 class="channel-title">Canal stable</h3>
|
||||
|
||||
<div *ngIf="updateStatus && !updateStatus.enabled" class="hint">
|
||||
Feature non configuree (WATCHTOWER_TOKEN absent).
|
||||
</div>
|
||||
|
||||
<div *ngIf="updateStatus?.enabled">
|
||||
<div *ngIf="updateStatus?.updateAvailable" class="alert alert-success">
|
||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||
<span>Une mise a jour est disponible.</span>
|
||||
</div>
|
||||
<div *ngIf="updateStatus?.anyUnknown && !updateStatus?.updateAvailable" class="alert alert-warn">
|
||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||
<span>Verification impossible pour certaines images — voir details ci-dessous.</span>
|
||||
</div>
|
||||
<div *ngIf="!updateStatus?.updateAvailable && !updateStatus?.anyUnknown" class="hint">
|
||||
Tout est a jour (verifie le {{ updateStatus?.checkedAt | date:'short' }}).
|
||||
</div>
|
||||
|
||||
<ul class="update-images" *ngIf="updateStatus?.images?.length">
|
||||
<li *ngFor="let img of updateStatus?.images">
|
||||
<strong>{{ img.image }}</strong>
|
||||
<span *ngIf="img.status === 'UPDATE_AVAILABLE'" class="badge-update">MAJ dispo</span>
|
||||
<span *ngIf="img.status === 'UP_TO_DATE'" class="badge-ok">a jour</span>
|
||||
<span *ngIf="img.status === 'UNKNOWN'" class="badge-warn"
|
||||
title="Impossible de comparer (baseline absente ou registry injoignable)">verification impossible</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="form-row" *ngIf="updateStatus?.updateAvailable">
|
||||
<button type="button" class="btn-primary" (click)="applyUpdate()" [disabled]="updateApplying">
|
||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||
<span>{{ updateApplying ? 'Mise a jour en cours...' : 'Mettre a jour maintenant' }}</span>
|
||||
<div class="form-row">
|
||||
<button type="button" class="btn-secondary" (click)="checkUpdates()" [disabled]="updateChecking">
|
||||
<lucide-icon [img]="RefreshCw" [size]="14"></lucide-icon>
|
||||
<span>{{ updateChecking ? 'Verification...' : 'Verifier maintenant' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="updateMessage" class="alert alert-success">
|
||||
<lucide-icon [img]="Check" [size]="16"></lucide-icon>
|
||||
<span>{{ updateMessage }}</span>
|
||||
<div *ngIf="updateStatus && !updateStatus.enabled" class="hint">
|
||||
Feature non configuree (WATCHTOWER_TOKEN absent).
|
||||
</div>
|
||||
|
||||
<div *ngIf="updateStatus?.enabled">
|
||||
<div *ngIf="updateStatus?.updateAvailable" class="alert alert-success">
|
||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||
<span>Une mise a jour est disponible.</span>
|
||||
</div>
|
||||
<div *ngIf="updateStatus?.anyUnknown && !updateStatus?.updateAvailable" class="alert alert-warn">
|
||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||
<span>Verification impossible pour certaines images — voir details ci-dessous.</span>
|
||||
</div>
|
||||
<div *ngIf="!updateStatus?.updateAvailable && !updateStatus?.anyUnknown" class="hint">
|
||||
Tout est a jour (verifie le {{ updateStatus?.checkedAt | date:'short' }}).
|
||||
</div>
|
||||
|
||||
<ul class="update-images" *ngIf="updateStatus?.images?.length">
|
||||
<li *ngFor="let img of updateStatus?.images">
|
||||
<strong>{{ img.image }}</strong>
|
||||
<span *ngIf="img.status === 'UPDATE_AVAILABLE'" class="badge-update">MAJ dispo</span>
|
||||
<span *ngIf="img.status === 'UP_TO_DATE'" class="badge-ok">a jour</span>
|
||||
<span *ngIf="img.status === 'UNKNOWN'" class="badge-warn"
|
||||
title="Impossible de comparer (baseline absente ou registry injoignable)">verification impossible</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="form-row" *ngIf="updateStatus?.updateAvailable">
|
||||
<button type="button" class="btn-primary" (click)="applyUpdate()" [disabled]="updateApplying">
|
||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||
<span>{{ updateApplying ? 'Mise a jour en cours...' : 'Mettre a jour maintenant' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="updateMessage" class="alert alert-success">
|
||||
<lucide-icon [img]="Check" [size]="16"></lucide-icon>
|
||||
<span>{{ updateMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================================================== -->
|
||||
<!-- Sous-section : canal beta (Patreon) -->
|
||||
<!-- ====================================================== -->
|
||||
<div class="channel-block" *ngIf="licenseStatus?.enabled">
|
||||
<h3 class="channel-title">
|
||||
<lucide-icon [img]="Heart" [size]="16"></lucide-icon>
|
||||
Canal beta — reserve aux patrons
|
||||
</h3>
|
||||
<p class="hint">
|
||||
Soutiens LoreMind sur Patreon pour acceder aux nouvelles features en avant-premiere.
|
||||
Le tier <strong>Compagnon</strong> (7€/mois) ou superieur debloque ce canal.
|
||||
</p>
|
||||
|
||||
<!-- Pas de licence installee -->
|
||||
<ng-container *ngIf="licenseStatus?.status === 'NONE'">
|
||||
<div class="form-row">
|
||||
<button type="button" class="btn-primary" (click)="connectPatreon()">
|
||||
<lucide-icon [img]="Link2" [size]="16"></lucide-icon>
|
||||
<span>Connecter mon compte Patreon</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="hint">
|
||||
Une nouvelle fenetre va s'ouvrir vers Patreon. Apres autorisation, copie le token affiche
|
||||
et colle-le ci-dessous.
|
||||
</p>
|
||||
<div class="form-row">
|
||||
<label for="license-jwt">Token Patreon</label>
|
||||
<input
|
||||
id="license-jwt"
|
||||
type="text"
|
||||
[(ngModel)]="licenseJwtInput"
|
||||
placeholder="eyJhbGciOiJFZERTQS..."
|
||||
autocomplete="off"
|
||||
>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<button type="button" class="btn-primary" (click)="installLicense()" [disabled]="!licenseJwtInput.trim()">
|
||||
<lucide-icon [img]="Check" [size]="16"></lucide-icon>
|
||||
<span>Activer la licence</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="licenseError" class="alert alert-error">
|
||||
<lucide-icon [img]="AlertCircle" [size]="16"></lucide-icon>
|
||||
<span>{{ licenseError }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Licence installee (VALID / GRACE / EXPIRED / UNVERIFIABLE) -->
|
||||
<ng-container *ngIf="licenseStatus && licenseStatus.status !== 'NONE'">
|
||||
<div *ngIf="licenseStatus.status === 'VALID'" class="alert alert-success">
|
||||
<lucide-icon [img]="Check" [size]="16"></lucide-icon>
|
||||
<span>Compte Patreon connecte. Tier {{ licenseStatus.tierId }} actif.</span>
|
||||
</div>
|
||||
<div *ngIf="licenseStatus.status === 'GRACE'" class="alert alert-warn">
|
||||
<lucide-icon [img]="AlertCircle" [size]="16"></lucide-icon>
|
||||
<span>
|
||||
Connexion Patreon expiree, mais acces beta maintenu pendant la periode de tolerance.
|
||||
Verifie que ton abonnement Patreon est toujours actif et clique sur "Verifier maintenant".
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="licenseStatus.status === 'EXPIRED'" class="alert alert-error">
|
||||
<lucide-icon [img]="AlertCircle" [size]="16"></lucide-icon>
|
||||
<span>
|
||||
Connexion Patreon expiree depuis trop longtemps. Reconnecte-toi pour retrouver l'acces beta.
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="licenseStatus.status === 'UNVERIFIABLE'" class="alert alert-error">
|
||||
<lucide-icon [img]="AlertCircle" [size]="16"></lucide-icon>
|
||||
<span>Le token installe ne peut plus etre verifie. Reconnecte-toi.</span>
|
||||
</div>
|
||||
|
||||
<ul class="license-info">
|
||||
<li *ngIf="licenseStatus.tierId"><strong>Tier :</strong> {{ licenseStatus.tierId }}</li>
|
||||
<li *ngIf="licenseStatus.expiresAt">
|
||||
<strong>Validite :</strong>
|
||||
jusqu'au {{ formatDate(licenseStatus.expiresAt) }}
|
||||
<span *ngIf="daysUntilExpiry !== null && daysUntilExpiry > 0">
|
||||
(renouvellement dans {{ daysUntilExpiry }} jour<span *ngIf="daysUntilExpiry > 1">s</span>)
|
||||
</span>
|
||||
</li>
|
||||
<li *ngIf="licenseStatus.lastRefreshAttemptAt">
|
||||
<strong>Dernier refresh :</strong>
|
||||
{{ formatDate(licenseStatus.lastRefreshAttemptAt) }}
|
||||
<span *ngIf="licenseStatus.lastRefreshSucceeded === true" class="badge-ok">OK</span>
|
||||
<span *ngIf="licenseStatus.lastRefreshSucceeded === false" class="badge-warn">echec</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="form-row form-row-inline">
|
||||
<label class="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
[checked]="licenseStatus.betaChannelEnabled"
|
||||
(change)="toggleBetaChannel(!licenseStatus.betaChannelEnabled)"
|
||||
[disabled]="licenseStatus.status !== 'VALID' && licenseStatus.status !== 'GRACE'"
|
||||
>
|
||||
<span>Activer le canal beta</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row form-row-actions">
|
||||
<button type="button" class="btn-secondary" (click)="refreshLicense()" [disabled]="licenseLoading">
|
||||
<lucide-icon [img]="RefreshCw" [size]="14"></lucide-icon>
|
||||
<span>{{ licenseLoading ? 'Verification...' : 'Verifier maintenant' }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary btn-danger" (click)="disconnectPatreon()">
|
||||
<lucide-icon [img]="Unlink" [size]="14"></lucide-icon>
|
||||
<span>Deconnecter Patreon</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Etat du canal beta -->
|
||||
<div *ngIf="licenseStatus.betaChannelEnabled" class="beta-status">
|
||||
<div *ngIf="betaChecking" class="hint">Verification des images beta...</div>
|
||||
<div *ngIf="!betaChecking && betaStatus && !betaStatus.enabled" class="hint">
|
||||
Indisponible : {{ betaStatus.disabledReason }}
|
||||
</div>
|
||||
<div *ngIf="!betaChecking && betaStatus?.enabled">
|
||||
<div *ngIf="betaStatus?.updateAvailable" class="alert alert-success">
|
||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||
<span>Une version beta est disponible. Pour l'installer, modifie ton fichier <code>.env</code> :
|
||||
<code>IMAGE_NAMESPACE=igmlcreation/loremind-beta-</code> puis
|
||||
<code>docker compose pull && docker compose up -d</code>.</span>
|
||||
</div>
|
||||
<div *ngIf="betaStatus?.anyUnknown && !betaStatus?.updateAvailable" class="alert alert-warn">
|
||||
<lucide-icon [img]="AlertCircle" [size]="16"></lucide-icon>
|
||||
<span>Verification beta impossible pour certaines images.</span>
|
||||
</div>
|
||||
<ul class="update-images" *ngIf="betaStatus?.images?.length">
|
||||
<li *ngFor="let img of betaStatus?.images">
|
||||
<strong>{{ img.image }}</strong>
|
||||
<span *ngIf="img.status === 'UPDATE_AVAILABLE'" class="badge-update">version dispo</span>
|
||||
<span *ngIf="img.status === 'UNKNOWN'" class="badge-warn">verification impossible</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -364,3 +364,103 @@
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
// --- Sous-blocs canaux (stable / beta) ----------------------------------
|
||||
.channel-block {
|
||||
margin-top: 16px;
|
||||
|
||||
& + & {
|
||||
margin-top: 28px;
|
||||
padding-top: 22px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-title {
|
||||
font-size: 1rem;
|
||||
margin: 0 0 12px;
|
||||
color: #c4b8e0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
// --- Section Patreon / canal beta ---------------------------------------
|
||||
.license-info {
|
||||
list-style: none;
|
||||
margin: 12px 0 16px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
li {
|
||||
font-size: 0.9rem;
|
||||
padding: 6px 10px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 4px;
|
||||
|
||||
strong {
|
||||
color: #c4b8e0;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-row-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.form-row-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-secondary.btn-danger {
|
||||
border-color: rgba(220, 80, 80, 0.4);
|
||||
color: #ff7878;
|
||||
|
||||
&:hover {
|
||||
background: rgba(220, 80, 80, 0.12);
|
||||
border-color: rgba(220, 80, 80, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.beta-status {
|
||||
margin-top: 16px;
|
||||
|
||||
code {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
label.checkbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:disabled + span {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { LucideAngularModule, ArrowLeft, RefreshCw, Save, Check, AlertCircle, Download, Trash2, Plus, X } from 'lucide-angular';
|
||||
import { LucideAngularModule, ArrowLeft, RefreshCw, Save, Check, AlertCircle, Download, Trash2, Plus, X, Heart, Link2, Unlink } from 'lucide-angular';
|
||||
import { SettingsService, AppSettings, AppSettingsUpdate, OneMinModelGroup, OllamaPullEvent } from '../services/settings.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { UpdatesService, UpdateStatus } from '../services/updates.service';
|
||||
import { ConfigService } from '../services/config.service';
|
||||
import { LicenseService, LicenseStatusDTO, BetaStatusDTO } from '../services/license.service';
|
||||
|
||||
/**
|
||||
* Ecran de parametrage du LLM utilise par le Brain.
|
||||
@@ -37,6 +38,19 @@ export class SettingsComponent implements OnInit {
|
||||
readonly Trash2 = Trash2;
|
||||
readonly Plus = Plus;
|
||||
readonly X = X;
|
||||
readonly Heart = Heart;
|
||||
readonly Link2 = Link2;
|
||||
readonly Unlink = Unlink;
|
||||
|
||||
// --- Licence Patreon (canal beta) ---
|
||||
licenseStatus: LicenseStatusDTO | null = null;
|
||||
licenseLoading = false;
|
||||
licenseError = '';
|
||||
/** Token JWT colle par l'utilisateur apres OAuth. */
|
||||
licenseJwtInput = '';
|
||||
/** Etat du canal beta (digests des images privees). */
|
||||
betaStatus: BetaStatusDTO | null = null;
|
||||
betaChecking = false;
|
||||
|
||||
// --- Pull / delete de modeles Ollama ---
|
||||
/** Dialog d'ajout de modele ouvert/ferme. */
|
||||
@@ -105,7 +119,8 @@ export class SettingsComponent implements OnInit {
|
||||
private settingsService: SettingsService,
|
||||
private router: Router,
|
||||
private updatesService: UpdatesService,
|
||||
public config: ConfigService
|
||||
public config: ConfigService,
|
||||
private licenseService: LicenseService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -113,6 +128,117 @@ export class SettingsComponent implements OnInit {
|
||||
if (this.config.updateCheckEnabled) {
|
||||
this.checkUpdates();
|
||||
}
|
||||
this.loadLicense();
|
||||
}
|
||||
|
||||
// --- Licence Patreon ---------------------------------------------------
|
||||
|
||||
loadLicense(): void {
|
||||
this.licenseLoading = true;
|
||||
this.licenseService.getStatus().subscribe({
|
||||
next: (s) => {
|
||||
this.licenseStatus = s;
|
||||
this.licenseLoading = false;
|
||||
if (s?.enabled && (s.status === 'VALID' || s.status === 'GRACE') && s.betaChannelEnabled) {
|
||||
this.checkBeta();
|
||||
}
|
||||
},
|
||||
error: () => { this.licenseLoading = false; }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ouvre la page OAuth Patreon dans une nouvelle fenetre.
|
||||
* L'utilisateur copie ensuite le JWT et le colle dans l'input ci-dessous.
|
||||
*/
|
||||
connectPatreon(): void {
|
||||
this.licenseError = '';
|
||||
this.licenseService.getConnectUrl().subscribe({
|
||||
next: (r) => {
|
||||
if (!r?.url) {
|
||||
this.licenseError = 'Impossible de generer l\'URL de connexion. Verifie ta config.';
|
||||
return;
|
||||
}
|
||||
window.open(r.url, '_blank', 'noopener');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
installLicense(): void {
|
||||
const jwt = this.licenseJwtInput.trim();
|
||||
if (!jwt) {
|
||||
this.licenseError = 'Colle d\'abord le token recu apres connexion Patreon.';
|
||||
return;
|
||||
}
|
||||
this.licenseError = '';
|
||||
this.licenseService.install(jwt).subscribe((res) => {
|
||||
if ((res as any)?.error) {
|
||||
this.licenseError = (res as any).error;
|
||||
return;
|
||||
}
|
||||
this.licenseStatus = res as LicenseStatusDTO;
|
||||
this.licenseJwtInput = '';
|
||||
this.successMessage = 'Compte Patreon connecte. L\'acces beta est actif.';
|
||||
if (this.licenseStatus.betaChannelEnabled) {
|
||||
this.checkBeta();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refreshLicense(): void {
|
||||
this.licenseLoading = true;
|
||||
this.licenseService.refresh().subscribe({
|
||||
next: (s) => {
|
||||
this.licenseStatus = s;
|
||||
this.licenseLoading = false;
|
||||
},
|
||||
error: () => { this.licenseLoading = false; }
|
||||
});
|
||||
}
|
||||
|
||||
disconnectPatreon(): void {
|
||||
if (!confirm('Deconnecter ton compte Patreon ? Tu perdras l\'acces au canal beta.')) return;
|
||||
this.licenseService.disconnect().subscribe(() => {
|
||||
this.licenseStatus = null;
|
||||
this.betaStatus = null;
|
||||
this.successMessage = 'Compte Patreon deconnecte.';
|
||||
this.loadLicense();
|
||||
});
|
||||
}
|
||||
|
||||
toggleBetaChannel(enabled: boolean): void {
|
||||
this.licenseService.setBetaChannel(enabled).subscribe({
|
||||
next: (s) => {
|
||||
if (s) this.licenseStatus = s;
|
||||
if (enabled) this.checkBeta();
|
||||
else this.betaStatus = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkBeta(): void {
|
||||
this.betaChecking = true;
|
||||
this.licenseService.checkBeta().subscribe({
|
||||
next: (s) => {
|
||||
this.betaStatus = s;
|
||||
this.betaChecking = false;
|
||||
},
|
||||
error: () => { this.betaChecking = false; }
|
||||
});
|
||||
}
|
||||
|
||||
/** Format human-readable des dates renvoyees par le backend. */
|
||||
formatDate(iso: string | null | undefined): string {
|
||||
if (!iso) return '';
|
||||
try { return new Date(iso).toLocaleString(); } catch { return iso; }
|
||||
}
|
||||
|
||||
/** Nombre de jours restants avant expiration JWT (peut etre negatif). */
|
||||
get daysUntilExpiry(): number | null {
|
||||
if (!this.licenseStatus?.expiresAt) return null;
|
||||
const exp = new Date(this.licenseStatus.expiresAt).getTime();
|
||||
const now = Date.now();
|
||||
return Math.ceil((exp - now) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
checkUpdates(): void {
|
||||
|
||||
Reference in New Issue
Block a user