Changement dans la config pour éviter les url en dur + mise en place d'un mode démo
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
package com.loremind.infrastructure.web.controller;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose la configuration publique consommee par le frontend au demarrage.
|
||||||
|
* Activer le mode demo via la variable d'env DEMO_MODE=true : le front
|
||||||
|
* masque alors Settings / Export VTT, et les endpoints sensibles sont
|
||||||
|
* verrouilles cote serveur (cf. SettingsController).
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/config")
|
||||||
|
public class ConfigController {
|
||||||
|
|
||||||
|
private final boolean demoMode;
|
||||||
|
|
||||||
|
public ConfigController(@Value("${app.demo-mode:false}") boolean demoMode) {
|
||||||
|
this.demoMode = demoMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Map<String, Object> getPublicConfig() {
|
||||||
|
return Map.of("demoMode", demoMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -32,20 +34,25 @@ public class SettingsController {
|
|||||||
|
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
private final String brainBaseUrl;
|
private final String brainBaseUrl;
|
||||||
|
private final boolean demoMode;
|
||||||
|
|
||||||
public SettingsController(RestTemplate restTemplate,
|
public SettingsController(RestTemplate restTemplate,
|
||||||
@Value("${brain.base-url}") String brainBaseUrl) {
|
@Value("${brain.base-url}") String brainBaseUrl,
|
||||||
|
@Value("${app.demo-mode:false}") boolean demoMode) {
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
this.brainBaseUrl = brainBaseUrl;
|
this.brainBaseUrl = brainBaseUrl;
|
||||||
|
this.demoMode = demoMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Map<String, Object>> getSettings() {
|
public ResponseEntity<Map<String, Object>> getSettings() {
|
||||||
|
guardDemoMode();
|
||||||
return forward(HttpMethod.GET, "/settings", null);
|
return forward(HttpMethod.GET, "/settings", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
public ResponseEntity<Map<String, Object>> updateSettings(@RequestBody Map<String, Object> patch) {
|
public ResponseEntity<Map<String, Object>> updateSettings(@RequestBody Map<String, Object> patch) {
|
||||||
|
guardDemoMode();
|
||||||
return forward(HttpMethod.PUT, "/settings", patch);
|
return forward(HttpMethod.PUT, "/settings", patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +71,12 @@ public class SettingsController {
|
|||||||
return forward(HttpMethod.GET, "/models/onemin", null);
|
return forward(HttpMethod.GET, "/models/onemin", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void guardDemoMode() {
|
||||||
|
if (demoMode) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Settings disabled in demo mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
private ResponseEntity<Map<String, Object>> forward(HttpMethod method, String path, Object body) {
|
private ResponseEntity<Map<String, Object>> forward(HttpMethod method, String path, Object body) {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ spring.jpa.show-sql=true
|
|||||||
spring.jpa.properties.hibernate.format_sql=true
|
spring.jpa.properties.hibernate.format_sql=true
|
||||||
|
|
||||||
# Configuration CORS pour autoriser le Frontend Angular
|
# Configuration CORS pour autoriser le Frontend Angular
|
||||||
spring.web.cors.allowed-origins=http://localhost:4200
|
spring.web.cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:4200}
|
||||||
spring.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
|
spring.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
|
||||||
spring.web.cors.allowed-headers=*
|
spring.web.cors.allowed-headers=*
|
||||||
spring.web.cors.allow-credentials=true
|
spring.web.cors.allow-credentials=true
|
||||||
|
|
||||||
# Configuration du Brain (service IA Python)
|
# Configuration du Brain (service IA Python)
|
||||||
brain.base-url=http://localhost:8000
|
brain.base-url=${BRAIN_BASE_URL:http://localhost:8000}
|
||||||
brain.timeout-seconds=120
|
brain.timeout-seconds=120
|
||||||
|
|
||||||
# Secret partage Core <-> Brain (auth inter-service via entete X-Internal-Secret).
|
# Secret partage Core <-> Brain (auth inter-service via entete X-Internal-Secret).
|
||||||
@@ -50,3 +50,7 @@ minio.bucket=${MINIO_BUCKET:loremind-images}
|
|||||||
# Limites d'upload d'images (MB)
|
# Limites d'upload d'images (MB)
|
||||||
spring.servlet.multipart.max-file-size=10MB
|
spring.servlet.multipart.max-file-size=10MB
|
||||||
spring.servlet.multipart.max-request-size=10MB
|
spring.servlet.multipart.max-request-size=10MB
|
||||||
|
|
||||||
|
# Mode demo : masque Settings/Export cote front et bloque les PUT /api/settings
|
||||||
|
# cote serveur. Activer via DEMO_MODE=true sur les deploiements publics.
|
||||||
|
app.demo-mode=${DEMO_MODE:false}
|
||||||
|
|||||||
@@ -60,7 +60,8 @@
|
|||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "web:build"
|
"buildTarget": "web:build",
|
||||||
|
"proxyConfig": "proxy.conf.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
web/proxy.conf.json
Normal file
8
web/proxy.conf.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"/api": {
|
||||||
|
"target": "http://localhost:8080",
|
||||||
|
"secure": false,
|
||||||
|
"changeOrigin": true,
|
||||||
|
"logLevel": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
import { hiddenInDemoGuard } from './guards/demo-mode.guard';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: 'lore', loadComponent: () => import('./lore/lore.component').then(m => m.LoreComponent) },
|
{ path: 'lore', loadComponent: () => import('./lore/lore.component').then(m => m.LoreComponent) },
|
||||||
@@ -30,6 +31,8 @@ export const routes: Routes = [
|
|||||||
{ path: 'game-systems', loadComponent: () => import('./game-systems/game-systems.component').then(m => m.GameSystemsComponent) },
|
{ path: 'game-systems', loadComponent: () => import('./game-systems/game-systems.component').then(m => m.GameSystemsComponent) },
|
||||||
{ path: 'game-systems/create', loadComponent: () => import('./game-systems/game-system-edit/game-system-edit.component').then(m => m.GameSystemEditComponent) },
|
{ path: 'game-systems/create', loadComponent: () => import('./game-systems/game-system-edit/game-system-edit.component').then(m => m.GameSystemEditComponent) },
|
||||||
{ path: 'game-systems/:id/edit', loadComponent: () => import('./game-systems/game-system-edit/game-system-edit.component').then(m => m.GameSystemEditComponent) },
|
{ path: 'game-systems/:id/edit', loadComponent: () => import('./game-systems/game-system-edit/game-system-edit.component').then(m => m.GameSystemEditComponent) },
|
||||||
{ path: 'settings', loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent) },
|
// Routes masquees en mode demo : ajouter canActivate: [hiddenInDemoGuard]
|
||||||
|
// (a prevoir aussi sur la future route d'export VTT).
|
||||||
|
{ path: 'settings', canActivate: [hiddenInDemoGuard], loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent) },
|
||||||
{ path: '', redirectTo: '/lore', pathMatch: 'full' }
|
{ path: '', redirectTo: '/lore', pathMatch: 'full' }
|
||||||
];
|
];
|
||||||
|
|||||||
17
web/src/app/guards/demo-mode.guard.ts
Normal file
17
web/src/app/guards/demo-mode.guard.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { CanActivateFn, Router } from '@angular/router';
|
||||||
|
import { ConfigService } from '../services/config.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bloque l'acces aux routes sensibles quand demoMode est actif et redirige
|
||||||
|
* vers la home. Defense UX ; le verrou serveur reste la source de verite.
|
||||||
|
*/
|
||||||
|
export const hiddenInDemoGuard: CanActivateFn = () => {
|
||||||
|
const config = inject(ConfigService);
|
||||||
|
const router = inject(Router);
|
||||||
|
if (config.demoMode) {
|
||||||
|
router.navigate(['/']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -45,8 +45,8 @@ export type NarrativeEntityType = 'arc' | 'chapter' | 'scene' | 'character';
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AiChatService {
|
export class AiChatService {
|
||||||
private readonly loreEndpoint = 'http://localhost:8080/api/ai/chat/stream';
|
private readonly loreEndpoint = '/api/ai/chat/stream';
|
||||||
private readonly campaignEndpoint = 'http://localhost:8080/api/ai/chat/stream-campaign';
|
private readonly campaignEndpoint = '/api/ai/chat/stream-campaign';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Streame la réponse de l'IA pour un historique de messages donné (chat ancré Lore).
|
* Streame la réponse de l'IA pour un historique de messages donné (chat ancré Lore).
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export interface ChapterDeletionImpact {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class CampaignService {
|
export class CampaignService {
|
||||||
private apiUrl = 'http://localhost:8080/api/campaigns';
|
private apiUrl = '/api/campaigns';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
@@ -60,73 +60,73 @@ export class CampaignService {
|
|||||||
|
|
||||||
// ========== ARC ==========
|
// ========== ARC ==========
|
||||||
getArcs(campaignId: string): Observable<Arc[]> {
|
getArcs(campaignId: string): Observable<Arc[]> {
|
||||||
return this.http.get<Arc[]>(`http://localhost:8080/api/arcs/campaign/${campaignId}`);
|
return this.http.get<Arc[]>(`/api/arcs/campaign/${campaignId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getArcById(id: string): Observable<Arc> {
|
getArcById(id: string): Observable<Arc> {
|
||||||
return this.http.get<Arc>(`http://localhost:8080/api/arcs/${id}`);
|
return this.http.get<Arc>(`/api/arcs/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
createArc(payload: ArcCreate): Observable<Arc> {
|
createArc(payload: ArcCreate): Observable<Arc> {
|
||||||
return this.http.post<Arc>('http://localhost:8080/api/arcs', payload);
|
return this.http.post<Arc>('/api/arcs', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateArc(id: string, payload: ArcCreate): Observable<Arc> {
|
updateArc(id: string, payload: ArcCreate): Observable<Arc> {
|
||||||
return this.http.put<Arc>(`http://localhost:8080/api/arcs/${id}`, payload);
|
return this.http.put<Arc>(`/api/arcs/${id}`, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteArc(id: string): Observable<void> {
|
deleteArc(id: string): Observable<void> {
|
||||||
return this.http.delete<void>(`http://localhost:8080/api/arcs/${id}`);
|
return this.http.delete<void>(`/api/arcs/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getArcDeletionImpact(id: string): Observable<ArcDeletionImpact> {
|
getArcDeletionImpact(id: string): Observable<ArcDeletionImpact> {
|
||||||
return this.http.get<ArcDeletionImpact>(`http://localhost:8080/api/arcs/${id}/deletion-impact`);
|
return this.http.get<ArcDeletionImpact>(`/api/arcs/${id}/deletion-impact`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== CHAPTER ==========
|
// ========== CHAPTER ==========
|
||||||
getChapters(arcId: string): Observable<Chapter[]> {
|
getChapters(arcId: string): Observable<Chapter[]> {
|
||||||
return this.http.get<Chapter[]>(`http://localhost:8080/api/chapters/arc/${arcId}`);
|
return this.http.get<Chapter[]>(`/api/chapters/arc/${arcId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChapterById(id: string): Observable<Chapter> {
|
getChapterById(id: string): Observable<Chapter> {
|
||||||
return this.http.get<Chapter>(`http://localhost:8080/api/chapters/${id}`);
|
return this.http.get<Chapter>(`/api/chapters/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
createChapter(payload: ChapterCreate): Observable<Chapter> {
|
createChapter(payload: ChapterCreate): Observable<Chapter> {
|
||||||
return this.http.post<Chapter>('http://localhost:8080/api/chapters', payload);
|
return this.http.post<Chapter>('/api/chapters', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChapter(id: string, payload: ChapterCreate): Observable<Chapter> {
|
updateChapter(id: string, payload: ChapterCreate): Observable<Chapter> {
|
||||||
return this.http.put<Chapter>(`http://localhost:8080/api/chapters/${id}`, payload);
|
return this.http.put<Chapter>(`/api/chapters/${id}`, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteChapter(id: string): Observable<void> {
|
deleteChapter(id: string): Observable<void> {
|
||||||
return this.http.delete<void>(`http://localhost:8080/api/chapters/${id}`);
|
return this.http.delete<void>(`/api/chapters/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChapterDeletionImpact(id: string): Observable<ChapterDeletionImpact> {
|
getChapterDeletionImpact(id: string): Observable<ChapterDeletionImpact> {
|
||||||
return this.http.get<ChapterDeletionImpact>(`http://localhost:8080/api/chapters/${id}/deletion-impact`);
|
return this.http.get<ChapterDeletionImpact>(`/api/chapters/${id}/deletion-impact`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== SCENE ==========
|
// ========== SCENE ==========
|
||||||
getScenes(chapterId: string): Observable<Scene[]> {
|
getScenes(chapterId: string): Observable<Scene[]> {
|
||||||
return this.http.get<Scene[]>(`http://localhost:8080/api/scenes/chapter/${chapterId}`);
|
return this.http.get<Scene[]>(`/api/scenes/chapter/${chapterId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSceneById(id: string): Observable<Scene> {
|
getSceneById(id: string): Observable<Scene> {
|
||||||
return this.http.get<Scene>(`http://localhost:8080/api/scenes/${id}`);
|
return this.http.get<Scene>(`/api/scenes/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
createScene(payload: SceneCreate): Observable<Scene> {
|
createScene(payload: SceneCreate): Observable<Scene> {
|
||||||
return this.http.post<Scene>('http://localhost:8080/api/scenes', payload);
|
return this.http.post<Scene>('/api/scenes', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScene(id: string, payload: SceneCreate): Observable<Scene> {
|
updateScene(id: string, payload: SceneCreate): Observable<Scene> {
|
||||||
return this.http.put<Scene>(`http://localhost:8080/api/scenes/${id}`, payload);
|
return this.http.put<Scene>(`/api/scenes/${id}`, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteScene(id: string): Observable<void> {
|
deleteScene(id: string): Observable<void> {
|
||||||
return this.http.delete<void>(`http://localhost:8080/api/scenes/${id}`);
|
return this.http.delete<void>(`/api/scenes/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
search(q: string): Observable<Campaign[]> {
|
search(q: string): Observable<Campaign[]> {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Character, CharacterCreate } from './character.model';
|
|||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class CharacterService {
|
export class CharacterService {
|
||||||
private apiUrl = 'http://localhost:8080/api/characters';
|
private apiUrl = '/api/characters';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
|||||||
31
web/src/app/services/config.service.ts
Normal file
31
web/src/app/services/config.service.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration publique chargee une seule fois au demarrage via APP_INITIALIZER.
|
||||||
|
* Le flag demoMode bascule l'UI en mode vitrine (Settings/Export masques).
|
||||||
|
*/
|
||||||
|
export interface PublicConfig {
|
||||||
|
demoMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class ConfigService {
|
||||||
|
private config: PublicConfig = { demoMode: false };
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
async load(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.config = await firstValueFrom(this.http.get<PublicConfig>('/api/config'));
|
||||||
|
} catch {
|
||||||
|
// Si l'endpoint n'est pas joignable au boot, on reste sur le default
|
||||||
|
// (demoMode=false) pour ne pas bloquer l'app en dev.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get demoMode(): boolean {
|
||||||
|
return this.config.demoMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ConversationService {
|
export class ConversationService {
|
||||||
private readonly apiUrl = 'http://localhost:8080/api/conversations';
|
private readonly apiUrl = '/api/conversations';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { GameSystem, GameSystemCreate } from './game-system.model';
|
|||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class GameSystemService {
|
export class GameSystemService {
|
||||||
private apiUrl = 'http://localhost:8080/api/game-systems';
|
private apiUrl = '/api/game-systems';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { Image } from './image.model';
|
|||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ImageService {
|
export class ImageService {
|
||||||
/** Base absolue du backend — utile pour construire des URLs complètes (<img src>). */
|
/** Base du backend (vide = même origine que le front, résolue par le navigateur). */
|
||||||
readonly apiBase = 'http://localhost:8080';
|
readonly apiBase = '';
|
||||||
private apiUrl = `${this.apiBase}/api/images`;
|
private apiUrl = `${this.apiBase}/api/images`;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export interface LoreDeletionImpact {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class LoreService {
|
export class LoreService {
|
||||||
private apiUrl = 'http://localhost:8080/api/lores';
|
private apiUrl = '/api/lores';
|
||||||
private nodesUrl = 'http://localhost:8080/api/lore-nodes';
|
private nodesUrl = '/api/lore-nodes';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Page, PageCreate } from './page.model';
|
|||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PageService {
|
export class PageService {
|
||||||
private apiUrl = 'http://localhost:8080/api/pages';
|
private apiUrl = '/api/pages';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export interface OllamaModelInfo {
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SettingsService {
|
export class SettingsService {
|
||||||
private readonly apiUrl = 'http://localhost:8080/api/settings';
|
private readonly apiUrl = '/api/settings';
|
||||||
|
|
||||||
// HTTP Basic : le browser gere le prompt natif de credentials au premier 401.
|
// HTTP Basic : le browser gere le prompt natif de credentials au premier 401.
|
||||||
// withCredentials=true pour que les creds soient renvoyees sur les appels
|
// withCredentials=true pour que les creds soient renvoyees sur les appels
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Template, TemplateCreate } from './template.model';
|
|||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TemplateService {
|
export class TemplateService {
|
||||||
private apiUrl = 'http://localhost:8080/api/templates';
|
private apiUrl = '/api/templates';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,11 @@
|
|||||||
<lucide-icon [img]="Dices" [size]="16"></lucide-icon>
|
<lucide-icon [img]="Dices" [size]="16"></lucide-icon>
|
||||||
<span>Systèmes de JDR</span>
|
<span>Systèmes de JDR</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="tool-btn">
|
<button class="tool-btn" *ngIf="!config.demoMode">
|
||||||
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
<lucide-icon [img]="Download" [size]="16"></lucide-icon>
|
||||||
<span>Export VTT</span>
|
<span>Export VTT</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="tool-btn" [class.active]="currentRoute.startsWith('/settings')" (click)="navigateTo('/settings')">
|
<button class="tool-btn" *ngIf="!config.demoMode" [class.active]="currentRoute.startsWith('/settings')" (click)="navigateTo('/settings')">
|
||||||
<lucide-icon [img]="Settings" [size]="16"></lucide-icon>
|
<lucide-icon [img]="Settings" [size]="16"></lucide-icon>
|
||||||
<span>Paramètres</span>
|
<span>Paramètres</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Router } from '@angular/router';
|
|||||||
import { LucideAngularModule, Search, Download, Settings, ArrowLeft, Dices } from 'lucide-angular';
|
import { LucideAngularModule, Search, Download, Settings, ArrowLeft, Dices } from 'lucide-angular';
|
||||||
import { LayoutService } from '../services/layout.service';
|
import { LayoutService } from '../services/layout.service';
|
||||||
import { GlobalSearchService } from '../services/global-search.service';
|
import { GlobalSearchService } from '../services/global-search.service';
|
||||||
|
import { ConfigService } from '../services/config.service';
|
||||||
// Single source of truth pour la version affichée dans le footer :
|
// Single source of truth pour la version affichée dans le footer :
|
||||||
// on lit directement package.json à la compilation (resolveJsonModule).
|
// on lit directement package.json à la compilation (resolveJsonModule).
|
||||||
import packageJson from '../../../package.json';
|
import packageJson from '../../../package.json';
|
||||||
@@ -30,7 +31,8 @@ export class SidebarComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private globalSearch: GlobalSearchService
|
private globalSearch: GlobalSearchService,
|
||||||
|
public config: ConfigService
|
||||||
) {
|
) {
|
||||||
this.router.events.subscribe(() => {
|
this.router.events.subscribe(() => {
|
||||||
this.currentRoute = this.router.url;
|
this.currentRoute = this.router.url;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { AppComponent } from './app/app.component';
|
|||||||
import { PreloadAllModules, provideRouter, withPreloading } from '@angular/router';
|
import { PreloadAllModules, provideRouter, withPreloading } from '@angular/router';
|
||||||
import { routes } from './app/app.routes';
|
import { routes } from './app/app.routes';
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { APP_INITIALIZER } from '@angular/core';
|
||||||
|
import { ConfigService } from './app/services/config.service';
|
||||||
|
|
||||||
// withPreloading(PreloadAllModules) : une fois l'app initiale rendue, Angular
|
// withPreloading(PreloadAllModules) : une fois l'app initiale rendue, Angular
|
||||||
// telecharge en arriere-plan tous les chunks lazy-loades. Consequence : la
|
// telecharge en arriere-plan tous les chunks lazy-loades. Consequence : la
|
||||||
@@ -13,5 +15,11 @@ bootstrapApplication(AppComponent, {
|
|||||||
providers: [
|
providers: [
|
||||||
provideRouter(routes, withPreloading(PreloadAllModules)),
|
provideRouter(routes, withPreloading(PreloadAllModules)),
|
||||||
provideHttpClient(),
|
provideHttpClient(),
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: (config: ConfigService) => () => config.load(),
|
||||||
|
deps: [ConfigService],
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).catch((err: Error) => console.error(err));
|
}).catch((err: Error) => console.error(err));
|
||||||
|
|||||||
Reference in New Issue
Block a user