@@ -19,6 +19,7 @@ import {
1919} from '../viewer-filters.js' ;
2020import { logToHar } from '../services/har-service.js' ;
2121import { logToPython } from '../services/python-service.js' ;
22+ import { pinLog , unpinLog , isPinned , getPinnedSet } from '../pinned.js' ;
2223
2324export function createViewerController ( config ) {
2425 return {
@@ -29,19 +30,21 @@ export function createViewerController(config) {
2930 const baseUrlFilters = normalizeBaseUrlFilters ( parseCsvParam ( req . query . baseUrl ) ) ;
3031 const aliasFilters = normalizeAliasFilters ( parseCsvParam ( req . query . alias ) ) ;
3132 const methodFilters = normalizeMethodFilters ( parseCsvParam ( req . query . method ) ) ;
33+ const pinnedFilter = req . query . pinned === '1' ;
3234 const { aliasByHost, aliasHostMap, aliasNameMap } = buildAliasMaps ( config . aliases ) ;
35+ const pinnedSet = getPinnedSet ( ) ;
36+
3337 const { logs, total } = await getViewerIndexData (
3438 config . outputDir ,
3539 {
36- limit,
37- offset,
40+ limit : pinnedFilter ? 1000 : limit ,
41+ offset : pinnedFilter ? 0 : offset ,
3842 baseUrls : baseUrlFilters ,
3943 aliases : aliasFilters ,
4044 methods : methodFilters ,
4145 aliasHostMap,
4246 }
4347 ) ;
44- const totalPages = Math . ceil ( total / limit ) ;
4548
4649 const processedLogs = logs . map ( ( log ) => {
4750 const baseUrl = normalizeBaseUrlValue ( log ?. request ?. url ) ;
@@ -51,6 +54,8 @@ export function createViewerController(config) {
5154 ( baseUrl ? aliasByHost [ baseUrl ] : null ) ||
5255 null ;
5356 const messageCount = getRequestMessageCount ( log ) ;
57+ const logId = `${ log . _viewer_provider } /${ log . _viewer_file } ` ;
58+ const logPinned = pinnedSet . has ( logId ) ;
5459 try {
5560 const url = new URL ( log . request . url ) ;
5661 const hidden = shouldHideFromViewer ( url . pathname ) ;
@@ -61,6 +66,7 @@ export function createViewerController(config) {
6166 _base_url : baseUrl ,
6267 _alias : aliasLabel ,
6368 _message_count : messageCount ,
69+ _pinned : logPinned ,
6470 } ;
6571 } catch {
6672 return {
@@ -69,20 +75,32 @@ export function createViewerController(config) {
6975 _base_url : baseUrl ,
7076 _alias : aliasLabel ,
7177 _message_count : messageCount ,
78+ _pinned : logPinned ,
7279 } ;
7380 }
7481 } ) ;
7582
83+ // Filter to pinned only if requested
84+ const filteredLogs = pinnedFilter
85+ ? processedLogs . filter ( ( log ) => log . _pinned )
86+ : processedLogs ;
87+ const filteredTotal = pinnedFilter ? filteredLogs . length : total ;
88+ const paginatedLogs = pinnedFilter
89+ ? filteredLogs . slice ( offset , offset + limit )
90+ : filteredLogs ;
91+ const totalPages = Math . ceil ( filteredTotal / limit ) ;
92+
7693 const html = await renderViewer (
7794 {
78- logs : processedLogs ,
95+ logs : paginatedLogs ,
7996 limit,
8097 page,
8198 totalPages,
82- total,
99+ total : filteredTotal ,
83100 baseUrlFilters,
84101 aliasFilters,
85102 methodFilters,
103+ pinnedFilter,
86104 aliasByHost,
87105 }
88106 ) ;
@@ -140,7 +158,7 @@ export function createViewerController(config) {
140158
141159 if ( invalid . length || selections . length < 2 ) {
142160 const message = invalid . length
143- ? 'Invalid compare selection. Choose two or three valid log entries.'
161+ ? 'Invalid compare selection. Choose two to five valid log entries.'
144162 : 'Select at least two logs to compare.' ;
145163 const html = await renderViewerCompare ( {
146164 logs : [ ] ,
@@ -306,6 +324,46 @@ export function createViewerController(config) {
306324 res . status ( 500 ) . json ( { error : 'Download Python error' , message : error . message } ) ;
307325 }
308326 } ,
327+
328+ pin : async ( req , res ) => {
329+ try {
330+ const { provider, filename } = req . params ;
331+ const logId = `${ provider } /${ filename } ` ;
332+ const log = await loadViewerLog ( config . outputDir , provider , filename ) ;
333+ if ( ! log ) {
334+ res . status ( 404 ) . json ( { error : 'Not found' } ) ;
335+ return ;
336+ }
337+ const result = pinLog ( logId ) ;
338+ res . json ( { success : true , ...result } ) ;
339+ } catch ( error ) {
340+ console . error ( 'Viewer pin error:' , error . message ) ;
341+ res . status ( 500 ) . json ( { error : 'Pin error' , message : error . message } ) ;
342+ }
343+ } ,
344+
345+ unpin : async ( req , res ) => {
346+ try {
347+ const { provider, filename } = req . params ;
348+ const logId = `${ provider } /${ filename } ` ;
349+ const result = unpinLog ( logId ) ;
350+ res . json ( { success : true , ...result } ) ;
351+ } catch ( error ) {
352+ console . error ( 'Viewer unpin error:' , error . message ) ;
353+ res . status ( 500 ) . json ( { error : 'Unpin error' , message : error . message } ) ;
354+ }
355+ } ,
356+
357+ getPinStatus : async ( req , res ) => {
358+ try {
359+ const { provider, filename } = req . params ;
360+ const logId = `${ provider } /${ filename } ` ;
361+ res . json ( { pinned : isPinned ( logId ) , logId } ) ;
362+ } catch ( error ) {
363+ console . error ( 'Viewer pin status error:' , error . message ) ;
364+ res . status ( 500 ) . json ( { error : 'Pin status error' , message : error . message } ) ;
365+ }
366+ } ,
309367 } ;
310368}
311369
0 commit comments