Skip to content

Commit ff99cf7

Browse files
authored
Merge pull request #447 from opcodesio/feature/log-viewer-defaults
Feature / LogViewer defaults
2 parents 726afaa + 09b4ff6 commit ff99cf7

15 files changed

+255
-19
lines changed

config/log-viewer.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<?php
22

3+
use Opcodes\LogViewer\Enums\FolderSortingMethod;
4+
use Opcodes\LogViewer\Enums\SortingOrder;
5+
use Opcodes\LogViewer\Enums\Theme;
6+
37
return [
48

59
/*
@@ -241,4 +245,52 @@
241245
*/
242246

243247
'per_page_options' => [10, 25, 50, 100, 250, 500],
248+
249+
/*
250+
|--------------------------------------------------------------------------
251+
| Default settings for Log Viewer
252+
|--------------------------------------------------------------------------
253+
| These settings determine the default behaviour of Log Viewer. Many of
254+
| these can be persisted for the user in their browser's localStorage,
255+
| if the `use_local_storage` option is set to true.
256+
|
257+
*/
258+
259+
'defaults' => [
260+
261+
// Whether to use browser's localStorage to store user preferences.
262+
// If true, user preferences saved in the browser will take precedence over the defaults below.
263+
'use_local_storage' => true,
264+
265+
// Method to sort the folders. Other options: `Alphabetical`, `ModifiedTime`
266+
'folder_sorting_method' => FolderSortingMethod::ModifiedTime,
267+
268+
// Order to sort the folders. Other options: `Ascending`, `Descending`
269+
'folder_sorting_order' => SortingOrder::Descending,
270+
271+
// Order to sort the logs. Other options: `Ascending`, `Descending`
272+
'log_sorting_order' => SortingOrder::Descending,
273+
274+
// Number of results per page. Must be one of the above `per_page_options` values
275+
'per_page' => 25,
276+
277+
// Color scheme for the Log Viewer. Other options: `System`, `Light`, `Dark`
278+
'theme' => Theme::System,
279+
280+
// Whether to enable `Shorter Stack Traces` option by default
281+
'shorter_stack_traces' => false,
282+
283+
],
284+
285+
/*
286+
|--------------------------------------------------------------------------
287+
| Root folder prefix
288+
|--------------------------------------------------------------------------
289+
| The prefix for log files inside Laravel's `storage/logs` folder.
290+
| Log Viewer does not show the full path to these files in the UI,
291+
| but only the filename prefixed with this value.
292+
|
293+
*/
294+
295+
'root_folder_prefix' => 'root',
244296
];

public/app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/mix-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"/app.js": "/app.js?id=c5e0f20ee42d437f446958f2c1001581",
2+
"/app.js": "/app.js?id=aad92fca2262c03653db52125757e99d",
33
"/app.css": "/app.css?id=5593a0331dd40729ff41e32a6035d872",
44
"/img/log-viewer-128.png": "/img/log-viewer-128.png?id=d576c6d2e16074d3f064e60fe4f35166",
55
"/img/log-viewer-32.png": "/img/log-viewer-32.png?id=f8ec67d10f996aa8baf00df3b61eea6d",

resources/js/components/FileList.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@
9898
<ChevronRightIcon :class="[fileStore.isOpen(folder) ? 'rotate-90' : '', 'transition duration-100']" />
9999
</span>
100100
<span class="file-name">
101-
<span v-if="String(folder.clean_path || '').startsWith('root')">
102-
<span class="text-gray-500 dark:text-gray-400">root</span>{{ String(folder.clean_path).substring(4) }}
101+
<span v-if="String(folder.clean_path || '').startsWith(rootFolderPrefix)">
102+
<span class="text-gray-500 dark:text-gray-400">{{ rootFolderPrefix }}</span>{{ String(folder.clean_path).substring(rootFolderPrefix.length) }}
103103
</span>
104104
<span v-else>{{ folder.clean_path }}</span>
105105
</span>
@@ -255,6 +255,8 @@ const selectFile = (fileIdentifier) => {
255255
}
256256
};
257257
258+
const rootFolderPrefix = window.LogViewer?.root_folder_prefix || 'root';
259+
258260
onMounted(async () => {
259261
hostStore.selectHost(route.query.host || null);
260262
});

resources/js/stores/logViewer.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,24 @@ const defaultColumns = [
2121
{ label: 'Message', data_key: 'message' },
2222
]
2323

24+
const shouldUseLocalStorage = window.LogViewer?.defaults?.use_local_storage ?? true;
25+
2426
export const useLogViewerStore = defineStore({
2527
id: 'logViewer',
2628

2729
state: () => ({
28-
theme: useLocalStorage('logViewerTheme', Theme.System),
29-
shorterStackTraces: useLocalStorage('logViewerShorterStackTraces', false),
30-
direction: useLocalStorage('logViewerDirection', 'desc'),
31-
resultsPerPage: useLocalStorage('logViewerResultsPerPage', 25),
30+
theme: shouldUseLocalStorage
31+
? useLocalStorage('logViewerTheme', window.LogViewer?.defaults?.theme || Theme.System)
32+
: (window.LogViewer?.defaults?.theme || Theme.System),
33+
shorterStackTraces: shouldUseLocalStorage
34+
? useLocalStorage('logViewerShorterStackTraces', window.LogViewer?.defaults?.shorter_stack_traces ?? false)
35+
: (window.LogViewer?.defaults?.shorter_stack_traces ?? false),
36+
resultsPerPage: shouldUseLocalStorage
37+
? useLocalStorage('logViewerResultsPerPage', window.LogViewer?.defaults?.per_page ?? 25)
38+
: (window.LogViewer?.defaults?.per_page ?? 25),
39+
direction: shouldUseLocalStorage
40+
? useLocalStorage('logViewerDirection', window.LogViewer?.defaults?.log_sorting_order || 'desc')
41+
: (window.LogViewer?.defaults?.log_sorting_order || 'desc'),
3242
helpSlideOverOpen: false,
3343

3444
// Log data

src/Enums/FolderSortingMethod.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Opcodes\LogViewer\Enums;
4+
5+
class FolderSortingMethod
6+
{
7+
public const Alphabetical = 'Alphabetical';
8+
public const ModifiedTime = 'ModifiedTime';
9+
}

src/Enums/SortingOrder.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Opcodes\LogViewer\Enums;
4+
5+
class SortingOrder
6+
{
7+
public const Ascending = 'asc';
8+
public const Descending = 'desc';
9+
}

src/Enums/Theme.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Opcodes\LogViewer\Enums;
4+
5+
class Theme
6+
{
7+
public const System = 'System';
8+
public const Light = 'Light';
9+
public const Dark = 'Dark';
10+
}

src/Http/Controllers/FoldersController.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use Illuminate\Http\Request;
66
use Illuminate\Support\Facades\Gate;
77
use Illuminate\Support\Facades\URL;
8+
use Opcodes\LogViewer\Enums\FolderSortingMethod;
9+
use Opcodes\LogViewer\Enums\SortingOrder;
810
use Opcodes\LogViewer\Facades\LogViewer;
911
use Opcodes\LogViewer\Http\Resources\LogFolderResource;
1012
use Opcodes\LogViewer\LogFile;
@@ -15,10 +17,32 @@ public function index(Request $request)
1517
{
1618
$folders = LogViewer::getFilesGroupedByFolder();
1719

18-
if ($request->query('direction', 'desc') === 'asc') {
19-
$folders = $folders->sortByEarliestFirstIncludingFiles();
20-
} else {
21-
$folders = $folders->sortByLatestFirstIncludingFiles();
20+
$sortingMethod = config('log-viewer.defaults.folder_sorting_method', FolderSortingMethod::ModifiedTime);
21+
$sortingOrder = config('log-viewer.defaults.folder_sorting_order', SortingOrder::Descending);
22+
23+
$fileSortingOrder = $request->query('direction', 'desc');
24+
25+
if ($sortingMethod === FolderSortingMethod::Alphabetical) {
26+
if ($sortingOrder === SortingOrder::Ascending) {
27+
$folders = $folders->sortAlphabeticallyAsc();
28+
} else {
29+
$folders = $folders->sortAlphabeticallyDesc();
30+
}
31+
32+
// Still sort files inside folders by direction param
33+
$folders->each(function ($folder) use ($fileSortingOrder) {
34+
if ($fileSortingOrder === 'asc') {
35+
$folder->files()->sortByEarliestFirst();
36+
} else {
37+
$folder->files()->sortByLatestFirst();
38+
}
39+
});
40+
} else { // ModifiedTime
41+
if ($fileSortingOrder === 'asc') {
42+
$folders = $folders->sortByEarliestFirstIncludingFiles();
43+
} else {
44+
$folders = $folders->sortByLatestFirstIncludingFiles();
45+
}
2246
}
2347

2448
return LogFolderResource::collection($folders->values());

src/Http/Controllers/IndexController.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Opcodes\LogViewer\Http\Controllers;
44

55
use Opcodes\LogViewer\Facades\LogViewer;
6+
use Opcodes\LogViewer\LogFolder;
67
use Opcodes\LogViewer\Utils\Utils;
78

89
class IndexController
@@ -28,6 +29,14 @@ public function __invoke()
2829
'supports_hosts' => LogViewer::supportsHostsFeature(),
2930
'hosts' => LogViewer::getHosts(),
3031
'per_page_options' => config('log-viewer.per_page_options') ?? [10, 25, 50, 100, 250, 500],
32+
'defaults' => [
33+
'use_local_storage' => config('log-viewer.defaults.use_local_storage'),
34+
'log_sorting_order' => config('log-viewer.defaults.log_sorting_order'),
35+
'per_page' => config('log-viewer.defaults.per_page'),
36+
'theme' => config('log-viewer.defaults.theme'),
37+
'shorter_stack_traces' => config('log-viewer.defaults.shorter_stack_traces'),
38+
],
39+
'root_folder_prefix' => LogFolder::rootPrefix(),
3140
],
3241
]);
3342
}

src/LogFolder.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class LogFolder
1111
{
1212
public string $identifier;
1313
protected mixed $files;
14+
protected static string $rootPrefix;
1415

1516
public function __construct(
1617
public string $path,
@@ -38,15 +39,27 @@ public function isRoot(): bool
3839
|| $this->path === rtrim(LogViewer::basePathForLogs(), DIRECTORY_SEPARATOR);
3940
}
4041

42+
/**
43+
* Returns the prefix string used to represent the root folder.
44+
*/
45+
public static function rootPrefix(): string
46+
{
47+
if (! isset(self::$rootPrefix)) {
48+
self::$rootPrefix = config('log-viewer.root_folder_prefix', 'root');
49+
}
50+
51+
return self::$rootPrefix;
52+
}
53+
4154
public function cleanPath(): string
4255
{
4356
if ($this->isRoot()) {
44-
return 'root';
57+
return self::rootPrefix();
4558
}
4659

4760
$folder = $this->path;
4861

49-
$folder = str_replace(LogViewer::basePathForLogs(), 'root'.DIRECTORY_SEPARATOR, $folder);
62+
$folder = str_replace(LogViewer::basePathForLogs(), self::rootPrefix().DIRECTORY_SEPARATOR, $folder);
5063

5164
if ($unixHomePath = getenv('HOME')) {
5265
$folder = str_replace($unixHomePath, '~', $folder);

src/LogFolderCollection.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,42 @@ public function sortByLatestFirstIncludingFiles(): self
4545

4646
return $this;
4747
}
48+
49+
public function sortAlphabeticallyAsc(): self
50+
{
51+
$this->items = collect($this->items)
52+
->sort(function (LogFolder $a, LogFolder $b) {
53+
if ($a->isRoot() && ! $b->isRoot()) {
54+
return -1;
55+
}
56+
if (! $a->isRoot() && $b->isRoot()) {
57+
return 1;
58+
}
59+
60+
return strcmp($a->cleanPath(), $b->cleanPath());
61+
})
62+
->values()
63+
->toArray();
64+
65+
return $this;
66+
}
67+
68+
public function sortAlphabeticallyDesc(): self
69+
{
70+
$this->items = collect($this->items)
71+
->sort(function (LogFolder $a, LogFolder $b) {
72+
if ($a->isRoot() && ! $b->isRoot()) {
73+
return -1;
74+
}
75+
if (! $a->isRoot() && $b->isRoot()) {
76+
return 1;
77+
}
78+
79+
return strcmp($b->cleanPath(), $a->cleanPath());
80+
})
81+
->values()
82+
->toArray();
83+
84+
return $this;
85+
}
4886
}

tests/Feature/Authorization/CanDownloadFoldersTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function assertCannotDownloadFolder(string $folderName): void
2929
generateLogFiles([$fileName = 'laravel.log']);
3030
$folder = LogViewer::getFolder('');
3131

32-
assertCanDownloadFolder($folder->identifier, 'root.zip');
32+
assertCanDownloadFolder($folder->identifier, LogFolder::rootPrefix().'.zip');
3333
});
3434

3535
test('cannot download a folder that\'s not found', function () {
@@ -47,7 +47,7 @@ function assertCannotDownloadFolder(string $folderName): void
4747
// now let's allow access again
4848
Gate::define('downloadLogFolder', fn (mixed $user) => true);
4949

50-
assertCanDownloadFolder($folder->identifier, 'root.zip');
50+
assertCanDownloadFolder($folder->identifier, LogFolder::rootPrefix().'.zip');
5151
});
5252

5353
test('"downloadLogFolder" gate is supplied with a log folder object', function () {
@@ -63,7 +63,7 @@ function assertCannotDownloadFolder(string $folderName): void
6363
return true;
6464
});
6565

66-
assertCanDownloadFolder($expectedFolder->identifier, 'root.zip');
66+
assertCanDownloadFolder($expectedFolder->identifier, LogFolder::rootPrefix().'.zip');
6767

6868
expect($gateChecked)->toBeTrue();
6969
});

tests/Feature/LogFoldersControllerTest.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
<?php
22

3+
use Opcodes\LogViewer\Enums\FolderSortingMethod;
4+
use Opcodes\LogViewer\Enums\SortingOrder;
5+
use Opcodes\LogViewer\LogFolder;
6+
37
use function Pest\Laravel\getJson;
48

59
beforeEach(function () {
610
config(['log-viewer.include_files' => ['*/**.log']]);
711
});
812

913
it('can get the log files', function () {
14+
config(['log-viewer.defaults.folder_sorting_method' => FolderSortingMethod::ModifiedTime]);
15+
config(['log-viewer.defaults.folder_sorting_order' => SortingOrder::Descending]);
16+
1017
generateLogFiles([
1118
'one/1.one.log',
1219
'one/2.two.log',
@@ -17,6 +24,29 @@
1724

1825
expect($response->json())->not->toHaveKey('data');
1926
$response->assertJsonCount(2)
20-
->assertJsonFragment(['clean_path' => 'root'.DIRECTORY_SEPARATOR.'one'])
21-
->assertJsonFragment(['clean_path' => 'root'.DIRECTORY_SEPARATOR.'two']);
27+
->assertJsonFragment(['clean_path' => LogFolder::rootPrefix().DIRECTORY_SEPARATOR.'one'])
28+
->assertJsonFragment(['clean_path' => LogFolder::rootPrefix().DIRECTORY_SEPARATOR.'two']);
29+
});
30+
31+
it('folders are sorted alphabetically descending when configured', function () {
32+
config(['log-viewer.include_files' => ['*.log', '*/**.log']]);
33+
config(['log-viewer.defaults.folder_sorting_method' => FolderSortingMethod::Alphabetical]);
34+
config(['log-viewer.defaults.folder_sorting_order' => SortingOrder::Ascending]);
35+
36+
generateLogFiles([
37+
'one/1.one.log',
38+
'one/2.two.log',
39+
'two/3.three.log',
40+
'alpha/4.alpha.log',
41+
'laravel.log',
42+
], randomContent: true);
43+
44+
$response = getJson(route('log-viewer.folders'));
45+
$folders = $response->json();
46+
// Should be sorted: 'root', 'alpha', 'one', 'two'
47+
$response->assertJsonCount(4);
48+
expect($folders[0]['clean_path'])->toBe(LogFolder::rootPrefix());
49+
expect($folders[1]['clean_path'])->toBe(LogFolder::rootPrefix().DIRECTORY_SEPARATOR.'alpha');
50+
expect($folders[2]['clean_path'])->toBe(LogFolder::rootPrefix().DIRECTORY_SEPARATOR.'one');
51+
expect($folders[3]['clean_path'])->toBe(LogFolder::rootPrefix().DIRECTORY_SEPARATOR.'two');
2252
});

0 commit comments

Comments
 (0)