Skip to content

Commit f4a8e28

Browse files
authored
Add support for multiple hosts (#63)
1 parent 5e0b862 commit f4a8e28

File tree

70 files changed

+1572
-240
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1572
-240
lines changed

.github/workflows/test.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ jobs:
8585
runs-on: ubuntu-latest
8686
steps:
8787
- uses: actions/checkout@v4
88+
8889
- name: Use Node.js 21.x
8990
uses: actions/setup-node@v4
9091
with:
@@ -97,10 +98,17 @@ jobs:
9798
run: cd frontend && npm run lint
9899

99100
build-assets-check:
100-
name: Check build assets
101+
name: Build assets
101102
runs-on: ubuntu-latest
103+
104+
permissions:
105+
contents: write
106+
102107
steps:
103108
- uses: actions/checkout@v4
109+
with:
110+
ref: ${{ github.head_ref }}
111+
104112
- name: Use Node.js 21.x
105113
uses: actions/setup-node@v4
106114
with:
@@ -112,5 +120,6 @@ jobs:
112120
- name: Build assets
113121
run: cd frontend && npm run build
114122

115-
- name: Git diff - expecting no changes
116-
run: git status | grep "nothing to commit, working tree clean"
123+
- uses: stefanzweifel/git-auto-commit-action@v5
124+
with:
125+
commit_message: 'Auto build assets [skip ci]'

CONTRIBUTING.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ A minimal Symfony project has been setup under `/dev/`. To start development, in
3535
After the containers are up and running, the project is available at http://localhost:8888.
3636

3737
> **_NOTE:_** the nodejs container will automatically rebuild the frontend assets and deploy them in the `/public/` directory
38-
39-
> **_NOTE:_** When making changes to the frontend, ensure committing the compiled assets.
4038
4139
## Running tests and code style checks
4240

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ read logs from any directory.
1919

2020
### Features
2121

22-
- 📂 **View all the Monolog, Apache2 or Nginx logs** in specified directories,
22+
- 📂 View all the **Monolog logs** in the `%kernel.logs_dir%` directory,
23+
- 📂 **View other types of logs** - Apache, Nginx, or custom logs,
2324
- 🔍 **Search** the logs,
24-
- 🎚 **Filter** by log level (error, info, debug, etc.), by channel, date range or log content,
25+
- 🔍 **Filter** by log level (error, info, debug, etc.), by channel, date range or log content inclusion or exclusion,
2526
- 🌑 **Dark mode**,
27+
- 🖥️ **Multiple host** support,
2628
- 💾 **Download** or **delete** log files from the UI,
2729
- ☎️ **API access** for folders, files & log entries,
2830

@@ -83,6 +85,7 @@ By default, it is available at: `/log-viewer` on your domain.
8385
- [Adding additional log files](docs/adding-additional-log-files.md)
8486
- [Adding apache logs](docs/configuring-apache-logs.md)
8587
- [Adding nginx logs](docs/configuring-nginx-logs.md)
88+
- [Adding remote hosts](docs/adding-remote-hosts.md)
8689
- [Configuring the back home url](docs/configuring-the-back-home-route.md)
8790
- [Advanced search queries](docs/advanced-search-queries.md)
8891
- [Full configuration reference](docs/configuration-reference.md)

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"minimum-stability": "stable",
2121
"config": {
2222
"sort-packages": true,
23+
"lock": false,
2324
"allow-plugins": {
2425
"symfony/runtime": true,
2526
"phpstan/extension-installer": true
@@ -56,6 +57,7 @@
5657
"symfony/browser-kit": "^6.0||^7.0",
5758
"symfony/console": "^6.0||^7.0",
5859
"symfony/css-selector": "^6.0||^7.0",
60+
"symfony/http-client": "^6.0||^7.0",
5961
"symfony/monolog-bridge": "^6.0||^7.0",
6062
"symfony/monolog-bundle": "^3.10",
6163
"symfony/phpunit-bridge": "^6.4||^7.0",
@@ -81,7 +83,8 @@
8183
}
8284
},
8385
"suggest": {
84-
"ext-zip": "To use the zip download feature"
86+
"ext-zip": "To use the zip download feature",
87+
"symfony/http-client": "Required for the remote hosts feature"
8588
},
8689
"scripts": {
8790
"check": ["@check:phpstan", "@check:phpmd", "@check:phpcs"],

dev/config/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ parameters:
44
framework:
55
test: ~
66
secret: dev
7+
ide: "javascript:fetch('http://localhost:63342/api/file?file=%%f&line=%%l'.replace('/app/', ''));"
78
router:
89
resource: "%kernel.project_dir%/config/routing.yml"
910
strict_requirements: true
@@ -13,6 +14,8 @@ framework:
1314
log: true
1415
handle_all_throwables: true
1516
default_locale: en
17+
http_client:
18+
enabled: true
1619
profiler:
1720
enabled: true
1821
only_exceptions: false

docs/adding-remote-hosts.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
## Add remote hosts
2+
3+
By default the log viewer only shows logs from the local server. If you want to add logs from a remote server, you can do so by adding a `hosts`
4+
section to the `fd_log_viewer` configuration.
5+
6+
### Configuration
7+
Add one or more hosts to the `fd_log_viewer` configuration. Each host should have a unique key. Different types of authentication can be used.
8+
```yaml
9+
fd_log_viewer:
10+
hosts:
11+
# The default localhost. Can be removed to only have remote hosts.
12+
localhost:
13+
name: Local
14+
host: null
15+
16+
remote-public:
17+
name: Remote without authentication
18+
host: https://example.com/log-viewer
19+
20+
# basic auth. Header: Authorization: Basic dXNlcjpwYXNz
21+
remote-basic:
22+
name: Remote basic auth
23+
host: https://example.com/log-viewer
24+
auth:
25+
type: basic
26+
options:
27+
username: user
28+
password: pass
29+
30+
# bearer token. Header: Authorization: Bearer my-token
31+
remote-bearer:
32+
name: Remote bearer
33+
host: https://example.com/log-viewer
34+
auth:
35+
type: bearer
36+
options:
37+
token: my-token
38+
39+
# custom header. Header: X-Private-Token: my-token
40+
remote-header:
41+
name: Remote custom header
42+
host: https://example.com/log-viewer
43+
auth:
44+
type: header
45+
options:
46+
x-private-token: my-token
47+
48+
# custom authentication class
49+
remote-custom:
50+
name: Remote custom authenticator
51+
host: https://example.com/log-viewer
52+
auth:
53+
type: My\Class\That\Implements\AuthenticatorInterface
54+
options:
55+
key: value
56+
```

docs/configuration-reference.md

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ Out of the box, [Log viewer](../README.md) will have the following configuration
66

77
```yaml
88
fd_log_viewer:
9-
enable_default_monolog: true
109
home_route: null
1110

1211
log_files:
@@ -25,17 +24,14 @@ fd_log_viewer:
2524
log_message_pattern: '/^\[(?P<date>[^\]]+)\]\s+(?P<channel>[^\.]+)\.(?P<severity>[^:]+):\s+(?P<message>.*)\s+(?P<context>[[{].*?[\]}])\s+(?P<extra>[[{].*?[\]}])\s+$/s'
2625
date_format: "Y-m-d H:i:s"
2726

27+
hosts:
28+
localhost:
29+
name: Local
30+
host: null
2831
```
2932
3033
## Configuration
3134
32-
### enable_default_monolog
33-
34-
**type**: `boolean`. Default: `true`
35-
36-
Out of the box the bundle is configured with a default monolog configuration. Set this to `false` to override this behaviour.
37-
<br><br>
38-
3935
### home_route
4036
4137
**type**: `string|null`. Default: `null`
@@ -54,13 +50,14 @@ This entry allows you to add more log file directories to the Log Viewer. Each e
5450

5551
**type**: `string` (`enum: monolog(.json)|http-access|apache-error|nginx-error`)
5652

57-
This is the type of log file that will be read.
53+
This is the type of log file that will be read.
54+
5855
- `monolog` is the default type and will read the default monolog log files.
5956
- `monolog.json` will read the monolog log files that use `formatter: 'monolog.formatter.json'`.
6057
- `http-access` will read the access log files of Apache and Nginx.
6158
- `apache-error` will read the error log files of Apache.
6259
- `nginx-error` will read the error log files of Nginx.
63-
<br><br>
60+
<br><br>
6461

6562
### log_files.name
6663

@@ -101,19 +98,21 @@ Example:
10198
```text
10299
*.log,*.txt
103100
```
101+
104102
<br>
105103

106104
### log_files.finder.depth
105+
107106
**type**: `int|string|string[]|null`. Default: `'== 0'`
108107

109108
The maximum depth of directories to search for log files. If set to null all subdirectories will be added.
110109

111110
Example:
111+
112112
- `'== 0'` will only search in the specified directory.
113113
- `'>= 0'` will search in the specified directory and all subdirectories.
114114
- `['>= 0', '< 3]` will search in the specified directory and all subdirectories up to a depth of 2.
115-
<br><br>
116-
115+
<br><br>
117116

118117
### log_files.finder.ignoreUnreadableDirs
119118

@@ -188,3 +187,65 @@ If you use a custom monolog format, adjust this pattern to your needs.
188187

189188
This is the date format that will be used to format the date in frontend.
190189
<br><br>
190+
191+
### hosts
192+
193+
**type**: `array<string, mixed>`
194+
195+
This entry allows you to add more hosts to the log viewer
196+
197+
**Full example:**
198+
```yaml
199+
hosts:
200+
local:
201+
name: Local
202+
host: null
203+
remote:
204+
name: Remote
205+
host: https://example.com/log-viewer
206+
auth:
207+
type: basic
208+
options:
209+
username: user
210+
password: pass
211+
```
212+
<br>
213+
214+
### hosts.name
215+
216+
**type**: `string`
217+
218+
The display name of the host. Will be shown in the UI host selector.
219+
<br><br>
220+
221+
### hosts.host
222+
223+
**type**: `string|null`
224+
225+
The url to the remote host log-viewer API. If null, the local host will be used. Must include the log-viewer route prefix.
226+
Example: `host: https://example.com/log-viewer`
227+
228+
### hosts.auth
229+
230+
**type**: `array<string, mixed>`
231+
232+
For remote hosts, specifies the authentication method.
233+
<br><br>
234+
235+
### hosts.auth.type
236+
237+
**type**: `string` (`enum: basic|bearer|header|class-string`)
238+
239+
The remote host authentication method:
240+
241+
- `basic`: Basic authentication. Requires `username` and `password` option.
242+
- `bearer`: Bearer token authentication. Requires `token` option.
243+
- `header`: Custom header authentication. The key-values in the options will be added to the request headers.
244+
- `class-string`: Custom authentication. The class must implement `FD\LogViewerBundle\Authentication\AuthenticationInterface`.
245+
<br><br>
246+
247+
### hosts.auth.options
248+
249+
**type**: `array<string, string>`
250+
The options specific for the authentication method.
251+
<br><br>

frontend/src/components/FileTree.vue

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22
import LogFolder from '@/components/LogFolder.vue';
33
import bus from '@/services/EventBus';
44
import {useFolderStore} from '@/stores/folders';
5+
import {useHostsStore} from '@/stores/hosts';
6+
import {watch} from 'vue';
57
68
const folderStore = useFolderStore();
9+
const hostsStore = useHostsStore();
10+
11+
watch(() => hostsStore.selected, () => folderStore.update());
712
813
bus.on('file-deleted', () => folderStore.update());
914
bus.on('folder-deleted', () => folderStore.update());
@@ -13,10 +18,16 @@ bus.on('folder-deleted', () => folderStore.update());
1318
<!-- FileTree -->
1419
<div class="p-1 pe-2 overflow-auto">
1520
<div class="slv-control-layout m-0">
16-
<div><!-- Host: Local --></div>
21+
<div>
22+
<select class="form-select pb-0 pt-0 ps-0 slv-form-select border-0"
23+
v-model="hostsStore.selected"
24+
v-if="Object.keys(hostsStore.hosts).length > 0">
25+
<option v-for="(name, key) in hostsStore.hosts" :value="key" :key="key">{{ name }}</option>
26+
</select>
27+
</div>
1728
<div></div>
1829
<div>
19-
<select class="form-control p-0 border-0" v-model="folderStore.direction" v-on:change="folderStore.update">
30+
<select class="form-select pb-0 pt-0 ps-0 slv-form-select border-0" v-model="folderStore.direction" v-on:change="folderStore.update">
2031
<option value="desc">Newest First</option>
2132
<option value="asc">Oldest First</option>
2233
</select>
@@ -34,4 +45,9 @@ bus.on('folder-deleted', () => folderStore.update());
3445
display: grid;
3546
grid-template-columns: auto 1fr auto;
3647
}
48+
49+
.slv-form-select {
50+
padding-right: 1.8rem;
51+
background-position: right 0.35rem center;
52+
}
3753
</style>

frontend/src/components/LogFile.vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<script setup lang="ts">
22
import ButtonGroup from '@/components/ButtonGroup.vue';
33
import type LogFile from '@/models/LogFile';
4+
import ParameterBag from '@/models/ParameterBag';
45
import bus from '@/services/EventBus';
6+
import {useHostsStore} from '@/stores/hosts';
57
import {useSearchStore} from '@/stores/search';
68
import axios from 'axios';
79
import {ref, watch} from 'vue';
@@ -16,9 +18,12 @@ const selectedFile = ref<string | null>(null);
1618
const route = useRoute();
1719
const router = useRouter();
1820
const searchStore = useSearchStore();
19-
const baseUri = axios.defaults.baseURL;
20-
const deleteFile = (identifier: string) => {
21-
axios.delete('/api/file/' + encodeURI(identifier))
21+
const hostsStore = useHostsStore();
22+
23+
const baseUri = axios.defaults.baseURL;
24+
const deleteFile = (identifier: string) => {
25+
const params = new ParameterBag().set('host', hostsStore.selected, 'localhost').all();
26+
axios.delete('/api/file/' + encodeURI(identifier), {params: params})
2227
.then(() => {
2328
if (selectedFile.value === identifier) {
2429
router.push({name: 'home'});
@@ -53,7 +58,9 @@ watch(() => route.query.file, () => selectedFile.value = String(route.query.file
5358
</template>
5459
<template v-slot:dropdown>
5560
<li>
56-
<a class="dropdown-item" :href="baseUri + 'api/file/' + encodeURI(file.identifier)" v-if="file.can_download">
61+
<a class="dropdown-item"
62+
:href="baseUri + 'api/file/' + encodeURI(file.identifier) + '?' + new ParameterBag().set('host', hostsStore.selected, 'localhost').toString()"
63+
v-if="file.can_download">
5764
<i class="bi bi-cloud-download me-3"></i>Download
5865
</a>
5966
</li>

0 commit comments

Comments
 (0)